pull chrisforbes

This commit is contained in:
Paul Chote
2010-01-13 19:28:56 +13:00
160 changed files with 3992 additions and 5393 deletions

View File

@@ -1,10 +1,13 @@
using System;
using System.Collections.Generic;
using System.Linq;
using OpenRa.Game.GameRules;
using System.Collections.Generic;
using OpenRa.Game.Effects;
namespace OpenRa.Game.Traits
{
class APMineInfo : ITraitInfo
{
public object Create(Actor self) { return new APMine(self); }
}
class APMine : ICrushable, IOccupySpace
{
readonly Actor self;

View File

@@ -1,11 +1,13 @@
using System;
using System.Collections.Generic;
using System.Linq;
using OpenRa.Game.GameRules;
using System.Collections.Generic;
using OpenRa.Game.Effects;
namespace OpenRa.Game.Traits
{
class ATMineInfo : ITraitInfo
{
public object Create(Actor self) { return new ATMine(self); }
}
class ATMine : ICrushable, IOccupySpace
{
readonly Actor self;

View File

@@ -2,6 +2,11 @@
namespace OpenRa.Game.Traits
{
class AcceptsOreInfo : ITraitInfo
{
public object Create(Actor self) { return new AcceptsOre(self); }
}
class AcceptsOre
{
public AcceptsOre(Actor self)
@@ -9,7 +14,7 @@ namespace OpenRa.Game.Traits
Game.world.AddFrameEndTask(
w =>
{ /* create the free harvester! */
var harvester = new Actor(Rules.UnitInfo["harv"], self.Location + new int2(1, 2), self.Owner);
var harvester = new Actor("harv", self.Location + new int2(1, 2), self.Owner);
var unit = harvester.traits.Get<Unit>();
var mobile = harvester.traits.Get<Mobile>();
unit.Facing = 64;

View File

@@ -27,7 +27,7 @@ namespace OpenRa.Game.Traits.Activities
return new Move( Target, Range ) { NextActivity = this };
var desiredFacing = Util.GetFacing((Target.Location - self.Location).ToFloat2(), 0);
var renderUnit = self.traits.WithInterface<RenderUnit>().FirstOrDefault();
var renderUnit = self.traits.GetOrDefault<RenderUnit>();
var numDirs = (renderUnit != null)
? renderUnit.anim.CurrentSequence.Length : 8;
@@ -37,7 +37,7 @@ namespace OpenRa.Game.Traits.Activities
return new Turn( desiredFacing ) { NextActivity = this };
}
var attack = self.traits.WithInterface<AttackBase>().First();
var attack = self.traits.Get<AttackBase>();
attack.target = Target;
attack.DoAttack(self);
return this;

View File

@@ -19,7 +19,7 @@ namespace OpenRa.Game.Traits.Activities
if (target.Owner == self.Owner)
{
if (target.Health == target.Info.Strength)
if (target.Health == target.Info.Traits.Get<OwnedActorInfo>().HP)
return NextActivity;
target.InflictDamage(self, -EngineerCapture.EngineerDamage, Rules.WarheadInfo["Super"]);
}

View File

@@ -64,7 +64,7 @@ namespace OpenRa.Game.Traits.Activities
else if( unit.Facing != 64 )
return new Turn( 64 ) { NextActivity = this };
var renderUnit = self.traits.WithInterface<RenderUnit>().First();
var renderUnit = self.traits.Get<RenderUnit>();
if( renderUnit.anim.CurrentSequence.Name != "empty" )
renderUnit.PlayCustomAnimation( self, "empty",
() => isDone = true );

View File

@@ -17,7 +17,7 @@ namespace OpenRa.Game.Traits.Activities
Sound.Play("placbldg.aud");
Sound.Play("build5.aud");
}
Game.world.Add( new Actor( Rules.UnitInfo["fact"], self.Location - new int2( 1, 1 ), self.Owner ) );
Game.world.Add( new Actor( "fact", self.Location - new int2( 1, 1 ), self.Owner ) );
} );
return this;
}

View File

@@ -31,7 +31,7 @@ namespace OpenRa.Game.Traits.Activities
var desiredFacing = Util.GetFacing(d, unit.Facing);
if (unit.Altitude == CruiseAltitude)
Util.TickFacing(ref unit.Facing, desiredFacing, self.Info.ROT);
Util.TickFacing(ref unit.Facing, desiredFacing, self.Info.Traits.Get<UnitInfo>().ROT);
var speed = .2f * Util.GetEffectiveSpeed(self);
var angle = unit.Facing / 128f * Math.PI;

View File

@@ -33,7 +33,7 @@ namespace OpenRa.Game.Traits.Activities
{
var unit = self.traits.Get<Unit>();
var harv = self.traits.Get<Harvester>();
var renderUnit = self.traits.WithInterface<RenderUnit>().First(); /* better have one of these! */
var renderUnit = self.traits.Get<RenderUnit>(); /* better have one of these! */
var isGem = false;
if (!Rules.Map.ContainsResource(self.Location) ||

View File

@@ -30,11 +30,11 @@ namespace OpenRa.Game.Traits.Activities
return this;
}
var range = Rules.WeaponInfo[ self.Info.Primary ].Range - 1;
var range = self.GetPrimaryWeapon().Range - 1;
var dist = target.CenterLocation - self.CenterLocation;
var desiredFacing = Util.GetFacing(dist, unit.Facing);
Util.TickFacing(ref unit.Facing, desiredFacing, self.Info.ROT);
Util.TickFacing(ref unit.Facing, desiredFacing, self.Info.Traits.Get<UnitInfo>().ROT);
if (!float2.WithinEpsilon(float2.Zero, dist, range * Game.CellSize))
{

View File

@@ -39,7 +39,8 @@ namespace OpenRa.Game.Traits.Activities
}
var desiredFacing = Util.GetFacing(dist, unit.Facing);
Util.TickFacing(ref unit.Facing, desiredFacing, self.Info.ROT);
Util.TickFacing(ref unit.Facing, desiredFacing,
self.Info.Traits.Get<UnitInfo>().ROT);
var rawSpeed = .2f * Util.GetEffectiveSpeed(self);
self.CenterLocation += (rawSpeed / dist.Length) * dist;

View File

@@ -14,7 +14,7 @@ namespace OpenRa.Game.Traits.Activities
static Actor ChooseHelipad(Actor self)
{
return Game.world.Actors.FirstOrDefault(
a => a.Info == Rules.UnitInfo["HPAD"] &&
a => a.Info == Rules.NewUnitInfo["HPAD"] &&
a.Owner == self.Owner &&
!Reservable.IsReserved(a));
}
@@ -24,9 +24,11 @@ namespace OpenRa.Game.Traits.Activities
if (isCanceled) return NextActivity;
var dest = ChooseHelipad(self);
var initialFacing = self.Info.Traits.Get<UnitInfo>().InitialFacing;
if (dest == null)
return Util.SequenceActivities(
new Turn(self.Info.InitialFacing),
new Turn(initialFacing),
new HeliLand(true),
NextActivity);
@@ -34,12 +36,13 @@ namespace OpenRa.Game.Traits.Activities
if (res != null)
self.traits.Get<Helicopter>().reservation = res.Reserve(self);
var offset = (dest.Info as BuildingInfo).SpawnOffset;
var pi = dest.Info.Traits.GetOrDefault<ProductionInfo>();
var offset = pi != null ? pi.SpawnOffset : null;
var offsetVec = offset != null ? new float2(offset[0], offset[1]) : float2.Zero;
return Util.SequenceActivities(
new HeliFly(dest.CenterLocation + offsetVec),
new Turn(self.Info.InitialFacing),
new Turn(initialFacing),
new HeliLand(false),
new Rearm(),
NextActivity);

View File

@@ -28,7 +28,7 @@ namespace OpenRa.Game.Traits.Activities
--unit.Altitude;
var desiredFacing = Util.GetFacing(d, unit.Facing);
Util.TickFacing(ref unit.Facing, desiredFacing, self.Info.ROT);
Util.TickFacing(ref unit.Facing, desiredFacing, self.Info.Traits.Get<UnitInfo>().ROT);
var speed = .2f * Util.GetEffectiveSpeed(self);
var angle = unit.Facing / 128f * Math.PI;

View File

@@ -16,8 +16,11 @@ namespace OpenRa.Game.Traits.Activities
if (isCanceled) return NextActivity;
if (remainingTicks == 0)
{
var costPerHp = (Rules.General.URepairPercent * self.Info.Cost) / self.Info.Strength;
var hpToRepair = Math.Min(Rules.General.URepairStep, self.Info.Strength - self.Health);
var unitCost = self.Info.Traits.Get<BuildableInfo>().Cost;
var hp = self.Info.Traits.Get<OwnedActorInfo>().HP;
var costPerHp = (Rules.General.URepairPercent * unitCost) / hp;
var hpToRepair = Math.Min(Rules.General.URepairStep, hp - self.Health);
var cost = (int)Math.Ceiling(costPerHp * hpToRepair);
if (!self.Owner.TakeCash(cost))
{
@@ -26,7 +29,7 @@ namespace OpenRa.Game.Traits.Activities
}
self.InflictDamage(self, -hpToRepair, Rules.WarheadInfo["Super"]);
if (self.Health == self.Info.Strength)
if (self.Health == hp)
return NextActivity;
var hostBuilding = Game.FindUnits(self.CenterLocation, self.CenterLocation)

View File

@@ -19,7 +19,7 @@ namespace OpenRa.Game.Traits.Activities
Actor ChooseAirfield(Actor self)
{
var airfield = Game.world.Actors
.Where(a => a.Info == Rules.UnitInfo["AFLD"] /* todo: generalize this */
.Where(a => a.Info.Name == "afld"
&& a.Owner == self.Owner
&& !Reservable.IsReserved(a))
.FirstOrDefault();
@@ -41,7 +41,7 @@ namespace OpenRa.Game.Traits.Activities
var unit = self.traits.Get<Unit>();
var speed = .2f * Util.GetEffectiveSpeed(self);
var approachStart = landPos - new float2(unit.Altitude * speed, 0);
var turnRadius = (128f / self.Info.ROT) * speed / (float)Math.PI;
var turnRadius = (128f / self.Info.Traits.Get<UnitInfo>().ROT) * speed / (float)Math.PI;
/* work out the center points */
var fwd = -float2.FromAngle(unit.Facing / 128f * (float)Math.PI);

View File

@@ -13,8 +13,9 @@ namespace OpenRa.Game.Traits.Activities
void DoSell(Actor self)
{
var refund = Rules.General.RefundPercent
* self.Health * self.Info.Cost / self.Info.Strength;
var cost = self.Info.Traits.Get<BuildableInfo>().Cost;
var hp = self.Info.Traits.Get<OwnedActorInfo>().HP;
var refund = Rules.General.RefundPercent * self.Health * cost / hp;
self.Owner.GiveCash((int)refund);
self.Health = 0;
@@ -29,7 +30,7 @@ namespace OpenRa.Game.Traits.Activities
{
if (!started)
{
var rb = self.traits.WithInterface<RenderBuilding>().First();
var rb = self.traits.Get<RenderBuilding>();
//var rb = self.traits.Get<RenderBuilding>();
rb.PlayCustomAnimBackwards(self, "make",
() => Game.world.AddFrameEndTask(w => DoSell(self)));

View File

@@ -19,7 +19,7 @@ namespace OpenRa.Game.Traits.Activities
if( desiredFacing == unit.Facing )
return NextActivity;
Util.TickFacing( ref unit.Facing, desiredFacing, self.Info.ROT );
Util.TickFacing( ref unit.Facing, desiredFacing, self.Info.Traits.Get<UnitInfo>().ROT );
return this;
}

View File

@@ -14,7 +14,7 @@ namespace OpenRa.Game.Traits.Activities
ns.Sold(self);
w.Remove(self);
var mcv = new Actor(Rules.UnitInfo["MCV"], self.Location + new int2(1, 1), self.Owner);
var mcv = new Actor("mcv", self.Location + new int2(1, 1), self.Owner);
mcv.traits.Get<Unit>().Facing = 96;
w.Add(mcv);
}

View File

@@ -33,8 +33,9 @@ namespace OpenRa.Game.Traits.Activities
// if we're a thing that can turn, turn to the
// right facing for the unload animation
var unit = self.traits.GetOrDefault<Unit>();
if (unit != null && unit.Facing != self.Info.UnloadFacing)
return new Turn(self.Info.UnloadFacing) { NextActivity = this };
var unloadFacing = self.Info.Traits.Get<CargoInfo>().UnloadFacing;
if (unit != null && unit.Facing != unloadFacing)
return new Turn(unloadFacing) { NextActivity = this };
// todo: handle the BS of open/close sequences, which are inconsistent,
// for reasons that probably make good sense to the westwood guys.
@@ -43,7 +44,7 @@ namespace OpenRa.Game.Traits.Activities
if (cargo.IsEmpty(self))
return NextActivity;
var ru = self.traits.WithInterface<RenderUnit>().FirstOrDefault();
var ru = self.traits.GetOrDefault<RenderUnit>();
if (ru != null)
ru.PlayCustomAnimation(self, "unload", null);

View File

@@ -6,6 +6,21 @@ using OpenRa.Game.Effects;
namespace OpenRa.Game.Traits
{
class AttackBaseInfo : ITraitInfo
{
public readonly string PrimaryWeapon = null;
public readonly string SecondaryWeapon = null;
public readonly int Recoil = 0;
public readonly int[] PrimaryLocalOffset = { };
public readonly int[] SecondaryLocalOffset = { };
public readonly int[] PrimaryOffset = { 0, 0 };
public readonly int[] SecondaryOffset = null;
public readonly bool MuzzleFlash = false;
public readonly int FireDelay = 0;
public virtual object Create(Actor self) { return new AttackBase(self); }
}
class AttackBase : IIssueOrder, IResolveOrder, ITick
{
[Sync] public Actor target;
@@ -23,8 +38,8 @@ namespace OpenRa.Game.Traits
public AttackBase(Actor self)
{
var primaryWeapon = self.Info.Primary != null ? Rules.WeaponInfo[self.Info.Primary] : null;
var secondaryWeapon = self.Info.Secondary != null ? Rules.WeaponInfo[self.Info.Secondary] : null;
var primaryWeapon = self.GetPrimaryWeapon();
var secondaryWeapon = self.GetSecondaryWeapon();
primaryBurst = primaryWeapon != null ? primaryWeapon.Burst : 1;
secondaryBurst = secondaryWeapon != null ? secondaryWeapon.Burst : 1;
@@ -73,19 +88,20 @@ namespace OpenRa.Game.Traits
public void DoAttack(Actor self)
{
var unit = self.traits.GetOrDefault<Unit>();
var info = self.Info.Traits.Get<AttackBaseInfo>();
if (self.Info.Primary != null && CheckFire(self, unit, self.Info.Primary, ref primaryFireDelay,
self.Info.PrimaryOffset, ref primaryBurst, self.Info.PrimaryLocalOffset))
if (info.PrimaryWeapon != null && CheckFire(self, unit, info.PrimaryWeapon, ref primaryFireDelay,
info.PrimaryOffset, ref primaryBurst, info.PrimaryLocalOffset))
{
secondaryFireDelay = Math.Max(4, secondaryFireDelay);
primaryRecoil = 1;
return;
}
if (self.Info.Secondary != null && CheckFire(self, unit, self.Info.Secondary, ref secondaryFireDelay,
self.Info.SecondaryOffset ?? self.Info.PrimaryOffset, ref secondaryBurst, self.Info.SecondaryLocalOffset))
if (info.SecondaryWeapon != null && CheckFire(self, unit, info.SecondaryWeapon, ref secondaryFireDelay,
info.SecondaryOffset ?? info.PrimaryOffset, ref secondaryBurst, info.SecondaryLocalOffset))
{
if (self.Info.SecondaryOffset != null) secondaryRecoil = 1;
if (info.SecondaryOffset != null) secondaryRecoil = 1;
else primaryRecoil = 1;
return;
}
@@ -126,8 +142,9 @@ namespace OpenRa.Game.Traits
var firePos = self.CenterLocation.ToInt2() + Util.GetTurretPosition(self, unit, fireOffset, 0f).ToInt2();
var thisTarget = target; // closure.
var destUnit = thisTarget.traits.GetOrDefault<Unit>();
var info = self.Info.Traits.Get<AttackBaseInfo>();
ScheduleDelayedAction(self.Info.FireDelay, () =>
ScheduleDelayedAction(info.FireDelay, () =>
{
var srcAltitude = unit != null ? unit.Altitude : 0;
var destAltitude = destUnit != null ? destUnit.Altitude : 0;
@@ -140,11 +157,11 @@ namespace OpenRa.Game.Traits
var fireFacing = thisLocalOffset.ElementAtOrDefault(2) +
(self.traits.Contains<Turreted>() ? self.traits.Get<Turreted>().turretFacing : unit.Facing);
Game.world.Add(new Missile(weaponName, self.Owner, self,
Game.world.Add(new Missile(weapon, self.Owner, self,
firePos, thisTarget, srcAltitude, fireFacing));
}
else
Game.world.Add(new Bullet(weaponName, self.Owner, self,
Game.world.Add(new Bullet(weapon, self.Owner, self,
firePos, thisTarget.CenterLocation.ToInt2(), srcAltitude, destAltitude));
if (!string.IsNullOrEmpty(weapon.Report))
@@ -161,10 +178,13 @@ namespace OpenRa.Game.Traits
{
if (mi.Button == MouseButton.Left || underCursor == null) return null;
if (self == underCursor) return null;
var isHeal = Rules.WeaponInfo[self.Info.Primary].Damage < 0;
var isHeal = self.GetPrimaryWeapon().Damage < 0;
if (((underCursor.Owner == self.Owner) ^ isHeal)
&& !mi.Modifiers.HasModifier( Modifiers.Ctrl )) return null;
if (!Combat.HasAnyValidWeapons(self, underCursor)) return null;
return new Order(isHeal ? "Heal" : "Attack", self, underCursor, int2.Zero, null);
}
@@ -186,10 +206,10 @@ namespace OpenRa.Game.Traits
{
const int RangeTolerance = 1; /* how far inside our maximum range we should try to sit */
/* todo: choose the appropriate weapon, when only one works against this target */
var weapon = order.Subject.Info.Primary ?? order.Subject.Info.Secondary;
var weapon = self.GetPrimaryWeapon() ?? self.GetSecondaryWeapon();
self.QueueActivity(new Activities.Attack(order.TargetActor,
Math.Max(0, (int)Rules.WeaponInfo[weapon].Range - RangeTolerance)));
Math.Max(0, (int)weapon.Range - RangeTolerance)));
}
}
}

View File

@@ -2,6 +2,11 @@
namespace OpenRa.Game.Traits
{
class AttackHeliInfo : AttackBaseInfo
{
public override object Create(Actor self) { return new AttackHeli(self); }
}
class AttackHeli : AttackFrontal
{
public AttackHeli(Actor self) : base(self, 20) { }

View File

@@ -2,6 +2,11 @@
namespace OpenRa.Game.Traits
{
class AttackPlaneInfo : AttackBaseInfo
{
public override object Create(Actor self) { return new AttackPlane(self); }
}
class AttackPlane : AttackFrontal
{
public AttackPlane(Actor self) : base(self, 20) { }

View File

@@ -3,9 +3,14 @@ using OpenRa.Game.GameRules;
namespace OpenRa.Game.Traits
{
class AttackTurretedInfo : AttackBaseInfo
{
public override object Create(Actor self) { return new AttackTurreted( self ); }
}
class AttackTurreted : AttackBase, INotifyBuildComplete
{
public AttackTurreted( Actor self ) : base(self) { self.traits.Get<Turreted>(); }
public AttackTurreted(Actor self) : base(self) { }
public override void Tick(Actor self)
{
@@ -31,11 +36,11 @@ namespace OpenRa.Game.Traits
const int RangeTolerance = 1; /* how far inside our maximum range we should try to sit */
/* todo: choose the appropriate weapon, when only one works against this target */
var weapon = order.Subject.Info.Primary ?? order.Subject.Info.Secondary;
var weapon = order.Subject.GetPrimaryWeapon() ?? order.Subject.GetSecondaryWeapon();
if (self.traits.Contains<Mobile>())
self.QueueActivity( new Traits.Activities.Follow( order.TargetActor,
Math.Max( 0, (int)Rules.WeaponInfo[ weapon ].Range - RangeTolerance ) ) );
Math.Max( 0, (int)weapon.Range - RangeTolerance ) ) );
target = order.TargetActor;

View File

@@ -3,13 +3,13 @@ using OpenRa.Game.Traits.Activities;
namespace OpenRa.Game.Traits
{
class AutoHealInfo : StatelessTraitInfo<AutoHeal> { }
class AutoHeal : ITick
{
public AutoHeal(Actor self) { }
void AttackTarget(Actor self, Actor target)
{
var attack = self.traits.WithInterface<AttackBase>().First();
var attack = self.traits.Get<AttackBase>();
if (target != null)
attack.ResolveOrder(self, new Order("Attack", self, target, int2.Zero, null));
else
@@ -17,23 +17,16 @@ namespace OpenRa.Game.Traits
self.CancelActivity();
}
float GetMaximumRange(Actor self)
{
return new[] { self.Info.Primary, self.Info.Secondary }
.Where(w => w != null)
.Max(w => Rules.WeaponInfo[w].Range);
}
bool NeedsNewTarget(Actor self)
{
var attack = self.traits.WithInterface<AttackBase>().First();
var range = GetMaximumRange(self);
var attack = self.traits.Get<AttackBase>();
var range = Util.GetMaximumRange(self);
if (attack.target == null)
return true; // he's dead.
if ((attack.target.Location - self.Location).LengthSquared > range * range + 2)
return true; // wandered off faster than we could follow
if (attack.target.Health == attack.target.Info.Strength)
if (attack.target.Health == attack.target.Info.Traits.Get<OwnedActorInfo>().HP)
return true; // fully healed
return false;
@@ -41,8 +34,8 @@ namespace OpenRa.Game.Traits
public void Tick(Actor self)
{
var attack = self.traits.WithInterface<AttackBase>().First();
var range = GetMaximumRange(self);
var attack = self.traits.Get<AttackBase>();
var range = Util.GetMaximumRange(self);
if (NeedsNewTarget(self))
AttackTarget(self, ChooseTarget(self, range));
@@ -55,7 +48,7 @@ namespace OpenRa.Game.Traits
return inRange
.Where(a => a.Owner == self.Owner && a != self) /* todo: one day deal with friendly players */
.Where(a => Combat.HasAnyValidWeapons(self, a))
.Where(a => a.Health < a.Info.Strength)
.Where(a => a.Health < a.Info.Traits.Get<OwnedActorInfo>().HP)
.OrderBy(a => (a.Location - self.Location).LengthSquared)
.FirstOrDefault();
}

View File

@@ -2,30 +2,23 @@
namespace OpenRa.Game.Traits
{
class AutoTargetInfo : StatelessTraitInfo<AutoTarget> { }
class AutoTarget : ITick, INotifyDamage
{
public AutoTarget(Actor self) {}
void AttackTarget(Actor self, Actor target)
{
var attack = self.traits.WithInterface<AttackBase>().First();
var attack = self.traits.Get<AttackBase>();
if (target != null)
attack.ResolveOrder(self, new Order("Attack", self, target, int2.Zero, null));
}
float GetMaximumRange(Actor self)
{
return new[] { self.Info.Primary, self.Info.Secondary }
.Where(w => w != null)
.Max(w => Rules.WeaponInfo[w].Range);
}
public void Tick(Actor self)
{
if (!self.IsIdle) return;
var attack = self.traits.WithInterface<AttackBase>().First();
var range = GetMaximumRange(self);
var attack = self.traits.Get<AttackBase>();
var range = Util.GetMaximumRange(self);
if (attack.target == null ||
(attack.target.Location - self.Location).LengthSquared > range * range + 2)
@@ -55,7 +48,7 @@ namespace OpenRa.Game.Traits
if (e.Damage < 0)
return; // don't retaliate against healers
var attack = self.traits.WithInterface<AttackBase>().First();
var attack = self.traits.Get<AttackBase>();
if (attack.target != null) return;
AttackTarget(self, e.Attacker);

View File

@@ -3,10 +3,10 @@ using System.Linq;
namespace OpenRa.Game.Traits
{
class BelowUnitsInfo : StatelessTraitInfo<BelowUnits> { }
class BelowUnits : IRenderModifier
{
public BelowUnits(Actor self) { }
public IEnumerable<Renderable> ModifyRender(Actor self, IEnumerable<Renderable> r)
{
return r.Select(a => a.WithZOffset(-1));

23
OpenRa.Game/Traits/Buildable.cs Executable file
View File

@@ -0,0 +1,23 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace OpenRa.Game.Traits
{
class BuildableInfo : StatelessTraitInfo<Buildable>
{
public readonly int TechLevel = -1;
public readonly string Tab = null;
public readonly string[] Prerequisites = { };
public readonly string[] BuiltAt = { };
public readonly Race[] Owner = { };
public readonly int Cost = 0;
public readonly string Description = "";
public readonly string LongDesc = "";
public readonly string Icon = null;
public readonly string[] AlternateName = { };
}
class Buildable { }
}

View File

@@ -9,35 +9,63 @@ using OpenRa.Game.Graphics;
namespace OpenRa.Game.Traits
{
class OwnedActorInfo
{
public readonly int HP = 0;
public readonly ArmorType Armor = ArmorType.none;
public readonly bool Crewed = false; // replace with trait?
public readonly int Sight = 0;
public readonly bool WaterBound = false;
}
class BuildingInfo : OwnedActorInfo, ITraitInfo
{
public readonly int Power = 0;
public readonly bool RequiresPower = false;
public readonly bool BaseNormal = true;
public readonly int Adjacent = 1;
public readonly bool Bib = false;
public readonly bool Capturable = false;
public readonly bool Repairable = true;
public readonly string Footprint = "x";
public readonly string[] Produces = { }; // does this go somewhere else?
public readonly int2 Dimensions = new int2(1, 1);
public readonly bool Unsellable = false;
public object Create(Actor self) { return new Building(self); }
}
class Building : INotifyDamage, IResolveOrder, ITick
{
readonly Actor self;
public readonly BuildingInfo unitInfo;
public readonly BuildingInfo Info;
[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)); } }
public bool Disabled { get { return (manuallyDisabled || (Info.RequiresPower && self.Owner.GetPowerState() != PowerState.Normal)); } }
bool wasDisabled = false;
public Building(Actor self)
{
this.self = self;
unitInfo = (BuildingInfo)self.Info;
Info = self.Info.Traits.Get<BuildingInfo>();
self.CenterLocation = Game.CellSize
* ((float2)self.Location + .5f * (float2)unitInfo.Dimensions);
* ((float2)self.Location + .5f * (float2)Info.Dimensions);
}
public int GetPowerUsage()
{
if (manuallyDisabled)
return 0;
if (unitInfo.Power > 0) /* todo: is this how real-ra scales it? */
return (self.Health * unitInfo.Power) / unitInfo.Strength;
var maxHP = self.Info.Traits.Get<BuildingInfo>().HP;
if (Info.Power > 0)
return (self.Health * Info.Power) / maxHP;
else
return unitInfo.Power;
return Info.Power;
}
public void Damaged(Actor self, AttackInfo e)
@@ -79,8 +107,9 @@ namespace OpenRa.Game.Traits
if (remainingTicks == 0)
{
var costPerHp = (Rules.General.URepairPercent * self.Info.Cost) / self.Info.Strength;
var hpToRepair = Math.Min(Rules.General.URepairStep, self.Info.Strength - self.Health);
var maxHP = self.Info.Traits.Get<BuildingInfo>().HP;
var costPerHp = (Rules.General.URepairPercent * self.Info.Traits.Get<BuildableInfo>().Cost) / maxHP;
var hpToRepair = Math.Min(Rules.General.URepairStep, maxHP - self.Health);
var cost = (int)Math.Ceiling(costPerHp * hpToRepair);
if (!self.Owner.TakeCash(cost))
{
@@ -90,7 +119,7 @@ namespace OpenRa.Game.Traits
Game.world.AddFrameEndTask(w => w.Add(new RepairIndicator(self)));
self.InflictDamage(self, -hpToRepair, Rules.WarheadInfo["Super"]);
if (self.Health == self.Info.Strength)
if (self.Health == maxHP)
{
isRepairing = false;
return;

View File

@@ -2,10 +2,10 @@
namespace OpenRa.Game.Traits
{
class C4DemolitionInfo : StatelessTraitInfo<C4Demolition> { }
class C4Demolition : IIssueOrder, IResolveOrder
{
public C4Demolition(Actor self) { }
public Order IssueOrder(Actor self, int2 xy, MouseInput mi, Actor underCursor)
{
if (mi.Button != MouseButton.Right) return null;

View File

@@ -7,6 +7,15 @@ using OpenRa.Game.Traits.Activities;
namespace OpenRa.Game.Traits
{
class CargoInfo : ITraitInfo
{
public readonly int Passengers = 0;
public readonly UnitMovementType[] PassengerTypes = { };
public readonly int UnloadFacing = 0;
public object Create(Actor self) { return new Cargo(self); }
}
class Cargo : IPips, IIssueOrder, IResolveOrder
{
List<Actor> cargo = new List<Actor>();
@@ -39,7 +48,7 @@ namespace OpenRa.Game.Traits
public bool IsFull(Actor self)
{
return cargo.Count == self.Info.Passengers;
return cargo.Count == self.Info.Traits.Get<CargoInfo>().Passengers;
}
public bool IsEmpty(Actor self)
@@ -56,7 +65,8 @@ namespace OpenRa.Game.Traits
public IEnumerable<PipType> GetPips( Actor self )
{
for (var i = 0; i < self.Info.Passengers; i++)
var numPips = self.Info.Traits.Get<CargoInfo>().Passengers;
for (var i = 0; i < numPips; i++)
if (i >= cargo.Count)
yield return PipType.Transparent;
else

View File

@@ -4,6 +4,11 @@ using OpenRa.Game.Orders;
namespace OpenRa.Game.Traits
{
class ChronoshiftDeployInfo : ITraitInfo
{
public object Create(Actor self) { return new ChronoshiftDeploy(self); }
}
class ChronoshiftDeploy : IIssueOrder, IResolveOrder, ISpeedModifier, ITick, IPips
{
// Recharge logic
@@ -35,7 +40,7 @@ namespace OpenRa.Game.Traits
return;
}
var movement = self.traits.WithInterface<IMovement>().FirstOrDefault();
var movement = self.traits.GetOrDefault<IMovement>();
if (order.OrderString == "ChronoshiftSelf" && movement.CanEnterCell(order.TargetLocation))
{
// Cannot chronoshift into unexplored location

View File

@@ -3,6 +3,9 @@ using OpenRa.Game.Graphics;
namespace OpenRa.Game.Traits
{
// this is NOT bound through rules (it belongs on the world actor!)
// so no *Info required
class ChronoshiftPaletteEffect : IPaletteModifier, ITick
{
const int chronoEffectLength = 20;

View File

@@ -5,6 +5,11 @@ using System.Linq;
namespace OpenRa.Game.Traits
{
class ChronoshiftableInfo : ITraitInfo
{
public object Create(Actor self) { return new Chronoshiftable(self); }
}
class Chronoshiftable : IResolveOrder, ISpeedModifier, ITick
{
// Return-to-sender logic
@@ -40,7 +45,7 @@ namespace OpenRa.Game.Traits
Game.controller.orderGenerator = new ChronoshiftDestinationOrderGenerator(self, power);
}
var movement = self.traits.WithInterface<IMovement>().FirstOrDefault();
var movement = self.traits.GetOrDefault<IMovement>();
if (order.OrderString == "Chronoshift" && movement.CanEnterCell(order.TargetLocation))
{
// Cannot chronoshift into unexplored location

View File

@@ -5,10 +5,10 @@ using System.Text;
namespace OpenRa.Game.Traits
{
class ChronosphereInfo : StatelessTraitInfo<Chronosphere> { }
class Chronosphere : IResolveOrder
{
public Chronosphere(Actor self) { }
public void ResolveOrder(Actor self, Order order)
{
if (order.OrderString == "PlayAnimation")

View File

@@ -4,6 +4,11 @@ using OpenRa.Game.Graphics;
namespace OpenRa.Game.Traits
{
class CloakInfo : ITraitInfo
{
public object Create(Actor self) { return new Cloak(self); }
}
class Cloak : IRenderModifier, INotifyAttack, ITick
{
[Sync]

View File

@@ -3,6 +3,11 @@ using OpenRa.Game.Traits.Activities;
namespace OpenRa.Game.Traits
{
class ConstructionYardInfo : ITraitInfo
{
public object Create(Actor self) { return new ConstructionYard(self); }
}
class ConstructionYard : IIssueOrder, IResolveOrder, IMovement
{
readonly Actor self;

View File

@@ -6,19 +6,19 @@ using OpenRa.Game.Orders;
namespace OpenRa.Game.Traits
{
class DemoTruckInfo : ITraitInfo
{
public object Create(Actor self) { return new DemoTruck(self); }
}
class DemoTruck : Chronoshiftable, IResolveOrder, INotifyDamage
{
readonly Actor self;
public DemoTruck(Actor self)
: base(self)
{
this.self = self;
}
public DemoTruck(Actor self) : base(self) {}
public new void ResolveOrder(Actor self, Order order)
{
// Override chronoshifting action to detonate vehicle
var movement = self.traits.WithInterface<IMovement>().FirstOrDefault();
var movement = self.traits.GetOrDefault<IMovement>();
var chronosphere = Game.world.Actors.Where(a => a.Owner == order.Subject.Owner && a.traits.Contains<Chronosphere>()).FirstOrDefault();
if (order.OrderString == "Chronoshift" && movement.CanEnterCell(order.TargetLocation))
{
@@ -44,7 +44,7 @@ namespace OpenRa.Game.Traits
int2 detonateLocation = self.CenterLocation.ToInt2();
Game.world.AddFrameEndTask(
w => w.Add(new Bullet(self.Info.Primary, detonatedBy.Owner, detonatedBy,
w => w.Add( new Bullet( self.Info.Traits.Get<AttackBaseInfo>().PrimaryWeapon, detonatedBy.Owner, detonatedBy,
detonateLocation, detonateLocation, altitude, altitude)));
}
}

View File

@@ -2,12 +2,12 @@
namespace OpenRa.Game.Traits
{
class EngineerCaptureInfo : StatelessTraitInfo<EngineerCapture> { }
class EngineerCapture : IIssueOrder, IResolveOrder
{
public const int EngineerDamage = 300; // todo: push into rules, as a weapon
public EngineerCapture(Actor self) { }
public Order IssueOrder(Actor self, int2 xy, MouseInput mi, Actor underCursor)
{
if (mi.Button != MouseButton.Right) return null;

View File

@@ -2,10 +2,10 @@
namespace OpenRa.Game.Traits
{
class ExplodesInfo : StatelessTraitInfo<Explodes> { }
class Explodes : INotifyDamage
{
public Explodes(Actor self) {}
public void Damaged(Actor self, AttackInfo e)
{
if (self.IsDead)

View File

@@ -2,13 +2,10 @@
namespace OpenRa.Game.Traits
{
class FakeInfo : StatelessTraitInfo<Fake> { }
class Fake : ITags
{
public Fake(Actor self){}
public IEnumerable<TagType> GetTags()
{
yield return TagType.Fake;
}
public IEnumerable<TagType> GetTags() { yield return TagType.Fake; }
}
}

View File

@@ -2,5 +2,6 @@
namespace OpenRa.Game.Traits
{
class GpsLaunchSite { public GpsLaunchSite(Actor self) { } }
class GpsLaunchSiteInfo : StatelessTraitInfo<GpsLaunchSite> { }
class GpsLaunchSite { }
}

View File

@@ -3,6 +3,11 @@ using OpenRa.Game.Traits.Activities;
namespace OpenRa.Game.Traits
{
class HarvesterInfo : ITraitInfo
{
public object Create(Actor self) { return new Harvester(); }
}
class Harvester : IIssueOrder, IResolveOrder, IPips
{
[Sync]
@@ -13,8 +18,6 @@ namespace OpenRa.Game.Traits
public bool IsFull { get { return oreCarried + gemsCarried == Rules.General.BailCount; } }
public bool IsEmpty { get { return oreCarried == 0 && gemsCarried == 0; } }
public Harvester(Actor self) { }
public void AcceptResource(bool isGem)
{
if (isGem) gemsCarried++;

View File

@@ -1,19 +1,24 @@
using OpenRa.Game.Traits.Activities;
using System;
using System;
using System.Linq;
using OpenRa.Game.GameRules;
using OpenRa.Game.Traits.Activities;
namespace OpenRa.Game.Traits
{
class HelicopterInfo : ITraitInfo
{
public object Create(Actor self) { return new Helicopter(self); }
}
class Helicopter : IIssueOrder, IResolveOrder, IMovement
{
public IDisposable reservation;
public Helicopter(Actor self) {}
// todo: push into data!
static bool HeliCanEnter(Actor a)
{
if (a.Info == Rules.UnitInfo["HPAD"]) return true;
if (a.Info == Rules.UnitInfo["FIX"]) return true;
if (a.Info.Name == "hpad") return true;
if (a.Info.Name == "fix") return true;
return false;
}
@@ -44,7 +49,7 @@ namespace OpenRa.Game.Traits
{
self.CancelActivity();
self.QueueActivity(new HeliFly(Util.CenterOfCell(order.TargetLocation)));
self.QueueActivity(new Turn(self.Info.InitialFacing));
self.QueueActivity( new Turn( self.Info.Traits.GetOrDefault<UnitInfo>().InitialFacing ) );
self.QueueActivity(new HeliLand(true));
}
@@ -55,14 +60,15 @@ namespace OpenRa.Game.Traits
if (res != null)
reservation = res.Reserve(self);
var offset = (order.TargetActor.Info as BuildingInfo).SpawnOffset;
var productionInfo = order.TargetActor.Info.Traits.Get<ProductionInfo>();
var offset = productionInfo.SpawnOffset;
var offsetVec = offset != null ? new float2(offset[0], offset[1]) : float2.Zero;
self.CancelActivity();
self.QueueActivity(new HeliFly(order.TargetActor.CenterLocation + offsetVec));
self.QueueActivity(new Turn(self.Info.InitialFacing));
self.QueueActivity( new Turn( self.Info.Traits.GetOrDefault<UnitInfo>().InitialFacing ) );
self.QueueActivity(new HeliLand(false));
self.QueueActivity(order.TargetActor.Info == Rules.UnitInfo["HPAD"]
self.QueueActivity(order.TargetActor.Info.Name == "hpad"
? (IActivity)new Rearm() : new Repair());
}
}

View File

@@ -2,10 +2,10 @@
namespace OpenRa.Game.Traits
{
class InvisibleToOthersInfo : StatelessTraitInfo<InvisibleToOthers> { }
class InvisibleToOthers : IRenderModifier
{
public InvisibleToOthers(Actor self) { }
public IEnumerable<Renderable> ModifyRender(Actor self, IEnumerable<Renderable> r)
{
return Game.LocalPlayer == self.Owner

View File

@@ -1,14 +1,10 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace OpenRa.Game.Traits
{
class IronCurtainInfo : StatelessTraitInfo<IronCurtain> { }
class IronCurtain : IResolveOrder
{
public IronCurtain(Actor self) {}
public void ResolveOrder(Actor self, Order order)
{
if (order.OrderString == "PlayAnimation")

View File

@@ -3,13 +3,16 @@ using OpenRa.Game.Effects;
namespace OpenRa.Game.Traits
{
class IronCurtainableInfo : ITraitInfo
{
public object Create(Actor self) { return new IronCurtainable(); }
}
class IronCurtainable : IResolveOrder, IDamageModifier, ITick
{
[Sync]
int RemainingTicks = 0;
public IronCurtainable(Actor self) { }
public void Tick(Actor self)
{
if (RemainingTicks > 0)

View File

@@ -2,6 +2,13 @@
namespace OpenRa.Game.Traits
{
class LimitedAmmoInfo : ITraitInfo
{
public readonly int Ammo = 0;
public object Create(Actor self) { return new LimitedAmmo(self); }
}
class LimitedAmmo : INotifyAttack, IPips
{
[Sync]
@@ -10,14 +17,14 @@ namespace OpenRa.Game.Traits
public LimitedAmmo(Actor self)
{
ammo = self.Info.Ammo;
ammo = self.Info.Traits.Get<LimitedAmmoInfo>().Ammo;
this.self = self;
}
public bool HasAmmo() { return ammo > 0; }
public bool GiveAmmo()
{
if (ammo >= self.Info.Ammo) return false;
if (ammo >= self.Info.Traits.Get<LimitedAmmoInfo>().Ammo) return false;
++ammo;
return true;
}
@@ -26,7 +33,8 @@ namespace OpenRa.Game.Traits
public IEnumerable<PipType> GetPips(Actor self)
{
return Graphics.Util.MakeArray(self.Info.Ammo,
var maxAmmo = self.Info.Traits.Get<LimitedAmmoInfo>().Ammo;
return Graphics.Util.MakeArray(maxAmmo,
i => ammo > i ? PipType.Green : PipType.Transparent);
}
}

View File

@@ -3,6 +3,11 @@ using OpenRa.Game.Traits.Activities;
namespace OpenRa.Game.Traits
{
class McvDeployInfo : ITraitInfo
{
public object Create(Actor self) { return new McvDeploy(self); }
}
class McvDeploy : IIssueOrder, IResolveOrder
{
public McvDeploy(Actor self) { }
@@ -19,8 +24,8 @@ namespace OpenRa.Game.Traits
{
if( order.OrderString == "DeployMcv" )
{
var factBuildingInfo = (BuildingInfo)Rules.UnitInfo[ "fact" ];
if( Game.CanPlaceBuilding( factBuildingInfo, self.Location - new int2( 1, 1 ), self, false ) )
var factBuildingInfo = Rules.NewUnitInfo[ "fact" ].Traits.Get<BuildingInfo>();
if( Game.CanPlaceBuilding( "fact", factBuildingInfo, self.Location - new int2( 1, 1 ), self, false ) )
{
self.CancelActivity();
self.QueueActivity( new Turn( 96 ) );

View File

@@ -1,8 +1,6 @@
namespace OpenRa.Game.Traits
{
class MineImmune
{
public MineImmune(Actor self) { }
}
class MineImmuneInfo : StatelessTraitInfo<MineImmune> { }
class MineImmune { }
}

View File

@@ -1,22 +1,27 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Linq;
namespace OpenRa.Game.Traits
{
class MinelayerInfo : ITraitInfo
{
public readonly string Mine = "minv";
public object Create( Actor self )
{
return new Minelayer();
}
}
class Minelayer : IIssueOrder, IResolveOrder
{
public Minelayer(Actor self) { }
public Order IssueOrder(Actor self, int2 xy, MouseInput mi, Actor underCursor)
{
var limitedAmmo = self.traits.GetOrDefault<LimitedAmmo>();
if (limitedAmmo != null && !limitedAmmo.HasAmmo())
return null;
// Ensure that the cell is empty except for the minelayer
if (Game.UnitInfluence.GetUnitsAt( xy ).Any(a => a != self))
if (Game.UnitInfluence.GetUnitsAt(xy).Any(a => a != self))
return null;
if (mi.Button == MouseButton.Right && underCursor == self)
@@ -36,7 +41,7 @@ namespace OpenRa.Game.Traits
// todo: delay a bit? (req making deploy-mine an activity)
Game.world.AddFrameEndTask(
w => w.Add(new Actor(Rules.UnitInfo[self.Info.Primary], self.Location, self.Owner)));
w => w.Add(new Actor(self.Info.Traits.Get<MinelayerInfo>().Mine, self.Location, self.Owner)));
}
}
}

View File

@@ -5,6 +5,13 @@ using OpenRa.Game.GameRules;
namespace OpenRa.Game.Traits
{
class MobileInfo : ITraitInfo
{
public readonly UnitMovementType MovementType = UnitMovementType.Wheel;
public object Create(Actor self) { return new Mobile(self); }
}
class Mobile : IIssueOrder, IResolveOrder, IOccupySpace, IMovement
{
readonly Actor self;
@@ -78,19 +85,7 @@ namespace OpenRa.Game.Traits
public UnitMovementType GetMovementType()
{
switch (Rules.UnitCategory[self.Info.Name])
{
case "Infantry":
return UnitMovementType.Foot;
case "Vehicle":
return (self.Info as VehicleInfo).Tracked ? UnitMovementType.Track : UnitMovementType.Wheel;
case "Ship":
return UnitMovementType.Float;
case "Plane":
return UnitMovementType.Fly;
default:
throw new InvalidOperationException("GetMovementType on unit that shouldn't be able to move.");
}
return self.Info.Traits.Get<MobileInfo>().MovementType;
}
public bool CanEnterCell(int2 a)

View File

@@ -1,15 +1,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Linq;
using OpenRa.Game.Traits.Activities;
namespace OpenRa.Game.Traits
{
class PassengerInfo : StatelessTraitInfo<Passenger> {}
class Passenger : IIssueOrder, IResolveOrder
{
public Passenger(Actor self) { }
public Order IssueOrder(Actor self, int2 xy, MouseInput mi, Actor underCursor)
{
if (mi.Button != MouseButton.Right)
@@ -22,8 +19,8 @@ namespace OpenRa.Game.Traits
if (cargo == null || cargo.IsFull(underCursor))
return null;
var umt = self.traits.WithInterface<IMovement>().First().GetMovementType();
if (!underCursor.Info.PassengerTypes.Contains(umt))
var umt = self.traits.Get<IMovement>().GetMovementType();
if (!underCursor.Info.Traits.Get<CargoInfo>().PassengerTypes.Contains(umt))
return null;
return new Order("EnterTransport", self, underCursor, int2.Zero, null);

View File

@@ -1,11 +1,13 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using OpenRa.Game.Traits.Activities;
namespace OpenRa.Game.Traits
{
class PlaneInfo : ITraitInfo
{
public object Create(Actor self) { return new Plane(self); }
}
class Plane : IIssueOrder, IResolveOrder, IMovement
{
public IDisposable reservation;
@@ -15,8 +17,8 @@ namespace OpenRa.Game.Traits
// todo: push into data!
static bool PlaneCanEnter(Actor a)
{
if (a.Info == Rules.UnitInfo["AFLD"]) return true;
if (a.Info == Rules.UnitInfo["FIX"]) return true;
if (a.Info.Name == "afld") return true;
if (a.Info.Name == "fix") return true;
return false;
}
@@ -59,7 +61,7 @@ namespace OpenRa.Game.Traits
self.CancelActivity();
self.QueueActivity(new ReturnToBase(self, order.TargetActor));
self.QueueActivity(order.TargetActor.Info == Rules.UnitInfo["AFLD"]
self.QueueActivity(order.TargetActor.Info.Name == "afld"
? (IActivity)new Rearm() : new Repair());
}
}

View File

@@ -1,9 +1,17 @@
using OpenRa.Game.GameRules;
using System.Collections.Generic;
using System.Linq;
using System.Collections.Generic;
using OpenRa.Game.GameRules;
namespace OpenRa.Game.Traits
{
class ProductionInfo : ITraitInfo
{
public readonly int[] SpawnOffset = null;
public readonly string[] Produces = { };
public object Create(Actor self) { return new Production(self); }
}
class Production : IIssueOrder, IResolveOrder, IProducer, ITags
{
bool isPrimary = false;
@@ -11,23 +19,23 @@ namespace OpenRa.Game.Traits
public Production( Actor self ) { }
public virtual int2? CreationLocation( Actor self, UnitInfo producee )
public virtual int2? CreationLocation( Actor self, NewUnitInfo producee )
{
return ( 1 / 24f * self.CenterLocation ).ToInt2();
}
public virtual int CreationFacing( Actor self, Actor newUnit )
{
return newUnit.Info.InitialFacing;
return newUnit.Info.Traits.GetOrDefault<UnitInfo>().InitialFacing;
}
public bool Produce( Actor self, UnitInfo producee )
public bool Produce( Actor self, NewUnitInfo producee )
{
var location = CreationLocation( self, producee );
if( location == null || Game.UnitInfluence.GetUnitsAt( location.Value ).Any() )
return false;
var newUnit = new Actor( producee, location.Value, self.Owner );
var newUnit = new Actor( producee.Name, location.Value, self.Owner );
newUnit.traits.Get<Unit>().Facing = CreationFacing( self, newUnit ); ;
var rp = self.traits.GetOrDefault<RallyPoint>();
@@ -38,10 +46,10 @@ namespace OpenRa.Game.Traits
newUnit.QueueActivity( new Activities.Move( rp.rallyPoint, 1 ) );
}
var bi = self.Info as BuildingInfo;
if (bi != null && bi.SpawnOffset != null)
var pi = self.Info.Traits.Get<ProductionInfo>();
if (pi != null && pi.SpawnOffset != null)
newUnit.CenterLocation = self.CenterLocation
+ new float2(bi.SpawnOffset[0], bi.SpawnOffset[1]);
+ new float2(pi.SpawnOffset[0], pi.SpawnOffset[1]);
Game.world.Add( newUnit );
@@ -78,12 +86,12 @@ namespace OpenRa.Game.Traits
}
// Cancel existing primaries
foreach (var p in (self.Info as BuildingInfo).Produces)
foreach (var p in self.Info.Traits.Get<ProductionInfo>().Produces)
{
foreach (var b in Game.world.Actors.Where(x => x.traits.Contains<Production>()
&& x.Owner == self.Owner
&& x.traits.Get<Production>().IsPrimary == true
&& (x.Info as BuildingInfo).Produces.Contains(p)))
&& (x.Info.Traits.Get<ProductionInfo>().Produces.Contains(p))))
{
b.traits.Get<Production>().SetPrimaryProducer(b, false);
}

View File

@@ -6,6 +6,11 @@ using IjwFramework.Collections;
namespace OpenRa.Game.Traits
{
class ProductionQueueInfo : ITraitInfo
{
public object Create(Actor self) { return new ProductionQueue(self); }
}
class ProductionQueue : IResolveOrder, ITick
{
Actor self;
@@ -29,7 +34,7 @@ namespace OpenRa.Game.Traits
case "StartProduction":
{
string group = Rules.UnitCategory[ order.TargetString ];
var ui = Rules.UnitInfo[ order.TargetString ];
var ui = Rules.NewUnitInfo[ order.TargetString ].Traits.Get<BuildableInfo>();
var time = ui.Cost
* Rules.General.BuildSpeed /* todo: country-specific build speed bonus */
* ( 25 * 60 ) /* frames per min */ /* todo: build acceleration, if we do that */
@@ -121,7 +126,7 @@ namespace OpenRa.Game.Traits
public void BuildUnit( string name )
{
var newUnitType = Rules.UnitInfo[ name ];
var newUnitType = Rules.NewUnitInfo[ name ];
var producerTypes = Rules.TechTree.UnitBuiltAt( newUnitType );
Actor producer = null;

View File

@@ -1,11 +1,13 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Linq;
using OpenRa.Game.GameRules;
namespace OpenRa.Game.Traits
{
class ProductionSurroundInfo : ITraitInfo
{
public object Create(Actor self) { return new ProductionSurround(self); }
}
class ProductionSurround : Production
{
public ProductionSurround(Actor self) : base(self) { }
@@ -24,9 +26,9 @@ namespace OpenRa.Game.Traits
return null;
}
public override int2? CreationLocation(Actor self, UnitInfo producee)
public override int2? CreationLocation(Actor self, NewUnitInfo producee)
{
return FindAdjacentTile(self, producee.WaterBound ?
return FindAdjacentTile(self, producee.Traits.Get<OwnedActorInfo>().WaterBound ?
UnitMovementType.Float : UnitMovementType.Wheel); /* hackety hack */
}

View File

@@ -1,19 +1,11 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace OpenRa.Game.Traits
{
class ProvidesRadarInfo : StatelessTraitInfo<ProvidesRadar> {}
class ProvidesRadar
{
Actor self;
public ProvidesRadar(Actor self)
{
this.self = self;
}
public bool IsActive()
public bool IsActive(Actor self)
{
// TODO: Check for nearby MRJ

View File

@@ -4,6 +4,13 @@ using OpenRa.Game.Orders;
namespace OpenRa.Game.Traits
{
class RallyPointInfo : ITraitInfo
{
public readonly int[] RallyPoint = { 1, 3 };
public object Create(Actor self) { return new RallyPoint(self); }
}
class RallyPoint : IRender, IIssueOrder, IResolveOrder, ITick
{
[Sync]
@@ -12,8 +19,8 @@ namespace OpenRa.Game.Traits
public RallyPoint(Actor self)
{
var bi = self.traits.Get<Building>().unitInfo;
rallyPoint = self.Location + new int2(bi.RallyPoint[0], bi.RallyPoint[1]);
var info = self.Info.Traits.Get<RallyPointInfo>();
rallyPoint = self.Location + new int2(info.RallyPoint[0], info.RallyPoint[1]);
anim = new Animation("flagfly");
anim.PlayRepeating("idle");
}

View File

@@ -1,10 +1,13 @@
using System;
using System.Collections.Generic;
using OpenRa.Game.Graphics;
using OpenRa.Game.Effects;
namespace OpenRa.Game.Traits
{
class RenderBuildingInfo : RenderSimpleInfo
{
public override object Create(Actor self) { return new RenderBuilding(self); }
}
class RenderBuilding : RenderSimple, INotifyDamage, INotifySold
{
const int SmallBibStart = 1;
@@ -30,7 +33,7 @@ namespace OpenRa.Game.Traits
void DoBib(Actor self, bool isRemove)
{
var buildingInfo = self.traits.Get<Building>().unitInfo;
var buildingInfo = self.Info.Traits.Get<BuildingInfo>();
if (buildingInfo.Bib)
{
var size = buildingInfo.Dimensions.X;

View File

@@ -1,10 +1,11 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace OpenRa.Game.Traits
{
class RenderBuildingChargeInfo : RenderBuildingInfo
{
public override object Create(Actor self) { return new RenderBuildingCharge(self); }
}
/* used for tesla */
class RenderBuildingCharge : RenderBuilding, INotifyAttack
{

View File

@@ -1,7 +1,11 @@
using System;
namespace OpenRa.Game.Traits
{
class RenderBuildingOreInfo : RenderBuildingInfo
{
public override object Create(Actor self) { return new RenderBuildingOre(self); }
}
class RenderBuildingOre : RenderBuilding, INotifyBuildComplete
{
public RenderBuildingOre(Actor self)

View File

@@ -1,6 +1,11 @@
namespace OpenRa.Game.Traits
{
class RenderBuildingTurretedInfo : RenderBuildingInfo
{
public override object Create(Actor self) { return new RenderBuildingTurreted(self); }
}
class RenderBuildingTurreted : RenderBuilding, INotifyBuildComplete
{
public RenderBuildingTurreted(Actor self)

View File

@@ -4,6 +4,11 @@ using OpenRa.Game.Graphics;
namespace OpenRa.Game.Traits
{
class RenderWarFactoryInfo : ITraitInfo
{
public object Create(Actor self) { return new RenderWarFactory(self); }
}
class RenderWarFactory : IRender, INotifyBuildComplete, INotifyDamage, ITick, INotifyProduction
{
public Animation roof;
@@ -21,7 +26,7 @@ namespace OpenRa.Game.Traits
public RenderWarFactory(Actor self)
{
this.self = self;
roof = new Animation(self.Info.Image ?? self.Info.Name);
roof = new Animation(self.traits.Get<RenderSimple>().GetImage(self));
}
public void BuildingComplete( Actor self )

View File

@@ -1,13 +1,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using OpenRa.Game.Graphics;
using OpenRa.Game.GameRules;
using OpenRa.Game.Effects;
using OpenRa.Game.Effects;
namespace OpenRa.Game.Traits
{
class RenderInfantryInfo : RenderSimpleInfo
{
public override object Create(Actor self) { return new RenderInfantry(self); }
}
class RenderInfantry : RenderSimple, INotifyAttack, INotifyDamage
{
public RenderInfantry(Actor self)

View File

@@ -1,18 +1,30 @@
using System;
using System.Collections.Generic;
using IjwFramework.Collections;
using System.Linq;
using OpenRa.Game.Graphics;
namespace OpenRa.Game.Traits
{
abstract class RenderSimpleInfo : ITraitInfo
{
public readonly string Image = null;
public abstract object Create(Actor self);
}
abstract class RenderSimple : IRender, ITick
{
public Dictionary<string, AnimationWithOffset> anims = new Dictionary<string, AnimationWithOffset>();
public Animation anim { get { return anims[ "" ].Animation; } }
public string GetImage(Actor self)
{
return self.Info.Traits.Get<RenderSimpleInfo>().Image ?? self.Info.Name;
}
public RenderSimple(Actor self)
{
anims.Add( "", new Animation( self.Info.Image ?? self.Info.Name ) );
anims.Add( "", new Animation( GetImage(self) ) );
}
public virtual IEnumerable<Renderable> Render( Actor self )

View File

@@ -5,6 +5,11 @@ using OpenRa.Game.GameRules;
namespace OpenRa.Game.Traits
{
class RenderUnitInfo : RenderSimpleInfo
{
public override object Create(Actor self) { return new RenderUnit(self); }
}
class RenderUnit : RenderSimple, INotifyDamage
{
public RenderUnit(Actor self)

View File

@@ -5,22 +5,26 @@ using OpenRa.Game.Graphics;
namespace OpenRa.Game.Traits
{
class RenderUnitMuzzleFlashInfo : RenderUnitInfo
{
public override object Create(Actor self) { return new RenderUnitMuzzleFlash(self); }
}
class RenderUnitMuzzleFlash : RenderUnit
{
public RenderUnitMuzzleFlash(Actor self)
: base(self)
{
if (!self.Info.MuzzleFlash) throw new InvalidOperationException("wtf??");
var unit = self.traits.Get<Unit>();
var attack = self.traits.WithInterface<AttackBase>().First();
var attack = self.traits.Get<AttackBase>();
var attackInfo = self.Info.Traits.Get<AttackBaseInfo>();
var muzzleFlash = new Animation(self.Info.Name);
var muzzleFlash = new Animation(GetImage(self));
muzzleFlash.PlayFetchIndex("muzzle",
() => (Util.QuantizeFacing(unit.Facing, 8)) * 6 + (int)(attack.primaryRecoil * 5.9f));
anims.Add( "muzzle", new AnimationWithOffset(
muzzleFlash,
() => self.Info.PrimaryOffset.AbsOffset(),
() => attackInfo.PrimaryOffset.AbsOffset(),
() => attack.primaryRecoil <= 0 ) );
}
}

View File

@@ -2,6 +2,11 @@
namespace OpenRa.Game.Traits
{
class RenderUnitReloadInfo : RenderUnitInfo
{
public override object Create(Actor self) { return new RenderUnitReload(self); }
}
class RenderUnitReload : RenderUnit
{
public RenderUnitReload(Actor self)
@@ -11,7 +16,7 @@ namespace OpenRa.Game.Traits
{
var isAttacking = self.GetCurrentActivity() is Activities.Attack;
var attack = self.traits.WithInterface<AttackBase>().FirstOrDefault();
var attack = self.traits.GetOrDefault<AttackBase>();
if (attack != null)
anim.ReplaceAnim((attack.IsReloading() ? "empty-" : "")

View File

@@ -2,6 +2,14 @@
namespace OpenRa.Game.Traits
{
class RenderUnitRotorInfo : RenderUnitInfo
{
public readonly int[] PrimaryOffset = { 0, 0 };
public readonly int[] SecondaryOffset = null;
public override object Create(Actor self) { return new RenderUnitRotor(self); }
}
class RenderUnitRotor : RenderUnit
{
public Animation rotorAnim, secondRotorAnim;
@@ -10,21 +18,22 @@ namespace OpenRa.Game.Traits
: base(self)
{
var unit = self.traits.Get<Unit>();
var info = self.Info.Traits.Get<RenderUnitRotorInfo>();
rotorAnim = new Animation(self.Info.Name);
rotorAnim = new Animation(GetImage(self));
rotorAnim.PlayRepeating("rotor");
anims.Add( "rotor_1", new AnimationWithOffset(
rotorAnim,
() => Util.GetTurretPosition( self, unit, self.Info.RotorOffset, 0 ),
() => Util.GetTurretPosition( self, unit, info.PrimaryOffset, 0 ),
null ) );
if (self.Info.RotorOffset2 == null) return;
if (info.SecondaryOffset == null) return;
secondRotorAnim = new Animation( self.Info.Name );
secondRotorAnim = new Animation(GetImage(self));
secondRotorAnim.PlayRepeating( "rotor2" );
anims.Add( "rotor_2", new AnimationWithOffset(
secondRotorAnim,
() => Util.GetTurretPosition(self, unit, self.Info.RotorOffset2, 0),
() => Util.GetTurretPosition(self, unit, info.SecondaryOffset, 0),
null ) );
}

View File

@@ -1,22 +1,28 @@
using System.Collections.Generic;
using System.Linq;
using OpenRa.Game.Graphics;
namespace OpenRa.Game.Traits
{
class RenderUnitSpinnerInfo : RenderUnitInfo
{
public readonly int[] Offset = { 0, 0 };
public override object Create(Actor self) { return new RenderUnitSpinner(self); }
}
class RenderUnitSpinner : RenderUnit
{
public Animation spinnerAnim;
public RenderUnitSpinner( Actor self )
: base(self)
{
var unit = self.traits.Get<Unit>();
var info = self.Info.Traits.Get<RenderUnitSpinnerInfo>();
spinnerAnim = new Animation( self.Info.Name );
var spinnerAnim = new Animation( GetImage(self) );
spinnerAnim.PlayRepeating( "spinner" );
anims.Add( "spinner", new AnimationWithOffset(
spinnerAnim,
() => Util.GetTurretPosition( self, unit, self.Info.PrimaryOffset, 0 ),
() => Util.GetTurretPosition( self, unit, info.Offset, 0 ),
null ) );
}
}

View File

@@ -4,41 +4,45 @@ using OpenRa.Game.Graphics;
namespace OpenRa.Game.Traits
{
class RenderUnitTurretedInfo : RenderUnitInfo
{
public override object Create(Actor self) { return new RenderUnitTurreted(self); }
}
class RenderUnitTurreted : RenderUnit
{
public Animation muzzleFlash;
public RenderUnitTurreted(Actor self)
: base(self)
{
var unit = self.traits.Get<Unit>();
var turreted = self.traits.Get<Turreted>();
var attack = self.traits.WithInterface<AttackBase>().FirstOrDefault();
var attack = self.traits.GetOrDefault<AttackBase>();
var attackInfo = self.Info.Traits.Get<AttackBaseInfo>();
var turretAnim = new Animation(self.Info.Name);
var turretAnim = new Animation(GetImage(self));
turretAnim.PlayFacing( "turret", () => turreted.turretFacing );
if( self.Info.PrimaryOffset != null )
if( attackInfo.PrimaryOffset != null )
anims.Add("turret_1", new AnimationWithOffset(
turretAnim,
() => Util.GetTurretPosition(self, unit, self.Info.PrimaryOffset, attack.primaryRecoil),
() => Util.GetTurretPosition(self, unit, attackInfo.PrimaryOffset, attack.primaryRecoil),
null) { ZOffset = 1 });
if( self.Info.SecondaryOffset != null )
if (attackInfo.SecondaryOffset != null)
anims.Add("turret_2", new AnimationWithOffset(
turretAnim,
() => Util.GetTurretPosition(self, unit, self.Info.SecondaryOffset, attack.secondaryRecoil),
() => Util.GetTurretPosition(self, unit, attackInfo.SecondaryOffset, attack.secondaryRecoil),
null) { ZOffset = 1 });
if( self.Info.MuzzleFlash )
if( attackInfo.MuzzleFlash )
{
muzzleFlash = new Animation( self.Info.Name );
var muzzleFlash = new Animation( GetImage(self) );
muzzleFlash.PlayFetchIndex( "muzzle",
() => ( Util.QuantizeFacing( self.traits.Get<Turreted>().turretFacing, 8 ) ) * 6
+ (int)( attack.primaryRecoil * 5.9f ) ); /* hack: recoil can be 1.0f, but don't overflow into next anim */
anims.Add( "muzzle_flash", new AnimationWithOffset(
muzzleFlash,
() => Util.GetTurretPosition( self, unit, self.Info.PrimaryOffset, attack.primaryRecoil ),
() => Util.GetTurretPosition(self, unit, attackInfo.PrimaryOffset, attack.primaryRecoil),
() => attack.primaryRecoil <= 0 ) );
}
}

View File

@@ -6,6 +6,11 @@ using OpenRa.Game.Traits.Activities;
namespace OpenRa.Game.Traits
{
class RepairableInfo : ITraitInfo
{
public object Create(Actor self) { return new Repairable(self); }
}
class Repairable : IIssueOrder, IResolveOrder
{
IDisposable reservation;
@@ -16,7 +21,7 @@ namespace OpenRa.Game.Traits
if (mi.Button != MouseButton.Right) return null;
if (underCursor == null) return null;
if (underCursor.Info == Rules.UnitInfo["FIX"]
if (underCursor.Info.Name == "fix"
&& underCursor.Owner == self.Owner
&& !Reservable.IsReserved(underCursor))
return new Order("Enter", self, underCursor, int2.Zero, null);

View File

@@ -2,6 +2,11 @@
namespace OpenRa.Game.Traits
{
class ReservableInfo : ITraitInfo
{
public object Create(Actor self) { return new Reservable(self); }
}
class Reservable : ITick
{
public Reservable(Actor self) { }

View File

@@ -1,10 +1,10 @@
namespace OpenRa.Game.Traits
{
class SeedsOreInfo : StatelessTraitInfo<SeedsOre> {}
class SeedsOre : ITick
{
public SeedsOre( Actor self ) {}
const double OreSeedProbability = .05; // todo: push this out into rules
public void Tick(Actor self)

View File

@@ -0,0 +1,16 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace OpenRa.Game.Traits
{
class SelectableInfo : StatelessTraitInfo<Selectable>
{
public readonly int Priority = 10;
public readonly int[] Bounds = null;
public readonly string Voice = "GenericVoice";
}
class Selectable {}
}

View File

@@ -5,6 +5,11 @@ using OpenRa.Game.GameRules;
namespace OpenRa.Game.Traits
{
class SquishByTankInfo : ITraitInfo
{
public object Create(Actor self) { return new SquishByTank(self); }
}
class SquishByTank : ICrushable
{
readonly Actor self;

View File

@@ -3,21 +3,18 @@ using System;
using OpenRa.Game.GameRules;
namespace OpenRa.Game.Traits
{
class StoresOreInfo : StatelessTraitInfo<StoresOre>
{
public readonly int Pips = 0;
public readonly int Capacity = 0;
}
class StoresOre : IPips, IAcceptThief
{
public const int MaxStealAmount = 100; //todo: How is cash stolen determined?
readonly Actor self;
public StoresOre(Actor self)
{
this.self = self;
}
public void OnSteal(Actor self, Actor thief)
{
// Steal half the ore the building holds
var toSteal = (self.Info as BuildingInfo).Storage/2;
var toSteal = self.Info.Traits.Get<StoresOreInfo>().Capacity / 2;
self.Owner.TakeCash(toSteal);
thief.Owner.GiveCash(toSteal);
@@ -31,15 +28,12 @@ namespace OpenRa.Game.Traits
public IEnumerable<PipType> GetPips(Actor self)
{
for (int i = 0; i < self.Info.OrePips; i++)
{
if (Game.LocalPlayer.GetSiloFullness() > i * 1.0f / self.Info.OrePips)
{
yield return PipType.Yellow;
continue;
}
yield return PipType.Transparent;
}
var numPips = self.Info.Traits.Get<StoresOreInfo>().Pips;
return Graphics.Util.MakeArray( numPips,
i => (Game.LocalPlayer.GetSiloFullness() > i * 1.0f / numPips)
? PipType.Yellow : PipType.Transparent );
}
}
}

View File

@@ -4,6 +4,11 @@ using OpenRa.Game.Graphics;
namespace OpenRa.Game.Traits
{
class SubmarineInfo : ITraitInfo
{
public object Create(Actor self) { return new Submarine(self); }
}
class Submarine : IRenderModifier, INotifyAttack, ITick, INotifyDamage
{
[Sync]

View File

@@ -1,6 +1,11 @@
namespace OpenRa.Game.Traits
{
class TakeCoverInfo : ITraitInfo
{
public object Create(Actor self) { return new TakeCover(self); }
}
// infantry prone behavior
class TakeCover : ITick, INotifyDamage, IDamageModifier, ISpeedModifier
{

View File

@@ -3,10 +3,10 @@ using System.Collections.Generic;
using System.Linq;
namespace OpenRa.Game.Traits
{
class ThiefInfo : StatelessTraitInfo<Thief> { }
class Thief : IIssueOrder, IResolveOrder
{
public Thief(Actor self) { }
public Order IssueOrder(Actor self, int2 xy, MouseInput mi, Actor underCursor)
{
if (mi.Button != MouseButton.Right) return null;

View File

@@ -2,6 +2,7 @@
using System.Drawing;
using OpenRa.Game.GameRules;
using OpenRa.Game.Graphics;
using IjwFramework.Types;
namespace OpenRa.Game.Traits
{
@@ -24,7 +25,7 @@ namespace OpenRa.Game.Traits
interface IProducer
{
bool Produce( Actor self, UnitInfo producee );
bool Produce( Actor self, NewUnitInfo producee );
void SetPrimaryProducer(Actor self, bool isPrimary);
}
interface IOccupySpace { IEnumerable<int2> OccupiedCells(); }
@@ -69,4 +70,14 @@ namespace OpenRa.Game.Traits
public Renderable WithZOffset(int newOffset) { return new Renderable(Sprite, Pos, Palette, newOffset); }
public Renderable WithPos(float2 newPos) { return new Renderable(Sprite, newPos, Palette, ZOffset); }
}
interface ITraitInfo { object Create(Actor self); }
class StatelessTraitInfo<T> : ITraitInfo
where T : new()
{
static Lazy<T> Instance = Lazy.New(() => new T());
public object Create(Actor self) { return Instance.Value; }
}
}

View File

@@ -1,20 +0,0 @@
using System.Collections.Generic;
using OpenRa.Game.Graphics;
namespace OpenRa.Game.Traits
{
class Tree : IRender
{
Sprite Image;
public Tree(Sprite treeImage)
{
Image = treeImage;
}
public IEnumerable<Renderable> Render(Actor self)
{
yield return new Renderable(Image, Game.CellSize * (float2)self.Location, 0);
}
}
}

View File

@@ -1,6 +1,15 @@
using System.Linq;
namespace OpenRa.Game.Traits
{
class TurretedInfo : ITraitInfo
{
public readonly int ROT = 0;
public readonly int InitialFacing = 128;
public object Create(Actor self) { return new Turreted(self); }
}
class Turreted : ITick
{
[Sync]
@@ -9,13 +18,13 @@ namespace OpenRa.Game.Traits
public Turreted(Actor self)
{
turretFacing = self.Info.InitialFacing;
turretFacing = self.Info.Traits.Get<TurretedInfo>().InitialFacing;
}
public void Tick( Actor self )
{
var df = desiredFacing ?? ( self.traits.Contains<Unit>() ? self.traits.Get<Unit>().Facing : turretFacing );
Util.TickFacing( ref turretFacing, df, self.Info.ROT );
Util.TickFacing(ref turretFacing, df, self.Info.Traits.Get<TurretedInfo>().ROT);
}
}
}

View File

@@ -1,6 +1,16 @@
using OpenRa.Game.GameRules;
namespace OpenRa.Game.Traits
{
class UnitInfo : OwnedActorInfo, ITraitInfo
{
public readonly int InitialFacing = 128;
public readonly int ROT = 0;
public readonly int Speed = 0;
public object Create( Actor self ) { return new Unit( self ); }
}
class Unit : INotifyDamage
{
[Sync]

View File

@@ -82,14 +82,14 @@ namespace OpenRa.Game.Traits
static float2 GetRecoil(Actor self, float recoil)
{
if (self.Info.Recoil == 0) return float2.Zero;
var rut = self.traits.WithInterface<RenderUnitTurreted>().FirstOrDefault();
if (self.Info.Traits.Get<AttackBaseInfo>().Recoil == 0) return float2.Zero;
var rut = self.traits.GetOrDefault<RenderUnitTurreted>();
if (rut == null) return float2.Zero;
var facing = self.traits.Get<Turreted>().turretFacing;
var quantizedFacing = QuantizeFacing(facing, rut.anim.CurrentSequence.Length) * (256 / rut.anim.CurrentSequence.Length);
return RotateVectorByFacing(new float2(0, recoil * self.Info.Recoil), quantizedFacing, .7f);
return RotateVectorByFacing(new float2(0, recoil * self.Info.Traits.Get<AttackBaseInfo>().Recoil), quantizedFacing, .7f);
}
public static float2 CenterOfCell(int2 loc)
@@ -106,7 +106,7 @@ namespace OpenRa.Game.Traits
{
if( unit == null ) return int2.Zero; /* things that don't have a rotating base don't need the turrets repositioned */
var ru = self.traits.WithInterface<RenderUnit>().FirstOrDefault();
var ru = self.traits.GetOrDefault<RenderUnit>();
var numDirs = (ru != null) ? ru.anim.CurrentSequence.Length : 8;
var bodyFacing = unit.Facing;
var quantizedFacing = QuantizeFacing(bodyFacing, numDirs) * (256 / numDirs);
@@ -127,14 +127,14 @@ namespace OpenRa.Game.Traits
public static float GetEffectiveSpeed(Actor self)
{
var mi = self.Info as MobileInfo;
if (mi == null) return 0f;
var unitInfo = self.Info.Traits.GetOrDefault<UnitInfo>();
if( unitInfo == null ) return 0f;
var modifier = self.traits
.WithInterface<ISpeedModifier>()
.Select(t => t.GetSpeedModifier())
.Product();
return mi.Speed * modifier;
return unitInfo.Speed * modifier;
}
public static IActivity SequenceActivities(params IActivity[] acts)
@@ -142,5 +142,12 @@ namespace OpenRa.Game.Traits
return acts.Reverse().Aggregate(
(next, a) => { a.NextActivity = next; return a; });
}
public static float GetMaximumRange(Actor self)
{
var info = self.Info.Traits.Get<AttackBaseInfo>();
return new[] { self.GetPrimaryWeapon(), self.GetSecondaryWeapon() }
.Where(w => w != null).Max(w => w.Range);
}
}
}

View File

@@ -2,6 +2,9 @@
namespace OpenRa.Game.Traits
{
// this is NOT bound through rules (it belongs on the world actor!)
// so no *Info required
class WaterPaletteRotation : ITick, IPaletteModifier
{
public WaterPaletteRotation(Actor self) { }

View File

@@ -6,10 +6,10 @@ using OpenRa.Game.Graphics;
namespace OpenRa.Game.Traits
{
class WithShadowInfo : StatelessTraitInfo<WithShadow> {}
class WithShadow : IRenderModifier
{
public WithShadow(Actor self) {}
public IEnumerable<Renderable> ModifyRender(Actor self, IEnumerable<Renderable> r)
{
var unit = self.traits.Get<Unit>();