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

@@ -33,6 +33,10 @@
<ErrorReport>prompt</ErrorReport>
</PropertyGroup>
<ItemGroup>
<Reference Include="IjwFramework, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\Ijw.DirectX\Release\IjwFramework.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Core">
<RequiredTargetFramework>3.5</RequiredTargetFramework>

View File

@@ -1,50 +1,56 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Linq;
using System.Reflection;
using IjwFramework.Collections;
namespace OpenRa
{
public class TypeDictionary
{
Dictionary<Type, object> inner = new Dictionary<Type, object>();
public void Add( Type t, object val )
{
inner.Add( t, val );
}
Cache<Type, List<object>> innerInherit = new Cache<Type, List<object>>( _ => new List<object>() );
public void Add( object val )
{
Add( val.GetType(), val );
}
var t = val.GetType();
public void Remove<T>()
{
inner.Remove( typeof( T ) );
foreach( var i in t.GetInterfaces() )
innerInherit[ i ].Add( val );
foreach( var tt in t.BaseTypes() )
innerInherit[ tt ].Add( val );
}
public bool Contains<T>()
{
return inner.ContainsKey( typeof( T ) );
return innerInherit[ typeof( T ) ].Count != 0;
}
public T Get<T>()
{
return (T)inner[ typeof( T ) ];
var l = innerInherit[ typeof( T ) ];
if( l.Count == 1 )
return (T)l[ 0 ];
else if( l.Count == 0 )
throw new InvalidOperationException( string.Format( "TypeDictionary does not contain instance of type `{0}`", typeof( T ) ) );
else
throw new InvalidOperationException( string.Format( "TypeDictionary contains multiple instance of type `{0}`", typeof( T ) ) );
}
public T GetOrDefault<T>()
{
object o = null;
inner.TryGetValue(typeof(T), out o);
return (T)o;
var l = innerInherit[ typeof( T ) ];
if( l.Count == 1 )
return (T)l[ 0 ];
else if( l.Count == 0 )
return default( T );
else
throw new InvalidOperationException( string.Format( "TypeDictionary contains multiple instance of type `{0}`", typeof( T ) ) );
}
public IEnumerable<T> WithInterface<T>()
{
foreach( var i in inner )
if( i.Value is T )
yield return (T)i.Value;
foreach( var i in innerInherit[ typeof( T ) ] )
yield return (T)i;
}
public IEnumerator<object> GetEnumerator()
@@ -52,4 +58,16 @@ namespace OpenRa
return WithInterface<object>().GetEnumerator();
}
}
static class TypeExts
{
public static IEnumerable<Type> BaseTypes( this Type t )
{
while( t != null )
{
yield return t;
t = t.BaseType;
}
}
}
}

90
OpenRa.FileFormats/MiniYaml.cs Executable file
View File

@@ -0,0 +1,90 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
namespace OpenRa.FileFormats
{
public class MiniYaml
{
public string Value;
public Dictionary<string, MiniYaml> Nodes = new Dictionary<string,MiniYaml>();
public MiniYaml( string value ) : this( value, new Dictionary<string, MiniYaml>() ) { }
public MiniYaml( string value, Dictionary<string, MiniYaml> nodes )
{
Value = value;
Nodes = nodes;
}
public static Dictionary<string, MiniYaml> FromFile( string path )
{
var lines = File.ReadAllLines( path );
var levels = new List<Dictionary<string, MiniYaml>>();
levels.Add( new Dictionary<string, MiniYaml>() );
foreach( var line in lines )
{
var t = line.TrimStart( ' ', '\t' );
if( t.Length == 0 || t[ 0 ] == '#' )
continue;
var level = line.Length - t.Length;
if( levels.Count <= level )
throw new InvalidOperationException( "Bad indent in miniyaml" );
while( levels.Count > level + 1 )
levels.RemoveAt( levels.Count - 1 );
var colon = t.IndexOf( ':' );
var d = new Dictionary<string, MiniYaml>();
if( colon == -1 )
levels[ level ].Add( t.Trim(), new MiniYaml( null, d ) );
else
{
var value = t.Substring( colon + 1 ).Trim();
if( value.Length == 0 )
value = null;
levels[ level ].Add( t.Substring( 0, colon ).Trim(), new MiniYaml( value, d ) );
}
levels.Add( d );
}
return levels[ 0 ];
}
public static Dictionary<string, MiniYaml> Merge( Dictionary<string, MiniYaml> a, Dictionary<string, MiniYaml> b )
{
if( a.Count == 0 )
return b;
if( b.Count == 0 )
return a;
var ret = new Dictionary<string, MiniYaml>();
var keys = a.Keys.Union( b.Keys ).ToList();
foreach( var key in keys )
{
MiniYaml aa, bb;
a.TryGetValue( key, out aa );
b.TryGetValue( key, out bb );
ret.Add( key, Merge( aa, bb ) );
}
return ret;
}
public static MiniYaml Merge( MiniYaml a, MiniYaml b )
{
if( a == null )
return b;
if( b == null )
return a;
return new MiniYaml( a.Value ?? b.Value, Merge( a.Nodes, b.Nodes ) );
}
}
}

View File

@@ -57,6 +57,7 @@
<Compile Include="IniWriter.cs" />
<Compile Include="IPaletteRemap.cs" />
<Compile Include="Map.cs" />
<Compile Include="MiniYaml.cs" />
<Compile Include="PackageEntry.cs" />
<Compile Include="Package.cs" />
<Compile Include="Palette.cs" />

View File

