From 886d2a88b0bd1369053e93d3365b0a427be5e123 Mon Sep 17 00:00:00 2001 From: Michele Scandura Date: Mon, 9 Nov 2020 16:47:17 +0000 Subject: [PATCH] more refactoring --- Sledgemapper.Api/Hubs/SledgemapperHub.cs | 104 ++- .../Clients/ISledgemapperClient.cs | 2 +- Sledgemapper.Shared/Entities/Overlay.cs | 6 +- .../Entities/{SessionData.cs => Session.cs} | 107 +--- Sledgemapper.Shared/Entities/Tile.cs | 17 +- Sledgemapper.Shared/Entities/Wall.cs | 13 +- Sledgemapper/ChannelsQueue.cs | 40 ++ Sledgemapper/CommunicationManager.cs | 160 +++++ Sledgemapper/MyDatabase.db | Bin 69632 -> 143360 bytes Sledgemapper/Sledgemapper.cs | 595 ++++++------------ Sledgemapper/Sledgemapper.csproj | 1 + Sledgemapper/State.cs | 100 +++ Sledgemapper/Utils.cs | 20 + 13 files changed, 592 insertions(+), 573 deletions(-) rename Sledgemapper.Shared/Entities/{SessionData.cs => Session.cs} (55%) create mode 100644 Sledgemapper/ChannelsQueue.cs create mode 100644 Sledgemapper/CommunicationManager.cs create mode 100644 Sledgemapper/State.cs create mode 100644 Sledgemapper/Utils.cs diff --git a/Sledgemapper.Api/Hubs/SledgemapperHub.cs b/Sledgemapper.Api/Hubs/SledgemapperHub.cs index d8a5134..3fd5f67 100644 --- a/Sledgemapper.Api/Hubs/SledgemapperHub.cs +++ b/Sledgemapper.Api/Hubs/SledgemapperHub.cs @@ -15,12 +15,8 @@ namespace SignalRChat.Hubs { public class SledgemapperHub : Hub { - private readonly IMediator _mediator; - private readonly MyDbContext _dbcontext; - - // public SledgemapperHub(IMediator mediator) => _mediator = mediator; - public SledgemapperHub(MyDbContext dbcontext, IMediator mediator) { _dbcontext = dbcontext; _mediator = mediator; } - private static Dictionary _sessions = new Dictionary(); + public SledgemapperHub() { } + private static Dictionary _sessions = new Dictionary(); public List Colors = new List{"CC0000", "CC3300", "FFCC00", @@ -33,86 +29,86 @@ namespace SignalRChat.Hubs public async Task NewTile(string sessionName, Tile tile) { - var timestamp = DateTimeOffset.Now.ToUnixTimeMilliseconds(); + // var timestamp = DateTimeOffset.Now.ToUnixTimeMilliseconds(); - var existingTile = _sessions[sessionName].Map.TryGetValue(tile.ToString(), out var t); - if (existingTile) - { - _sessions[sessionName].Map.TryRemove(t.ToString(), out var rtile); - } - _sessions[sessionName].Map.TryAdd(tile.ToString(), tile); + // var existingTile = _sessions[sessionName].Map.TryGetValue(tile.ToString(), out var t); + // if (existingTile) + // { + // _sessions[sessionName].Map.TryRemove(t.ToString(), out var rtile); + // } + // _sessions[sessionName].Map.TryAdd(tile.ToString(), tile); - var jsonString = JsonSerializer.Serialize(tile); + // var jsonString = JsonSerializer.Serialize(tile); - _dbcontext.MapLogs.Add(new Sledgemapper.Api.Models.MapLog - { - Operation = "N", - SessionName = sessionName, - Type = "T", - Timestamp = timestamp, - Object = jsonString - }); - await _dbcontext.SaveChangesAsync(); + // _dbcontext.MapLogs.Add(new Sledgemapper.Api.Models.MapLog + // { + // Operation = "N", + // SessionName = sessionName, + // Type = "T", + // Timestamp = timestamp, + // Object = jsonString + // }); + // await _dbcontext.SaveChangesAsync(); await Clients.Group(sessionName).NewTile(tile); } public async Task NewWall(string sessionName, Wall tile) { - var existingTile = _sessions[sessionName].Walls.TryGetValue(tile.ToString(), out var t); - if (existingTile) - { - _sessions[sessionName].Walls.TryRemove(t.ToString(), out var rtile); - } - _sessions[sessionName].Walls.TryAdd(tile.ToString(), tile); + // var existingTile = _sessions[sessionName].Walls.TryGetValue(tile.ToString(), out var t); + // if (existingTile) + // { + // _sessions[sessionName].Walls.TryRemove(t.ToString(), out var rtile); + // } + // _sessions[sessionName].Walls.TryAdd(tile.ToString(), tile); await Clients.Group(sessionName).NewWall(tile); } public async Task NewOverlay(string sessionName, Overlay tile) { - var timestamp = DateTimeOffset.Now.ToUnixTimeMilliseconds(); - var existingTile = _sessions[sessionName].Overlays.TryGetValue(tile.ToString(), out var t); - if (existingTile) - { - _sessions[sessionName].Overlays.TryRemove(t.ToString(), out var rtile); - } - _sessions[sessionName].Overlays.TryAdd(tile.ToString(), tile); - var jsonString = JsonSerializer.Serialize(tile); + // var timestamp = DateTimeOffset.Now.ToUnixTimeMilliseconds(); + // var existingTile = _sessions[sessionName].Overlays.TryGetValue(tile.ToString(), out var t); + // if (existingTile) + // { + // _sessions[sessionName].Overlays.TryRemove(t.ToString(), out var rtile); + // } + // _sessions[sessionName].Overlays.TryAdd(tile.ToString(), tile); + // var jsonString = JsonSerializer.Serialize(tile); - _dbcontext.MapLogs.Add(new Sledgemapper.Api.Models.MapLog - { - Operation = "N", - SessionName = sessionName, - Type = "O", - Timestamp = timestamp, - Object = jsonString - }); - await _dbcontext.SaveChangesAsync(); + // _dbcontext.MapLogs.Add(new Sledgemapper.Api.Models.MapLog + // { + // Operation = "N", + // SessionName = sessionName, + // Type = "O", + // Timestamp = timestamp, + // Object = jsonString + // }); + // await _dbcontext.SaveChangesAsync(); await Clients.Group(sessionName).NewOverlay(tile); } public async Task DeleteTile(string sessionName, Tile tile) { - _sessions[sessionName].Map.TryRemove(tile.ToString(), out var rtile); + // _sessions[sessionName].Map.TryRemove(tile.ToString(), out var rtile); await Clients.Group(sessionName).DeleteTile(tile); } public async Task DeleteWall(string sessionName, Wall tile) { - _sessions[sessionName].Walls.TryRemove(tile.ToString(), out var rtile); + //_sessions[sessionName].Walls.TryRemove(tile.ToString(), out var rtile); await Clients.Group(sessionName).DeleteWall(tile); } public async Task DeleteOverlay(string sessionName, Overlay tile) { - _sessions[sessionName].Overlays.TryRemove(tile.ToString(), out var rtile); + //_sessions[sessionName].Overlays.TryRemove(tile.ToString(), out var rtile); await Clients.Group(sessionName).DeleteOverlay(tile); } - public async Task NewSession(string sessionName, string initials) + public async Task NewSession(string sessionName, string initials) { - var session = new SessionData(); + var session = new Session(); session.Colors = new List(Colors); session.Colors.Shuffle(); var player = new Player { Position = new Tile { X = 0, Y = 0 }, ConnectionId = Context.ConnectionId, Color = session.Colors[0], Initials = initials }; @@ -124,7 +120,7 @@ namespace SignalRChat.Hubs return session; } - public async Task JoinSession(string sessionName, string initials) + public async Task JoinSession(string sessionName, string initials) { if (_sessions.ContainsKey(sessionName)) { @@ -148,12 +144,12 @@ namespace SignalRChat.Hubs await Clients.Group(sessionName).PlayerUpdate(player); } - public async Task Refresh(string sessionName) + public async Task Refresh(string sessionName) { return _sessions[sessionName]; } - public async Task Sync(string sessionName, SessionData map) + public async Task Sync(string sessionName, Session map) { _sessions[sessionName].Map = map.Map; _sessions[sessionName].Overlays = map.Overlays; diff --git a/Sledgemapper.Shared/Clients/ISledgemapperClient.cs b/Sledgemapper.Shared/Clients/ISledgemapperClient.cs index dc78f9e..f9d56b7 100644 --- a/Sledgemapper.Shared/Clients/ISledgemapperClient.cs +++ b/Sledgemapper.Shared/Clients/ISledgemapperClient.cs @@ -14,6 +14,6 @@ namespace Sledgemapper.Clients Task DeleteOverlay(Overlay overlay); Task NewPlayer(Player player); Task PlayerUpdate(Player player); - Task UpdateMap(SessionData player); + Task UpdateMap(Session player); } } diff --git a/Sledgemapper.Shared/Entities/Overlay.cs b/Sledgemapper.Shared/Entities/Overlay.cs index ffe2171..f15213b 100644 --- a/Sledgemapper.Shared/Entities/Overlay.cs +++ b/Sledgemapper.Shared/Entities/Overlay.cs @@ -1,12 +1,8 @@ namespace Sledgemapper.Shared.Entities { - public class Overlay + public class Overlay :BaseMapEntity { - public int X { get; set; } - public int Y { get; set; } - public string ID { get; set; } public bool Intersection { get; set; } - public int Rotation { get; set; } public override string ToString() { diff --git a/Sledgemapper.Shared/Entities/SessionData.cs b/Sledgemapper.Shared/Entities/Session.cs similarity index 55% rename from Sledgemapper.Shared/Entities/SessionData.cs rename to Sledgemapper.Shared/Entities/Session.cs index 5df2ec2..9f3924d 100644 --- a/Sledgemapper.Shared/Entities/SessionData.cs +++ b/Sledgemapper.Shared/Entities/Session.cs @@ -4,52 +4,24 @@ using System; namespace Sledgemapper.Shared.Entities { - public class TileAddedEventArgs : EventArgs + public class MapEntityAddedEventArgs : EventArgs { - public Tile Tile { get; set; } - public TileAddedEventArgs(Tile tile) => Tile = tile; + public BaseMapEntity MapEntity { get; private set; } + public MapEntityAddedEventArgs(BaseMapEntity mapEntity) => MapEntity = mapEntity; } - public class OverlayAddedEventArgs : EventArgs + public class MapEntityDeletedEventArgs : EventArgs { - public Overlay Overlay { get; set; } - public OverlayAddedEventArgs(Overlay overlay) => Overlay = overlay; + public BaseMapEntity MapEntity { get; private set; } + public MapEntityDeletedEventArgs(BaseMapEntity mapEntity) => MapEntity = mapEntity; } - public class WallAddedEventArgs : EventArgs + public class Session { - public Wall Wall { get; set; } - public WallAddedEventArgs(Wall wall) => Wall = wall; - } + public event EventHandler MapEntityAdded; + public event EventHandler MapEntityDeleted; - public class WallDeletedEventArgs : EventArgs - { - public Wall Wall { get; set; } - public WallDeletedEventArgs(Wall wall) => Wall = wall; - } - - public class OverlayDeletedEventArgs : EventArgs - { - public Overlay Overlay { get; set; } - public OverlayDeletedEventArgs(Overlay overlay) => Overlay = overlay; - } - - public class TileDeletedEventArgs : EventArgs - { - public Tile Tile { get; set; } - public TileDeletedEventArgs(Tile tile) => Tile = tile; - } - - public class SessionData - { - public event EventHandler TileAdded; - public event EventHandler OverlayAdded; - public event EventHandler WallAdded; - public event EventHandler WallDeleted; - public event EventHandler OverlayDeleted; - public event EventHandler TileDeleted; - - public SessionData() + public Session() { Map = new ConcurrentDictionary(); Overlays = new ConcurrentDictionary(); @@ -64,6 +36,7 @@ namespace Sledgemapper.Shared.Entities public bool IsValid { get; set; } public List Players { get; set; } public List Colors; + public string SessionName { get; set; } public void NewTile(Tile selectedTile, string tileId) { @@ -84,7 +57,7 @@ namespace Sledgemapper.Shared.Entities } Map.TryAdd(newTile.ToString(), newTile); - OnRaiseTileAddedEvent(new TileAddedEventArgs(newTile)); + OnRaiseMapEntityAddedEvent(new MapEntityAddedEventArgs(newTile)); } public void NewOverlay(Overlay selectedOverlay, string overlayId) @@ -105,7 +78,7 @@ namespace Sledgemapper.Shared.Entities } Overlays.TryAdd(newOverlay.ToString(), newOverlay); - OnRaiseOverlayAddedEvent(new OverlayAddedEventArgs(newOverlay)); + OnRaiseMapEntityAddedEvent(new MapEntityAddedEventArgs(newOverlay)); } public void NewWall(Wall selectedWall, string wallId) @@ -122,7 +95,7 @@ namespace Sledgemapper.Shared.Entities } Walls.TryAdd(newWall.ToString(), newWall); - OnRaiseWallAddedEvent(new WallAddedEventArgs(newWall)); + OnRaiseMapEntityAddedEvent(new MapEntityAddedEventArgs(newWall)); } public void DeleteWall(Wall wall) @@ -134,82 +107,48 @@ namespace Sledgemapper.Shared.Entities var removed = Walls.TryRemove(wall.ToString(), out var _); if (removed) { - OnRaiseWallDeletedEvent(new WallDeletedEventArgs(wall)); + OnRaiseMapEntityDeletedEvent(new MapEntityDeletedEventArgs(wall)); } } public void DeleteOverlay(Overlay overlay) { - if (overlay is null) + if (overlay is null) { return; } var removed = Overlays.TryRemove(overlay.ToString(), out var _); if (removed) { - OnRaiseOverlayDeletedEvent(new OverlayDeletedEventArgs(overlay)); + OnRaiseMapEntityDeletedEvent(new MapEntityDeletedEventArgs(overlay)); } } public void DeleteTile(Tile tile) { - if (tile is null) + if (tile is null) { return; } var removed = Map.TryRemove(tile.ToString(), out var _); if (removed) { - OnRaiseTileDeletedEvent(new TileDeletedEventArgs(tile)); + OnRaiseMapEntityDeletedEvent(new MapEntityDeletedEventArgs(tile)); } } - protected virtual void OnRaiseTileAddedEvent(TileAddedEventArgs e) + protected virtual void OnRaiseMapEntityAddedEvent(MapEntityAddedEventArgs e) { - var raiseEvent = TileAdded; + var raiseEvent = MapEntityAdded; if (raiseEvent != null) { raiseEvent(this, e); } } - protected virtual void OnRaiseOverlayAddedEvent(OverlayAddedEventArgs e) + protected virtual void OnRaiseMapEntityDeletedEvent(MapEntityDeletedEventArgs e) { - var raiseEvent = OverlayAdded; - if (raiseEvent != null) - { - raiseEvent(this, e); - } - } - - protected virtual void OnRaiseWallAddedEvent(WallAddedEventArgs e) - { - var raiseEvent = WallAdded - ; - if (raiseEvent != null) - { - raiseEvent(this, e); - } - } - protected virtual void OnRaiseWallDeletedEvent(WallDeletedEventArgs e) - { - var raiseEvent = WallDeleted; - if (raiseEvent != null) - { - raiseEvent(this, e); - } - } - protected virtual void OnRaiseOverlayDeletedEvent(OverlayDeletedEventArgs e) - { - var raiseEvent = OverlayDeleted; - if (raiseEvent != null) - { - raiseEvent(this, e); - } - } - protected virtual void OnRaiseTileDeletedEvent(TileDeletedEventArgs e) - { - var raiseEvent = TileDeleted; + var raiseEvent = MapEntityDeleted; if (raiseEvent != null) { raiseEvent(this, e); diff --git a/Sledgemapper.Shared/Entities/Tile.cs b/Sledgemapper.Shared/Entities/Tile.cs index 4e17c1e..5c43114 100644 --- a/Sledgemapper.Shared/Entities/Tile.cs +++ b/Sledgemapper.Shared/Entities/Tile.cs @@ -1,18 +1,19 @@ namespace Sledgemapper.Shared.Entities { - - - public class Tile - { - public int X { get; set; } + public abstract class BaseMapEntity + { + public int X { get; set; } public int Y { get; set; } public string ID { get; set; } - public int Rotation { get; set; } - - public override string ToString() + public int Rotation { get; set; } + public override string ToString() { return $"{X}_{Y}"; } + } + + public class Tile :BaseMapEntity + { } diff --git a/Sledgemapper.Shared/Entities/Wall.cs b/Sledgemapper.Shared/Entities/Wall.cs index 5949a46..9f671a3 100644 --- a/Sledgemapper.Shared/Entities/Wall.cs +++ b/Sledgemapper.Shared/Entities/Wall.cs @@ -2,15 +2,10 @@ { - public class Wall + public class Wall :BaseMapEntity { - public int X { get; set; } - public int Y { get; set; } - public string ID { get; set; } - public int Rotation { get; set; } - public override string ToString() - { - return $"{X}_{Y}"; - } + + + } } diff --git a/Sledgemapper/ChannelsQueue.cs b/Sledgemapper/ChannelsQueue.cs new file mode 100644 index 0000000..a830fe3 --- /dev/null +++ b/Sledgemapper/ChannelsQueue.cs @@ -0,0 +1,40 @@ +using System; +using System.Threading.Tasks; +using System.Threading.Channels; + +namespace Sledgemapper +{ + public class ChannelsQueue +{ + private readonly ChannelWriter _writer; + + public ChannelsQueue() + { + var channel = Channel.CreateUnbounded(new UnboundedChannelOptions() { SingleReader = true }); + var reader = channel.Reader; + _writer = channel.Writer; + + Task.Run(async () => + { + while (await reader.WaitToReadAsync()) + { + // Fast loop around available jobs + while (reader.TryRead(out var job)) + { + job.Invoke(); + } + } + }); + } + + public void Enqueue(Action job) + { + _writer.TryWrite(job); + } + + public void Stop() + { + _writer.Complete(); + } +} +} diff --git a/Sledgemapper/CommunicationManager.cs b/Sledgemapper/CommunicationManager.cs new file mode 100644 index 0000000..041aa38 --- /dev/null +++ b/Sledgemapper/CommunicationManager.cs @@ -0,0 +1,160 @@ +using Microsoft.AspNetCore.SignalR.Client; +using Polly; +using Refit; +using Sledgemapper.Shared.Entities; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Threading.Tasks; + +namespace Sledgemapper +{ + public class CommunicationManager + { + public IMapApi Api { get; private set; } + public HubConnection Connection { get; private set; } + public Session SessionData; + private ChannelsQueue Queue = new ChannelsQueue(); + + public CommunicationManager(Session sessionData) + { + SessionData = sessionData; + Connection = new HubConnectionBuilder() + .WithAutomaticReconnect() + + .WithUrl("http://localhost:5000/SledgemapperHub") + + // .WithUrl("http://hub.michelescandura.com:5000/SledgemapperHub") + .Build(); + + + + var httpClientHandler = new HttpClientHandler(); + + //if (myConfigurationService.VerifySslCertificate == false) + //{ + httpClientHandler.ServerCertificateCustomValidationCallback = + (message, certificate, chain, sslPolicyErrors) => true; + //} + + Api = RestService.For( + new HttpClient(httpClientHandler) + { + BaseAddress = new Uri("http://localhost:5000") + } + ); + + Connection.On("UpdateMap", (map) => + { + SessionData.Map = map.Map; + SessionData.Walls = map.Walls; + SessionData.Overlays = map.Overlays; + }); + + Connection.On("PlayerUpdate", (player) => + { + var p = SessionData.Players.FirstOrDefault(m => m.ConnectionId == player.ConnectionId); + if (p != null) + { + p.Position = player.Position; + } + }); + + Connection.On("DeleteTile", (tile) => + { + SessionData.Map.Remove(tile.ToString(), out var _); + }); + + Connection.On("DeleteWall", (tile) => + { + SessionData.Walls.Remove(tile.ToString(), out var _); + }); + + Connection.On("DeleteOverlay", (tile) => + { + SessionData.Overlays.Remove(tile.ToString(), out var _); + }); + + Connection.On("NewTile", (tile) => + { + SessionData.Map.Remove(tile.ToString(), out var _); + SessionData.Map.TryAdd(tile.ToString(), tile); + }); + + Connection.On("NewWall", (tile) => + { + SessionData.Walls.Remove(tile.ToString(), out var _); + SessionData.Walls.TryAdd(tile.ToString(), tile); + }); + + Connection.On("NewOverlay", (tile) => + { + SessionData.Overlays.Remove(tile.ToString(), out var _); + SessionData.Overlays.TryAdd(tile.ToString(), tile); + }); + + Connection.On("NewPlayer", (player) => + { + var p = SessionData.Players.FirstOrDefault(m => m.ConnectionId == player.ConnectionId); + if (p is null) + { + SessionData.Players.Add(player); + } + else + { + p.Color = player.Color; + p.Position = player.Position; + } + }); + } + + private async Task Execute(Func call) + { + await Policy + .Handle(ex => ex.StatusCode == HttpStatusCode.RequestTimeout) + .RetryForeverAsync() + //.RetryAsync(Polly.RetrySyntax., async (exception, retryCount) => await Task.Delay(500)) + .ExecuteAsync(async () => await call().ConfigureAwait(false)) + .ConfigureAwait(false); + } + + public void Enqueue(BaseMapEntity entity, TileAction action) + { + switch (action) + { + case TileAction.Add: + switch (entity) + { + case Tile tile: + Queue.Enqueue(async () => await Execute(async () => await Api.NewTile(tile, SessionData.SessionName))); + break; + case Overlay overlay: + Queue.Enqueue(async () => await Execute(async () => await Api.NewOverlay(overlay, SessionData.SessionName))); + break; + case Wall wall: + Queue.Enqueue(async () => await Execute(async () => await Api.NewWall(wall, SessionData.SessionName))); + break; + } + break; + + case TileAction.Delete: + switch (entity) + { + case Tile tile: + Queue.Enqueue(async () => await Execute(async () => await Api.DeleteTile(tile, SessionData.SessionName))); + break; + case Overlay overlay: + Queue.Enqueue(async () => await Execute(async () => await Api.DeleteOverlay(overlay, SessionData.SessionName))); + break; + case Wall wall: + Queue.Enqueue(async () => await Execute(async () => await Api.DeleteWall(wall, SessionData.SessionName))); + break; + } + break; + } + + } + } +} diff --git a/Sledgemapper/MyDatabase.db b/Sledgemapper/MyDatabase.db index f50d871f723100219ff3140bccd1f396f141f2ad..ca53fb411f671093e951a013ed4a1ab9964f4deb 100644 GIT binary patch literal 143360 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 delta 71 zcmZp8z|pXPWr8#-KLZ1U;6w#`M*fWnOZYi>`L_TC7z8Kt2uN*KGw9>ryhYwifQf0+ bCIglW%zBKMr?NA~ZY<1a+!`J diff --git a/Sledgemapper/Sledgemapper.cs b/Sledgemapper/Sledgemapper.cs index 8a0893c..7821992 100644 --- a/Sledgemapper/Sledgemapper.cs +++ b/Sledgemapper/Sledgemapper.cs @@ -1,4 +1,4 @@ -using Microsoft.AspNetCore.SignalR.Client; +using Microsoft.AspNetCore.SignalR.Client; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Input; @@ -12,44 +12,23 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System; -using System.Collections.Concurrent; using Sledgemapper.Shared.Entities; -using Refit; -using System.Net.Http; -using Polly.Retry; -using Polly.Timeout; -using Polly; -using Polly.Extensions.Http; -using System.Threading.Tasks; -using System.Net; namespace Sledgemapper { public class Sledgemapper : Game { + private CommunicationManager _communicationManager; + private State _state; private GraphicsDeviceManager _graphics; private SpriteBatch _spriteBatch; - private Tile _selectedTile = new Tile { X = 1, Y = 1 }; - private Tile _hoveredTile = new Tile { X = 1, Y = 1 }; - private Wall _selectedWall = new Wall { X = 1, Y = 1 }; - private Overlay _selectedOverlay = new Overlay { X = 1, Y = 1 }; - private int _tileSize = 30; - private HubConnection connection; - private AsyncRetryPolicy retryPolicy; - private AsyncTimeoutPolicy timeoutPolicy; private readonly Desktop _desktop; - private string _currentTileId = ""; - private string _currentWallId = ""; - private string _currentOverlayId = ""; - private InsertMode _insertMode; - private string _session; private KeyboardState oldState; private MouseState oldMouseState; private Vector3 _viewportCenter = new Vector3(0, 0, 0); private SpriteFont font; private Dictionary _fonts; - private SessionData _sessionData; - private IMapApi _api; + private Session _sessionData; public Sledgemapper() { @@ -57,118 +36,19 @@ namespace Sledgemapper Content.RootDirectory = "Content"; _desktop = new Desktop(); MyraEnvironment.Game = this; - _sessionData = new SessionData(); + _sessionData = new Session(); IsFixedTimeStep = false; + _communicationManager = new CommunicationManager(_sessionData); + _state = new State(); } - - protected override void Initialize() { - // TODO: Add your initialization logic here IsMouseVisible = true; Window.AllowUserResizing = true; - Players = new List(); - connection = new HubConnectionBuilder() - .WithAutomaticReconnect() - - .WithUrl("http://localhost:5000/SledgemapperHub") - - // .WithUrl("http://hub.michelescandura.com:5000/SledgemapperHub") - .Build(); - - retryPolicy = HttpPolicyExtensions - .HandleTransientHttpError() - .Or() // Thrown by Polly's TimeoutPolicy if the inner call gets timeout. - .WaitAndRetryAsync(2, _ => TimeSpan.FromMilliseconds(500)); - - timeoutPolicy = Policy - .TimeoutAsync(TimeSpan.FromMilliseconds(500)); - - var httpClientHandler = new HttpClientHandler(); - - //if (myConfigurationService.VerifySslCertificate == false) - //{ - httpClientHandler.ServerCertificateCustomValidationCallback = - (message, certificate, chain, sslPolicyErrors) => true; - //} - - _api = RestService.For( - new HttpClient(httpClientHandler) - { - BaseAddress = new Uri("http://localhost:5000") - } - - ); - - connection.On("UpdateMap", (map) => - { - _sessionData.Map = map.Map; - _sessionData.Walls = map.Walls; - _sessionData.Overlays = map.Overlays; - }); - - connection.On("PlayerUpdate", (player) => - { - var p = Players.FirstOrDefault(m => m.ConnectionId == player.ConnectionId); - if (p != null) - { - p.Position = player.Position; - } - }); - - connection.On("DeleteTile", (tile) => - { - _sessionData.Map.Remove(tile.ToString(), out var _); - }); - - connection.On("DeleteWall", (tile) => - { - _sessionData.Walls.Remove(tile.ToString(), out var _); - }); - - connection.On("DeleteOverlay", (tile) => - { - _sessionData.Overlays.Remove(tile.ToString(), out var _); - }); - - connection.On("NewTile", (tile) => - { - _sessionData.Map.Remove(tile.ToString(), out var _); - _sessionData.Map.TryAdd(tile.ToString(), tile); - }); - - connection.On("NewWall", (tile) => - { - _sessionData.Walls.Remove(tile.ToString(), out var _); - _sessionData.Walls.TryAdd(tile.ToString(), tile); - }); - - connection.On("NewOverlay", (tile) => - { - _sessionData.Overlays.Remove(tile.ToString(), out var _); - _sessionData.Overlays.TryAdd(tile.ToString(), tile); - }); - - connection.On("NewPlayer", (player) => - { - var p = Players.FirstOrDefault(m => m.ConnectionId == player.ConnectionId); - if (p is null) - { - Players.Add(player); - } - else - { - p.Color = player.Color; - p.Position = player.Position; - } - }); - base.Initialize(); } - public List Players { get; set; } - private HorizontalMenu BuildMenu() { var menu = new HorizontalMenu(); @@ -240,25 +120,27 @@ namespace Sledgemapper { return; } - if (connection.State != HubConnectionState.Connected) - { await connection.StartAsync(); } + if (_communicationManager.Connection.State != HubConnectionState.Connected) + { await _communicationManager.Connection.StartAsync(); } var successful = false; try { - var result = await connection?.InvokeAsync("JoinSession", textbox.Text, initialsTextbox.Text); + var result = await _communicationManager.Connection?.InvokeAsync("JoinSession", textbox.Text, initialsTextbox.Text); if (result != null) { _sessionData.Map = result.Map; _sessionData.Walls = result.Walls; _sessionData.Overlays = result.Overlays; - Players = result.Players; + _sessionData.Players = result.Players; + _sessionData.MapEntityAdded += OnMapEntityAdded; + } successful = result != null; ; } catch { } if (successful) { - _session = textbox.Text; + _sessionData.SessionName = textbox.Text; window.Close(); } }; @@ -311,34 +193,30 @@ namespace Sledgemapper { return; } - if (connection.State != HubConnectionState.Connected) - { await connection.StartAsync(); } + if (_communicationManager.Connection.State != HubConnectionState.Connected) + { await _communicationManager.Connection.StartAsync(); } var successful = false; try { - var session = await connection?.InvokeAsync("NewSession", textbox.Text, initialsTextbox.Text); + var session = await _communicationManager.Connection?.InvokeAsync("NewSession", textbox.Text, initialsTextbox.Text); if (session != null) { _sessionData = session; - session.TileAdded += OnTileAdded; - session.OverlayAdded += OnOverlayAdded; - session.WallAdded += OnWallAdded; - session.TileDeleted += OnTileDeleted; - session.WallDeleted += OnWallDeleted; - session.OverlayDeleted += OnOverlayDeleted; - Players = session.Players; + _sessionData.SessionName = textbox.Text; + session.MapEntityAdded += OnMapEntityAdded; + session.Players = session.Players; } successful = session != null; } catch (Exception ex) - { } if (successful) { - _session = textbox.Text; + _sessionData.SessionName = textbox.Text; + _communicationManager.SessionData = _sessionData; window.Close(); } }; @@ -395,7 +273,7 @@ namespace Sledgemapper using (StreamReader file = File.OpenText(dialog.FilePath)) { JsonSerializer serializer = new JsonSerializer(); - _sessionData = (SessionData)serializer.Deserialize(file, typeof(SessionData)); + _sessionData = (Session)serializer.Deserialize(file, typeof(Session)); } }; @@ -404,7 +282,7 @@ namespace Sledgemapper private async void OnMenuConnectSyncSelected(object sender, EventArgs e) { - await connection?.InvokeAsync("Sync", _session, _sessionData); + await _communicationManager.Connection?.InvokeAsync("Sync", _sessionData.SessionName, _sessionData); } protected override void LoadContent() @@ -445,7 +323,7 @@ namespace Sledgemapper var tileButton = new ImageButton { Image = new TextureRegion(item.Value), GridColumn = indexY, GridRow = indexX, Id = item.Key, Width = 40, Height = 40 }; tileButton.Click += (s, e) => { - _currentTileId = ((ImageButton)s).Id; + _state._currentTileId = ((ImageButton)s).Id; ClearSelection(wallGrid); ClearSelection(tileGrid); @@ -453,7 +331,7 @@ namespace Sledgemapper ((ImageButton)s).Border = new SolidBrush(Color.Red); ((ImageButton)s).BorderThickness = new Myra.Graphics2D.Thickness(2); - _insertMode = InsertMode.Tile; + _state._insertMode = InsertMode.Tile; }; tileGrid.Widgets.Add(tileButton); indexY++; @@ -473,7 +351,7 @@ namespace Sledgemapper var wallButton = new ImageButton { Image = new TextureRegion(item.Value), GridColumn = indexY, GridRow = indexX, Id = item.Key, Width = 40, Height = 40 }; wallButton.Click += (s, e) => { - _currentWallId = ((ImageButton)s).Id; + _state._currentWallId = ((ImageButton)s).Id; ClearSelection(wallGrid); ClearSelection(tileGrid); ClearSelection(overlayGrid); @@ -481,7 +359,7 @@ namespace Sledgemapper ((ImageButton)s).Border = new SolidBrush(Color.Red); ((ImageButton)s).BorderThickness = new Myra.Graphics2D.Thickness(2); - _insertMode = InsertMode.Wall; + _state._insertMode = InsertMode.Wall; }; wallGrid.Widgets.Add(wallButton); @@ -502,16 +380,14 @@ namespace Sledgemapper var overlayButton = new ImageButton { Image = new TextureRegion(item.Value), GridColumn = indexY, GridRow = indexX, Id = item.Key, Width = 40, Height = 40 }; overlayButton.Click += (s, e) => { - _currentOverlayId = ((ImageButton)s).Id; + _state._currentOverlayId = ((ImageButton)s).Id; ClearSelection(wallGrid); ClearSelection(tileGrid); ClearSelection(overlayGrid); - ((ImageButton)s).Border = new SolidBrush(Color.Red); ((ImageButton)s).BorderThickness = new Myra.Graphics2D.Thickness(2); - _insertMode = InsertMode.Overlay; - + _state._insertMode = InsertMode.Overlay; }; overlayGrid.Widgets.Add(overlayButton); indexY++; @@ -529,7 +405,7 @@ namespace Sledgemapper _desktop.Root = mainPanel; // TODO: use this.Content to load your game content here } - bool _draw; + protected override void Update(GameTime gameTime) { // if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed || Keyboard.GetState().IsKeyDown(Keys.Escape)) @@ -542,55 +418,55 @@ namespace Sledgemapper var screenPosition = new Point((mouseState.Position.X - (int)_viewportCenter.X), (mouseState.Position.Y - (int)_viewportCenter.Y)); - _hoveredTile.X = screenPosition.X / _tileSize; - _hoveredTile.Y = screenPosition.Y / _tileSize; + _state._hoveredTile.X = screenPosition.X / _state._tileSize; + _state._hoveredTile.Y = screenPosition.Y / _state._tileSize; if (screenPosition.X < 0) { - _hoveredTile.X--; + _state._hoveredTile.X--; } if (screenPosition.Y < 0) { - _hoveredTile.Y--; + _state._hoveredTile.Y--; } - if (_insertMode == InsertMode.Wall) + if (_state._insertMode == InsertMode.Wall) { - SelectClosestWall(screenPosition); + _state.SelectClosestWall(screenPosition); } - if (_insertMode == InsertMode.Overlay) + if (_state._insertMode == InsertMode.Overlay) { - SelectOverlay(screenPosition); + _state.SelectOverlay(screenPosition); } if (mouseState.LeftButton == ButtonState.Pressed && mouseState.LeftButton != oldMouseState.LeftButton) { - _selectedTile.X = _hoveredTile.X; - _selectedTile.Y = _hoveredTile.Y; - connection?.SendAsync("UpdatePosition", _session, _selectedTile); + _state._selectedTile.X = _state._hoveredTile.X; + _state._selectedTile.Y = _state._hoveredTile.Y; + _communicationManager.Connection?.SendAsync("UpdatePosition", _sessionData.SessionName, _state._selectedTile); } if (newState.IsKeyDown(Keys.LeftControl) && mouseState.LeftButton == ButtonState.Pressed - && ((mouseState.LeftButton != oldMouseState.LeftButton) || (_selectedTile.X != _hoveredTile.X && _selectedTile.Y != _hoveredTile.Y))) + && ((mouseState.LeftButton != oldMouseState.LeftButton) || (_state._selectedTile.X != _state._hoveredTile.X && _state._selectedTile.Y != _state._hoveredTile.Y))) { - switch (_insertMode) + switch (_state._insertMode) { case InsertMode.Tile: - _selectedTile.X = _hoveredTile.X; - _selectedTile.Y = _hoveredTile.Y; - connection?.SendAsync("UpdatePosition", _session, _selectedTile); + _state._selectedTile.X = _state._hoveredTile.X; + _state._selectedTile.Y = _state._hoveredTile.Y; + _communicationManager.Connection?.SendAsync("UpdatePosition", _sessionData.SessionName, _state._selectedTile); - _sessionData.NewTile(_selectedTile, _currentTileId); + _sessionData.NewTile(_state._selectedTile, _state._currentTileId); break; case InsertMode.Wall: - _sessionData.NewWall(_selectedWall, _currentWallId); + _sessionData.NewWall(_state._selectedWall, _state._currentWallId); break; case InsertMode.Overlay: - _sessionData.NewOverlay(_selectedOverlay, _currentOverlayId); + _sessionData.NewOverlay(_state._selectedOverlay, _state._currentOverlayId); break; } } @@ -604,12 +480,12 @@ namespace Sledgemapper { if (mouseState.ScrollWheelValue > oldMouseState.ScrollWheelValue) { - _tileSize = System.Math.Min(120, _tileSize + 10); + _state._tileSize = Math.Min(120, _state._tileSize + 10); } else if (mouseState.ScrollWheelValue < oldMouseState.ScrollWheelValue) { - _tileSize = System.Math.Max(10, _tileSize - 10); + _state._tileSize = Math.Max(10, _state._tileSize - 10); } } @@ -619,18 +495,18 @@ namespace Sledgemapper if (newState.IsKeyDown(Keys.Delete)) { - switch (_insertMode) + switch (_state._insertMode) { case InsertMode.Tile: - _selectedTile.X = _hoveredTile.X; - _selectedTile.Y = _hoveredTile.Y; - _sessionData.DeleteTile(_selectedTile); + _state._selectedTile.X = _state._hoveredTile.X; + _state._selectedTile.Y = _state._hoveredTile.Y; + _sessionData.DeleteTile(_state._selectedTile); break; case InsertMode.Wall: - _sessionData.DeleteWall(_selectedWall); + _sessionData.DeleteWall(_state._selectedWall); break; case InsertMode.Overlay: - _sessionData.DeleteOverlay(_selectedOverlay); + _sessionData.DeleteOverlay(_state._selectedOverlay); break; } } @@ -641,22 +517,22 @@ namespace Sledgemapper { case Keys.Left: if (oldState.IsKeyUp(Keys.Left) && newState.IsKeyDown(Keys.Left)) - { _selectedTile.X--; } + { _state._selectedTile.X--; } break; case Keys.Right: if (oldState.IsKeyUp(Keys.Right) && newState.IsKeyDown(Keys.Right)) - { _selectedTile.X++; } + { _state._selectedTile.X++; } break; case Keys.Up: if (oldState.IsKeyUp(Keys.Up) && newState.IsKeyDown(Keys.Up)) - { _selectedTile.Y--; } + { _state._selectedTile.Y--; } break; case Keys.Down: if (oldState.IsKeyUp(Keys.Down) && newState.IsKeyDown(Keys.Down)) - { _selectedTile.Y++; } + { _state._selectedTile.Y++; } break; } - connection?.SendAsync("UpdatePosition", _session, _selectedTile); + _communicationManager.Connection?.SendAsync("UpdatePosition", _sessionData.SessionName, _state._selectedTile); } @@ -672,24 +548,57 @@ namespace Sledgemapper return; } GraphicsDevice.Clear(Color.DarkGray); - var sessionData = _sessionData; // TODO: Add your drawing code here - var visibleTilesX = GraphicsDevice.Viewport.Width / _tileSize + 1; - var visibleTilesY = GraphicsDevice.Viewport.Height / _tileSize + 1; + var visibleTilesX = GraphicsDevice.Viewport.Width / _state._tileSize + 1; + var visibleTilesY = GraphicsDevice.Viewport.Height / _state._tileSize + 1; _spriteBatch.Begin(transformMatrix: Matrix.CreateTranslation(_viewportCenter)); + DrawGrid(visibleTilesX, visibleTilesY); + DrawTiles(); + DrawWalls(); + DrawOverlays(); + + if (string.IsNullOrWhiteSpace(_sessionData.SessionName)) + { + _spriteBatch.DrawRectangle(new Rectangle(_state._selectedTile.X * _state._tileSize, _state._selectedTile.Y * _state._tileSize, _state._tileSize - 1, _state._tileSize - 1), Color.Red, 2); + } + + DrawPlayers(); + + var startWall = new Vector2(_state._selectedWall.X * _state._tileSize, _state._selectedWall.Y * _state._tileSize); + if (_state._insertMode == InsertMode.Wall) + { + _spriteBatch.DrawLine(startWall, _state._tileSize, MathHelper.ToRadians(90 * _state._selectedWall.Rotation), Color.Red, 2); + } + var overlay = new Vector2(_state._selectedOverlay.X * _state._tileSize, _state._selectedOverlay.Y * _state._tileSize); + if (_state._insertMode == InsertMode.Overlay) + { + if (_state._selectedOverlay.Intersection) + { + _spriteBatch.DrawCircle(overlay, _state._tileSize / 3, 100, Color.Red, 2); + } + } + + _spriteBatch.End(); + + _desktop?.Render(); + base.Draw(gameTime); + } + + private void DrawGrid(int visibleTilesX, int visibleTilesY) + { for (var i = -1; i < visibleTilesX + 2; i++) { - var posX1 = i * _tileSize - _viewportCenter.X; + var posX1 = i * _state._tileSize - _viewportCenter.X; var posY1 = -_viewportCenter.Y; - posX1 = posX1 - posX1 % _tileSize; - posY1 = posY1 - posY1 % _tileSize; - var posX2 = i * _tileSize - _viewportCenter.X; + posX1 = posX1 - posX1 % _state._tileSize; + posY1 = posY1 - posY1 % _state._tileSize; + var posX2 = i * _state._tileSize - _viewportCenter.X; var posY2 = GraphicsDevice.Viewport.Height - _viewportCenter.Y; - posX2 = posX2 - posX2 % _tileSize; - posY2 = posY2 - posY2 % _tileSize; + posX2 = posX2 - posX2 % _state._tileSize; + posY2 = posY2 - posY2 % _state._tileSize; _spriteBatch.DrawLine( posX1, posY1, @@ -701,41 +610,93 @@ namespace Sledgemapper for (var i = -1; i < visibleTilesY + 2; i++) { var posX1 = -_viewportCenter.X; - var posY1 = i * _tileSize - _viewportCenter.Y; - posX1 = posX1 - posX1 % _tileSize; - posY1 = posY1 - posY1 % _tileSize; + var posY1 = i * _state._tileSize - _viewportCenter.Y; + posX1 = posX1 - posX1 % _state._tileSize; + posY1 = posY1 - posY1 % _state._tileSize; var posX2 = GraphicsDevice.Viewport.Width - _viewportCenter.X; - var posY2 = i * _tileSize - _viewportCenter.Y; - posX2 = posX2 - posX2 % _tileSize; - posY2 = posY2 - posY2 % _tileSize; + var posY2 = i * _state._tileSize - _viewportCenter.Y; + posX2 = posX2 - posX2 % _state._tileSize; + posY2 = posY2 - posY2 % _state._tileSize; _spriteBatch.DrawLine(posX1, posY1, posX2, posY2, Color.Black); } + } - foreach (var tile in sessionData.Map.Values) + private void DrawTiles() + { + foreach (var tile in _sessionData.Map.Values) { - var content = Content.Load($"tiles/{tile.ID}"); - var destinationRectangle = new Rectangle(tile.X * _tileSize, tile.Y * _tileSize, _tileSize, _tileSize); - var posX = tile.X * _tileSize + _tileSize / 2f; - var posY = tile.Y * _tileSize + _tileSize / 2f; + var posX = tile.X * _state._tileSize + _state._tileSize / 2f; + var posY = tile.Y * _state._tileSize + _state._tileSize / 2f; _spriteBatch.Draw(content, new Vector2(posX, posY), - null, Color.White, MathHelper.ToRadians(90 * tile.Rotation), new Vector2(content.Width / 2, content.Height / 2), ((float)_tileSize - 1) / content.Width, SpriteEffects.None, 0); + null, Color.White, MathHelper.ToRadians(90 * tile.Rotation), new Vector2(content.Width / 2, content.Height / 2), ((float)_state._tileSize - 1) / content.Width, SpriteEffects.None, 0); } + } - foreach (var wall in sessionData.Walls.Values) + private void DrawPlayers() + { + foreach (var player in _sessionData.Players.Copy>()) { + var hexs = player.Color.Split(2).ToArray(); + var color = new Color(int.Parse(hexs[0], System.Globalization.NumberStyles.HexNumber), + int.Parse(hexs[1], System.Globalization.NumberStyles.HexNumber), + int.Parse(hexs[2], System.Globalization.NumberStyles.HexNumber)); + _spriteBatch.DrawRectangle(new Rectangle(player.Position.X * _state._tileSize, player.Position.Y * _state._tileSize, _state._tileSize - 1, _state._tileSize - 1), color, 2); + var ffont = _fonts.FirstOrDefault(m => int.Parse(m.Key.Replace("font", "")) > _state._tileSize).Value ?? _fonts.Last().Value; + + var fscale = (float)_state._tileSize / ((float)ffont.LineSpacing * 2); + _spriteBatch.DrawString(ffont, + player.Initials, + new Vector2(player.Position.X * _state._tileSize + 2, player.Position.Y * _state._tileSize + _state._tileSize - 2 - ffont.LineSpacing * fscale), + color, + 0, + Vector2.Zero, + fscale, + SpriteEffects.None, + 0); + } + } + + private void DrawOverlays() + { + foreach (var tile in _sessionData.Overlays.Values) + { + var content = Content.Load($"overlays/{tile.ID}"); + if (tile.Intersection) + { + var posX = tile.X * _state._tileSize; + var posY = tile.Y * _state._tileSize; + + _spriteBatch.Draw(content, new Vector2(posX, posY), + null, new Color(24, 118, 157), MathHelper.ToRadians(90 * tile.Rotation), new Vector2(content.Width / 2, content.Height / 2), ((float)_state._tileSize - 10) / content.Width, SpriteEffects.None, 0); + } + else + { + var posX = tile.X * _state._tileSize + _state._tileSize / 2f; + var posY = tile.Y * _state._tileSize + _state._tileSize / 2f; + + _spriteBatch.Draw(content, new Vector2(posX, posY), + null, new Color(24, 118, 157), MathHelper.ToRadians(90 * tile.Rotation), new Vector2(content.Width / 2, content.Height / 2), ((float)_state._tileSize - 10) / content.Width, SpriteEffects.None, 0); + } + } + } + + private void DrawWalls() + { + foreach (var wall in _sessionData.Walls.Values) + { var content = Content.Load($"walls/{wall.ID}"); - var scale = _tileSize / (float)content.Height; + var scale = _state._tileSize / (float)content.Height; var offset = scale * content.Width / 2f; - var posX = wall.X * _tileSize; - var posY = wall.Y * _tileSize; + var posX = wall.X * _state._tileSize; + var posY = wall.Y * _state._tileSize; if (wall.Rotation == 1) { posX -= (int)offset; @@ -744,82 +705,8 @@ namespace Sledgemapper { posY += (int)offset; } - // _spriteBatch.Draw(content, new Vector2(posX, posY),null, Color.White, MathHelper.ToRadians(90 * (wall.Rotation - 1)), new Vector2(offset, 0), scale, SpriteEffects.None, 0); _spriteBatch.Draw(content, new Vector2(posX, posY), null, Color.White, MathHelper.ToRadians(90 * (wall.Rotation - 1)), new Vector2(0, 0), scale, SpriteEffects.None, 0); - - } - - foreach (var tile in sessionData.Overlays.Values) - { - - var content = Content.Load($"overlays/{tile.ID}"); - // System.Console.WriteLine(tile.Rotation); - if (tile.Intersection) - { - var posX = tile.X * _tileSize; - var posY = tile.Y * _tileSize; - - _spriteBatch.Draw(content, new Vector2(posX, posY), - null, new Color(24, 118, 157), MathHelper.ToRadians(90 * tile.Rotation), new Vector2(content.Width / 2, content.Height / 2), ((float)_tileSize - 10) / content.Width, SpriteEffects.None, 0); - - } - else - { - var posX = tile.X * _tileSize + _tileSize / 2f; - var posY = tile.Y * _tileSize + _tileSize / 2f; - - _spriteBatch.Draw(content, new Vector2(posX, posY), - null, new Color(24, 118, 157), MathHelper.ToRadians(90 * tile.Rotation), new Vector2(content.Width / 2, content.Height / 2), ((float)_tileSize - 10) / content.Width, SpriteEffects.None, 0); - } - } - - if (string.IsNullOrWhiteSpace(_session)) - { - _spriteBatch.DrawRectangle(new Rectangle(_selectedTile.X * _tileSize, _selectedTile.Y * _tileSize, _tileSize - 1, _tileSize - 1), Color.Red, 2); - } - - foreach (var player in Players.Copy>()) - { - - var hexs = player.Color.Split(2).ToArray(); - var color = new Color(int.Parse(hexs[0], System.Globalization.NumberStyles.HexNumber), - int.Parse(hexs[1], System.Globalization.NumberStyles.HexNumber), - int.Parse(hexs[2], System.Globalization.NumberStyles.HexNumber)); - _spriteBatch.DrawRectangle(new Rectangle(player.Position.X * _tileSize, player.Position.Y * _tileSize, _tileSize - 1, _tileSize - 1), color, 2); - - var ffont = _fonts.FirstOrDefault(m => int.Parse(m.Key.Replace("font", "")) > _tileSize).Value ?? _fonts.Last().Value; - - var fscale = (float)_tileSize / ((float)ffont.LineSpacing * 2); - _spriteBatch.DrawString(ffont, - player.Initials, - new Vector2(player.Position.X * _tileSize + 2, player.Position.Y * _tileSize + _tileSize - 2 - ffont.LineSpacing * fscale), - color, - 0, - Vector2.Zero, - fscale, - SpriteEffects.None, - 0); - } - - var startWall = new Vector2(_selectedWall.X * _tileSize, _selectedWall.Y * _tileSize); - if (_insertMode == InsertMode.Wall) - { - _spriteBatch.DrawLine(startWall, _tileSize, MathHelper.ToRadians(90 * _selectedWall.Rotation), Color.Red, 2); - } - var overlay = new Vector2(_selectedOverlay.X * _tileSize, _selectedOverlay.Y * _tileSize); - if (_insertMode == InsertMode.Overlay) - { - if (_selectedOverlay.Intersection) - { - _spriteBatch.DrawCircle(overlay, _tileSize / 3, 100, Color.Red, 2); - } - } - - _spriteBatch.End(); - - _desktop?.Render(); - base.Draw(gameTime); } private void ClearSelection(Grid grid) @@ -830,136 +717,20 @@ namespace Sledgemapper } } - private float Sign(Point p1, Point p2, Point p3) { return (p1.X - p3.X) * (p2.Y - p3.Y) - (p2.X - p3.X) * (p1.Y - p3.Y); } - - private bool PointInTri(Point pt, Point v1, Point v2, Point v3) + private void OnMapEntityAdded(object sender, MapEntityAddedEventArgs e) { - bool b1, b2, b3; - b1 = Sign(pt, v1, v2) < 0.0f; - b2 = Sign(pt, v2, v3) < 0.0f; - b3 = Sign(pt, v3, v1) < 0.0f; - return ((b1 == b2) && (b2 == b3)); + _communicationManager.Enqueue(e.MapEntity, TileAction.Add); } - private void SelectClosestWall(Point mousePosition) + private void OnMapEntityDeleted(object sender, MapEntityDeletedEventArgs e) { - var topLeft = new Point(_hoveredTile.X * _tileSize, _hoveredTile.Y * _tileSize); - var bottomLeft = new Point(_hoveredTile.X * _tileSize, _hoveredTile.Y * _tileSize + _tileSize); - var topRight = new Point(_hoveredTile.X * _tileSize + _tileSize, _hoveredTile.Y * _tileSize); - var bottomRight = new Point(_hoveredTile.X * _tileSize + _tileSize, _hoveredTile.Y * _tileSize + _tileSize); - var center = new Point(_hoveredTile.X * _tileSize + _tileSize / 2, _hoveredTile.Y * _tileSize + _tileSize / 2); - var leftWall = PointInTri(mousePosition, topLeft, center, bottomLeft); - var rightWall = PointInTri(mousePosition, topRight, bottomRight, center); - var topWall = PointInTri(mousePosition, topLeft, topRight, center); - var bottomtWall = PointInTri(mousePosition, bottomLeft, center, bottomRight); - - if (leftWall) - { - _selectedWall.X = _hoveredTile.X; - _selectedWall.Y = _hoveredTile.Y; - _selectedWall.Rotation = 1; - } - else if (rightWall) - { - _selectedWall.X = _hoveredTile.X + 1; - _selectedWall.Y = _hoveredTile.Y; - _selectedWall.Rotation = 1; - } - else if (topWall) - { - _selectedWall.X = _hoveredTile.X; - _selectedWall.Y = _hoveredTile.Y; - _selectedWall.Rotation = 0; - } - else if (bottomtWall) - { - _selectedWall.X = _hoveredTile.X; - _selectedWall.Y = _hoveredTile.Y + 1; - _selectedWall.Rotation = 0; - } - } - - private void SelectOverlay(Point mousePosition) - { - _selectedOverlay.X = _hoveredTile.X; - _selectedOverlay.Y = _hoveredTile.Y; - var q1 = System.Math.Pow(mousePosition.X - _hoveredTile.X * _tileSize, 2); - var q2 = System.Math.Pow((_hoveredTile.Y * _tileSize - mousePosition.Y), 2); - var s = System.Math.Sqrt(q1 + q2); - - if (s < _tileSize / 3) - { - _selectedOverlay.Intersection = true; - return; - } - - q1 = System.Math.Pow(mousePosition.X - (_hoveredTile.X + 1) * _tileSize, 2); - // var q2 = System.Math.Pow((_hoveredTile.Y * _tileSize - mousePosition.Y), 2); - s = System.Math.Sqrt(q1 + q2); - if (s < _tileSize / 3) - { - _selectedOverlay.X = _selectedOverlay.X + 1; - _selectedOverlay.Intersection = true; - return; - } - - //q1 = System.Math.Pow(mousePosition.X - (_hoveredTile.X + 1) * _tileSize, 2); - q2 = System.Math.Pow(((_hoveredTile.Y + 1) * _tileSize - mousePosition.Y), 2); - s = System.Math.Sqrt(q1 + q2); - if (s < _tileSize / 3) - { - _selectedOverlay.X = _selectedOverlay.X + 1; - _selectedOverlay.Y = _selectedOverlay.Y + 1; - _selectedOverlay.Intersection = true; - return; - } - - q1 = System.Math.Pow(mousePosition.X - _hoveredTile.X * _tileSize, 2); - q2 = System.Math.Pow(((_hoveredTile.Y + 1) * _tileSize - mousePosition.Y), 2); - s = System.Math.Sqrt(q1 + q2); - if (s < _tileSize / 3) - { - _selectedOverlay.X = _selectedOverlay.X; - _selectedOverlay.Y = _selectedOverlay.Y + 1; - _selectedOverlay.Intersection = true; - return; - } - - _selectedOverlay.Intersection = false; - } - - private async Task Execute(Func call) - { - await Policy - .Handle(ex => ex.StatusCode == HttpStatusCode.RequestTimeout) - .RetryAsync(5, async (exception, retryCount) => await Task.Delay(500)) - .ExecuteAsync(async () => await call().ConfigureAwait(false)) - .ConfigureAwait(false); - } - - private async void OnOverlayAdded(object sender, OverlayAddedEventArgs e) - { - await Execute(() => _api.NewOverlay(e.Overlay, _session)); - } - private void OnTileAdded(object sender, TileAddedEventArgs e) - { - _api.NewTile(e.Tile, _session); - } - private void OnWallAdded(object sender, WallAddedEventArgs e) - { - _api.NewWall(e.Wall, _session); - } - private void OnOverlayDeleted(object sender, OverlayDeletedEventArgs e) - { - _api.DeleteOverlay(e.Overlay, _session); - } - private void OnTileDeleted(object sender, TileDeletedEventArgs e) - { - _api.DeleteTile(e.Tile, _session); - } - private void OnWallDeleted(object sender, WallDeletedEventArgs e) - { - _api.DeleteWall(e.Wall, _session); + _communicationManager.Enqueue(e.MapEntity, TileAction.Delete); } } + + public enum TileAction + { + Add, + Delete + } } diff --git a/Sledgemapper/Sledgemapper.csproj b/Sledgemapper/Sledgemapper.csproj index 7e542df..6b2424c 100644 --- a/Sledgemapper/Sledgemapper.csproj +++ b/Sledgemapper/Sledgemapper.csproj @@ -35,6 +35,7 @@ + diff --git a/Sledgemapper/State.cs b/Sledgemapper/State.cs new file mode 100644 index 0000000..c8e372c --- /dev/null +++ b/Sledgemapper/State.cs @@ -0,0 +1,100 @@ +using Microsoft.Xna.Framework; +using System; +using Sledgemapper.Shared.Entities; + +namespace Sledgemapper +{ + public class State + { + public Tile _selectedTile = new Tile { X = 1, Y = 1 }; + public Tile _hoveredTile = new Tile { X = 1, Y = 1 }; + public Wall _selectedWall = new Wall { X = 1, Y = 1 }; + public Overlay _selectedOverlay = new Overlay { X = 1, Y = 1 }; + public int _tileSize = 30; + public string _currentTileId = ""; + public string _currentWallId = ""; + public string _currentOverlayId = ""; + public InsertMode _insertMode; + + public void SelectClosestWall(Point mousePosition) + { + var topLeft = new Point(_hoveredTile.X * _tileSize, _hoveredTile.Y * _tileSize); + var bottomLeft = new Point(_hoveredTile.X * _tileSize, _hoveredTile.Y * _tileSize + _tileSize); + var topRight = new Point(_hoveredTile.X * _tileSize + _tileSize, _hoveredTile.Y * _tileSize); + var bottomRight = new Point(_hoveredTile.X * _tileSize + _tileSize, _hoveredTile.Y * _tileSize + _tileSize); + var center = new Point(_hoveredTile.X * _tileSize + _tileSize / 2, _hoveredTile.Y * _tileSize + _tileSize / 2); + + if (Utils.PointInTri(mousePosition, topLeft, center, bottomLeft)) //left wall + { + _selectedWall.X = _hoveredTile.X; + _selectedWall.Y = _hoveredTile.Y; + _selectedWall.Rotation = 1; + } + else if (Utils.PointInTri(mousePosition, topRight, bottomRight, center)) //right wall + { + _selectedWall.X = _hoveredTile.X + 1; + _selectedWall.Y = _hoveredTile.Y; + _selectedWall.Rotation = 1; + } + else if (Utils.PointInTri(mousePosition, topLeft, topRight, center)) //top wall + { + _selectedWall.X = _hoveredTile.X; + _selectedWall.Y = _hoveredTile.Y; + _selectedWall.Rotation = 0; + } + else if (Utils.PointInTri(mousePosition, bottomLeft, center, bottomRight)) //bottom wall + { + _selectedWall.X = _hoveredTile.X; + _selectedWall.Y = _hoveredTile.Y + 1; + _selectedWall.Rotation = 0; + } + } + public void SelectOverlay(Point mousePosition) + { + _selectedOverlay.X = _hoveredTile.X; + _selectedOverlay.Y = _hoveredTile.Y; + var q1 = Math.Pow(mousePosition.X - _hoveredTile.X * _tileSize, 2); + var q2 = Math.Pow((_hoveredTile.Y * _tileSize - mousePosition.Y), 2); + var s = Math.Sqrt(q1 + q2); + + if (s < _tileSize / 3) + { + _selectedOverlay.Intersection = true; + return; + } + + q1 = Math.Pow(mousePosition.X - (_hoveredTile.X + 1) * _tileSize, 2); + s = Math.Sqrt(q1 + q2); + if (s < _tileSize / 3) + { + _selectedOverlay.X = _selectedOverlay.X + 1; + _selectedOverlay.Intersection = true; + return; + } + + //q1 = System.Math.Pow(mousePosition.X - (_hoveredTile.X + 1) * _tileSize, 2); + q2 = Math.Pow(((_hoveredTile.Y + 1) * _tileSize - mousePosition.Y), 2); + s = Math.Sqrt(q1 + q2); + if (s < _tileSize / 3) + { + _selectedOverlay.X = _selectedOverlay.X + 1; + _selectedOverlay.Y = _selectedOverlay.Y + 1; + _selectedOverlay.Intersection = true; + return; + } + + q1 = Math.Pow(mousePosition.X - _hoveredTile.X * _tileSize, 2); + q2 = Math.Pow(((_hoveredTile.Y + 1) * _tileSize - mousePosition.Y), 2); + s = Math.Sqrt(q1 + q2); + if (s < _tileSize / 3) + { + _selectedOverlay.X = _selectedOverlay.X; + _selectedOverlay.Y = _selectedOverlay.Y + 1; + _selectedOverlay.Intersection = true; + return; + } + + _selectedOverlay.Intersection = false; + } + } +} diff --git a/Sledgemapper/Utils.cs b/Sledgemapper/Utils.cs new file mode 100644 index 0000000..36b8dd8 --- /dev/null +++ b/Sledgemapper/Utils.cs @@ -0,0 +1,20 @@ +using Microsoft.Xna.Framework; +using Sledgemapper.Shared.Entities; +using System; + +namespace Sledgemapper +{ + public static class Utils + { + private static float Sign(Point p1, Point p2, Point p3) { return (p1.X - p3.X) * (p2.Y - p3.Y) - (p2.X - p3.X) * (p1.Y - p3.Y); } + + public static bool PointInTri(Point pt, Point v1, Point v2, Point v3) + { + bool b1, b2, b3; + b1 = Sign(pt, v1, v2) < 0.0f; + b2 = Sign(pt, v2, v3) < 0.0f; + b3 = Sign(pt, v3, v1) < 0.0f; + return ((b1 == b2) && (b2 == b3)); + } + } +} \ No newline at end of file