2017-01-10 20:53:50 +00:00
using System ;
2017-02-05 14:46:40 +00:00
using System.Collections.Generic ;
2017-01-10 20:53:50 +00:00
using System.Threading.Tasks ;
2017-02-04 21:26:48 +00:00
using System.Linq ;
2017-02-05 14:46:40 +00:00
using System.Reflection ;
2017-01-22 20:56:31 +00:00
2017-02-05 14:46:40 +00:00
using Newtonsoft.Json ;
2017-01-27 21:24:58 +00:00
using SBRL.Utilities ;
2017-01-28 18:41:35 +00:00
using Nibriboard.Client.Messages ;
2017-03-04 21:49:51 +00:00
using Nibriboard.RippleSpace ;
2017-01-22 20:56:31 +00:00
2017-09-10 20:34:01 +00:00
using SBRL.GlidingSquirrel.Websocket ;
2017-12-16 12:16:43 +00:00
using System.Net ;
2017-09-10 20:34:01 +00:00
2017-01-21 18:13:42 +00:00
namespace Nibriboard.Client
2017-01-10 20:53:50 +00:00
{
2017-02-05 14:46:40 +00:00
/// <summary>
/// A delegate that is used in the event that is fired when a nibri client disconnects.
/// </summary>
public delegate void NibriDisconnectedEvent ( NibriClient disconnectedClient ) ;
2017-01-28 18:49:26 +00:00
/// <summary>
/// Represents a single client connected to the ripple-space on this Nibriboard server.
/// </summary>
2017-01-10 20:53:50 +00:00
public class NibriClient
{
2017-03-20 18:06:20 +00:00
#region Id Generation Logic
2017-01-28 18:41:35 +00:00
private static int nextId = 1 ;
private static int getNextId ( ) { return nextId + + ; }
/// <summary>
/// This client's unique id.
/// </summary>
public readonly int Id = getNextId ( ) ;
2017-03-20 18:06:20 +00:00
#endregion
2017-01-28 18:41:35 +00:00
/// <summary>
/// The nibri client manager
/// </summary>
2017-09-10 20:34:01 +00:00
private readonly NibriboardApp manager ;
2017-03-20 18:06:20 +00:00
/// <summary>
/// The plane that this client is currently on.
/// </summary>
2017-03-20 18:28:56 +00:00
public Plane CurrentPlane ;
2017-03-20 18:06:20 +00:00
2017-01-28 18:41:35 +00:00
/// <summary>
/// The underlying websocket connection to the client.
/// Please try not to call the send method on here - use the NibriClient Send() method instead.
/// </summary>
2017-09-10 20:34:01 +00:00
private readonly WebsocketClient connection ;
2017-01-10 20:53:50 +00:00
2017-03-25 19:49:44 +00:00
private static readonly Dictionary < string , Type > messageEventTypes = new Dictionary < string , Type > ( ) {
2017-02-07 21:29:17 +00:00
["HandshakeRequest"] = typeof ( HandshakeRequestMessage ) ,
2017-03-20 18:29:50 +00:00
["CursorPosition"] = typeof ( CursorPositionMessage ) ,
2017-03-25 19:49:44 +00:00
["PlaneChange"] = typeof ( PlaneChangeMessage ) ,
2017-03-29 20:34:24 +00:00
["ChunkUpdateRequest"] = typeof ( ChunkUpdateRequestMessage ) ,
2017-06-30 15:47:29 +00:00
["LineStart"] = typeof ( LineStartMessage ) ,
2017-04-16 15:48:37 +00:00
["LinePart"] = typeof ( LinePartMessage ) ,
2017-07-29 19:42:42 +00:00
["LineComplete"] = typeof ( LineCompleteMessage ) ,
2017-12-08 16:15:43 +00:00
["LineRemove"] = typeof ( LineRemoveMessage ) ,
2017-07-29 19:42:42 +00:00
["ViewportUpdate"] = typeof ( ViewportUpdateMessage )
2017-01-27 21:24:58 +00:00
} ;
2017-02-05 14:46:40 +00:00
/// <summary>
/// Whether this nibri client is still connected.
/// </summary>
2017-09-10 20:34:01 +00:00
public bool Connected {
get {
2017-09-20 21:39:14 +00:00
return ! connection . IsClosing ;
2017-09-10 20:34:01 +00:00
}
}
2017-12-16 12:16:43 +00:00
public IPEndPoint RemoteEndpoint {
get {
return connection . RemoteEndpoint ;
}
}
2017-03-20 18:34:34 +00:00
/// <summary>
/// Fires when this nibri client disconnects.
/// </summary>
2017-02-05 14:46:40 +00:00
public event NibriDisconnectedEvent Disconnected ;
2017-02-19 16:35:12 +00:00
/// <summary>
/// The date and time at which the last message was received from this client.
/// </summary>
public DateTime LastMessageTime = DateTime . Now ;
/// <summary>
/// The number of milliseconds since we last received a message from this client.
/// </summary>
/// <value>The milliseconds since last message.</value>
public int MillisecondsSinceLastMessage {
get {
return ( int ) ( ( DateTime . Now - LastMessageTime ) . TotalMilliseconds ) ;
}
}
2017-01-28 18:41:35 +00:00
/// <summary>
/// Whether this client has completed the handshake yet or not.
/// </summary>
public bool HandshakeCompleted = false ;
2017-02-19 16:35:12 +00:00
2017-01-28 18:41:35 +00:00
/// <summary>
/// The name this client has assignedto themselves.
/// </summary>
/// <value>The name.</value>
public string Name { get ; private set ; }
/// <summary>
/// The current area that this client is looking at.
/// </summary>
/// <value>The current view port.</value>
2017-02-05 14:46:40 +00:00
public Rectangle CurrentViewPort { get ; private set ; } = Rectangle . Zero ;
2017-01-28 18:41:35 +00:00
/// <summary>
/// The absolute position in plane-space of this client's cursor.
/// </summary>
/// <value>The absolute cursor position.</value>
2017-02-05 14:46:40 +00:00
public Vector2 AbsoluteCursorPosition { get ; private set ; } = Vector2 . Zero ;
2017-02-19 11:33:03 +00:00
/// <summary>
/// This client's colour. Used to tell multiple clients apart visually.
/// </summary>
public readonly ColourHSL Colour = ColourHSL . RandomSaturated ( ) ;
2017-03-25 19:49:44 +00:00
/// <summary>
/// The chunk cache. Keeps track of which chunks this client currently has.
/// </summary>
protected ChunkCache chunkCache = new ChunkCache ( ) ;
2017-01-28 18:41:35 +00:00
#region Core Setup & Message Routing Logic
2017-09-10 20:34:01 +00:00
public NibriClient ( NibriboardApp inManager , WebsocketClient inClient )
2017-01-10 20:53:50 +00:00
{
2017-02-19 15:55:22 +00:00
Log . WriteLine ( "[Nibriboard/WebSocket] New NibriClient connected with id #{0}." , Id ) ;
2017-01-10 20:53:50 +00:00
manager = inManager ;
2017-09-10 20:34:01 +00:00
connection = inClient ;
2017-01-10 20:53:50 +00:00
2017-09-10 20:34:01 +00:00
// Attach a few events
connection . OnDisconnection + = handleDisconnection ;
connection . OnTextMessage + = handleMessage ;
2017-01-10 20:53:50 +00:00
}
2017-09-10 20:34:01 +00:00
private async Task handleMessage ( object sender , TextMessageEventArgs eventArgs )
2017-01-28 18:41:35 +00:00
{
2017-06-27 14:14:40 +00:00
// Update the last time we received a message from the client
2017-02-19 16:35:12 +00:00
LastMessageTime = DateTime . Now ;
// Extract the event name from the message that the client sent.
2017-09-10 20:34:01 +00:00
string eventName = JsonUtilities . DeserializeProperty < string > ( eventArgs . Payload , "Event" ) ;
2017-01-28 18:41:35 +00:00
2017-02-04 21:26:48 +00:00
if ( eventName = = null ) {
Log . WriteLine ( "[NibriClient#{0}] Received message that didn't have an event." , Id ) ;
return ;
}
if ( ! messageEventTypes . ContainsKey ( eventName ) ) {
Log . WriteLine ( "[NibriClient#{0}] Received message with invalid event {1}." , Id , eventName ) ;
return ;
}
2017-04-23 16:41:41 +00:00
if ( eventName ! = "CursorPosition" )
Log . WriteLine ( "[NibriClient#{0}] Recieved message with event {1}." , Id , eventName ) ;
2017-04-24 17:09:18 +00:00
try
{
Type messageType = messageEventTypes [ eventName ] ;
Type jsonNet = typeof ( JsonConvert ) ;
MethodInfo deserialiserInfo = jsonNet . GetMethods ( ) . First ( method = > method . Name = = "DeserializeObject" & & method . IsGenericMethod ) ;
MethodInfo genericInfo = deserialiserInfo . MakeGenericMethod ( messageType ) ;
2017-09-10 20:34:01 +00:00
var decodedMessage = genericInfo . Invoke ( null , new object [ ] { eventArgs . Payload } ) ;
2017-04-24 17:09:18 +00:00
string handlerMethodName = "handle" + decodedMessage . GetType ( ) . Name ;
Type clientType = this . GetType ( ) ;
MethodInfo handlerInfo = clientType . GetMethod ( handlerMethodName , BindingFlags . Instance | BindingFlags . NonPublic ) ;
await ( Task ) handlerInfo . Invoke ( this , new object [ ] { decodedMessage } ) ;
}
catch ( Exception error )
{
Log . WriteLine ( "[NibriClient#{0}] Error decoding and / or handling message." , Id ) ;
2017-09-10 20:34:01 +00:00
Log . WriteLine ( "[NibriClient#{0}] Raw frame content: {1}" , Id , eventArgs . Payload ) ;
2017-04-24 17:09:18 +00:00
Log . WriteLine ( "[NibriClient#{0}] Exception details: {1}" , Id , error ) ;
}
2017-01-28 18:41:35 +00:00
}
2017-09-10 20:34:01 +00:00
private Task handleDisconnection ( object sender , ClientDisconnectedEventArgs eventArgs )
{
Disconnected ? . Invoke ( this ) ;
Log . WriteLine ( "[NibriClient] Client #{0} disconnected." , Id ) ;
return Task . CompletedTask ;
}
2017-01-28 18:41:35 +00:00
#endregion
2017-03-25 19:49:44 +00:00
2017-03-20 18:06:20 +00:00
#region Message Sending
2017-01-28 18:41:35 +00:00
/// <summary>
2017-12-08 16:15:43 +00:00
/// Sends a <see cref="Message"/> to the client.
2017-01-28 18:41:35 +00:00
/// If you *really* need to send a raw message to the client, you can do so with the SendRawa() method.
/// </summary>
/// <param name="message">The message to send.</param>
public void Send ( Message message )
{
2017-08-05 21:04:51 +00:00
try
{
string payload = JsonConvert . SerializeObject ( message ) ;
SendRaw ( payload ) ;
}
catch ( Exception error )
{
Log . WriteLine ( "[NibriClient/#{0}] Error serialising message!" , Id ) ;
Log . WriteLine ( "[NibriClient/#{0}] {1}" , Id , error ) ;
}
2017-01-28 18:41:35 +00:00
}
/// <summary>
2017-09-20 21:39:14 +00:00
/// Sends a raw string to the client. Don't use unless you know what you're doing!
2017-01-28 18:41:35 +00:00
/// Use the regular Send() method if you can possibly help it.
/// </summary>
/// <param name="message">The message to send.</param>
2017-05-29 11:56:32 +00:00
public bool SendRaw ( string message )
2017-01-22 20:56:31 +00:00
{
2017-05-29 11:56:32 +00:00
if ( ! Connected ) {
Log . WriteLine ( $"[NibriClient#{Id}] Can't send a message as the client has disconnected." ) ;
return false ;
}
2017-08-05 21:04:51 +00:00
Log . WriteLine ( "[NibriClient/#{0}] Sending message with length {1}." , Id , message . Length ) ;
2017-02-05 14:46:40 +00:00
2017-09-10 20:34:01 +00:00
connection . Send ( message ) ;
2017-05-29 11:56:32 +00:00
return true ;
2017-01-22 20:56:31 +00:00
}
2017-02-19 16:35:12 +00:00
/// <summary>
/// Sends a heartbeat message to this client.
/// </summary>
public void SendHeartbeat ( )
{
Send ( new HeartbeatMessage ( ) ) ;
}
2017-03-20 18:06:20 +00:00
#endregion
2017-02-19 16:35:12 +00:00
/// <summary>
/// Closes the connection to the client gracefully.
/// </summary>
2017-09-10 20:34:01 +00:00
public async Task CloseConnection ( Message lastMessage )
2017-02-19 16:35:12 +00:00
{
if ( ! Connected )
return ;
// Tell the client that we're shutting down
Send ( lastMessage ) ;
2017-09-20 21:39:14 +00:00
await connection . Close ( WebsocketCloseReason . Normal , "Goodbye!" ) ;
2017-02-19 16:35:12 +00:00
}
2017-01-28 18:41:35 +00:00
/// <summary>
/// Generates a new ClientState object representing this client's state at the current time.
/// </summary>
public ClientState GenerateStateSnapshot ( )
{
ClientState result = new ClientState ( ) ;
result . Id = Id ;
result . Name = Name ;
2017-02-19 11:58:00 +00:00
result . Colour = Colour ;
2017-02-21 18:11:17 +00:00
result . CursorPosition = AbsoluteCursorPosition ;
2017-01-28 18:41:35 +00:00
result . Viewport = CurrentViewPort ;
return result ;
}
2017-03-04 21:54:06 +00:00
/// <summary>
/// Determines whether this client can see the chunk at the specified chunk reference.
/// </summary>
/// <param name="chunkRef">The chunk reference to check the visibility of.</param>
/// <returns>Whether this client can see the chunk located at the specified chunk reference</returns>
public bool CanSee ( ChunkReference chunkRef )
{
2017-03-20 19:47:33 +00:00
if ( chunkRef . Plane ! = CurrentPlane )
return false ;
2017-03-25 19:49:44 +00:00
2017-03-20 18:47:21 +00:00
Rectangle chunkArea = chunkRef . InPlanespaceRectangle ( ) ;
2017-03-20 19:47:33 +00:00
return chunkArea . Overlap ( CurrentViewPort ) ;
2017-03-04 21:54:06 +00:00
}
2017-03-25 19:49:44 +00:00
2017-01-28 18:41:35 +00:00
#region Message Handlers
/// <summary>
/// Handles an incoming handshake request. We should only receive one of these!
/// </summary>
protected Task handleHandshakeRequestMessage ( HandshakeRequestMessage message )
2017-01-10 20:53:50 +00:00
{
2017-01-28 18:41:35 +00:00
CurrentViewPort = message . InitialViewport ;
AbsoluteCursorPosition = message . InitialAbsCursorPosition ;
2017-01-27 21:24:58 +00:00
2017-02-04 21:26:48 +00:00
// Tell everyone else about the new client
2017-02-19 11:33:03 +00:00
ClientStatesMessage newClientNotification = new ClientStatesMessage ( ) ;
newClientNotification . ClientStates . Add ( GenerateStateSnapshot ( ) ) ;
2017-02-19 11:34:34 +00:00
manager . Broadcast ( this , newClientNotification ) ;
2017-02-19 11:33:03 +00:00
// Send the new client a response to their handshake request
HandshakeResponseMessage handshakeResponse = new HandshakeResponseMessage ( ) ;
handshakeResponse . Id = Id ;
handshakeResponse . Colour = Colour ;
2017-03-20 18:06:20 +00:00
foreach ( Plane plane in manager . SpaceManager . Planes )
handshakeResponse . Planes . Add ( plane . Name ) ;
2017-02-19 11:33:03 +00:00
Send ( handshakeResponse ) ;
// Tell the new client about everyone else who's connected
// FUTURE: If we need to handle a large number of connections, we should generate this message based on the chunks surrounding the client
2017-01-28 18:41:35 +00:00
Send ( GenerateClientStateUpdate ( ) ) ;
2017-01-10 20:53:50 +00:00
2017-01-28 18:41:35 +00:00
return Task . CompletedTask ;
}
2017-03-20 18:21:41 +00:00
/// <summary>
/// Handles an incoming plane change request.
/// </summary>
2017-05-01 18:12:26 +00:00
protected async Task handlePlaneChangeMessage ( PlaneChangeMessage message )
2017-03-20 18:21:41 +00:00
{
2017-04-23 16:41:41 +00:00
Log . WriteLine ( "[NibriClient#{0}] Changing to plane {1}." , Id , message . NewPlaneName ) ;
2017-04-16 14:19:59 +00:00
// Create a new plane with the specified name if it doesn't exist already
2017-07-18 19:43:34 +00:00
// future we might want to allow the user to specify the chunk size
2017-03-20 18:21:41 +00:00
if ( manager . SpaceManager [ message . NewPlaneName ] = = default ( Plane ) )
2017-07-18 19:43:34 +00:00
manager . SpaceManager . CreatePlane ( new PlaneInfo ( message . NewPlaneName ) ) ;
2017-03-20 18:21:41 +00:00
2017-04-16 14:19:59 +00:00
// Remove the event listener from the old plane if there is indeed an old plane to remove it from
if ( CurrentPlane ! = null )
CurrentPlane . OnChunkUpdate - = handleChunkUpdateEvent ;
// Swap out the current plane
CurrentPlane = manager . SpaceManager [ message . NewPlaneName ] ;
// Attach a listener to the new plane
CurrentPlane . OnChunkUpdate + = handleChunkUpdateEvent ;
2017-03-20 18:21:41 +00:00
2017-04-16 14:54:29 +00:00
// Tell the client that the switch over all went according to plan
2017-04-24 18:32:28 +00:00
Send ( new PlaneChangeOkMessage ( ) {
NewPlaneName = message . NewPlaneName ,
GridSize = CurrentPlane . ChunkSize
} ) ;
2017-03-20 18:21:41 +00:00
2017-05-01 18:12:26 +00:00
// Reset the position to (0, 0) since we've just changed planes
Rectangle workingViewport = CurrentViewPort ;
workingViewport . X = 0 ;
workingViewport . Y = 0 ;
CurrentViewPort = workingViewport ;
List < ChunkReference > initialChunks = new List < ChunkReference > ( ) ;
2017-05-04 20:36:24 +00:00
ChunkReference currentChunkRef = new ChunkReference (
CurrentPlane ,
2017-12-15 20:47:45 +00:00
( int ) Math . Floor ( CurrentViewPort . X / CurrentPlane . ChunkSize ) ,
( int ) Math . Floor ( CurrentViewPort . Y / CurrentPlane . ChunkSize )
2017-05-04 20:36:24 +00:00
) ;
2017-05-01 18:12:26 +00:00
while ( CanSee ( currentChunkRef ) )
{
while ( CanSee ( currentChunkRef ) )
{
initialChunks . Add ( currentChunkRef ) ;
currentChunkRef = currentChunkRef . Clone ( ) as ChunkReference ;
2017-05-04 20:36:24 +00:00
currentChunkRef . X + + ;
2017-05-01 18:12:26 +00:00
}
2017-12-15 20:47:45 +00:00
currentChunkRef . X = ( int ) Math . Floor ( CurrentViewPort . X / CurrentPlane . ChunkSize ) ;
2017-05-04 20:36:24 +00:00
currentChunkRef . Y + + ;
2017-05-01 18:12:26 +00:00
}
await SendChunks ( initialChunks ) ;
2017-03-20 18:21:41 +00:00
}
2017-04-16 14:19:59 +00:00
/// <summary>
/// Handles requests from clients for chunk updates.
/// </summary>
/// <param name="message">The message to process.</param>
2017-03-25 19:49:44 +00:00
protected async Task handleChunkUpdateRequestMessage ( ChunkUpdateRequestMessage message )
{
2017-12-15 20:05:18 +00:00
List < ChunkReference > requestedChunkRefs = message . ForgottenChunksAsReferences ( this . CurrentPlane ) ;
chunkCache . Remove ( requestedChunkRefs ) ;
2017-03-25 19:49:44 +00:00
2017-12-15 20:05:18 +00:00
await SendChunks ( requestedChunkRefs ) ;
2017-03-25 19:49:44 +00:00
}
2017-01-28 18:41:35 +00:00
/// <summary>
/// Handles an incoming cursor position message from the client..
/// </summary>
2017-04-16 14:19:59 +00:00
/// <param name="message">The message to process.</param>
2017-01-28 18:41:35 +00:00
protected Task handleCursorPositionMessage ( CursorPositionMessage message ) {
AbsoluteCursorPosition = message . AbsCursorPosition ;
2017-02-04 21:26:48 +00:00
// Send the update to the other clients
2017-02-09 22:24:17 +00:00
// TODO: Buffer these updates and send them about 5 times a second
ClientStatesMessage updateMessage = new ClientStatesMessage ( ) ;
2017-02-04 21:26:48 +00:00
updateMessage . ClientStates . Add ( this . GenerateStateSnapshot ( ) ) ;
2017-03-20 18:34:34 +00:00
manager . BroadcastPlane ( this , updateMessage ) ;
2017-02-04 21:26:48 +00:00
2017-01-28 18:41:35 +00:00
return Task . CompletedTask ;
}
2017-03-25 19:49:44 +00:00
2017-07-29 19:42:42 +00:00
/// <summary>
/// Handles viewport updates from the remote client.
/// </summary>
/// <param name="message">The viewport update message to handle.</param>
protected Task handleViewportUpdateMessage ( ViewportUpdateMessage message ) {
CurrentViewPort = message . NewViewport ;
return Task . CompletedTask ;
}
2017-06-30 15:47:29 +00:00
/// <summary>
/// Handles line start events from the client.
/// These messages are currently only required to let other clients know about
/// lines that are being drawn and their properties for live display.
/// </summary>
/// <param name="message">The LineStartMessage to process.</param>
protected Task handleLineStartMessage ( LineStartMessage message )
{
// Create a new line
manager . LineIncubator . CreateLine (
message . LineId ,
message . LineColour ,
message . LineWidth
) ;
manager . BroadcastPlane ( this , new LineStartReflectionMessage ( ) {
OtherClientId = Id ,
LineId = message . LineId ,
LineColour = message . LineColour ,
LineWidth = message . LineWidth
} ) ;
return Task . CompletedTask ;
}
2017-04-15 15:40:25 +00:00
/// <summary>
/// Handles messages containing a fragment of a line from the client.
/// </summary>
/// <param name="message">The message to process.</param>
2017-03-29 20:34:24 +00:00
protected Task handleLinePartMessage ( LinePartMessage message )
{
// Forward the line part to everyone on this plane
manager . BroadcastPlane ( this , message ) ;
2017-04-15 15:40:25 +00:00
List < LocationReference > linePoints = new List < LocationReference > ( message . Points . Count ) ;
foreach ( Vector2 point in message . Points )
linePoints . Add ( new LocationReference ( CurrentPlane , point . X , point . Y ) ) ;
manager . LineIncubator . AddBit ( message . LineId , linePoints ) ;
2017-03-29 20:34:24 +00:00
2017-06-30 09:33:35 +00:00
manager . BroadcastPlane ( this , new LinePartReflectionMessage ( ) {
OtherClientId = Id ,
LineId = message . LineId ,
Points = message . Points
} ) ;
2017-03-29 20:34:24 +00:00
return Task . CompletedTask ;
}
2017-04-15 15:40:25 +00:00
/// <summary>
/// Handles notifications from clients telling us that they've finished drawing a line.
/// </summary>
/// <param name="message">The message to handle.</param>
protected async Task handleLineCompleteMessage ( LineCompleteMessage message )
{
2017-04-16 15:00:49 +00:00
// If the line doesn't exist, then ignore it
if ( ! manager . LineIncubator . LineExists ( message . LineId ) )
{
2017-04-24 19:18:09 +00:00
Log . WriteLine ( "[NibriClient#{0}/handlers] Ignoring LineComplete event for line that doesn't exist" , Id ) ;
2017-04-16 15:00:49 +00:00
return ;
}
2017-04-15 15:40:25 +00:00
DrawnLine line = manager . LineIncubator . CompleteLine ( message . LineId ) ;
2017-04-23 16:22:21 +00:00
if ( CurrentPlane = = null )
{
2017-04-23 17:06:58 +00:00
Log . WriteLine ( "[NibriClient#{0}] Attempt to complete a line before selecting a plane - ignoring" ) ;
2017-04-23 16:22:21 +00:00
Send ( new ErrorMessage ( ) {
2017-04-23 16:26:29 +00:00
Message = "Error: You can't complete a line until you've selected a plane " +
2017-04-23 16:22:21 +00:00
"to draw it on!"
} ) ;
return ;
}
2017-04-28 17:14:14 +00:00
Log . WriteLine ( "[NibriClient#{0}] Adding {1}px {2} line" , Id , line . Width , line . Colour ) ;
2017-06-30 14:42:02 +00:00
manager . BroadcastPlane ( this , new LineCompleteReflectionMessage ( ) {
OtherClientId = Id ,
2017-06-30 15:47:29 +00:00
LineId = line . LineId
2017-06-30 14:42:02 +00:00
} ) ;
2017-04-15 15:40:25 +00:00
await CurrentPlane . AddLine ( line ) ;
}
2017-12-08 16:15:43 +00:00
/// <summary>
/// Handles messages requesting that a line be removed from a chunk.
/// </summary>
/// <param name="message">The message to handle.</param>
protected async Task handleLineRemoveMessage ( LineRemoveMessage message )
{
bool removeSuccess = await CurrentPlane . RemoveLineSegment (
message . ConvertedContainingChunk ( CurrentPlane ) ,
message . UniqueId
) ;
Log . WriteLine ( "[NibriClient#{1}] " + ( removeSuccess ? "Removed" : "Failed to remove" ) + " line segment with unique id {0} from {1}" , message . UniqueId , message . ConvertedContainingChunk ( CurrentPlane ) ) ;
}
2017-01-28 18:41:35 +00:00
#endregion
2017-04-16 14:19:59 +00:00
#region RippleSpace Event Handlers
protected void handleChunkUpdateEvent ( object sender , ChunkUpdateEventArgs eventArgs )
{
2017-04-29 12:43:19 +00:00
Chunk sendingChunk = sender as Chunk ;
Log . WriteLine ( "[NibriClient#{0}] Sending chunk update for {1}" , Id , sendingChunk . Location ) ;
2017-04-16 14:19:59 +00:00
ChunkUpdateMessage clientNotification = new ChunkUpdateMessage ( ) {
2017-04-29 12:43:19 +00:00
Chunks = new List < Chunk > ( ) { sendingChunk }
2017-04-16 14:19:59 +00:00
} ;
Send ( clientNotification ) ;
}
#endregion
2017-03-25 19:49:44 +00:00
2017-01-28 18:41:35 +00:00
/// <summary>
/// Generates an update message that contains information about the locations and states of all connected clients.
2017-03-20 18:34:34 +00:00
/// Automatically omits information about the current client, and clients on other planes.
2017-01-28 18:41:35 +00:00
/// </summary>
/// <returns>The client state update message.</returns>
2017-02-09 22:24:17 +00:00
protected ClientStatesMessage GenerateClientStateUpdate ( )
2017-01-28 18:41:35 +00:00
{
2017-02-09 22:24:17 +00:00
ClientStatesMessage result = new ClientStatesMessage ( ) ;
2017-09-10 20:34:01 +00:00
foreach ( NibriClient otherClient in manager . NibriClients )
2017-01-28 18:41:35 +00:00
{
// Don't include ourselves in the update message!
2017-03-20 18:28:56 +00:00
if ( otherClient = = this )
2017-01-28 18:41:35 +00:00
continue ;
2017-03-20 18:34:34 +00:00
// Only include other nibri clients on our plane
if ( otherClient . CurrentPlane ! = CurrentPlane )
continue ;
2017-01-28 18:41:35 +00:00
2017-03-20 18:28:56 +00:00
result . ClientStates . Add ( otherClient . GenerateStateSnapshot ( ) ) ;
2017-01-28 18:41:35 +00:00
}
return result ;
2017-01-10 20:53:50 +00:00
}
2017-03-04 21:49:51 +00:00
/// <summary>
/// Sends variable list of chunks to this client.
2017-03-20 18:34:34 +00:00
/// Automatically fetches the chunks by reference from the current plane.
2017-03-04 21:49:51 +00:00
/// </summary>
2017-05-01 18:12:26 +00:00
/// <param name="chunkRefs">The references of the chunks to send.</param>
protected async Task SendChunks ( IEnumerable < ChunkReference > chunkRefs )
2017-03-04 21:49:51 +00:00
{
2017-03-20 18:28:56 +00:00
if ( CurrentPlane = = default ( Plane ) )
{
Send ( new ExceptionMessage ( "You're not on a plane yet, so you can't request chunks." +
2017-03-20 18:34:34 +00:00
"Try joining a plane and sending that request again." ) ) ;
2017-03-20 18:28:56 +00:00
return ;
}
2017-03-20 18:34:34 +00:00
2017-12-15 20:05:18 +00:00
if ( chunkRefs . Count ( ) = = 0 ) {
Log . WriteLine ( "[NibriClient#{0}/SendChunks] Can't send 0 chunks!" , Id ) ;
return ;
}
// Keep track of the fact that we've sent the client a bunch of chunks
chunkCache . Add ( chunkRefs ) ;
2017-03-20 18:34:34 +00:00
ChunkUpdateMessage updateMessage = new ChunkUpdateMessage ( ) ;
foreach ( ChunkReference chunkRef in chunkRefs )
updateMessage . Chunks . Add ( await CurrentPlane . FetchChunk ( chunkRef ) ) ;
2017-06-26 11:48:58 +00:00
Log . WriteLine ( "[NibriClient#{0}/SendChunks] Sending {1} chunks" , Id , updateMessage . Chunks . Count ) ;
2017-03-20 18:34:34 +00:00
Send ( updateMessage ) ;
2017-03-04 21:49:51 +00:00
}
2017-01-10 20:53:50 +00:00
}
}