diff --git a/OpenRa.DataStructures/TypeDictionary.cs b/OpenRa.DataStructures/TypeDictionary.cs index 957e9d0284..574e624cbe 100755 --- a/OpenRa.DataStructures/TypeDictionary.cs +++ b/OpenRa.DataStructures/TypeDictionary.cs @@ -46,5 +46,10 @@ namespace OpenRa if( i.Value is T ) yield return (T)i.Value; } + + public IEnumerator GetEnumerator() + { + return WithInterface().GetEnumerator(); + } } } diff --git a/OpenRa.Game/Actor.cs b/OpenRa.Game/Actor.cs index b445f68937..2c7e3b9677 100755 --- a/OpenRa.Game/Actor.cs +++ b/OpenRa.Game/Actor.cs @@ -10,12 +10,16 @@ namespace OpenRa.Game { class Actor { + [Sync] public readonly TypeDictionary traits = new TypeDictionary(); public readonly UnitInfo Info; public readonly uint ActorID; + [Sync] public int2 Location; + [Sync] public Player Owner; + [Sync] public int Health; IActivity currentActivity; diff --git a/OpenRa.Game/Chrome.cs b/OpenRa.Game/Chrome.cs index 1568c505c1..e350f282f6 100644 --- a/OpenRa.Game/Chrome.cs +++ b/OpenRa.Game/Chrome.cs @@ -414,7 +414,7 @@ namespace OpenRa.Game shpRenderer.Flush(); } } - + void DrawChat() { var chatpos = new int2(400, Game.viewport.Height - 20); diff --git a/OpenRa.Game/Controller.cs b/OpenRa.Game/Controller.cs index 2653cc28d3..2d677d6de9 100644 --- a/OpenRa.Game/Controller.cs +++ b/OpenRa.Game/Controller.cs @@ -147,14 +147,24 @@ namespace OpenRa.Game public Cursor ChooseCursor() { - var mi = new MouseInput - { - Location = (Game.CellSize * MousePosition - Game.viewport.Location).ToInt2(), - Button = MouseButton.Right, - Modifiers = GetModifierKeys(), - }; + int sync = Game.world.SyncHash(); - return orderGenerator.GetCursor(MousePosition.ToInt2(), mi); + try + { + var mi = new MouseInput + { + Location = ( Game.CellSize * MousePosition - Game.viewport.Location ).ToInt2(), + Button = MouseButton.Right, + Modifiers = GetModifierKeys(), + }; + + return orderGenerator.GetCursor( MousePosition.ToInt2(), mi ); + } + finally + { + if( sync != Game.world.SyncHash() ) + throw new InvalidOperationException( "Desync in Controller.ChooseCursor" ); + } } Cache> controlGroups = new Cache>(_ => new List()); diff --git a/OpenRa.Game/Effects/Bullet.cs b/OpenRa.Game/Effects/Bullet.cs old mode 100644 new mode 100755 diff --git a/OpenRa.Game/Effects/Corpse.cs b/OpenRa.Game/Effects/Corpse.cs old mode 100644 new mode 100755 diff --git a/OpenRa.Game/Effects/DelayedAction.cs b/OpenRa.Game/Effects/DelayedAction.cs old mode 100644 new mode 100755 diff --git a/OpenRa.Game/Effects/Explosion.cs b/OpenRa.Game/Effects/Explosion.cs old mode 100644 new mode 100755 diff --git a/OpenRa.Game/Effects/FlashTarget.cs b/OpenRa.Game/Effects/FlashTarget.cs old mode 100644 new mode 100755 diff --git a/OpenRa.Game/Effects/GpsSatellite.cs b/OpenRa.Game/Effects/GpsSatellite.cs old mode 100644 new mode 100755 diff --git a/OpenRa.Game/Effects/IEffect.cs b/OpenRa.Game/Effects/IEffect.cs old mode 100644 new mode 100755 diff --git a/OpenRa.Game/Effects/InvulnEffect.cs b/OpenRa.Game/Effects/InvulnEffect.cs old mode 100644 new mode 100755 diff --git a/OpenRa.Game/Effects/Missile.cs b/OpenRa.Game/Effects/Missile.cs old mode 100644 new mode 100755 diff --git a/OpenRa.Game/Effects/MoveFlash.cs b/OpenRa.Game/Effects/MoveFlash.cs old mode 100644 new mode 100755 diff --git a/OpenRa.Game/Effects/PowerDownIndicator.cs b/OpenRa.Game/Effects/PowerDownIndicator.cs old mode 100644 new mode 100755 diff --git a/OpenRa.Game/Effects/RepairIndicator.cs b/OpenRa.Game/Effects/RepairIndicator.cs old mode 100644 new mode 100755 diff --git a/OpenRa.Game/Effects/SatelliteLaunch.cs b/OpenRa.Game/Effects/SatelliteLaunch.cs old mode 100644 new mode 100755 diff --git a/OpenRa.Game/Effects/Smoke.cs b/OpenRa.Game/Effects/Smoke.cs old mode 100644 new mode 100755 diff --git a/OpenRa.Game/MainWindow.cs b/OpenRa.Game/MainWindow.cs index ea0af1b54f..6be9b4fc8a 100755 --- a/OpenRa.Game/MainWindow.cs +++ b/OpenRa.Game/MainWindow.cs @@ -95,6 +95,8 @@ namespace OpenRa.Game void DispatchMouseInput(MouseInputEvent ev, MouseEventArgs e) { + int sync = Game.world.SyncHash(); + Game.viewport.DispatchMouseInput( new MouseInput { @@ -103,6 +105,9 @@ namespace OpenRa.Game Location = new int2(e.Location), Modifiers = (Modifiers)(int)ModifierKeys, }); + + if( sync != Game.world.SyncHash() ) + throw new InvalidOperationException( "Desync in DispatchMouseInput" ); } protected override void OnMouseDown(MouseEventArgs e) @@ -136,6 +141,8 @@ namespace OpenRa.Game { base.OnKeyDown(e); + int sync = Game.world.SyncHash(); + /* hack hack hack */ if (e.KeyCode == Keys.F8 && !Game.orderManager.GameStarted) { @@ -156,16 +163,24 @@ namespace OpenRa.Game if (!Game.chat.isChatting) if (e.KeyCode >= Keys.D0 && e.KeyCode <= Keys.D9) Game.controller.DoControlGroup( (int)e.KeyCode - (int)Keys.D0, (Modifiers)(int)e.Modifiers ); + + if( sync != Game.world.SyncHash() ) + throw new InvalidOperationException( "Desync in OnKeyDown" ); } protected override void OnKeyPress(KeyPressEventArgs e) { base.OnKeyPress(e); + int sync = Game.world.SyncHash(); + if (e.KeyChar == '\r') Game.chat.Toggle(); else if (Game.chat.isChatting) Game.chat.TypeChar(e.KeyChar); + + if( sync != Game.world.SyncHash() ) + throw new InvalidOperationException( "Desync in OnKeyPress" ); } } diff --git a/OpenRa.Game/OpenRa.Game.csproj b/OpenRa.Game/OpenRa.Game.csproj index 31292b686b..088f626e4e 100644 --- a/OpenRa.Game/OpenRa.Game.csproj +++ b/OpenRa.Game/OpenRa.Game.csproj @@ -129,6 +129,7 @@ + @@ -318,4 +319,4 @@ --> - + \ No newline at end of file diff --git a/OpenRa.Game/Orders/Order.cs b/OpenRa.Game/Orders/Order.cs index 754207101b..828d44b4d8 100644 --- a/OpenRa.Game/Orders/Order.cs +++ b/OpenRa.Game/Orders/Order.cs @@ -30,7 +30,7 @@ namespace OpenRa.Game this.SubjectId = subjectId; this.TargetActorId = targetActorId; this.TargetLocation = targetLocation; - this.TargetString = targetString; + this.TargetString = targetString; } public bool Validate() diff --git a/OpenRa.Game/Sync.cs b/OpenRa.Game/Sync.cs new file mode 100755 index 0000000000..10261f506f --- /dev/null +++ b/OpenRa.Game/Sync.cs @@ -0,0 +1,57 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Reflection; + +namespace OpenRa.Game +{ + class SyncAttribute : Attribute { } + + static class Sync + { + public static int CalculateSyncHash( object obj ) + { + int hash = 0; // TODO: start with a more interesting initial value. + + // TODO: cache the Syncable fields; maybe use DynamicMethod to make this fast? + // FIXME: does GetFields even give fields in a well-defined order? + const BindingFlags bf = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance; + foreach( var field in obj.GetType().GetFields( bf ).Where( x => x.GetCustomAttributes( typeof( SyncAttribute ), true ).Length != 0 ) ) + { + if( field.FieldType == typeof( int ) ) + hash ^= (int)field.GetValue( obj ); + + else if( field.FieldType == typeof( Actor ) ) + { + var a = (Actor)field.GetValue( obj ); + if( a != null ) + hash ^= (int)( a.ActorID << 16 ); + } + else if( field.FieldType == typeof( TypeDictionary ) ) + { + foreach( var o in (TypeDictionary)field.GetValue( obj ) ) + hash += CalculateSyncHash( o ); + } + else if( field.FieldType == typeof( bool ) ) + hash ^= (bool)field.GetValue( obj ) ? 0xaaa : 0x555; + + else if( field.FieldType == typeof( int2 ) ) + { + var i2 = (int2)field.GetValue( obj ); + hash ^= ( ( i2.X * 5 ) ^ ( i2.Y * 3 ) ) / 4; + } + else if( field.FieldType == typeof( Player ) ) + { + var p = (Player)field.GetValue( obj ); + if( p != null ) + hash ^= p.Index * 0x567; + } + else + throw new NotImplementedException( "SyncAttribute on unhashable field" ); + } + + return hash; + } + } +} diff --git a/OpenRa.Game/Traits/AttackBase.cs b/OpenRa.Game/Traits/AttackBase.cs index 9cc97b871f..6b78edfe9c 100644 --- a/OpenRa.Game/Traits/AttackBase.cs +++ b/OpenRa.Game/Traits/AttackBase.cs @@ -8,10 +8,12 @@ namespace OpenRa.Game.Traits { class AttackBase : IIssueOrder, IResolveOrder, ITick { - public Actor target; + [Sync] public Actor target; // time (in frames) until each weapon can fire again. + [Sync] protected int primaryFireDelay = 0; + [Sync] protected int secondaryFireDelay = 0; int primaryBurst; diff --git a/OpenRa.Game/Traits/AttackFrontal.cs b/OpenRa.Game/Traits/AttackFrontal.cs index e10b596652..a300fc308d 100644 --- a/OpenRa.Game/Traits/AttackFrontal.cs +++ b/OpenRa.Game/Traits/AttackFrontal.cs @@ -7,7 +7,7 @@ namespace OpenRa.Game.Traits public AttackFrontal(Actor self, int facingTolerance) : base(self) { FacingTolerance = facingTolerance; } - int FacingTolerance; + readonly int FacingTolerance; public override void Tick(Actor self) { diff --git a/OpenRa.Game/Traits/Building.cs b/OpenRa.Game/Traits/Building.cs index 927fc58eee..1fa3538314 100644 --- a/OpenRa.Game/Traits/Building.cs +++ b/OpenRa.Game/Traits/Building.cs @@ -13,7 +13,9 @@ namespace OpenRa.Game.Traits { readonly Actor self; public readonly BuildingInfo unitInfo; + [Sync] bool isRepairing = false; + [Sync] bool manuallyDisabled = false; public bool ManuallyDisabled { get { return manuallyDisabled; } } public bool Disabled { get { return (manuallyDisabled || (unitInfo.Powered && self.Owner.GetPowerState() != PowerState.Normal)); } } diff --git a/OpenRa.Game/Traits/ChronoshiftDeploy.cs b/OpenRa.Game/Traits/ChronoshiftDeploy.cs index 16aeb0d9fe..2cc8a2a9a4 100644 --- a/OpenRa.Game/Traits/ChronoshiftDeploy.cs +++ b/OpenRa.Game/Traits/ChronoshiftDeploy.cs @@ -7,8 +7,9 @@ namespace OpenRa.Game.Traits class ChronoshiftDeploy : IIssueOrder, IResolveOrder, ISpeedModifier, ITick, IPips { // Recharge logic + [Sync] int chargeTick = 0; // How long until we can chronoshift again? - int chargeLength = (int)(Rules.Aftermath.ChronoTankDuration * 60 * 25); // How long between shifts? + readonly int chargeLength = (int)(Rules.Aftermath.ChronoTankDuration * 60 * 25); // How long between shifts? public ChronoshiftDeploy(Actor self) { } diff --git a/OpenRa.Game/Traits/Chronoshiftable.cs b/OpenRa.Game/Traits/Chronoshiftable.cs index 82792b2bcf..2df077e0bb 100644 --- a/OpenRa.Game/Traits/Chronoshiftable.cs +++ b/OpenRa.Game/Traits/Chronoshiftable.cs @@ -8,9 +8,11 @@ namespace OpenRa.Game.Traits class Chronoshiftable : IResolveOrder, ISpeedModifier, ITick { // Return-to-sender logic + [Sync] int2 chronoshiftOrigin; + [Sync] int chronoshiftReturnTicks = 0; - + public Chronoshiftable(Actor self) { } public void Tick(Actor self) @@ -65,7 +67,7 @@ namespace OpenRa.Game.Traits self.QueueActivity(new Activities.Teleport(order.TargetLocation)); var power = self.Owner.SupportPowers[order.TargetString].Impl; - power.OnFireNotification(self, self.Location); + power.OnFireNotification(self, self.Location); } } diff --git a/OpenRa.Game/Traits/Cloak.cs b/OpenRa.Game/Traits/Cloak.cs index 491962f89d..5b225438d6 100644 --- a/OpenRa.Game/Traits/Cloak.cs +++ b/OpenRa.Game/Traits/Cloak.cs @@ -6,6 +6,7 @@ namespace OpenRa.Game.Traits { class Cloak : IRenderModifier, INotifyAttack, ITick { + [Sync] int remainingUncloakTime = 2; /* setup for initial cloak */ public Cloak(Actor self) {} diff --git a/OpenRa.Game/Traits/Harvester.cs b/OpenRa.Game/Traits/Harvester.cs index a7607e43aa..aa374e943c 100644 --- a/OpenRa.Game/Traits/Harvester.cs +++ b/OpenRa.Game/Traits/Harvester.cs @@ -5,7 +5,9 @@ namespace OpenRa.Game.Traits { class Harvester : IIssueOrder, IResolveOrder, IPips { + [Sync] public int oreCarried = 0; /* sum of these must not exceed capacity */ + [Sync] public int gemsCarried = 0; public bool IsFull { get { return oreCarried + gemsCarried == Rules.General.BailCount; } } diff --git a/OpenRa.Game/Traits/IronCurtainable.cs b/OpenRa.Game/Traits/IronCurtainable.cs index 1783136810..4feda533ad 100644 --- a/OpenRa.Game/Traits/IronCurtainable.cs +++ b/OpenRa.Game/Traits/IronCurtainable.cs @@ -5,6 +5,7 @@ namespace OpenRa.Game.Traits { class IronCurtainable : IResolveOrder, IDamageModifier, ITick { + [Sync] int RemainingTicks = 0; public IronCurtainable(Actor self) { } diff --git a/OpenRa.Game/Traits/LimitedAmmo.cs b/OpenRa.Game/Traits/LimitedAmmo.cs index b5c18c55a2..b3ffe3535f 100644 --- a/OpenRa.Game/Traits/LimitedAmmo.cs +++ b/OpenRa.Game/Traits/LimitedAmmo.cs @@ -4,6 +4,7 @@ namespace OpenRa.Game.Traits { class LimitedAmmo : INotifyAttack, IPips { + [Sync] int ammo; Actor self; diff --git a/OpenRa.Game/Traits/Mobile.cs b/OpenRa.Game/Traits/Mobile.cs index 4e53190603..de1f0dca25 100644 --- a/OpenRa.Game/Traits/Mobile.cs +++ b/OpenRa.Game/Traits/Mobile.cs @@ -9,6 +9,7 @@ namespace OpenRa.Game.Traits { readonly Actor self; + [Sync] int2 __fromCell; public int2 fromCell { diff --git a/OpenRa.Game/Traits/ProductionQueue.cs b/OpenRa.Game/Traits/ProductionQueue.cs index e42bd59a3f..eabd106c8c 100755 --- a/OpenRa.Game/Traits/ProductionQueue.cs +++ b/OpenRa.Game/Traits/ProductionQueue.cs @@ -74,6 +74,7 @@ namespace OpenRa.Game.Traits } // Key: Production category. + // TODO: sync this readonly Cache> production = new Cache>( _ => new List() ); diff --git a/OpenRa.Game/Traits/RallyPoint.cs b/OpenRa.Game/Traits/RallyPoint.cs index eebcef0b7e..fb2006fa5d 100644 --- a/OpenRa.Game/Traits/RallyPoint.cs +++ b/OpenRa.Game/Traits/RallyPoint.cs @@ -6,6 +6,7 @@ namespace OpenRa.Game.Traits { class RallyPoint : IRender, IIssueOrder, IResolveOrder, ITick { + [Sync] public int2 rallyPoint; public Animation anim; diff --git a/OpenRa.Game/Traits/RenderBuildingWarFactory.cs b/OpenRa.Game/Traits/RenderBuildingWarFactory.cs index 4237ef5867..074d058068 100644 --- a/OpenRa.Game/Traits/RenderBuildingWarFactory.cs +++ b/OpenRa.Game/Traits/RenderBuildingWarFactory.cs @@ -7,7 +7,9 @@ namespace OpenRa.Game.Traits class RenderWarFactory : IRender, INotifyBuildComplete, INotifyDamage, ITick, INotifyProduction { public Animation roof; + [Sync] bool doneBuilding; + [Sync] bool isOpen; public readonly Actor self; diff --git a/OpenRa.Game/Traits/Submarine.cs b/OpenRa.Game/Traits/Submarine.cs index 46da5d9a93..3183072b85 100644 --- a/OpenRa.Game/Traits/Submarine.cs +++ b/OpenRa.Game/Traits/Submarine.cs @@ -6,6 +6,7 @@ namespace OpenRa.Game.Traits { class Submarine : IRenderModifier, INotifyAttack, ITick, INotifyDamage { + [Sync] int remainingSurfaceTime = 2; /* setup for initial dive */ public Submarine(Actor self) { } diff --git a/OpenRa.Game/Traits/TakeCover.cs b/OpenRa.Game/Traits/TakeCover.cs index 59cba709aa..192f2e899b 100644 --- a/OpenRa.Game/Traits/TakeCover.cs +++ b/OpenRa.Game/Traits/TakeCover.cs @@ -8,6 +8,7 @@ namespace OpenRa.Game.Traits const float proneDamage = .5f; const float proneSpeed = .5f; + [Sync] int remainingProneTime = 0; public bool IsProne { get { return remainingProneTime > 0; } } diff --git a/OpenRa.Game/Traits/TraitsInterfaces.cs b/OpenRa.Game/Traits/TraitsInterfaces.cs index 8ac3f3258b..849679042b 100644 --- a/OpenRa.Game/Traits/TraitsInterfaces.cs +++ b/OpenRa.Game/Traits/TraitsInterfaces.cs @@ -10,18 +10,18 @@ namespace OpenRa.Game.Traits // depends on the order of pips in WorldRenderer.cs! enum PipType { Transparent, Green, Yellow, Red, Gray }; enum TagType { None, Fake, Primary }; - + interface ITick { void Tick(Actor self); } interface IRender { IEnumerable Render(Actor self); } + interface IIssueOrder { Order IssueOrder( Actor self, int2 xy, MouseInput mi, Actor underCursor ); } + interface IResolveOrder { void ResolveOrder( Actor self, Order order ); } + interface INotifySold { void Sold(Actor self); } interface INotifyDamage { void Damaged(Actor self, AttackInfo e); } interface INotifyBuildComplete { void BuildingComplete (Actor self); } interface INotifyProduction { void UnitProduced(Actor self, Actor other); } interface IAcceptThief { void OnSteal(Actor self, Actor thief); } - - interface IIssueOrder { Order IssueOrder(Actor self, int2 xy, MouseInput mi, Actor underCursor); } - interface IResolveOrder { void ResolveOrder(Actor self, Order order); } - + interface IProducer { bool Produce( Actor self, UnitInfo producee ); diff --git a/OpenRa.Game/Traits/Turreted.cs b/OpenRa.Game/Traits/Turreted.cs index ee2fde30c7..b20de37e63 100644 --- a/OpenRa.Game/Traits/Turreted.cs +++ b/OpenRa.Game/Traits/Turreted.cs @@ -3,6 +3,7 @@ namespace OpenRa.Game.Traits { class Turreted : ITick { + [Sync] public int turretFacing = 0; public int? desiredFacing; diff --git a/OpenRa.Game/Traits/Unit.cs b/OpenRa.Game/Traits/Unit.cs index 98f005c8d3..ebfc56cf31 100755 --- a/OpenRa.Game/Traits/Unit.cs +++ b/OpenRa.Game/Traits/Unit.cs @@ -3,7 +3,9 @@ namespace OpenRa.Game.Traits { class Unit : INotifyDamage { + [Sync] public int Facing; + [Sync] public int Altitude; public Unit( Actor self ) { } diff --git a/OpenRa.Game/World.cs b/OpenRa.Game/World.cs index f8db600406..1b963211bb 100644 --- a/OpenRa.Game/World.cs +++ b/OpenRa.Game/World.cs @@ -12,15 +12,15 @@ namespace OpenRa.Game public void Add(Actor a) { - a.IsInWorld = true; - actors.Add(a); + a.IsInWorld = true; + actors.Add(a); ActorAdded(a); } public void Remove(Actor a) { - a.IsInWorld = false; - actors.Remove(a); + a.IsInWorld = false; + actors.Remove(a); ActorRemoved(a); } @@ -52,5 +52,14 @@ namespace OpenRa.Game { return nextAID++; } + + public int SyncHash() + { + int ret = 0; + foreach( var a in Actors ) + ret += (int)a.ActorID * Sync.CalculateSyncHash( a ); + + return ret; + } } }