@@ -12,8 +12,7 @@ namespace OpenRa.Game
{
[Sync]
public readonly TypeDictionary traits = new TypeDictionary();
public readonly UnitInfo Info;
public readonly NewUnitInfo Info;
public readonly uint ActorID;
[Sync]
public int2 Location;
@@ -23,33 +22,21 @@ namespace OpenRa.Game
public int Health;
IActivity currentActivity;
object ConstructTrait(string traitName)
{
/* todo: allow mods to introduce traits */
var type = typeof(Mobile).Assembly.GetType(typeof(Mobile).Namespace + "." + traitName, true, false);
var ctor = type.GetConstructor(new[] { typeof(Actor) });
if (ctor == null)
throw new InvalidOperationException("Trait {0} does not have the correct constructor: {0}(Actor self)".F(type.Name));
return ctor.Invoke(new object[] { this });
}
public Actor( ActorInfo info, int2 location, Player owner )
public Actor( string name, int2 location, Player owner )
{
ActorID = Game.world.NextAID();
Info = (UnitInfo)info; // temporary
Location = location;
CenterLocation = Traits.Util.CenterOfCell(Location);
Owner = owner;
if (Info == null) return;
if (name != null)
{
Info = Rules.NewUnitInfo[name.ToLowerInvariant()];
Health = this.GetMaxHP();
Health = Info.Strength; /* todo: fix walls, etc so this is always true! */
if( Info.Traits == null )
throw new InvalidOperationException( "No Actor traits for {0}; add Traits= to units.ini for appropriate unit".F(Info.Name) );
foreach (var traitName in Info.Traits)
traits.Add(ConstructTrait(traitName));
foreach (var trait in Info.Traits.WithInterface<ITraitInfo>())
traits.Add(trait.Create(this));
}
}
public void Tick()
@@ -71,12 +58,13 @@ namespace OpenRa.Game
}
public float2 CenterLocation;
public float2 SelectedSize
float2 SelectedSize
{
get
get // todo: inline into GetBounds
{
if (Info != null && Info.SelectionSize != null)
return new float2(Info.SelectionSize[0], Info.SelectionSize[1]);
var si = Info != null ? Info.Traits.GetOrDefault<SelectableInfo>() : null;
if (si != null && si.Bounds != null)
return new float2(si.Bounds[0], si.Bounds[1]);
var firstSprite = Render().FirstOrDefault();
if (firstSprite.Sprite == null) return float2.Zero;
@@ -102,7 +90,7 @@ namespace OpenRa.Game
var loc = mi.Location + Game.viewport.Location;
var underCursor = Game.FindUnits(loc, loc).FirstOrDefault();
if (underCursor != null && !underCursor.Info.Selectable)
if (underCursor != null && !underCursor.traits.Contains<Selectable>())
underCursor = null;
return traits.WithInterface<IIssueOrder>()
@@ -112,10 +100,13 @@ namespace OpenRa.Game
public RectangleF GetBounds(bool useAltitude)
{
var si = Info != null ? Info.Traits.GetOrDefault<SelectableInfo>() : null;
var size = SelectedSize;
var loc = CenterLocation - 0.5f * size;
if (Info != null && Info.SelectionSize != null && Info.SelectionSize.Length > 2)
loc += new float2(Info.SelectionSize[2], Info.SelectionSize[3]);
if (si != null && si.Bounds != null && si.Bounds.Length > 2)
loc += new float2(si.Bounds[2], si.Bounds[3]);
if (useAltitude)
{
@@ -132,7 +123,7 @@ namespace OpenRa.Game
public DamageState GetDamageState()
{
if (Health <= 0) return DamageState.Dead;
var halfStrength = Info.Strength * Rules.General.ConditionYellow;
var halfStrength = this.GetMaxHP() * Rules.General.ConditionYellow;
return Health < halfStrength ? DamageState.Half : DamageState.Normal;
}
@@ -155,8 +146,10 @@ namespace OpenRa.Game
Game.world.AddFrameEndTask(w => w.Remove(this));
}
if (Health > Info.Strength)
Health = Info.Strength;
var maxHP = this.GetMaxHP();
if (Health > maxHP) Health = maxHP;
var newState = GetDamageState();

View File

@@ -18,15 +18,15 @@ namespace OpenRa.Game
ChangeInfluence(a, a.traits.Get<Building>(), false); };
}
void ChangeInfluence(Actor a, Building building, bool isAdd)
void ChangeInfluence( Actor a, Building building, bool isAdd )
{
foreach (var u in Footprint.UnpathableTiles(building.unitInfo, a.Location))
if (IsValid(u))
blocked[u.X, u.Y] = isAdd;
foreach( var u in Footprint.UnpathableTiles( a.Info.Name, a.Info.Traits.Get<BuildingInfo>(), a.Location ) )
if( IsValid( u ) )
blocked[ u.X, u.Y ] = isAdd;
foreach (var u in Footprint.Tiles(building.unitInfo, a.Location, false))
if (IsValid(u))
influence[u.X, u.Y] = isAdd ? a : null;
foreach( var u in Footprint.Tiles( a.Info.Name, a.Info.Traits.Get<BuildingInfo>(), a.Location, false ) )
if( IsValid( u ) )
influence[ u.X, u.Y ] = isAdd ? a : null;
}
bool IsValid(int2 t)

View File

@@ -112,10 +112,10 @@ namespace OpenRa.Game
tabSprites = groups
.SelectMany(g => Rules.Categories[g])
.Where(u => Rules.UnitInfo[u].TechLevel != -1)
.Where(u => Rules.NewUnitInfo[u].Traits.Contains<BuildableInfo>())
.ToDictionary(
u => u,
u => SpriteSheetBuilder.LoadAllSprites(Rules.UnitInfo[u].Icon ?? (u + "icon"))[0]);
u => SpriteSheetBuilder.LoadAllSprites(Rules.NewUnitInfo[u].Traits.Get<BuildableInfo>().Icon ?? (u + "icon"))[0]);
spsprites = Rules.SupportPowerInfo
.ToDictionary(
@@ -216,7 +216,7 @@ namespace OpenRa.Game
{
var hasNewRadar = Game.world.Actors.Any(a => a.Owner == Game.LocalPlayer
&& a.traits.Contains<ProvidesRadar>()
&& a.traits.Get<ProvidesRadar>().IsActive());
&& a.traits.Get<ProvidesRadar>().IsActive(a));
if (hasNewRadar != hasRadar)
{
@@ -529,8 +529,9 @@ namespace OpenRa.Game
var buildableItems = Rules.TechTree.BuildableItems(Game.LocalPlayer, queueName).ToArray();
var allItems = Rules.TechTree.AllItems(Game.LocalPlayer, queueName)
.Where(a => Rules.UnitInfo[a].TechLevel != -1)
.OrderBy(a => Rules.UnitInfo[a].TechLevel);
.Where(a => Rules.NewUnitInfo[a].Traits.Contains<BuildableInfo>())
.Where(a => Rules.NewUnitInfo[a].Traits.Get<BuildableInfo>().Owner.Contains(Game.LocalPlayer.Race))
.OrderBy(a => Rules.NewUnitInfo[a].Traits.Get<BuildableInfo>().TechLevel);
var queue = Game.LocalPlayer.PlayerActor.traits.Get<Traits.ProductionQueue>();
@@ -729,14 +730,15 @@ namespace OpenRa.Game
rgbaRenderer.DrawSprite(tooltipSprite, p, PaletteType.Chrome);
rgbaRenderer.Flush();
var info = Rules.UnitInfo[unit];
var info = Rules.NewUnitInfo[unit];
var buildable = info.Traits.Get<BuildableInfo>();
renderer.DrawText2(info.Description, p.ToInt2() + new int2(5,5), Color.White);
renderer.DrawText2(buildable.Description, p.ToInt2() + new int2(5,5), Color.White);
DrawRightAligned( "${0}".F(info.Cost), pos + new int2(-5,5),
Game.LocalPlayer.Cash + Game.LocalPlayer.Ore >= info.Cost ? Color.White : Color.Red);
DrawRightAligned( "${0}".F(buildable.Cost), pos + new int2(-5,5),
Game.LocalPlayer.Cash + Game.LocalPlayer.Ore >= buildable.Cost ? Color.White : Color.Red);
var bi = info as BuildingInfo;
var bi = info.Traits.GetOrDefault<BuildingInfo>();
if (bi != null)
DrawRightAligned("ϟ{0}".F(bi.Power), pos + new int2(-5, 20),
Game.LocalPlayer.PowerProvided - Game.LocalPlayer.PowerDrained + bi.Power >= 0
@@ -747,21 +749,27 @@ namespace OpenRa.Game
p += new int2(0, 15);
if (!Rules.TechTree.CanBuild(info, Game.LocalPlayer, buildings))
{
var prereqs = info.Prerequisite
.Select(a => Rules.UnitInfo[a.ToLowerInvariant()])
.Where( u => u.Owner.Any( o => o == Game.LocalPlayer.Race ) )
.Select( a => a.Description );
var prereqs = buildable.Prerequisites
.Select( a => Description( a ) );
renderer.DrawText("Requires {0}".F( string.Join( ", ", prereqs.ToArray() ) ), p.ToInt2(),
Color.White);
}
if (info.LongDesc != null)
if (buildable.LongDesc != null)
{
p += new int2(0, 15);
renderer.DrawText(info.LongDesc.Replace( "\\n", "\n" ), p.ToInt2(), Color.White);
renderer.DrawText(buildable.LongDesc.Replace( "\\n", "\n" ), p.ToInt2(), Color.White);
}
}
private static string Description( string a )
{
if( a[ 0 ] == '@' )
return "any " + a.Substring( 1 );
else
return Rules.NewUnitInfo[ a.ToLowerInvariant() ].Traits.Get<BuildableInfo>().Description;
}
void DrawSupportPowers()
{
var numPowers = Game.LocalPlayer.SupportPowers.Values

View File

@@ -49,7 +49,7 @@ namespace OpenRa.Game
var distance = (target.CenterLocation - loc).Length*1/24f;
var rawDamage = weapon.Damage * (float)Math.Exp(-distance / warhead.Spread);
var multiplier = warhead.EffectivenessAgainst(target.Info.Armor);
var multiplier = warhead.EffectivenessAgainst(target.Info.Traits.Get<OwnedActorInfo>().Armor);
return rawDamage * multiplier;
}
@@ -59,7 +59,7 @@ namespace OpenRa.Game
var warhead = Rules.WarheadInfo[weapon.Warhead];
var unit = target.traits.GetOrDefault<Unit>();
if (warhead.EffectivenessAgainst(target.Info.Armor) <= 0)
if (warhead.EffectivenessAgainst(target.Info.Traits.Get<OwnedActorInfo>().Armor) <= 0)
return false;
if (target.traits.Contains<Submarine>())
@@ -68,7 +68,7 @@ namespace OpenRa.Game
if (unit != null && unit.Altitude > 0)
return projectile.AA;
if (projectile.UnderWater && !target.Info.WaterBound)
if (projectile.UnderWater && !target.Info.Traits.Get<OwnedActorInfo>().WaterBound)
return false;
return projectile.AG;
@@ -76,10 +76,11 @@ namespace OpenRa.Game
public static bool HasAnyValidWeapons(Actor self, Actor target)
{
if (self.Info.Primary != null &&
WeaponValidForTarget(Rules.WeaponInfo[self.Info.Primary], target)) return true;
if (self.Info.Secondary != null &&
WeaponValidForTarget(Rules.WeaponInfo[self.Info.Secondary], target)) return true;
var info = self.Info.Traits.Get<AttackBaseInfo>();
if (info.PrimaryWeapon != null &&
WeaponValidForTarget(self.GetPrimaryWeapon(), target)) return true;
if (info.SecondaryWeapon != null &&
WeaponValidForTarget(self.GetSecondaryWeapon(), target)) return true;
return false;
}

View File

@@ -23,9 +23,13 @@ namespace OpenRa.Game.Effects
const int BaseBulletSpeed = 100; /* pixels / 40ms frame */
/* src, dest are *pixel* coords */
public Bullet(string weapon, Player owner, Actor firedBy,
int2 src, int2 dest, int srcAltitude, int destAltitude)
: this(Rules.WeaponInfo[weapon], owner, firedBy, src, dest, srcAltitude, destAltitude) { }
/* src, dest are *pixel* coords */
public Bullet(WeaponInfo weapon, Player owner, Actor firedBy,
int2 src, int2 dest, int srcAltitude, int destAltitude)
{
Owner = owner;
FiredBy = firedBy;
@@ -36,7 +40,7 @@ namespace OpenRa.Game.Effects
VisualDest = Dest + new int2(
Game.CosmeticRandom.Next(-10, 10),
Game.CosmeticRandom.Next(-10, 10));
Weapon = Rules.WeaponInfo[weapon];
Weapon = weapon;
Projectile = Rules.ProjectileInfo[Weapon.Projectile];
Warhead = Rules.WarheadInfo[Weapon.Warhead];

View File

@@ -15,7 +15,7 @@ namespace OpenRa.Game.Effects
public Corpse(Actor fromActor, int death)
{
anim = new Animation(fromActor.Info.Image ?? fromActor.Info.Name);
anim = new Animation(fromActor.traits.GetOrDefault<RenderSimple>().GetImage(fromActor));
anim.PlayThen("die{0}".F(death + 1),
() => Game.world.AddFrameEndTask(w => w.Remove(this)));

View File

@@ -20,10 +20,10 @@ namespace OpenRa.Game.Effects
int t;
int Altitude;
public Missile(string weapon, Player owner, Actor firedBy,
public Missile(WeaponInfo weapon, Player owner, Actor firedBy,
int2 src, Actor target, int altitude, int facing)
{
Weapon = Rules.WeaponInfo[weapon];
Weapon = weapon;
Projectile = Rules.ProjectileInfo[Weapon.Projectile];
Warhead = Rules.WarheadInfo[Weapon.Warhead];
FiredBy = firedBy;

View File

@@ -2,6 +2,8 @@
using System.Windows.Forms;
using System.Collections.Generic;
using System.Linq;
using OpenRa.Game.GameRules;
using OpenRa.Game.Traits;
namespace OpenRa.Game
{
@@ -27,5 +29,35 @@ namespace OpenRa.Game
{
return xs.Aggregate(1f, (a, x) => a * x);
}
public static WeaponInfo GetPrimaryWeapon(this Actor self)
{
var info = self.Info.Traits.GetOrDefault<AttackBaseInfo>();
if (info == null) return null;
var weapon = info.PrimaryWeapon;
if (weapon == null) return null;
return Rules.WeaponInfo[weapon];
}
public static WeaponInfo GetSecondaryWeapon(this Actor self)
{
var info = self.Info.Traits.GetOrDefault<AttackBaseInfo>();
if (info == null) return null;
var weapon = info.SecondaryWeapon;
if (weapon == null) return null;
return Rules.WeaponInfo[weapon];
}
public static int GetMaxHP(this Actor self)
{
if (self.Info == null) return 0;
var oai = self.Info.Traits.GetOrDefault<OwnedActorInfo>();
if (oai == null) return 0;
return oai.HP;
}
}
}

View File

@@ -61,7 +61,7 @@ namespace OpenRa.Game
world = new World();
Game.world.ActorAdded += a =>
{
if (a.Owner != null && a.Info != null)
if (a.Owner != null && a.Info != null && a.Info.Traits.Contains<OwnedActorInfo>())
a.Owner.Shroud.Explore(a);
};
@@ -96,9 +96,7 @@ namespace OpenRa.Game
skipMakeAnims = true;
foreach (var treeReference in Rules.Map.Trees)
world.Add(new Actor(Rules.UnitInfo[treeReference.Image],
new int2(treeReference.Location),
null));
world.Add(new Actor(treeReference.Image, new int2(treeReference.Location), null));
LoadMapActors(Rules.AllRules);
skipMakeAnims = false;
@@ -149,7 +147,7 @@ namespace OpenRa.Game
//num=owner,type,health,location,facing,...
var parts = s.Value.Split( ',' );
var loc = int.Parse(parts[3]);
world.Add(new Actor(Rules.UnitInfo[parts[1].ToLowerInvariant()], new int2(loc % 128, loc / 128),
world.Add(new Actor(parts[1].ToLowerInvariant(), new int2(loc % 128, loc / 128),
players.Values.FirstOrDefault(p => p.InternalName == parts[0]) ?? players[0]));
}
}
@@ -247,7 +245,7 @@ namespace OpenRa.Game
public static bool IsActorCrushableByActor(Actor a, Actor b)
{
return IsActorCrushableByMovementType(a, b.traits.WithInterface<IMovement>().FirstOrDefault().GetMovementType());
return IsActorCrushableByMovementType(a, b.traits.GetOrDefault<IMovement>().GetMovementType());
}
public static bool IsActorPathableToCrush(Actor a, UnitMovementType umt)
@@ -312,8 +310,8 @@ namespace OpenRa.Game
public static IEnumerable<Actor> SelectActorsInBox(float2 a, float2 b)
{
return FindUnits(a, b)
.Where( x => x.Info.Selectable )
.GroupBy(x => (x.Owner == LocalPlayer) ? x.Info.SelectionPriority : 0)
.Where( x => x.traits.Contains<Selectable>() )
.GroupBy(x => (x.Owner == LocalPlayer) ? x.Info.Traits.Get<SelectableInfo>().Priority : 0)
.OrderByDescending(g => g.Key)
.Select( g => g.AsEnumerable() )
.DefaultIfEmpty( new Actor[] {} )
@@ -323,15 +321,15 @@ namespace OpenRa.Game
public static Random SharedRandom = new Random(0); /* for things that require sync */
public static Random CosmeticRandom = new Random(); /* for things that are just fluff */
public static bool CanPlaceBuilding(BuildingInfo building, int2 xy, Actor toIgnore, bool adjust)
public static bool CanPlaceBuilding(string name, BuildingInfo building, int2 xy, Actor toIgnore, bool adjust)
{
return !Footprint.Tiles(building, xy, adjust).Any(
return !Footprint.Tiles(name, building, xy, adjust).Any(
t => !Rules.Map.IsInMap(t.X, t.Y) || Rules.Map.ContainsResource(t) || !Game.IsCellBuildable(t,
building.WaterBound ? UnitMovementType.Float : UnitMovementType.Wheel,
toIgnore));
}
public static bool IsCloseEnoughToBase(Player p, BuildingInfo bi, int2 position)
public static bool IsCloseEnoughToBase(Player p, string buildingName, BuildingInfo bi, int2 position)
{
var maxDistance = bi.Adjacent + 1;
@@ -340,7 +338,7 @@ namespace OpenRa.Game
heuristic = loc =>
{
var b = Game.BuildingInfluence.GetBuildingAt(loc);
if (b != null && b.Owner == p && (b.Info as BuildingInfo).BaseNormal) return 0;
if (b != null && b.Owner == p && b.Info.Traits.Get<BuildingInfo>().BaseNormal) return 0;
if ((loc - position).Length > maxDistance)
return float.PositiveInfinity; /* not quite right */
return 1;
@@ -349,7 +347,7 @@ namespace OpenRa.Game
ignoreTerrain = true,
};
foreach (var t in Footprint.Tiles(bi, position)) search.AddInitialCell(t);
foreach (var t in Footprint.Tiles(buildingName, bi, position)) search.AddInitialCell(t);
return Game.PathFinder.FindPath(search).Count != 0;
}

View File

@@ -0,0 +1,16 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace OpenRa.Game.GameRules
{
public enum ArmorType
{
none = 0,
wood = 1,
light = 2,
heavy = 3,
concrete = 4,
}
}

View File

@@ -1,17 +1,62 @@
using System;
using System.Linq;
using OpenRa.FileFormats;
using System.Collections.Generic;
namespace OpenRa.Game.GameRules
{
static class FieldLoader
{
public static void Load( object self, IniSection ini )
public static void Load(object self, IniSection ini)
{
foreach( var x in ini )
foreach (var x in ini)
{
var field = self.GetType().GetField( x.Key.Trim() );
field.SetValue( self, GetValue( field.FieldType, x.Value.Trim() ) );
var field = self.GetType().GetField(x.Key.Trim());
field.SetValue(self, GetValue(field.FieldType, x.Value.Trim()));
}
}
public static void Load(object self, MiniYaml my)
{
foreach (var x in my.Nodes)
{
var field = self.GetType().GetField(x.Key.Trim());
if (field == null)
throw new NotImplementedException("Missing field `{0}` on `{1}`".F(x.Key.Trim(), self.GetType().Name));
field.SetValue(self, GetValue(field.FieldType, x.Value.Value.Trim()));
}
}
public static void CheckYaml( object self, Dictionary<string, MiniYaml> d )
{
//foreach( var x in d )
//{
// if( x.Key == "Tab" ) continue;
// if( x.Key == "Description" ) continue;
// if( x.Key == "LongDesc" ) continue;
// var key = x.Key;
// if( key == "Prerequisites" ) key = "Prerequisite";
// if( key == "HP" ) key = "Strength";
// if( key == "Priority" ) key = "SelectionPriority";
// if( key == "Bounds" ) key = "SelectionSize";
// var field = self.GetType().GetField( key );
// var old = field.GetValue( self );
// var neww = GetValue( field.FieldType, x.Value.Value.Trim() );
// if( old.ToString() != neww.ToString() )
// throw new NotImplementedException();
//}
foreach( var x in d )
{
var key = x.Key;
if( key == "Tab" )
continue;
if( key == "Prerequisites" ) key = "Prerequisite";
if( key == "HP" ) key = "Strength";
if( key == "Priority" ) key = "SelectionPriority";
if( key == "Bounds" ) key = "SelectionSize";
var field = self.GetType().GetField( key.Trim() );
field.SetValue( self, GetValue( field.FieldType, x.Value.Value.Trim() ) );
}
}

View File

@@ -1,17 +1,18 @@
using System;
using System.Collections.Generic;
using System.Linq;
using OpenRa.Game.Traits;
namespace OpenRa.Game.GameRules
{
static class Footprint
{
public static IEnumerable<int2> Tiles( BuildingInfo buildingInfo, int2 position )
public static IEnumerable<int2> Tiles( string name, BuildingInfo buildingInfo, int2 position )
{
return Tiles(buildingInfo, position, true);
return Tiles(name, buildingInfo, position, true);
}
public static IEnumerable<int2> Tiles( BuildingInfo buildingInfo, int2 position, bool adjustForPlacement )
public static IEnumerable<int2> Tiles( string name, BuildingInfo buildingInfo, int2 position, bool adjustForPlacement )
{
var dim = buildingInfo.Dimensions;
@@ -24,19 +25,19 @@ namespace OpenRa.Game.GameRules
var adjustment = adjustForPlacement ? AdjustForBuildingSize(buildingInfo) : int2.Zero;
var tiles = TilesWhere(buildingInfo.Name, dim, footprint.ToArray(), a => a != '_');
var tiles = TilesWhere(name, dim, footprint.ToArray(), a => a != '_');
return tiles.Select(t => t + position - adjustment);
}
public static IEnumerable<int2> Tiles(Actor a, Traits.Building building)
{
return Tiles( building.unitInfo, a.Location, false );
return Tiles( a.Info.Name, a.Info.Traits.Get<BuildingInfo>(), a.Location, false );
}
public static IEnumerable<int2> UnpathableTiles( BuildingInfo buildingInfo, int2 position )
public static IEnumerable<int2> UnpathableTiles( string name, BuildingInfo buildingInfo, int2 position )
{
var footprint = buildingInfo.Footprint.Where( x => !char.IsWhiteSpace( x ) ).ToArray();
foreach( var tile in TilesWhere( buildingInfo.Name, buildingInfo.Dimensions, footprint, a => a == 'x' ) )
foreach( var tile in TilesWhere( name, buildingInfo.Dimensions, footprint, a => a == 'x' ) )
yield return tile + position;
}
@@ -52,9 +53,9 @@ namespace OpenRa.Game.GameRules
yield return new int2( x, y );
}
public static int2 AdjustForBuildingSize( BuildingInfo unitInfo )
public static int2 AdjustForBuildingSize( BuildingInfo buildingInfo )
{
var dim = unitInfo.Dimensions;
var dim = buildingInfo.Dimensions;
return new int2( dim.X / 2, dim.Y > 1 ? ( dim.Y + 1 ) / 2 : 0 );
}
}

View File

@@ -0,0 +1,58 @@
using System;
using System.Collections.Generic;
using System.Linq;
using OpenRa.FileFormats;
using OpenRa.Game.Traits;
namespace OpenRa.Game.GameRules
{
class NewUnitInfo
{
public readonly TypeDictionary Traits = new TypeDictionary();
public readonly string Name;
public NewUnitInfo( string name, MiniYaml node, Dictionary<string, MiniYaml> allUnits )
{
Name = name;
foreach( var t in MergeWithParent( node, allUnits ).Nodes )
if( t.Key != "Inherits" )
Traits.Add( LoadTraitInfo( t.Key, t.Value ) );
}
static MiniYaml GetParent( MiniYaml node, Dictionary<string, MiniYaml> allUnits )
{
MiniYaml inherits;
node.Nodes.TryGetValue( "Inherits", out inherits );
if( inherits == null || string.IsNullOrEmpty( inherits.Value ) )
return null;
MiniYaml parent;
allUnits.TryGetValue( inherits.Value, out parent );
if( parent == null )
return null;
return parent;
}
static MiniYaml MergeWithParent( MiniYaml node, Dictionary<string, MiniYaml> allUnits )
{
var parent = GetParent( node, allUnits );
if( parent != null )
return MiniYaml.Merge( node, MergeWithParent( parent, allUnits ) );
return node;
}
static ITraitInfo LoadTraitInfo(string traitName, MiniYaml my)
{
var fullTypeName = typeof(ITraitInfo).Namespace + "." + traitName + "Info";
var info = (ITraitInfo)typeof(ITraitInfo).Assembly.CreateInstance(fullTypeName);
if (info == null)
throw new NotImplementedException("Missing traitinfo type `{0}`".F(fullTypeName));
FieldLoader.Load(info, my);
return info;
}
}
}

View File

@@ -4,6 +4,7 @@ using System.Linq;
using IjwFramework.Types;
using OpenRa.FileFormats;
using OpenRa.Game.GameRules;
using OpenRa.Game.Traits;
namespace OpenRa.Game
{
@@ -12,7 +13,6 @@ namespace OpenRa.Game
public static IniFile AllRules;
public static Dictionary<string, List<string>> Categories = new Dictionary<string, List<string>>();
public static Dictionary<string, string> UnitCategory;
public static InfoLoader<UnitInfo> UnitInfo;
public static InfoLoader<WeaponInfo> WeaponInfo;
public static InfoLoader<WarheadInfo> WarheadInfo;
public static InfoLoader<ProjectileInfo> ProjectileInfo;
@@ -24,6 +24,8 @@ namespace OpenRa.Game
public static Map Map;
public static TileSet TileSet;
public static Dictionary<string, NewUnitInfo> NewUnitInfo;
public static void LoadRules(string mapFileName, bool useAftermath)
{
if( useAftermath )
@@ -61,14 +63,6 @@ namespace OpenRa.Game
"Plane");
UnitCategory = Categories.SelectMany(x => x.Value.Select(y => new KeyValuePair<string, string>(y, x.Key))).ToDictionary(x => x.Key, x => x.Value);
UnitInfo = new InfoLoader<UnitInfo>(
Pair.New<string, Func<string, UnitInfo>>("Building", s => new BuildingInfo(s)),
Pair.New<string, Func<string, UnitInfo>>("Defense", s => new BuildingInfo(s)),
Pair.New<string, Func<string, UnitInfo>>("Infantry", s => new InfantryInfo(s)),
Pair.New<string, Func<string, UnitInfo>>("Vehicle", s => new VehicleInfo(s)),
Pair.New<string, Func<string, UnitInfo>>("Ship", s => new VehicleInfo(s)),
Pair.New<string, Func<string, UnitInfo>>("Plane", s => new VehicleInfo(s)));
LoadCategories(
"Weapon",
"Warhead",
@@ -87,6 +81,14 @@ namespace OpenRa.Game
SupportPowerInfo = new InfoLoader<SupportPowerInfo>(
Pair.New<string, Func<string, SupportPowerInfo>>("SupportPower", _ => new SupportPowerInfo()));
var yamlRules = MiniYaml.Merge( MiniYaml.FromFile( "ra.yaml" ), MiniYaml.FromFile( "defaults.yaml" ) );
if( useAftermath )
yamlRules = MiniYaml.Merge( MiniYaml.FromFile( "aftermath.yaml" ), yamlRules );
NewUnitInfo = new Dictionary<string, NewUnitInfo>();
foreach( var kv in yamlRules )
NewUnitInfo.Add(kv.Key.ToLowerInvariant(), new NewUnitInfo(kv.Key.ToLowerInvariant(), kv.Value, yamlRules));
TechTree = new TechTree();
Map = new Map( AllRules );
FileSystem.MountTemporary( new Package( Rules.Map.Theater + ".mix" ) );

View File

@@ -1,19 +1,22 @@
using System.Collections.Generic;
using System.Linq;
using IjwFramework.Collections;
using OpenRa.Game.Traits;
namespace OpenRa.Game.GameRules
{
class TechTree
{
readonly Cache<string, List<UnitInfo>> producesIndex = new Cache<string, List<UnitInfo>>( x => new List<UnitInfo>() );
readonly Cache<string, List<NewUnitInfo>> producesIndex = new Cache<string, List<NewUnitInfo>>(x => new List<NewUnitInfo>());
public TechTree()
{
foreach( var b in Rules.Categories[ "Building" ] )
{
var info = (BuildingInfo)Rules.UnitInfo[ b ];
foreach( var p in info.Produces )
var info = Rules.NewUnitInfo[ b ];
var pi = info.Traits.GetOrDefault<ProductionInfo>();
if (pi != null)
foreach( var p in pi.Produces )
producesIndex[ p ].Add( info );
}
}
@@ -21,25 +24,30 @@ namespace OpenRa.Game.GameRules
public Cache<string, List<Actor>> GatherBuildings( Player player )
{
var ret = new Cache<string, List<Actor>>( x => new List<Actor>() );
foreach( var b in Game.world.Actors.Where( x => x.Owner == player && x.Info is BuildingInfo ) )
foreach( var b in Game.world.Actors.Where( x => x.Owner == player && x.Info != null && x.Info.Traits.Contains<BuildingInfo>() ) )
{
ret[ b.Info.Name ].Add( b );
var buildable = b.Info.Traits.GetOrDefault<BuildableInfo>();
if( buildable != null )
foreach( var alt in buildable.AlternateName )
ret[ alt ].Add( b );
}
return ret;
}
public bool CanBuild( UnitInfo unit, Player player, Cache<string, List<Actor>> playerBuildings )
public bool CanBuild( NewUnitInfo info, Player player, Cache<string, List<Actor>> playerBuildings )
{
if( unit.TechLevel == -1 )
var bi = info.Traits.GetOrDefault<BuildableInfo>();
if( bi == null ) return false;
if( !bi.Owner.Contains( player.Race ) )
return false;
if( !unit.Owner.Any( x => x == player.Race ) )
return false;
foreach( var p in unit.Prerequisite )
if (Rules.UnitInfo[p.ToLowerInvariant()].Owner.Any(x => x == player.Race))
foreach( var p in bi.Prerequisites )
if( playerBuildings[ p ].Count == 0 )
return false;
if( producesIndex[ Rules.UnitCategory[ unit.Name ] ].All( x => playerBuildings[ x.Name ].Count == 0 ) )
if( producesIndex[ Rules.UnitCategory[ info.Name ] ].All( x => playerBuildings[ x.Name ].Count == 0 ) )
return false;
return true;
@@ -48,21 +56,21 @@ namespace OpenRa.Game.GameRules
public IEnumerable<string> BuildableItems( Player player, params string[] categories )
{
var playerBuildings = GatherBuildings( player );
foreach( var unit in categories.SelectMany( x => Rules.Categories[ x ] ).Select( x => Rules.UnitInfo[ x ] ) )
foreach( var unit in categories.SelectMany( x => Rules.Categories[ x ] ).Select( x => Rules.NewUnitInfo[ x ] ) )
if( CanBuild( unit, player, playerBuildings ) )
yield return unit.Name;
}
public IEnumerable<string> AllItems(Player player, params string[] categories)
{
return categories.SelectMany(x => Rules.Categories[x]).Select(x => Rules.UnitInfo[x].Name)
.Where(x => Rules.UnitInfo[x].Owner.Contains(player.Race)); /* todo: fix for dual-race scenarios (captured buildings) */
return categories.SelectMany(x => Rules.Categories[x]).Select(x => Rules.NewUnitInfo[x].Name);
}
public IEnumerable<UnitInfo> UnitBuiltAt( UnitInfo info )
public IEnumerable<NewUnitInfo> UnitBuiltAt( NewUnitInfo info )
{
if( info.BuiltAt.Length != 0 )
return info.BuiltAt.Select( x => Rules.UnitInfo[ x.ToLowerInvariant() ] );
var builtAt = info.Traits.Get<BuildableInfo>().BuiltAt;
if( builtAt.Length != 0 )
return builtAt.Select( x => Rules.NewUnitInfo[ x.ToLowerInvariant() ] );
else
return producesIndex[ Rules.UnitCategory[ info.Name ] ];
}

View File

@@ -1,117 +0,0 @@

using System;
namespace OpenRa.Game.GameRules
{
public enum ArmorType
{
none = 0,
wood = 1,
light = 2,
heavy = 3,
concrete = 4,
}
public class UnitInfo : ActorInfo
{
public readonly string Name;
public readonly string Description = "";
public readonly string[] Traits;
public readonly int Ammo = -1;
public readonly ArmorType Armor = ArmorType.none;
[Obsolete] public readonly bool DoubleOwned = false;
[Obsolete] public readonly bool Cloakable = false;
public readonly int Cost = 0;
public readonly bool Crewed = false;
public readonly bool Explodes = false;
public readonly int GuardRange = -1; // -1 = use weapon's range
public readonly string Image = null; // sprite-set to use when rendering
public readonly bool Invisible = false;
public readonly Race[] Owner = { Race.Allies, Race.Soviet };
public readonly int Points = 0;
public readonly string[] Prerequisite = { };
public readonly string Primary = null;
public readonly string Secondary = null;
public readonly int ROT = 255;
public readonly int Reload = 0;
public readonly bool SelfHealing = false;
[Obsolete] public readonly bool Sensors = false; // no idea what this does
public readonly int Sight = 1;
public readonly int Strength = 1;
public readonly int TechLevel = -1;
public readonly bool WaterBound = false;
public readonly string[] BuiltAt = { };
public readonly int[] PrimaryOffset = { 0, 0 };
public readonly int[] SecondaryOffset = null;
public readonly int[] RotorOffset = { 0, 0 };
public readonly int[] RotorOffset2 = null;
public readonly int Recoil = 0;
public readonly bool MuzzleFlash = false;
public readonly int SelectionPriority = 10;
public readonly int InitialFacing = 128;
public readonly bool Selectable = true;
public readonly int FireDelay = 0;
public readonly string LongDesc = null;
public readonly int OrePips = 0;
public readonly string Icon = null;
public readonly int[] SelectionSize = null;
public readonly int Passengers = 0;
public readonly int UnloadFacing = 0;
public readonly UnitMovementType[] PassengerTypes = null;
// weapon origins and firing angles within the turrets. 3 values per position.
public readonly int[] PrimaryLocalOffset = { };
public readonly int[] SecondaryLocalOffset = { };
public UnitInfo(string name) { Name = name; }
}
public class MobileInfo : UnitInfo
{
public readonly int Speed = 0;
public readonly bool NoMovingFire = false;
public readonly string Voice = "GenericVoice";
public MobileInfo(string name) : base(name) { }
}
public class InfantryInfo : MobileInfo
{
public readonly bool C4 = false;
public readonly bool FraidyCat = false;
public readonly bool Infiltrate = false;
public readonly bool IsCanine = false;
public readonly int SquadSize = 1;
public InfantryInfo(string name) : base(name) { }
}
public class VehicleInfo : MobileInfo
{
public readonly bool Tracked = false;
public VehicleInfo(string name) : base(name) { }
}
public class BuildingInfo : UnitInfo
{
public readonly int2 Dimensions = new int2(1, 1);
public readonly string Footprint = "x";
public readonly string[] Produces = { };
public readonly bool BaseNormal = true;
public readonly int Adjacent = 1;
public readonly bool Bib = false;
public readonly bool Capturable = false;
public readonly int Power = 0;
public readonly bool Powered = false;
public readonly bool Repairable = true;
public readonly int Storage = 0;
public readonly bool Unsellable = false;
public readonly int[] RallyPoint = { 1, 3 };
public readonly float[] SpawnOffset = null;
public BuildingInfo(string name) : base(name) { }
}
}

View File

@@ -169,7 +169,7 @@ namespace OpenRa.Game.Graphics
lineRenderer.DrawLine(xy + new float2(0, -2), xy + new float2(0, -4), c, c);
lineRenderer.DrawLine(Xy + new float2(0, -2), Xy + new float2(0, -4), c, c);
var healthAmount = (float)selectedUnit.Health / selectedUnit.Info.Strength;
var healthAmount = (float)selectedUnit.Health / selectedUnit.Info.Traits.Get<OwnedActorInfo>().HP;
var healthColor = (healthAmount < Rules.General.ConditionRed) ? Color.Red
: (healthAmount < Rules.General.ConditionYellow) ? Color.Yellow
: Color.LimeGreen;
@@ -220,7 +220,7 @@ namespace OpenRa.Game.Graphics
spriteRenderer.DrawSprite(pipImages.Image, pipxyBase + pipxyOffset, PaletteType.Chrome);
pipxyOffset += new float2(4, 0);
if (pipxyOffset.X+5 > selectedUnit.SelectedSize.X)
if (pipxyOffset.X+5 > selectedUnit.GetBounds(false).Width)
{
pipxyOffset.X = 0;
pipxyOffset.Y -= 4;

View File

@@ -94,7 +94,9 @@
<Compile Include="Exts.cs" />
<Compile Include="GameRules\ActorInfo.cs" />
<Compile Include="GameRules\AftermathInfo.cs" />
<Compile Include="GameRules\ArmorType.cs" />
<Compile Include="GameRules\GeneralInfo.cs" />
<Compile Include="GameRules\NewUnitInfo.cs" />
<Compile Include="GameRules\SupportPowerInfo.cs" />
<Compile Include="GameRules\TechTree.cs" />
<Compile Include="GameRules\UserSettings.cs" />
@@ -156,7 +158,6 @@
<Compile Include="GameRules\InfoLoader.cs" />
<Compile Include="GameRules\ProjectileInfo.cs" />
<Compile Include="GameRules\Rules.cs" />
<Compile Include="GameRules\UnitInfo.cs" />
<Compile Include="GameRules\WarheadInfo.cs" />
<Compile Include="GameRules\WeaponInfo.cs" />
<Compile Include="Graphics\Animation.cs" />
@@ -216,6 +217,7 @@
<Compile Include="Traits\AutoHeal.cs" />
<Compile Include="Traits\AutoTarget.cs" />
<Compile Include="Traits\BelowUnits.cs" />
<Compile Include="Traits\Buildable.cs" />
<Compile Include="Traits\Building.cs" />
<Compile Include="Traits\C4Demolition.cs" />
<Compile Include="Traits\Cargo.cs" />
@@ -243,6 +245,7 @@
<Compile Include="Traits\ProvidesRadar.cs" />
<Compile Include="Traits\Repairable.cs" />
<Compile Include="Traits\Reservable.cs" />
<Compile Include="Traits\Selectable.cs" />
<Compile Include="Traits\SquishByTank.cs" />
<Compile Include="Traits\Plane.cs" />
<Compile Include="Traits\ProductionQueue.cs" />
@@ -271,7 +274,6 @@
<Compile Include="Traits\TakeCover.cs" />
<Compile Include="Traits\Thief.cs" />
<Compile Include="Traits\TraitsInterfaces.cs" />
<Compile Include="Traits\Tree.cs" />
<Compile Include="Traits\Turreted.cs" />
<Compile Include="Traits\Unit.cs" />
<Compile Include="Traits\WaterPaletteRotation.cs" />

View File

@@ -40,7 +40,7 @@ namespace OpenRa.Game.Orders
if (!Game.LocalPlayer.Shroud.IsExplored(xy))
return Cursor.MoveBlocked;
var movement = self.traits.WithInterface<IMovement>().FirstOrDefault();
var movement = self.traits.GetOrDefault<IMovement>();
return (movement.CanEnterCell(xy)) ? Cursor.Chronoshift : Cursor.MoveBlocked;
}
}

View File

@@ -38,7 +38,7 @@ namespace OpenRa.Game.Orders
if (!Game.LocalPlayer.Shroud.IsExplored(xy))
return Cursor.MoveBlocked;
var movement = self.traits.WithInterface<IMovement>().FirstOrDefault();
var movement = self.traits.GetOrDefault<IMovement>();
return (movement.CanEnterCell(xy)) ? Cursor.Chronoshift : Cursor.MoveBlocked;
}
}

View File

@@ -31,11 +31,9 @@ namespace OpenRa.Game.Orders
var underCursor = Game.FindUnits(loc, loc)
.Where(a => a.Owner == Game.LocalPlayer
&& a.traits.WithInterface<Chronoshiftable>().Any()
&& a.Info.Selectable).FirstOrDefault();
&& a.traits.Contains<Selectable>()).FirstOrDefault();
var unit = underCursor != null ? underCursor.Info as UnitInfo : null;
if (unit != null)
if (underCursor != null)
yield return new Order("ChronosphereSelect", underCursor, null, int2.Zero, power.Name);
}
}

View File

@@ -31,11 +31,9 @@ namespace OpenRa.Game.Orders
var underCursor = Game.FindUnits(loc, loc)
.Where(a => a.Owner == Game.LocalPlayer
&& a.traits.Contains<IronCurtainable>()
&& a.Info.Selectable).FirstOrDefault();
&& a.traits.Contains<Selectable>()).FirstOrDefault();
var unit = underCursor != null ? underCursor.Info as UnitInfo : null;
if (unit != null)
if (underCursor != null)
yield return new Order("IronCurtain", underCursor, null, int2.Zero, power.Name);
}
}

View File

@@ -1,17 +1,19 @@
using System.Collections.Generic;
using OpenRa.Game.GameRules;
using OpenRa.Game.Traits;
namespace OpenRa.Game.Orders
{
class PlaceBuildingOrderGenerator : IOrderGenerator
{
readonly Actor Producer;
readonly BuildingInfo Building;
readonly string Building;
BuildingInfo BuildingInfo { get { return Rules.NewUnitInfo[ Building ].Traits.Get<BuildingInfo>(); } }
public PlaceBuildingOrderGenerator(Actor producer, string name)
{
Producer = producer;
Building = (BuildingInfo)Rules.UnitInfo[ name ];
Building = name;
}
public IEnumerable<Order> Order(int2 xy, MouseInput mi)
@@ -26,27 +28,27 @@ namespace OpenRa.Game.Orders
{
if (mi.Button == MouseButton.Left)
{
if (!Game.CanPlaceBuilding(Building, xy, null, true)
|| !Game.IsCloseEnoughToBase(Producer.Owner, Building, xy))
if (!Game.CanPlaceBuilding( Building, BuildingInfo, xy, null, true)
|| !Game.IsCloseEnoughToBase(Producer.Owner, Building, BuildingInfo, xy))
{
Sound.Play("nodeply1.aud");
yield break;
}
yield return new Order("PlaceBuilding", Producer.Owner.PlayerActor, null, xy, Building.Name);
yield return new Order("PlaceBuilding", Producer.Owner.PlayerActor, null, xy, Building);
}
}
public void Tick()
{
var producing = Producer.traits.Get<Traits.ProductionQueue>().CurrentItem( Rules.UnitCategory[ Building.Name ] );
if (producing == null || producing.Item != Building.Name || producing.RemainingTime != 0)
var producing = Producer.traits.Get<Traits.ProductionQueue>().CurrentItem( Rules.UnitCategory[ Building ] );
if (producing == null || producing.Item != Building || producing.RemainingTime != 0)
Game.controller.CancelInputMode();
}
public void Render()
{
Game.worldRenderer.uiOverlay.DrawBuildingGrid( Building );
Game.worldRenderer.uiOverlay.DrawBuildingGrid( Building, BuildingInfo );
}
public Cursor GetCursor(int2 xy, MouseInput mi)

View File

@@ -25,11 +25,9 @@ namespace OpenRa.Game.Orders
var underCursor = Game.FindUnits(loc, loc)
.Where(a => a.Owner == Game.LocalPlayer
&& a.traits.Contains<Building>()
&& a.Info.Selectable).FirstOrDefault();
&& a.traits.Contains<Selectable>()).FirstOrDefault();
var building = underCursor != null ? underCursor.Info as BuildingInfo : null;
if (building != null)
if (underCursor != null)
yield return new Order("PowerDown", underCursor, null, int2.Zero, null);
}
}

View File

@@ -25,11 +25,11 @@ namespace OpenRa.Game.Orders
var underCursor = Game.FindUnits(loc, loc)
.Where(a => a.Owner == Game.LocalPlayer
&& a.traits.Contains<Building>()
&& a.Info.Selectable).FirstOrDefault();
&& a.traits.Contains<Selectable>()).FirstOrDefault();
var building = underCursor != null ? underCursor.Info as BuildingInfo : null;
var building = underCursor != null ? underCursor.Info.Traits.Get<BuildingInfo>() : null;
if (building != null && building.Repairable && underCursor.Health < building.Strength)
if (building != null && building.Repairable && underCursor.Health < building.HP)
yield return new Order("Repair", underCursor, null, int2.Zero, null);
}
}

View File

@@ -25,9 +25,9 @@ namespace OpenRa.Game.Orders
var underCursor = Game.FindUnits(loc, loc)
.Where(a => a.Owner == Game.LocalPlayer
&& a.traits.Contains<Building>()
&& a.Info.Selectable).FirstOrDefault();
&& a.traits.Contains<Selectable>()).FirstOrDefault();
var building = underCursor != null ? underCursor.Info as BuildingInfo : null;
var building = underCursor != null ? underCursor.Info.Traits.Get<BuildingInfo>() : null;
if (building != null && !building.Unsellable)
yield return new Order("Sell", underCursor, null, int2.Zero, null);

