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

This commit is contained in:
Paul Chote
2009-12-22 22:37:11 -08:00
parent 980ce2df56
commit 4667679f12
13 changed files with 146 additions and 43 deletions

View File

@@ -86,7 +86,9 @@ namespace OpenRa.Game
if (!Rules.Map.IsInMap(xy.X, xy.Y)) if (!Rules.Map.IsInMap(xy.X, xy.Y))
return null; 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 ); ?? Game.BuildingInfluence.GetBuildingAt( xy );
if (underCursor != null && !underCursor.Info.Selectable) if (underCursor != null && !underCursor.Info.Selectable)

View File

@@ -210,13 +210,35 @@ namespace OpenRa.Game
public static bool IsCellBuildable(int2 a, UnitMovementType umt, Actor toIgnore) public static bool IsCellBuildable(int2 a, UnitMovementType umt, Actor toIgnore)
{ {
if (BuildingInfluence.GetBuildingAt(a) != null) return false; 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) && return Rules.Map.IsInMap(a.X, a.Y) &&
TerrainCosts.Cost(umt, TerrainCosts.Cost(umt,
Rules.TileSet.GetWalkability(Rules.Map.MapTiles[a.X, a.Y])) < double.PositiveInfinity; 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<IMovement>().FirstOrDefault().GetMovementType());
}
public static bool IsActorCrushableByMovementType(Actor a, UnitMovementType umt)
{
if (a != null)
{
foreach (var crush in a.traits.WithInterface<ICrushable>())
{
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) public static bool IsWater(int2 a)
{ {
return Rules.Map.IsInMap(a.X, a.Y) && return Rules.Map.IsInMap(a.X, a.Y) &&

View File

@@ -173,6 +173,7 @@
<Compile Include="Traits\Fake.cs" /> <Compile Include="Traits\Fake.cs" />
<Compile Include="Traits\Harvester.cs" /> <Compile Include="Traits\Harvester.cs" />
<Compile Include="Traits\Helicopter.cs" /> <Compile Include="Traits\Helicopter.cs" />
<Compile Include="Traits\Infantry.cs" />
<Compile Include="Traits\Plane.cs" /> <Compile Include="Traits\Plane.cs" />
<Compile Include="Traits\ProductionQueue.cs" /> <Compile Include="Traits\ProductionQueue.cs" />
<Compile Include="Traits\RenderBuildingCharge.cs" /> <Compile Include="Traits\RenderBuildingCharge.cs" />

View File

@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using OpenRa.FileFormats; using OpenRa.FileFormats;
using OpenRa.Game.Support; using OpenRa.Game.Support;
using OpenRa.Game.Traits;
namespace OpenRa.Game namespace OpenRa.Game
{ {
@@ -22,20 +23,6 @@ namespace OpenRa.Game
: float.PositiveInfinity; : 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<int2> FindUnitPath( int2 from, int2 target, UnitMovementType umt ) public List<int2> FindUnitPath( int2 from, int2 target, UnitMovementType umt )
{ {
using (new PerfSample("find_unit_path")) using (new PerfSample("find_unit_path"))
@@ -67,7 +54,7 @@ namespace OpenRa.Game
return q => return q =>
p != q && p != q &&
((p - q).LengthSquared < dist * dist) && ((p - q).LengthSquared < dist * dist) &&
(Game.UnitInfluence.GetUnitAt(q) != null); (Game.UnitInfluence.GetUnitsAt(q).Any());
} }
public List<int2> FindPath( PathSearch search ) public List<int2> FindPath( PathSearch search )

View File

@@ -1,5 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using IjwFramework.Collections; using IjwFramework.Collections;
using OpenRa.Game.Graphics; using OpenRa.Game.Graphics;
@@ -54,8 +55,10 @@ namespace OpenRa.Game
continue; 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; continue;
if (customBlock != null && customBlock(newHere)) if (customBlock != null && customBlock(newHere))
continue; continue;
@@ -75,6 +78,7 @@ namespace OpenRa.Game
cellInfo[ newHere.X, newHere.Y ].MinCost = newCost; cellInfo[ newHere.X, newHere.Y ].MinCost = newCost;
queue.Add( new PathDistance( newCost + est, newHere ) ); queue.Add( new PathDistance( newCost + est, newHere ) );
} }
return p.Location; return p.Location;
} }
@@ -96,7 +100,6 @@ namespace OpenRa.Game
checkForBlocked = checkForBlocked }; checkForBlocked = checkForBlocked };
search.AddInitialCell( from ); search.AddInitialCell( from );
return search; return search;
} }

View File

@@ -45,8 +45,10 @@ namespace OpenRa.Game.Traits.Activities
static bool CanEnterCell( int2 c, Actor self ) static bool CanEnterCell( int2 c, Actor self )
{ {
if (!Game.BuildingInfluence.CanMoveHere(c)) return false; 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 ) public IActivity Tick( Actor self )

View File

@@ -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<UnitMovementType> CrushableBy()
{
yield return UnitMovementType.Track;
yield return UnitMovementType.Wheel;
}
}
}

View File

@@ -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<ICrushable>();
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<int2> GetCurrentPath() public IEnumerable<int2> GetCurrentPath()

View File

