diff --git a/VTT/Control/TurnTracker.cs b/VTT/Control/TurnTracker.cs index fd5d4e1..2bb256e 100644 --- a/VTT/Control/TurnTracker.cs +++ b/VTT/Control/TurnTracker.cs @@ -28,7 +28,7 @@ public TurnTracker(Map container) } public void Sort() => this.Entries.Sort((l, r) => r.NumericValue.CompareTo(l.NumericValue)); - public void Add(Entry e, int idx) + public int Add(Entry e, int idx) { if (idx <= this.EntryIndex) { @@ -38,10 +38,12 @@ public void Add(Entry e, int idx) if (idx >= this.Entries.Count) { this.Entries.Add(e); + return this.Entries.Count - 1; } else { this.Entries.Insert(idx, e); + return idx; } } diff --git a/VTT/Network/Packet/PacketAddOrUpdateDrawing.cs b/VTT/Network/Packet/PacketAddOrUpdateDrawing.cs index 58d50a6..d62a663 100644 --- a/VTT/Network/Packet/PacketAddOrUpdateDrawing.cs +++ b/VTT/Network/Packet/PacketAddOrUpdateDrawing.cs @@ -3,6 +3,7 @@ using System; using System.IO; using VTT.Control; + using VTT.Network.UndoRedo; using VTT.Util; public class PacketAddOrUpdateDrawing : PacketBase @@ -52,6 +53,7 @@ public override void Act(Guid sessionID, Server server, Client client, bool isSe m.Drawings.Add(this.DPC); } + this.Sender.ActionMemory.NewAction(new DrawingAction() { Container = m, DPC = this.DPC, LastModifyTime = DateTime.Now }); m.NeedsSave = true; this.Broadcast(c => c.ClientMapID.Equals(this.MapID)); } diff --git a/VTT/Network/Packet/PacketAddTurnEntry.cs b/VTT/Network/Packet/PacketAddTurnEntry.cs index 38820d9..dd5391b 100644 --- a/VTT/Network/Packet/PacketAddTurnEntry.cs +++ b/VTT/Network/Packet/PacketAddTurnEntry.cs @@ -3,6 +3,7 @@ using System; using System.IO; using VTT.Control; + using VTT.Network.UndoRedo; using VTT.Util; public class PacketAddTurnEntry : PacketBase @@ -13,7 +14,6 @@ public class PacketAddTurnEntry : PacketBase public int AdditionIndex { get; set; } public override uint PacketID => 2; - public override void Act(Guid sessionID, Server server, Client client, bool isServer) { Logger l = this.GetContextLogger(); @@ -33,14 +33,16 @@ public override void Act(Guid sessionID, Server server, Client client, bool isSe TurnTracker.Team t = m.TurnTracker.Teams.Find(p => p.Name.Equals(this.TeamName)) ?? m.TurnTracker.Teams[0]; TurnTracker.Entry e = new TurnTracker.Entry() { NumericValue = this.Value, ObjectID = this.ObjectID, Team = t }; + int ei; lock (m.TurnTracker.Lock) { - m.TurnTracker.Add(e, this.AdditionIndex == -1 ? m.TurnTracker.Entries.Count : this.AdditionIndex); + ei = m.TurnTracker.Add(e, this.AdditionIndex == -1 ? m.TurnTracker.Entries.Count : this.AdditionIndex); } if (isServer) { m.NeedsSave = true; + this.Sender.ActionMemory.NewAction(new AddTurnEntryAction() { EntryIndex = ei, AdditionIndex = this.AdditionIndex, EntryObjectID = this.ObjectID, Map = m, NumericValue = this.Value, TeamName = this.TeamName }); this.Broadcast(c => c.ClientMapID.Equals(m.ID)); } } diff --git a/VTT/Network/Packet/PacketAnimationRequest.cs b/VTT/Network/Packet/PacketAnimationRequest.cs index 4e2f2d4..476816e 100644 --- a/VTT/Network/Packet/PacketAnimationRequest.cs +++ b/VTT/Network/Packet/PacketAnimationRequest.cs @@ -74,6 +74,7 @@ public override void Act(Guid sessionID, Server server, Client client, bool isSe } } + // TODO animation request undo/redo memory! m.NeedsSave = true; this.Broadcast(x => x.ClientMapID.Equals(this.MapID)); } diff --git a/VTT/Network/Packet/PacketAura.cs b/VTT/Network/Packet/PacketAura.cs index 60b12e5..f29ca46 100644 --- a/VTT/Network/Packet/PacketAura.cs +++ b/VTT/Network/Packet/PacketAura.cs @@ -4,13 +4,14 @@ using System; using System.IO; using VTT.Control; + using VTT.Network.UndoRedo; using VTT.Util; public class PacketAura : PacketBase { public Guid MapID { get; set; } public Guid ObjectID { get; set; } - public int Index { get; set; } + public int Index { get; set; } = -1; public Color AuraColor { get; set; } public float AuraRange { get; set; } public Action ActionType { get; set; } @@ -63,7 +64,19 @@ public override void Act(Guid sessionID, Server server, Client client, bool isSe { lock (mo.Lock) { - mo.Auras.Add((this.AuraRange, this.AuraColor)); + if (this.Index == -1) + { + mo.Auras.Add((this.AuraRange, this.AuraColor)); + } + else + { + mo.Auras.Insert(this.Index, (this.AuraRange, this.AuraColor)); + } + } + + if (isServer) + { + this.Sender.ActionMemory.NewAction(new AuraAddOrDeleteAction() { IsAddition = true, AuraColor = this.AuraColor, AuraContainer = mo, AuraIndex = mo.Auras.Count - 1, AuraRange = this.AuraRange }); } break; @@ -71,17 +84,29 @@ public override void Act(Guid sessionID, Server server, Client client, bool isSe case Action.Delete: { + (float, Color) data = mo.Auras[this.Index]; lock (mo.Lock) { mo.Auras.RemoveAt(this.Index); } + if (isServer) + { + this.Sender.ActionMemory.NewAction(new AuraAddOrDeleteAction() { IsAddition = false, AuraColor = data.Item2, AuraContainer = mo, AuraIndex = this.Index, AuraRange = data.Item1 }); + } + break; } case Action.Update: { + (float, Color) data = mo.Auras[this.Index]; mo.Auras[this.Index] = (this.AuraRange, this.AuraColor); + if (isServer) + { + this.Sender.ActionMemory.NewAction(new AuraChangeAction() { AuraContainer = mo, AuraIndex = this.Index, InitialAuraColor = data.Item2, InitialAuraRange = data.Item1, LastModifyTime = DateTime.Now, NewAuraColor = this.AuraColor, NewAuraRange = this.AuraRange }); + } + break; } } diff --git a/VTT/Network/Packet/PacketHandshake.cs b/VTT/Network/Packet/PacketHandshake.cs index dbac2cf..2c45c4c 100644 --- a/VTT/Network/Packet/PacketHandshake.cs +++ b/VTT/Network/Packet/PacketHandshake.cs @@ -90,6 +90,11 @@ public override void Act(Guid sessionID, Server server, Client client, bool isSe } server.ClientInfos[ci.ID] = ci; + if (sc.IsAdmin) + { + sc.ActionMemory.ActionBufferSize = 128; + } + PacketClientInfo pci = new PacketClientInfo() { IsAdmin = sc.IsAdmin, IsObserver = sc.IsObserver, Session = sessionID, IsServer = isServer }; pci.Send(sc); new PacketClientData() { InfosToUpdate = server.ClientInfos.Values.ToList() }.Send(sc); diff --git a/VTT/Network/Server.cs b/VTT/Network/Server.cs index d1a5524..284cf73 100644 --- a/VTT/Network/Server.cs +++ b/VTT/Network/Server.cs @@ -14,6 +14,7 @@ using VTT.Asset; using VTT.Control; using VTT.Network.Packet; + using VTT.Network.UndoRedo; using VTT.Util; public class Server : TcpServer @@ -769,6 +770,7 @@ public class ServerClient : TcpSession { public ClientInfo Info { get; set; } public long LastPingResponseTime { get; set; } + public ActionMemory ActionMemory { get; set; } public Guid ID { @@ -819,6 +821,7 @@ public ServerClient(TcpServer server) : base(server) { this.Container = (Server)server; this.LocalNetManager = new PacketNetworkManager() { IsServer = true }; + this.ActionMemory = new ActionMemory(this); } public void SetClientInfo(ClientInfo info) diff --git a/VTT/Network/UndoRedo/ActionMemory.cs b/VTT/Network/UndoRedo/ActionMemory.cs new file mode 100644 index 0000000..9fec936 --- /dev/null +++ b/VTT/Network/UndoRedo/ActionMemory.cs @@ -0,0 +1,116 @@ +namespace VTT.Network.UndoRedo +{ + using System; + using System.Collections.Generic; + using VTT.Control; + using VTT.Network.Packet; + + public class ActionMemory + { + private readonly List _actions = new List(); + private readonly object _lock = new object(); + private ServerClient _owner; + + public ActionMemory(ServerClient serverClient) => this._owner = serverClient; + + public Guid Owner { get; set; } + public int ActionAmount => _actions.Count; + public int CurrentIndex { get; set; } = -1; + public int ActionBufferSize { get; set; } = 32; + + public void NewAction(ServerAction sa) + { + if (sa is SmallChangeAction sca && _actions.Count > 0) + { + lock (_lock) + { + ServerAction csa = _actions[CurrentIndex]; + if (csa is SmallChangeAction sccsa && sccsa.ActionType == sa.ActionType) + { + if (sccsa.AcceptSmallChange(sca)) + { + return; + } + } + } + } + + lock (_lock) + { + if (CurrentIndex < _actions.Count - 1) + { + _actions.RemoveRange(CurrentIndex + 1, _actions.Count - CurrentIndex - 1); + } + + if (this._actions.Count + 1 > this.ActionBufferSize) + { + this._actions.RemoveAt(0); + --this.CurrentIndex; + } + + _actions.Add(sa); + ++CurrentIndex; + } + } + + public bool UndoAction() + { + lock (_lock) + { + if (_actions.Count > 0 && CurrentIndex >= 0) + { + ServerAction sa = _actions[CurrentIndex]; + sa.Undo(); + --CurrentIndex; + return true; + } + + return false; + } + } + + public bool RedoAction() + { + lock (_lock) + { + if (_actions.Count < CurrentIndex + 1) + { + ServerAction sa = _actions[CurrentIndex + 1]; + sa.Redo(); + ++CurrentIndex; + return true; + } + + return false; + } + } + } + + public abstract class ServerAction + { + public abstract ServerActionType ActionType { get; } + + public abstract void Undo(); + public abstract void Redo(); + + public void SendToAllOnMap(Map cMap, PacketBase packet) => packet.Broadcast(x => x.ClientMapID.Equals(cMap.ID)); + } + + public abstract class SmallChangeAction : ServerAction + { + public DateTime LastModifyTime { get; set; } + + public abstract bool AcceptSmallChange(SmallChangeAction newAction); + + public bool CheckIfRecent(DateTime otherTime, int ms) => (otherTime - this.LastModifyTime).Milliseconds <= ms; + } + + public enum ServerActionType + { + Unknown, + AddDrawing, + AddTurnEntry, + AuraAddOrRemove, + AuraChange + } +} diff --git a/VTT/Network/UndoRedo/AddTurnEntryAction.cs b/VTT/Network/UndoRedo/AddTurnEntryAction.cs new file mode 100644 index 0000000..efef4bc --- /dev/null +++ b/VTT/Network/UndoRedo/AddTurnEntryAction.cs @@ -0,0 +1,58 @@ +namespace VTT.Network.UndoRedo +{ + using System; + using VTT.Control; + using VTT.Network.Packet; + + public class AddTurnEntryAction : ServerAction + { + public override ServerActionType ActionType => ServerActionType.AddTurnEntry; + public Guid EntryObjectID { get; set; } + public int AdditionIndex { get; set; } + public float NumericValue { get; set; } + public string TeamName { get; set; } + public int EntryIndex { get; set; } + public Map Map { get; set; } + + public int SafeGetEntryAtIndex() + { + lock (this.Map.TurnTracker.Lock) + { + if (this.EntryIndex < 0 || this.EntryIndex >= this.Map.TurnTracker.Entries.Count) + { + return -1; + } + + return this.EntryIndex; + } + } + + public override void Redo() + { + int e = this.SafeGetEntryAtIndex(); + if (e == -1) + { + TurnTracker.Team t = (string.IsNullOrEmpty(this.TeamName) ? this.Map.TurnTracker.Teams[0] : this.Map.TurnTracker.Teams.Find(x => x.Name.Equals(this.TeamName))) ?? this.Map.TurnTracker.Teams[0]; + lock (this.Map.TurnTracker.Lock) + { + this.Map.TurnTracker.Add(new TurnTracker.Entry() { NumericValue = this.NumericValue, ObjectID = this.EntryObjectID, Team = t }, this.AdditionIndex == -1 ? this.Map.TurnTracker.Entries.Count : this.AdditionIndex); + } + + this.Map.NeedsSave = true; + new PacketAddTurnEntry() { AdditionIndex = this.AdditionIndex, ObjectID = this.EntryObjectID, TeamName = this.TeamName, Value = this.NumericValue }.Broadcast(x => x.ClientMapID.Equals(this.Map.ID)); + } + } + + public override void Undo() + { + int e = this.SafeGetEntryAtIndex(); + if (e != -1) + { + lock (this.Map.TurnTracker.Lock) + { + this.Map.TurnTracker.Remove(e); + } + } + } + } +} diff --git a/VTT/Network/UndoRedo/AuraAddOrDeleteAction.cs b/VTT/Network/UndoRedo/AuraAddOrDeleteAction.cs new file mode 100644 index 0000000..7d7b800 --- /dev/null +++ b/VTT/Network/UndoRedo/AuraAddOrDeleteAction.cs @@ -0,0 +1,71 @@ +namespace VTT.Network.UndoRedo +{ + using SixLabors.ImageSharp; + using VTT.Control; + using VTT.Network.Packet; + + public class AuraAddOrDeleteAction : ServerAction + { + public override ServerActionType ActionType => ServerActionType.AuraAddOrRemove; + + public MapObject AuraContainer { get; set; } + public Color AuraColor { get; set; } + public float AuraRange { get; set; } + public int AuraIndex { get; set; } + + public bool IsAddition { get; set; } + + public override void Redo() + { + lock (this.AuraContainer.Lock) + { + if (this.IsAddition) + { + if (this.AuraContainer.Container != null) + { + this.AuraContainer.Auras.Add((this.AuraRange, this.AuraColor)); + this.AuraContainer.Container.NeedsSave = true; + new PacketAura() { ActionType = PacketAura.Action.Add, AuraColor = this.AuraColor, AuraRange = this.AuraRange, ObjectID = this.AuraContainer.ID, MapID = this.AuraContainer.MapID }.Broadcast(x => x.ClientMapID.Equals(this.AuraContainer.MapID)); + } + } + else + { + this.AuraContainer.Auras.RemoveAt(this.AuraIndex); + this.AuraContainer.Container.NeedsSave = true; + new PacketAura() { ActionType = PacketAura.Action.Delete, Index = this.AuraIndex, ObjectID = this.AuraContainer.ID, MapID = this.AuraContainer.MapID }.Broadcast(x => x.ClientMapID.Equals(this.AuraContainer.MapID)); + } + } + } + + public override void Undo() + { + lock (this.AuraContainer.Lock) + { + if (this.IsAddition) + { + if (this.AuraContainer.Container != null && this.AuraIndex >= 0 && this.AuraIndex < this.AuraContainer.Auras.Count) + { + this.AuraContainer.Auras.RemoveAt(this.AuraIndex); + this.AuraContainer.Container.NeedsSave = true; + new PacketAura() { ActionType = PacketAura.Action.Delete, Index = this.AuraIndex, ObjectID = this.AuraContainer.ID, MapID = this.AuraContainer.MapID }.Broadcast(x => x.ClientMapID.Equals(this.AuraContainer.MapID)); + } + } + else + { + if (this.AuraIndex >= this.AuraContainer.Auras.Count) + { + this.AuraContainer.Auras.Add((this.AuraRange, this.AuraColor)); + this.AuraContainer.Container.NeedsSave = true; + new PacketAura() { ActionType = PacketAura.Action.Add, AuraColor = this.AuraColor, AuraRange = this.AuraRange, ObjectID = this.AuraContainer.ID, MapID = this.AuraContainer.MapID }.Broadcast(x => x.ClientMapID.Equals(this.AuraContainer.MapID)); + } + else + { + this.AuraContainer.Auras.Insert(this.AuraIndex, (this.AuraRange, this.AuraColor)); + this.AuraContainer.Container.NeedsSave = true; + new PacketAura() { Index = this.AuraIndex, ActionType = PacketAura.Action.Add, AuraColor = this.AuraColor, AuraRange = this.AuraRange, ObjectID = this.AuraContainer.ID, MapID = this.AuraContainer.MapID }.Broadcast(x => x.ClientMapID.Equals(this.AuraContainer.MapID)); + } + } + } + } + } +} diff --git a/VTT/Network/UndoRedo/AuraChangeAction.cs b/VTT/Network/UndoRedo/AuraChangeAction.cs new file mode 100644 index 0000000..b238224 --- /dev/null +++ b/VTT/Network/UndoRedo/AuraChangeAction.cs @@ -0,0 +1,59 @@ +namespace VTT.Network.UndoRedo +{ + using SixLabors.ImageSharp; + using System; + using VTT.Control; + using VTT.Network.Packet; + + public class AuraChangeAction : SmallChangeAction + { + public override ServerActionType ActionType => ServerActionType.AuraAddOrRemove; + + public MapObject AuraContainer { get; set; } + public Color InitialAuraColor { get; set; } + public float InitialAuraRange { get; set; } + + public Color NewAuraColor { get; set; } + public float NewAuraRange { get; set; } + + public int AuraIndex { get; set; } + + public override bool AcceptSmallChange(SmallChangeAction newAction) + { + AuraChangeAction aca = newAction as AuraChangeAction; + if (aca != null && aca.AuraIndex == this.AuraIndex && this.CheckIfRecent(aca.LastModifyTime, 3000)) + { + this.NewAuraColor = aca.NewAuraColor; + this.NewAuraRange = aca.NewAuraRange; + this.LastModifyTime = newAction.LastModifyTime; + return true; + } + + return false; + } + + public override void Redo() + { + lock (this.AuraContainer.Lock) + { + if (this.AuraIndex >= 0 && this.AuraIndex < this.AuraContainer.Auras.Count) + { + this.AuraContainer.Auras[this.AuraIndex] = (this.NewAuraRange, this.NewAuraColor); + new PacketAura() { ActionType = PacketAura.Action.Update, AuraColor = this.NewAuraColor, AuraRange = this.NewAuraRange, Index = this.AuraIndex, MapID = this.AuraContainer.MapID, ObjectID = this.AuraContainer.ID }.Broadcast(x => x.ClientMapID.Equals(this.AuraContainer.MapID)); + } + } + } + + public override void Undo() + { + lock (this.AuraContainer.Lock) + { + if (this.AuraIndex >= 0 && this.AuraIndex < this.AuraContainer.Auras.Count) + { + this.AuraContainer.Auras[this.AuraIndex] = (this.InitialAuraRange, this.InitialAuraColor); + new PacketAura() { ActionType = PacketAura.Action.Update, AuraColor = this.InitialAuraColor, AuraRange = this.InitialAuraRange, Index = this.AuraIndex, MapID = this.AuraContainer.MapID, ObjectID = this.AuraContainer.ID }.Broadcast(x => x.ClientMapID.Equals(this.AuraContainer.MapID)); + } + } + } + } +} diff --git a/VTT/Network/UndoRedo/DrawingAction.cs b/VTT/Network/UndoRedo/DrawingAction.cs new file mode 100644 index 0000000..1365d5d --- /dev/null +++ b/VTT/Network/UndoRedo/DrawingAction.cs @@ -0,0 +1,47 @@ +namespace VTT.Network.UndoRedo +{ + using VTT.Control; + using VTT.Network.Packet; + + public class DrawingAction : SmallChangeAction + { + public override ServerActionType ActionType => ServerActionType.AddDrawing; + + public DrawingPointContainer DPC { get; set; } + public Map Container { get; set; } + + public override bool AcceptSmallChange(SmallChangeAction newAction) + { + DrawingAction da = newAction as DrawingAction; + if (da != null && da.DPC.ID.Equals(this.DPC.ID)) + { + this.DPC = da.DPC; + return true; + } + + return false; + } + + public override void Redo() + { + DrawingPointContainer dpc = this.Container.Drawings.Find(x => x.ID.Equals(this.DPC.ID)); + if (dpc == null) + { + this.Container.Drawings.Add(this.DPC); + this.Container.NeedsSave = true; + new PacketAddOrUpdateDrawing() { DPC = this.DPC, MapID = this.Container.ID }.Broadcast(x => x.ClientMapID.Equals(this.Container.ID)); + } + } + + public override void Undo() + { + DrawingPointContainer dpc = this.Container.Drawings.Find(x => x.ID.Equals(this.DPC.ID)); + if (dpc != null) + { + this.Container.Drawings.Remove(dpc); + this.Container.NeedsSave = true; + new PacketRemoveDrawing() { DrawingID = this.DPC.ID, MapID = this.Container.ID }.Broadcast(x => x.ClientMapID.Equals(this.Container.ID)); + } + } + } +}