View File

@@ -57,7 +57,7 @@ namespace OpenRa.Game.Orders
Cursor CursorForOrderString(string s, Actor a, int2 location)
{
var movement = a.traits.WithInterface<IMovement>().FirstOrDefault();
var movement = a.traits.GetOrDefault<IMovement>();
switch (s)
{
case "Attack": return Cursor.Attack;
@@ -69,8 +69,8 @@ namespace OpenRa.Game.Orders
else
return Cursor.MoveBlocked;
case "DeployMcv":
var factBuildingInfo = (BuildingInfo)Rules.UnitInfo["fact"];
if (Game.CanPlaceBuilding(factBuildingInfo, a.Location - new int2(1, 1), a, false))
var factBuildingInfo = Rules.NewUnitInfo["fact"].Traits.Get<BuildingInfo>();
if (Game.CanPlaceBuilding("fact", factBuildingInfo, a.Location - new int2(1, 1), a, false))
return Cursor.Deploy;
else
return Cursor.DeployBlocked;

View File

@@ -15,22 +15,19 @@ namespace OpenRa.Game.Orders
{
Game.world.AddFrameEndTask( _ =>
{
var queue = order.Player.PlayerActor.traits.Get<Traits.ProductionQueue>();
var building = (BuildingInfo)Rules.UnitInfo[ order.TargetString ];
var queue = order.Player.PlayerActor.traits.Get<ProductionQueue>();
var producing = queue.CurrentItem(Rules.UnitCategory[order.TargetString]);
if( producing == null || producing.Item != order.TargetString || producing.RemainingTime != 0 )
return;
Log.Write( "Player \"{0}\" builds {1}", order.Player.PlayerName, building.Name );
Game.world.Add( new Actor( building, order.TargetLocation - GameRules.Footprint.AdjustForBuildingSize( building ), order.Player ) );
Game.world.Add( new Actor( order.TargetString, order.TargetLocation - Footprint.AdjustForBuildingSize( Rules.NewUnitInfo[ order.TargetString ].Traits.Get<BuildingInfo>() ), order.Player ) );
if (order.Player == Game.LocalPlayer)
{
Sound.Play("placbldg.aud");
Sound.Play("build5.aud");
}
queue.FinishProduction(Rules.UnitCategory[building.Name]);
queue.FinishProduction(Rules.UnitCategory[order.TargetString]);
} );
break;
}

View File

@@ -84,9 +84,8 @@ namespace OpenRa.Game
{
OreCapacity = Game.world.Actors
.Where(a => a.Owner == this && a.traits.Contains<StoresOre>())
.Select(a => a.Info as BuildingInfo)
.Where(b => b != null)
.Sum(b => b.Storage);
.Select(a => a.Info.Traits.Get<StoresOreInfo>())
.Sum(b => b.Capacity);
}
void GiveAdvice(string advice)

View File

@@ -78,7 +78,9 @@ namespace OpenRa.Game
public void Explore(Actor a)
{
foreach (var t in Game.FindTilesInCircle((1f / Game.CellSize * a.CenterLocation).ToInt2(), a.Info.Sight))
foreach (var t in Game.FindTilesInCircle(
(1f / Game.CellSize * a.CenterLocation).ToInt2(),
a.Info.Traits.Get<OwnedActorInfo>().Sight))
{
explored[t.X, t.Y] = true;
gapField[t.X, t.Y] = 0;

View File

@@ -2,6 +2,7 @@
using IrrKlang;
using OpenRa.FileFormats;
using OpenRa.Game.GameRules;
using OpenRa.Game.Traits;
namespace OpenRa.Game
{
@@ -92,7 +93,7 @@ namespace OpenRa.Game
{
if (voicedUnit == null) return;
var mi = voicedUnit.Info as MobileInfo;
var mi = voicedUnit.Info.Traits.GetOrDefault<SelectableInfo>();
if (mi == null) return;
var vi = Rules.VoiceInfo[mi.Voice];

View File

@@ -9,7 +9,7 @@ namespace OpenRa.Game.Support
{
static class PerfHistory
{
static readonly Color[] colors = { Color.Red, Color.Green, Color.Blue, Color.Yellow, Color.Orange, Color.Fuchsia, Color.Lime, Color.LightBlue };
static readonly Color[] colors = { Color.Red, Color.Green, Color.Blue, Color.Yellow, Color.Orange, Color.Fuchsia, Color.Lime, Color.LightBlue, Color.White, Color.Black };
static int nextColor;
public static Cache<string, PerfItem> items = new Cache<string, PerfItem>(

View File

@@ -2,6 +2,7 @@
using System.Linq;
using OpenRa.Game.GameRules;
using OpenRa.Game.SupportPowers;
using OpenRa.Game.Traits;
namespace OpenRa.Game
{
@@ -49,8 +50,7 @@ namespace OpenRa.Game
var buildings = Rules.TechTree.GatherBuildings(Owner);
var effectivePrereq = Info.Prerequisite
.Select( a => a.ToLowerInvariant() )
.Where( a => Rules.UnitInfo[a].Owner
.Any( r => r == Owner.Race ));
.Where( a => Rules.NewUnitInfo[a].Traits.Get<BuildableInfo>().Owner.Contains( Owner.Race ));
IsAvailable = Info.TechLevel > -1
&& effectivePrereq.Any()

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,24 +9,50 @@ 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()
@@ -34,10 +60,12 @@ namespace OpenRa.Game.Traits
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,14 +1,19 @@
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>();
@@ -16,7 +21,7 @@ namespace OpenRa.Game.Traits
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 )

Some files were not shown because too many files have changed in this diff Show More