From 4667679f12f93511666e93b391e958091b50225b Mon Sep 17 00:00:00 2001 From: Paul Chote Date: Tue, 22 Dec 2009 22:37:11 -0800 Subject: [PATCH 1/4] Intermediate crushable-behavior checkin; Muliple units per cell in UIM; Crushable TraitInterface; Crushable units are taken into account in pathfinding; Crashes when trying to crush a unit --- OpenRa.Game/Actor.cs | 6 ++- OpenRa.Game/Game.cs | 24 +++++++++++- OpenRa.Game/OpenRa.Game.csproj | 1 + OpenRa.Game/PathFinder.cs | 21 ++--------- OpenRa.Game/PathSearch.cs | 13 ++++--- OpenRa.Game/Traits/Activities/Move.cs | 8 ++-- OpenRa.Game/Traits/Infantry.cs | 34 +++++++++++++++++ OpenRa.Game/Traits/Mobile.cs | 37 ++++++++++++++++++- OpenRa.Game/Traits/Production.cs | 2 +- .../Traits/RenderBuildingWarFactory.cs | 3 +- OpenRa.Game/Traits/TraitsInterfaces.cs | 8 ++++ OpenRa.Game/UiOverlay.cs | 4 +- OpenRa.Game/UnitInfluenceMap.cs | 28 +++++++++----- 13 files changed, 146 insertions(+), 43 deletions(-) create mode 100644 OpenRa.Game/Traits/Infantry.cs diff --git a/OpenRa.Game/Actor.cs b/OpenRa.Game/Actor.cs index ff765c9edb..e91f977190 100755 --- a/OpenRa.Game/Actor.cs +++ b/OpenRa.Game/Actor.cs @@ -85,8 +85,10 @@ namespace OpenRa.Game if (!Rules.Map.IsInMap(xy.X, xy.Y)) return null; - - var underCursor = Game.UnitInfluence.GetUnitAt( xy ) + + // HACK: Get the first unit in the cell + // This will need to be updated for multiple-infantry-in-a-cell + var underCursor = Game.UnitInfluence.GetUnitsAt( xy ).FirstOrDefault() ?? Game.BuildingInfluence.GetBuildingAt( xy ); if (underCursor != null && !underCursor.Info.Selectable) diff --git a/OpenRa.Game/Game.cs b/OpenRa.Game/Game.cs index 6fa6741559..8a18bdeedf 100644 --- a/OpenRa.Game/Game.cs +++ b/OpenRa.Game/Game.cs @@ -210,13 +210,35 @@ namespace OpenRa.Game public static bool IsCellBuildable(int2 a, UnitMovementType umt, Actor toIgnore) { if (BuildingInfluence.GetBuildingAt(a) != null) return false; - if (UnitInfluence.GetUnitAt(a) != null && UnitInfluence.GetUnitAt(a) != toIgnore) return false; + if (UnitInfluence.GetUnitsAt(a).Any(b => b != toIgnore)) return false; return Rules.Map.IsInMap(a.X, a.Y) && TerrainCosts.Cost(umt, Rules.TileSet.GetWalkability(Rules.Map.MapTiles[a.X, a.Y])) < double.PositiveInfinity; } + public static bool IsActorCrushableByActor(Actor a, Actor b) + { + return IsActorCrushableByMovementType(a, b.traits.WithInterface().FirstOrDefault().GetMovementType()); + } + public static bool IsActorCrushableByMovementType(Actor a, UnitMovementType umt) + { + if (a != null) + { + foreach (var crush in a.traits.WithInterface()) + { + if (((crush.IsCrushableByEnemy() && a.Owner != Game.LocalPlayer) || (crush.IsCrushableByFriend() && a.Owner == Game.LocalPlayer)) + && crush.CrushableBy().Contains(umt)) + { + Log.Write("{0} is crushable by MovementType {1}", a.Info.Name, umt); + return true; + } + } + Log.Write("{0} is NOT crushable by MovementType {1}", a.Info.Name, umt); + } + return false; + } + public static bool IsWater(int2 a) { return Rules.Map.IsInMap(a.X, a.Y) && diff --git a/OpenRa.Game/OpenRa.Game.csproj b/OpenRa.Game/OpenRa.Game.csproj index ca5f68d865..9dfa180837 100644 --- a/OpenRa.Game/OpenRa.Game.csproj +++ b/OpenRa.Game/OpenRa.Game.csproj @@ -173,6 +173,7 @@ + diff --git a/OpenRa.Game/PathFinder.cs b/OpenRa.Game/PathFinder.cs index 2a2717c51c..210613a1e6 100644 --- a/OpenRa.Game/PathFinder.cs +++ b/OpenRa.Game/PathFinder.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using OpenRa.FileFormats; using OpenRa.Game.Support; +using OpenRa.Game.Traits; namespace OpenRa.Game { @@ -22,20 +23,6 @@ namespace OpenRa.Game : float.PositiveInfinity; } - bool IsBlocked(int2 from, UnitMovementType umt) - { - for (int v = -1; v < 2; v++) - for (int u = -1; u < 2; u++) - if (u != 0 || v != 0) - { - var p = from + new int2(u, v); - if (passableCost[(int)umt][from.X + u, from.Y + v] < float.PositiveInfinity) - if (Game.BuildingInfluence.CanMoveHere(p) && (Game.UnitInfluence.GetUnitAt(p) == null)) - return false; - } - return true; - } - public List FindUnitPath( int2 from, int2 target, UnitMovementType umt ) { using (new PerfSample("find_unit_path")) @@ -61,13 +48,13 @@ namespace OpenRa.Game return path; } } - + Func AvoidUnitsNear(int2 p, int dist) { return q => p != q && - ((p - q).LengthSquared < dist * dist) && - (Game.UnitInfluence.GetUnitAt(q) != null); + ((p - q).LengthSquared < dist * dist) && + (Game.UnitInfluence.GetUnitsAt(q).Any()); } public List FindPath( PathSearch search ) diff --git a/OpenRa.Game/PathSearch.cs b/OpenRa.Game/PathSearch.cs index cb2bc4dcd2..0290d34056 100755 --- a/OpenRa.Game/PathSearch.cs +++ b/OpenRa.Game/PathSearch.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; using IjwFramework.Collections; using OpenRa.Game.Graphics; @@ -35,7 +36,7 @@ namespace OpenRa.Game if (!ignoreTerrain) if (passableCost[(int)umt][p.Location.X, p.Location.Y] == float.PositiveInfinity) return p.Location; - + foreach( int2 d in Util.directions ) { int2 newHere = p.Location + d; @@ -53,13 +54,15 @@ namespace OpenRa.Game if (Rules.Map.IsOverlaySolid(newHere)) continue; } - - if( checkForBlocked && Game.UnitInfluence.GetUnitAt( newHere ) != null ) + + // Replicate real-ra behavior of not being able to enter a cell if there is a mixture of crushable and uncrushable units + if (checkForBlocked && (Game.UnitInfluence.GetUnitsAt(newHere).Any(a => !Game.IsActorCrushableByMovementType(a, umt)))) continue; + if (customBlock != null && customBlock(newHere)) continue; - + var est = heuristic( newHere ); if( est == float.PositiveInfinity ) continue; @@ -75,6 +78,7 @@ namespace OpenRa.Game cellInfo[ newHere.X, newHere.Y ].MinCost = newCost; queue.Add( new PathDistance( newCost + est, newHere ) ); + } return p.Location; } @@ -96,7 +100,6 @@ namespace OpenRa.Game checkForBlocked = checkForBlocked }; search.AddInitialCell( from ); - return search; } diff --git a/OpenRa.Game/Traits/Activities/Move.cs b/OpenRa.Game/Traits/Activities/Move.cs index ef078d05fb..9a655495a1 100755 --- a/OpenRa.Game/Traits/Activities/Move.cs +++ b/OpenRa.Game/Traits/Activities/Move.cs @@ -45,8 +45,10 @@ namespace OpenRa.Game.Traits.Activities static bool CanEnterCell( int2 c, Actor self ) { if (!Game.BuildingInfluence.CanMoveHere(c)) return false; - var u = Game.UnitInfluence.GetUnitAt( c ); - return (u == null || u == self); + + // Cannot enter a cell if any unit inside is uncrushable + // This will need to be updated for multiple-infantry-in-a-cell + return (!Game.UnitInfluence.GetUnitsAt(c).Any(a => a != self && !Game.IsActorCrushableByActor(a, self))); } public IActivity Tick( Actor self ) @@ -68,7 +70,7 @@ namespace OpenRa.Game.Traits.Activities path = getPath( self, mobile ).TakeWhile( a => a != self.Location ).ToList(); SanityCheckPath( mobile ); } - + if( path.Count == 0 ) { destination = mobile.toCell; diff --git a/OpenRa.Game/Traits/Infantry.cs b/OpenRa.Game/Traits/Infantry.cs new file mode 100644 index 0000000000..d1122aa7b2 --- /dev/null +++ b/OpenRa.Game/Traits/Infantry.cs @@ -0,0 +1,34 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace OpenRa.Game.Traits +{ + class Infantry : ICrushable + { + public Infantry(Actor self){} + + public bool IsCrushableByFriend() + { + // HACK: should be false + return true; + } + public bool IsCrushableByEnemy() + { + // HACK: should be based off crushable tag + return true; + } + + public void OnCrush(Actor crusher) + { + Sound.Play("squishy2.aud"); + } + + public IEnumerable CrushableBy() + { + yield return UnitMovementType.Track; + yield return UnitMovementType.Wheel; + } + } +} diff --git a/OpenRa.Game/Traits/Mobile.cs b/OpenRa.Game/Traits/Mobile.cs index c25bfae764..dcc29f4ff5 100644 --- a/OpenRa.Game/Traits/Mobile.cs +++ b/OpenRa.Game/Traits/Mobile.cs @@ -80,9 +80,42 @@ namespace OpenRa.Game.Traits } } - public bool CanEnterCell(int2 location) + public bool CanEnterCell(int2 a) { - return Game.IsCellBuildable( location, GetMovementType(), self ); + if (Game.BuildingInfluence.GetBuildingAt(a) != null) return false; + + var actors = Game.UnitInfluence.GetUnitsAt(a); + var crushable = true; + foreach (Actor actor in actors) + { + if (actor == self) continue; + + var c = actor.traits.WithInterface(); + if (c == null) + { + crushable = false; + break; + } + + foreach (var crush in c) + { + // TODO: Unhack this. I can't wrap my head around this right now... + if (!(((crush.IsCrushableByEnemy() && actor.Owner != Game.LocalPlayer) || (crush.IsCrushableByFriend() && actor.Owner == Game.LocalPlayer)) + && crush.CrushableBy().Contains(GetMovementType()))) + { + crushable = false; + Log.Write("{0} is NOT crushable by {1} (mobile)", actor.Info.Name, self.Info.Name); + break; + } + } + Log.Write("{0} is crushable by {1} (mobile)", actor.Info.Name, self.Info.Name); + } + + if (!crushable) return false; + + return Rules.Map.IsInMap(a.X, a.Y) && + TerrainCosts.Cost(GetMovementType(), + Rules.TileSet.GetWalkability(Rules.Map.MapTiles[a.X, a.Y])) < double.PositiveInfinity; } public IEnumerable GetCurrentPath() diff --git a/OpenRa.Game/Traits/Production.cs b/OpenRa.Game/Traits/Production.cs index 4dcfe7fe63..a3cc336c15 100755 --- a/OpenRa.Game/Traits/Production.cs +++ b/OpenRa.Game/Traits/Production.cs @@ -21,7 +21,7 @@ namespace OpenRa.Game.Traits public bool Produce( Actor self, UnitInfo producee ) { var location = CreationLocation( self, producee ); - if( location == null || Game.UnitInfluence.GetUnitAt( location.Value ) != null ) + if( location == null || !Game.UnitInfluence.GetUnitsAt( location.Value ).Any() ) return false; var newUnit = new Actor( producee, location.Value, self.Owner ); diff --git a/OpenRa.Game/Traits/RenderBuildingWarFactory.cs b/OpenRa.Game/Traits/RenderBuildingWarFactory.cs index fe8a527911..40ccaf25e9 100644 --- a/OpenRa.Game/Traits/RenderBuildingWarFactory.cs +++ b/OpenRa.Game/Traits/RenderBuildingWarFactory.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Linq; using OpenRa.Game.Graphics; namespace OpenRa.Game.Traits @@ -35,7 +36,7 @@ namespace OpenRa.Game.Traits if (doneBuilding) roof.Tick(); var b = self.Bounds; - if (isOpen && null == Game.UnitInfluence.GetUnitAt(((1/24f) * self.CenterLocation).ToInt2())) + if (isOpen && !Game.UnitInfluence.GetUnitsAt(((1/24f) * self.CenterLocation).ToInt2()).Any()) { isOpen = false; roof.PlayBackwardsThen(prefix + "build-top", () => roof.Play(prefix + "idle-top")); diff --git a/OpenRa.Game/Traits/TraitsInterfaces.cs b/OpenRa.Game/Traits/TraitsInterfaces.cs index cb8903a396..da62f96de9 100644 --- a/OpenRa.Game/Traits/TraitsInterfaces.cs +++ b/OpenRa.Game/Traits/TraitsInterfaces.cs @@ -57,4 +57,12 @@ namespace OpenRa.Game.Traits UnitMovementType GetMovementType(); bool CanEnterCell(int2 location); } + + interface ICrushable + { + bool IsCrushableByFriend(); + bool IsCrushableByEnemy(); + void OnCrush(Actor crusher); + IEnumerableCrushableBy(); + } } diff --git a/OpenRa.Game/UiOverlay.cs b/OpenRa.Game/UiOverlay.cs index 309d44985a..f7abc7dd06 100644 --- a/OpenRa.Game/UiOverlay.cs +++ b/OpenRa.Game/UiOverlay.cs @@ -1,4 +1,6 @@ using System.Drawing; +using System.Collections.Generic; +using System.Linq; using OpenRa.Game.GameRules; using OpenRa.Game.Graphics; @@ -37,7 +39,7 @@ namespace OpenRa.Game if (ShowUnitDebug) for (var j = 0; j < 128; j++) for (var i = 0; i < 128; i++) - if (Game.UnitInfluence.GetUnitAt(new int2(i, j)) != null) + if (Game.UnitInfluence.GetUnitsAt(new int2(i, j)).Any()) spriteRenderer.DrawSprite(unitDebug, Game.CellSize * new float2(i, j), 0); } diff --git a/OpenRa.Game/UnitInfluenceMap.cs b/OpenRa.Game/UnitInfluenceMap.cs index 9fad708018..ee2b6beaea 100644 --- a/OpenRa.Game/UnitInfluenceMap.cs +++ b/OpenRa.Game/UnitInfluenceMap.cs @@ -1,17 +1,22 @@ using System; using System.Diagnostics; using System.Linq; +using System.Collections.Generic; using OpenRa.Game.Traits; namespace OpenRa.Game { class UnitInfluenceMap { - Actor[,] influence = new Actor[128, 128]; + List[,] influence = new List[128, 128]; readonly int2 searchDistance = new int2(2,2); public UnitInfluenceMap() { + for (int i = 0; i < 128; i++) + for (int j = 0; j < 128; j++) + influence[ i, j ] = new List(); + Game.world.ActorRemoved += a => Remove(a, a.traits.WithInterface().FirstOrDefault()); } @@ -25,25 +30,28 @@ namespace OpenRa.Game { for( int y = 0 ; y < 128 ; y++ ) for( int x = 0 ; x < 128 ; x++ ) - if( influence[ x, y ] != null && !influence[ x, y ].traits.WithInterface().First().OccupiedCells().Contains( new int2( x, y ) ) ) - throw new InvalidOperationException( "UIM: Sanity check failed A" ); + if( influence[ x, y ] != null ) + foreach (var a in influence[ x, y ]) + if (!a.traits.WithInterface().First().OccupiedCells().Contains( new int2( x, y ) ) ) + throw new InvalidOperationException( "UIM: Sanity check failed A" ); - foreach( var a in Game.world.Actors ) + foreach( Actor a in Game.world.Actors ) foreach( var ios in a.traits.WithInterface() ) foreach( var cell in ios.OccupiedCells() ) - if( influence[ cell.X, cell.Y ] != a ) - throw new InvalidOperationException( "UIM: Sanity check failed B" ); + if (!influence[cell.X, cell.Y].Contains(a)) + //if( influence[ cell.X, cell.Y ] != a ) + throw new InvalidOperationException( "UIM: Sanity check failed B" ); } [Conditional( "SANITY_CHECKS" )] void SanityCheckAdd( IOccupySpace a ) { foreach( var c in a.OccupiedCells() ) - if( influence[c.X, c.Y] != null ) + if( influence[c.X, c.Y].Any()) throw new InvalidOperationException( "UIM: Sanity check failed (Add)" ); } - public Actor GetUnitAt( int2 a ) + public IEnumerable GetUnitsAt( int2 a ) { return influence[ a.X, a.Y ]; } @@ -52,14 +60,14 @@ namespace OpenRa.Game { SanityCheckAdd( unit ); foreach( var c in unit.OccupiedCells() ) - influence[c.X, c.Y] = self; + influence[c.X, c.Y].Add(self); } public void Remove( Actor self, IOccupySpace unit ) { if (unit != null) foreach (var c in unit.OccupiedCells()) - influence[c.X, c.Y] = null; + influence[c.X, c.Y].Remove(self); } public void Update(Actor self, IOccupySpace unit) From 0b0a1fe11d3f4a8668c5597ff340a4183b5afc2d Mon Sep 17 00:00:00 2001 From: Paul Chote Date: Tue, 22 Dec 2009 23:00:15 -0800 Subject: [PATCH 2/4] Make infantry crushable, refactoring, fix UIM (in)sanity crash, fix unit production --- OpenRa.Game/Traits/Infantry.cs | 2 +- OpenRa.Game/Traits/Mobile.cs | 19 ++----------------- OpenRa.Game/Traits/Production.cs | 2 +- OpenRa.Game/UnitInfluenceMap.cs | 2 ++ units.ini | 20 ++++++++++---------- 5 files changed, 16 insertions(+), 29 deletions(-) diff --git a/OpenRa.Game/Traits/Infantry.cs b/OpenRa.Game/Traits/Infantry.cs index d1122aa7b2..9697605c55 100644 --- a/OpenRa.Game/Traits/Infantry.cs +++ b/OpenRa.Game/Traits/Infantry.cs @@ -28,7 +28,7 @@ namespace OpenRa.Game.Traits public IEnumerable CrushableBy() { yield return UnitMovementType.Track; - yield return UnitMovementType.Wheel; + //yield return UnitMovementType.Wheel; // Can infantry be crushed by wheel? } } } diff --git a/OpenRa.Game/Traits/Mobile.cs b/OpenRa.Game/Traits/Mobile.cs index dcc29f4ff5..c009ab0a1c 100644 --- a/OpenRa.Game/Traits/Mobile.cs +++ b/OpenRa.Game/Traits/Mobile.cs @@ -84,31 +84,16 @@ namespace OpenRa.Game.Traits { if (Game.BuildingInfluence.GetBuildingAt(a) != null) return false; - var actors = Game.UnitInfluence.GetUnitsAt(a); var crushable = true; - foreach (Actor actor in actors) + foreach (Actor actor in Game.UnitInfluence.GetUnitsAt(a)) { if (actor == self) continue; - var c = actor.traits.WithInterface(); - if (c == null) + if (!Game.IsActorCrushableByActor(actor, self)) { crushable = false; break; } - - foreach (var crush in c) - { - // TODO: Unhack this. I can't wrap my head around this right now... - if (!(((crush.IsCrushableByEnemy() && actor.Owner != Game.LocalPlayer) || (crush.IsCrushableByFriend() && actor.Owner == Game.LocalPlayer)) - && crush.CrushableBy().Contains(GetMovementType()))) - { - crushable = false; - Log.Write("{0} is NOT crushable by {1} (mobile)", actor.Info.Name, self.Info.Name); - break; - } - } - Log.Write("{0} is crushable by {1} (mobile)", actor.Info.Name, self.Info.Name); } if (!crushable) return false; diff --git a/OpenRa.Game/Traits/Production.cs b/OpenRa.Game/Traits/Production.cs index a3cc336c15..640f9143d1 100755 --- a/OpenRa.Game/Traits/Production.cs +++ b/OpenRa.Game/Traits/Production.cs @@ -21,7 +21,7 @@ namespace OpenRa.Game.Traits public bool Produce( Actor self, UnitInfo producee ) { var location = CreationLocation( self, producee ); - if( location == null || !Game.UnitInfluence.GetUnitsAt( location.Value ).Any() ) + if( location == null || Game.UnitInfluence.GetUnitsAt( location.Value ).Any() ) return false; var newUnit = new Actor( producee, location.Value, self.Owner ); diff --git a/OpenRa.Game/UnitInfluenceMap.cs b/OpenRa.Game/UnitInfluenceMap.cs index ee2b6beaea..6d5e5975ff 100644 --- a/OpenRa.Game/UnitInfluenceMap.cs +++ b/OpenRa.Game/UnitInfluenceMap.cs @@ -46,9 +46,11 @@ namespace OpenRa.Game [Conditional( "SANITY_CHECKS" )] void SanityCheckAdd( IOccupySpace a ) { + /* This check is too strict now that we can have multiple units in a cell foreach( var c in a.OccupiedCells() ) if( influence[c.X, c.Y].Any()) throw new InvalidOperationException( "UIM: Sanity check failed (Add)" ); + */ } public IEnumerable GetUnitsAt( int2 a ) diff --git a/units.ini b/units.ini index 0078c01ace..3e806737d3 100755 --- a/units.ini +++ b/units.ini @@ -522,52 +522,52 @@ MEDI Description=Attack Dog BuiltAt=KENN Voice=DogVoice -Traits=Unit, Mobile, RenderInfantry +Traits=Unit, Mobile, RenderInfantry, Infantry LongDesc=Anti-infantry unit. Not fooled by the \nSpy's disguise.\n Strong vs Infantry\n Weak vs Vehicles [E1] Description=Rifle Infantry -Traits=Unit, Mobile, RenderInfantry, AttackBase, TakeCover +Traits=Unit, Mobile, RenderInfantry, AttackBase, TakeCover, Infantry LongDesc=General-purpose infantry. Strong vs Infantry\n Weak vs Vehicles [E2] Description=Grenadier -Traits=Unit, Mobile, RenderInfantry, AttackBase, TakeCover +Traits=Unit, Mobile, RenderInfantry, AttackBase, TakeCover, Infantry FireDelay=15 PrimaryOffset=0,0,0,-13 LongDesc=Infantry armed with grenades. \n Strong vs Buildings, Infantry\n Weak vs Vehicles [E3] Description=Rocket Soldier -Traits=Unit, Mobile, RenderInfantry, AttackBase, TakeCover +Traits=Unit, Mobile, RenderInfantry, AttackBase, TakeCover, Infantry PrimaryOffset=0,0,0,-13 LongDesc=Anti-tank/Anti-aircraft infantry.\n Strong vs Tanks, Aircraft\n Weak vs Infantry [E4] Description=Flamethrower -Traits=Unit, Mobile, RenderInfantry, AttackBase, TakeCover +Traits=Unit, Mobile, RenderInfantry, AttackBase, TakeCover, Infantry FireDelay=8 LongDesc=Advanced Anti-infantry unit.\n Strong vs Infantry, Buildings\n Weak vs Vehicles [E6] Description=Engineer -Traits=Unit, Mobile, RenderInfantry, TakeCover +Traits=Unit, Mobile, RenderInfantry, TakeCover, Infantry Voice=EngineerVoice LongDesc=Infiltrates and captures enemy structures.\n Strong vs Nothing\n Weak vs Everything [SPY] Description=Spy Voice=SpyVoice -Traits=Unit, Mobile, RenderInfantry, TakeCover +Traits=Unit, Mobile, RenderInfantry, TakeCover, Infantry LongDesc=Infiltrates enemy structures to gather \nintelligence. Exact effect depends on the \nbuilding infiltrated.\n Strong vs Nothing\n Weak vs Everything\n Special Ability: Disguised [THF] Description=Thief Voice=ThiefVoice -Traits=Unit, Mobile, RenderInfantry, TakeCover +Traits=Unit, Mobile, RenderInfantry, TakeCover, Infantry LongDesc=Infiltrates enemy refineries & \nsilos, and steals money stored there.\n Unarmed [E7] Description=Tanya Voice=TanyaVoice -Traits=Unit, Mobile, RenderInfantry, AttackBase, TakeCover +Traits=Unit, Mobile, RenderInfantry, AttackBase, TakeCover, Infantry LongDesc=Elite commando infantry, armed with \ndual pistols and C4.\n Strong vs Infantry, Buildings\n Weak vs Vehicles\n Special Ability: Destroy Building with C4 [MEDI] Description=Medic Voice=MedicVoice -Traits=Unit, Mobile, RenderInfantry, AttackBase, TakeCover +Traits=Unit, Mobile, RenderInfantry, AttackBase, TakeCover, Infantry LongDesc=Heals nearby infantry.\n Strong vs Nothing\n Weak vs Everything From 9da1da81027e38eeb7cffc9a6c771279bb11958b Mon Sep 17 00:00:00 2001 From: Paul Chote Date: Tue, 22 Dec 2009 23:56:39 -0800 Subject: [PATCH 3/4] Add crush check and move crush effect into a custom warhead --- OpenRa.Game/Traits/Infantry.cs | 8 ++++++-- OpenRa.Game/UnitInfluenceMap.cs | 23 +++++++++++++++++++++++ units.ini | 5 +++++ 3 files changed, 34 insertions(+), 2 deletions(-) diff --git a/OpenRa.Game/Traits/Infantry.cs b/OpenRa.Game/Traits/Infantry.cs index 9697605c55..0c4d5e5530 100644 --- a/OpenRa.Game/Traits/Infantry.cs +++ b/OpenRa.Game/Traits/Infantry.cs @@ -7,7 +7,11 @@ namespace OpenRa.Game.Traits { class Infantry : ICrushable { - public Infantry(Actor self){} + readonly Actor self; + public Infantry(Actor self) + { + this.self = self; + } public bool IsCrushableByFriend() { @@ -22,7 +26,7 @@ namespace OpenRa.Game.Traits public void OnCrush(Actor crusher) { - Sound.Play("squishy2.aud"); + self.InflictDamage(crusher, self.Health, Rules.WarheadInfo["Crush"]); } public IEnumerable CrushableBy() diff --git a/OpenRa.Game/UnitInfluenceMap.cs b/OpenRa.Game/UnitInfluenceMap.cs index 6d5e5975ff..de3ecf044b 100644 --- a/OpenRa.Game/UnitInfluenceMap.cs +++ b/OpenRa.Game/UnitInfluenceMap.cs @@ -22,6 +22,29 @@ namespace OpenRa.Game public void Tick() { + // Does this belong here? + + // Get the crushable actors + foreach (var a in Game.world.Actors.Where(b => b.traits.WithInterface().Any())) + { + // Are there any units in the same cell that can crush this? + foreach( var ios in a.traits.WithInterface() ) + foreach( var cell in ios.OccupiedCells() ) + { + // There should only be one (counterexample: An infantry and a tank try to pick up a crate at the same time.) + // If there is more than one, do action on the first crusher + var crusher = GetUnitsAt(cell).Where(b => a != b && Game.IsActorCrushableByActor(a, b)).FirstOrDefault(); + if (crusher != null) + { + Log.Write("{0} crushes {1}", crusher.Info.Name, a.Info.Name); + // Apply the crush action + foreach (var crush in a.traits.WithInterface()) + { + crush.OnCrush(crusher); + } + } + } + } SanityCheck(); } diff --git a/units.ini b/units.ini index 3e806737d3..99dba0767b 100755 --- a/units.ini +++ b/units.ini @@ -657,6 +657,7 @@ Super Organic Nuke UnitExplodeWarhead +Crush [HE] ImpactSound=kaboom25 @@ -669,6 +670,10 @@ Explosion=8 InfDeath=3 ImpactSound=kaboom15 +[Crush] +Verses=100%,100%,100%,100%,100% +ImpactSound=squishy2 + [General] OreChance=.02 LowPowerSlowdown=3 From fffb39df523feb3e5bf2228998e4d844f689e7af Mon Sep 17 00:00:00 2001 From: Paul Chote Date: Wed, 23 Dec 2009 00:05:31 -0800 Subject: [PATCH 4/4] Fix medic deaths --- sequences.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sequences.xml b/sequences.xml index 9187292fa4..fe841d91a2 100644 --- a/sequences.xml +++ b/sequences.xml @@ -498,10 +498,12 @@ + +