@@ -21,7 +21,7 @@ namespace OpenRa.Game.Traits
public bool Produce( Actor self, UnitInfo producee ) public bool Produce( Actor self, UnitInfo producee )
{ {
var location = CreationLocation( self, 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; return false;
var newUnit = new Actor( producee, location.Value, self.Owner ); var newUnit = new Actor( producee, location.Value, self.Owner );

View File

@@ -1,4 +1,5 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using OpenRa.Game.Graphics; using OpenRa.Game.Graphics;
namespace OpenRa.Game.Traits namespace OpenRa.Game.Traits
@@ -35,7 +36,7 @@ namespace OpenRa.Game.Traits
if (doneBuilding) roof.Tick(); if (doneBuilding) roof.Tick();
var b = self.Bounds; 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; isOpen = false;
roof.PlayBackwardsThen(prefix + "build-top", () => roof.Play(prefix + "idle-top")); roof.PlayBackwardsThen(prefix + "build-top", () => roof.Play(prefix + "idle-top"));

View File

@@ -57,4 +57,12 @@ namespace OpenRa.Game.Traits
UnitMovementType GetMovementType(); UnitMovementType GetMovementType();
bool CanEnterCell(int2 location); bool CanEnterCell(int2 location);
} }
interface ICrushable
{
bool IsCrushableByFriend();
bool IsCrushableByEnemy();
void OnCrush(Actor crusher);
IEnumerable<UnitMovementType>CrushableBy();
}
} }

View File

@@ -1,4 +1,6 @@
using System.Drawing; using System.Drawing;
using System.Collections.Generic;
using System.Linq;
using OpenRa.Game.GameRules; using OpenRa.Game.GameRules;
using OpenRa.Game.Graphics; using OpenRa.Game.Graphics;
@@ -37,7 +39,7 @@ namespace OpenRa.Game
if (ShowUnitDebug) if (ShowUnitDebug)
for (var j = 0; j < 128; j++) for (var j = 0; j < 128; j++)
for (var i = 0; i < 128; i++) 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); spriteRenderer.DrawSprite(unitDebug, Game.CellSize * new float2(i, j), 0);
} }

View File

@@ -1,17 +1,22 @@
using System; using System;
using System.Diagnostics; using System.Diagnostics;
using System.Linq; using System.Linq;
using System.Collections.Generic;
using OpenRa.Game.Traits; using OpenRa.Game.Traits;
namespace OpenRa.Game namespace OpenRa.Game
{ {
class UnitInfluenceMap class UnitInfluenceMap
{ {
Actor[,] influence = new Actor[128, 128]; List<Actor>[,] influence = new List<Actor>[128, 128];
readonly int2 searchDistance = new int2(2,2); readonly int2 searchDistance = new int2(2,2);
public UnitInfluenceMap() public UnitInfluenceMap()
{ {
for (int i = 0; i < 128; i++)
for (int j = 0; j < 128; j++)
influence[ i, j ] = new List<Actor>();
Game.world.ActorRemoved += a => Remove(a, a.traits.WithInterface<IOccupySpace>().FirstOrDefault()); Game.world.ActorRemoved += a => Remove(a, a.traits.WithInterface<IOccupySpace>().FirstOrDefault());
} }
@@ -25,13 +30,16 @@ namespace OpenRa.Game
{ {
for( int y = 0 ; y < 128 ; y++ ) for( int y = 0 ; y < 128 ; y++ )
for( int x = 0 ; x < 128 ; x++ ) for( int x = 0 ; x < 128 ; x++ )
if( influence[ x, y ] != null && !influence[ x, y ].traits.WithInterface<IOccupySpace>().First().OccupiedCells().Contains( new int2( x, y ) ) ) if( influence[ x, y ] != null )
foreach (var a in influence[ x, y ])
if (!a.traits.WithInterface<IOccupySpace>().First().OccupiedCells().Contains( new int2( x, y ) ) )
throw new InvalidOperationException( "UIM: Sanity check failed A" ); 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<IOccupySpace>() ) foreach( var ios in a.traits.WithInterface<IOccupySpace>() )
foreach( var cell in ios.OccupiedCells() ) foreach( var cell in ios.OccupiedCells() )
if( influence[ cell.X, cell.Y ] != a ) if (!influence[cell.X, cell.Y].Contains(a))
//if( influence[ cell.X, cell.Y ] != a )
throw new InvalidOperationException( "UIM: Sanity check failed B" ); throw new InvalidOperationException( "UIM: Sanity check failed B" );
} }
@@ -39,11 +47,11 @@ namespace OpenRa.Game
void SanityCheckAdd( IOccupySpace a ) void SanityCheckAdd( IOccupySpace a )
{ {
foreach( var c in a.OccupiedCells() ) 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)" ); throw new InvalidOperationException( "UIM: Sanity check failed (Add)" );
} }
public Actor GetUnitAt( int2 a ) public IEnumerable<Actor> GetUnitsAt( int2 a )
{ {
return influence[ a.X, a.Y ]; return influence[ a.X, a.Y ];
} }
@@ -52,14 +60,14 @@ namespace OpenRa.Game
{ {
SanityCheckAdd( unit ); SanityCheckAdd( unit );
foreach( var c in unit.OccupiedCells() ) 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 ) public void Remove( Actor self, IOccupySpace unit )
{ {
if (unit != null) if (unit != null)
foreach (var c in unit.OccupiedCells()) 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) public void Update(Actor self, IOccupySpace unit)