basic Sync checking. No protocol stuff yet; just checks that input events are well-behaved.
This commit is contained in:
@@ -46,5 +46,10 @@ namespace OpenRa
|
||||
if( i.Value is T )
|
||||
yield return (T)i.Value;
|
||||
}
|
||||
|
||||
public IEnumerator<object> GetEnumerator()
|
||||
{
|
||||
return WithInterface<object>().GetEnumerator();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -146,15 +146,25 @@ namespace OpenRa.Game
|
||||
public float2 MousePosition { get { return dragEnd; } }
|
||||
|
||||
public Cursor ChooseCursor()
|
||||
{
|
||||
int sync = Game.world.SyncHash();
|
||||
|
||||
try
|
||||
{
|
||||
var mi = new MouseInput
|
||||
{
|
||||
Location = (Game.CellSize * MousePosition - Game.viewport.Location).ToInt2(),
|
||||
Location = ( Game.CellSize * MousePosition - Game.viewport.Location ).ToInt2(),
|
||||
Button = MouseButton.Right,
|
||||
Modifiers = GetModifierKeys(),
|
||||
};
|
||||
|
||||
return orderGenerator.GetCursor(MousePosition.ToInt2(), mi);
|
||||
return orderGenerator.GetCursor( MousePosition.ToInt2(), mi );
|
||||
}
|
||||
finally
|
||||
{
|
||||
if( sync != Game.world.SyncHash() )
|
||||
throw new InvalidOperationException( "Desync in Controller.ChooseCursor" );
|
||||
}
|
||||
}
|
||||
|
||||
Cache<int, List<Actor>> controlGroups = new Cache<int, List<Actor>>(_ => new List<Actor>());
|
||||
|
||||
0
OpenRa.Game/Effects/Bullet.cs
Normal file → Executable file
0
OpenRa.Game/Effects/Bullet.cs
Normal file → Executable file
0
OpenRa.Game/Effects/Corpse.cs
Normal file → Executable file
0
OpenRa.Game/Effects/Corpse.cs
Normal file → Executable file
0
OpenRa.Game/Effects/DelayedAction.cs
Normal file → Executable file
0
OpenRa.Game/Effects/DelayedAction.cs
Normal file → Executable file
0
OpenRa.Game/Effects/Explosion.cs
Normal file → Executable file
0
OpenRa.Game/Effects/Explosion.cs
Normal file → Executable file
0
OpenRa.Game/Effects/FlashTarget.cs
Normal file → Executable file
0
OpenRa.Game/Effects/FlashTarget.cs
Normal file → Executable file
0
OpenRa.Game/Effects/GpsSatellite.cs
Normal file → Executable file
0
OpenRa.Game/Effects/GpsSatellite.cs
Normal file → Executable file
0
OpenRa.Game/Effects/IEffect.cs
Normal file → Executable file
0
OpenRa.Game/Effects/IEffect.cs
Normal file → Executable file
0
OpenRa.Game/Effects/InvulnEffect.cs
Normal file → Executable file
0
OpenRa.Game/Effects/InvulnEffect.cs
Normal file → Executable file
0
OpenRa.Game/Effects/Missile.cs
Normal file → Executable file
0
OpenRa.Game/Effects/Missile.cs
Normal file → Executable file
0
OpenRa.Game/Effects/MoveFlash.cs
Normal file → Executable file
0
OpenRa.Game/Effects/MoveFlash.cs
Normal file → Executable file
0
OpenRa.Game/Effects/PowerDownIndicator.cs
Normal file → Executable file
0
OpenRa.Game/Effects/PowerDownIndicator.cs
Normal file → Executable file
0
OpenRa.Game/Effects/RepairIndicator.cs
Normal file → Executable file
0
OpenRa.Game/Effects/RepairIndicator.cs
Normal file → Executable file
0
OpenRa.Game/Effects/SatelliteLaunch.cs
Normal file → Executable file
0
OpenRa.Game/Effects/SatelliteLaunch.cs
Normal file → Executable file
0
OpenRa.Game/Effects/Smoke.cs
Normal file → Executable file
0
OpenRa.Game/Effects/Smoke.cs
Normal file → Executable file
@@ -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" );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -129,6 +129,7 @@
|
||||
<Compile Include="SupportPowers\NullPower.cs" />
|
||||
<Compile Include="Support\Stopwatch.cs" />
|
||||
<Compile Include="Support\PerfHistory.cs" />
|
||||
<Compile Include="Sync.cs" />
|
||||
<Compile Include="Traits\AcceptsOre.cs" />
|
||||
<Compile Include="Traits\Activities\Attack.cs" />
|
||||
<Compile Include="Traits\Activities\CaptureBuilding.cs" />
|
||||
|
||||
57
OpenRa.Game/Sync.cs
Executable file
57
OpenRa.Game/Sync.cs
Executable file
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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)); } }
|
||||
|
||||
@@ -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) { }
|
||||
|
||||
|
||||
@@ -8,7 +8,9 @@ namespace OpenRa.Game.Traits
|
||||
class Chronoshiftable : IResolveOrder, ISpeedModifier, ITick
|
||||
{
|
||||
// Return-to-sender logic
|
||||
[Sync]
|
||||
int2 chronoshiftOrigin;
|
||||
[Sync]
|
||||
int chronoshiftReturnTicks = 0;
|
||||
|
||||
public Chronoshiftable(Actor self) { }
|
||||
|
||||
@@ -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) {}
|
||||
|
||||
@@ -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; } }
|
||||
|
||||
@@ -5,6 +5,7 @@ namespace OpenRa.Game.Traits
|
||||
{
|
||||
class IronCurtainable : IResolveOrder, IDamageModifier, ITick
|
||||
{
|
||||
[Sync]
|
||||
int RemainingTicks = 0;
|
||||
|
||||
public IronCurtainable(Actor self) { }
|
||||
|
||||
@@ -4,6 +4,7 @@ namespace OpenRa.Game.Traits
|
||||
{
|
||||
class LimitedAmmo : INotifyAttack, IPips
|
||||
{
|
||||
[Sync]
|
||||
int ammo;
|
||||
Actor self;
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ namespace OpenRa.Game.Traits
|
||||
{
|
||||
readonly Actor self;
|
||||
|
||||
[Sync]
|
||||
int2 __fromCell;
|
||||
public int2 fromCell
|
||||
{
|
||||
|
||||
@@ -74,6 +74,7 @@ namespace OpenRa.Game.Traits
|
||||
}
|
||||
|
||||
// Key: Production category.
|
||||
// TODO: sync this
|
||||
readonly Cache<string, List<ProductionItem>> production
|
||||
= new Cache<string, List<ProductionItem>>( _ => new List<ProductionItem>() );
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ namespace OpenRa.Game.Traits
|
||||
{
|
||||
class RallyPoint : IRender, IIssueOrder, IResolveOrder, ITick
|
||||
{
|
||||
[Sync]
|
||||
public int2 rallyPoint;
|
||||
public Animation anim;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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) { }
|
||||
|
||||
@@ -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; } }
|
||||
|
||||
@@ -13,15 +13,15 @@ namespace OpenRa.Game.Traits
|
||||
|
||||
interface ITick { void Tick(Actor self); }
|
||||
interface IRender { IEnumerable<Renderable> 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 );
|
||||
|
||||
@@ -3,6 +3,7 @@ namespace OpenRa.Game.Traits
|
||||
{
|
||||
class Turreted : ITick
|
||||
{
|
||||
[Sync]
|
||||
public int turretFacing = 0;
|
||||
public int? desiredFacing;
|
||||
|
||||
|
||||
@@ -3,7 +3,9 @@ namespace OpenRa.Game.Traits
|
||||
{
|
||||
class Unit : INotifyDamage
|
||||
{
|
||||
[Sync]
|
||||
public int Facing;
|
||||
[Sync]
|
||||
public int Altitude;
|
||||
|
||||
public Unit( Actor self ) { }
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user