Harvesting works better, and other related stuff.

This commit is contained in:
Bob
2009-11-05 13:23:23 +13:00
parent 7e0b0541e2
commit edc4a8e6e7
12 changed files with 191 additions and 143 deletions

View File

@@ -1,119 +1,119 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using IjwFramework.Types; using IjwFramework.Types;
using OpenRa.FileFormats; using OpenRa.FileFormats;
using OpenRa.Game.GameRules; using OpenRa.Game.GameRules;
using OpenRa.Game.Graphics; using OpenRa.Game.Graphics;
using System.Drawing; using System.Drawing;
using OpenRa.Game.Traits; using OpenRa.Game.Traits;
namespace OpenRa.Game namespace OpenRa.Game
{ {
class Actor class Actor
{ {
public readonly TypeDictionary traits = new TypeDictionary(); public readonly TypeDictionary traits = new TypeDictionary();
public readonly UnitInfo unitInfo; public readonly UnitInfo unitInfo;
public readonly uint ActorID; public readonly uint ActorID;
public int2 Location; public int2 Location;
public Player Owner; public Player Owner;
public int Health; public int Health;
public Actor( string name, int2 location, Player owner ) public Actor( string name, int2 location, Player owner )
{ {
ActorID = Game.world.NextAID(); ActorID = Game.world.NextAID();
unitInfo = Rules.UnitInfo[ name ]; unitInfo = Rules.UnitInfo[ name ];
Location = location; Location = location;
CenterLocation = new float2( 12, 12 ) + Game.CellSize * (float2)Location; CenterLocation = new float2( 12, 12 ) + Game.CellSize * (float2)Location;
Owner = owner; Owner = owner;
Health = unitInfo.Strength; /* todo: handle cases where this is not true! */ Health = unitInfo.Strength; /* todo: handle cases where this is not true! */
if( unitInfo.Traits != null ) if( unitInfo.Traits != null )
{ {
foreach( var traitName in unitInfo.Traits ) foreach( var traitName in unitInfo.Traits )
{ {
var type = typeof( Traits.Mobile ).Assembly.GetType( typeof( Traits.Mobile ).Namespace + "." + traitName, true, false ); var type = typeof( Traits.Mobile ).Assembly.GetType( typeof( Traits.Mobile ).Namespace + "." + traitName, true, false );
var ctor = type.GetConstructor( new[] { typeof( Actor ) } ); var ctor = type.GetConstructor( new[] { typeof( Actor ) } );
traits.Add( type, ctor.Invoke( new object[] { this } ) ); traits.Add( type, ctor.Invoke( new object[] { this } ) );
} }
} }
else else
throw new InvalidOperationException( "No Actor traits for " + unitInfo.Name throw new InvalidOperationException( "No Actor traits for " + unitInfo.Name
+ "; add Traits= to units.ini for appropriate unit" ); + "; add Traits= to units.ini for appropriate unit" );
} }
public Actor( TreeReference tree, TreeCache treeRenderer ) public Actor( TreeReference tree, TreeCache treeRenderer )
{ {
Location = new int2( tree.Location ); Location = new int2( tree.Location );
traits.Add( new Traits.Tree( treeRenderer.GetImage( tree.Image ) ) ); traits.Add( new Traits.Tree( treeRenderer.GetImage( tree.Image ) ) );
} }
public void Tick() public void Tick()
{ {
foreach (var tick in traits.WithInterface<Traits.ITick>()) foreach (var tick in traits.WithInterface<Traits.ITick>())
tick.Tick(this); tick.Tick(this);
} }
public float2 CenterLocation; public float2 CenterLocation;
public float2 SelectedSize { get { return Render().First().First.size; } } public float2 SelectedSize { get { return Render().First().First.size; } }
public IEnumerable<Pair<Sprite, float2>> Render() public IEnumerable<Pair<Sprite, float2>> Render()
{ {
return traits.WithInterface<Traits.IRender>().SelectMany( x => x.Render( this ) ); return traits.WithInterface<Traits.IRender>().SelectMany( x => x.Render( this ) );
} }
public Order Order( int2 xy, bool lmb ) public Order Order( int2 xy, bool lmb )
{ {
if (Owner != Game.LocalPlayer) if (Owner != Game.LocalPlayer)
return null; return null;
var underCursor = Game.UnitInfluence.GetUnitAt( xy ) ?? Game.BuildingInfluence.GetBuildingAt( xy ); var underCursor = Game.UnitInfluence.GetUnitAt( xy ) ?? Game.BuildingInfluence.GetBuildingAt( xy );
return traits.WithInterface<Traits.IOrder>() return traits.WithInterface<Traits.IOrder>()
.Select( x => x.Order( this, xy, lmb, underCursor ) ) .Select( x => x.Order( this, xy, lmb, underCursor ) )
.FirstOrDefault( x => x != null ); .FirstOrDefault( x => x != null );
} }
public RectangleF Bounds public RectangleF Bounds
{ {
get get
{ {
var size = SelectedSize; var size = SelectedSize;
var loc = CenterLocation - 0.5f * size; var loc = CenterLocation - 0.5f * size;
return new RectangleF(loc.X, loc.Y, size.X, size.Y); return new RectangleF(loc.X, loc.Y, size.X, size.Y);
} }
} }
public bool IsDead { get { return Health <= 0; } } public bool IsDead { get { return Health <= 0; } }
public void InflictDamage(Actor attacker, Bullet inflictor, int damage) public void InflictDamage(Actor attacker, Bullet inflictor, int damage)
{ {
/* todo: auto-retaliate, etc */ /* todo: auto-retaliate, etc */
/* todo: death sequence for infantry based on inflictor */ /* todo: death sequence for infantry based on inflictor */
/* todo: start smoking if < conditionYellow and took damage, and not already smoking */ /* todo: start smoking if < conditionYellow and took damage, and not already smoking */
if (Health <= 0) return; /* overkill! don't count extra hits as more kills! */ if (Health <= 0) return; /* overkill! don't count extra hits as more kills! */
Health -= damage; Health -= damage;
if (Health <= 0) if (Health <= 0)
{ {
Health = 0; Health = 0;
if (attacker.Owner != null) if (attacker.Owner != null)
attacker.Owner.Kills++; attacker.Owner.Kills++;
Game.world.AddFrameEndTask(w => w.Remove(this)); Game.world.AddFrameEndTask(w => w.Remove(this));
if (Owner == Game.LocalPlayer && !traits.Contains<Building>()) if (Owner == Game.LocalPlayer && !traits.Contains<Building>())
Game.PlaySound("unitlst1.aud", false); Game.PlaySound("unitlst1.aud", false);
if (traits.Contains<Building>()) if (traits.Contains<Building>())
{ {
Game.PlaySound("kaboom22.aud", false); Game.PlaySound("kaboom22.aud", false);
// todo: spawn explosion sprites // todo: spawn explosion sprites
} }
} }
var halfStrength = unitInfo.Strength * Rules.General.ConditionYellow; var halfStrength = unitInfo.Strength * Rules.General.ConditionYellow;
@@ -122,7 +122,7 @@ namespace OpenRa.Game
/* we just went below half health! */ /* we just went below half health! */
foreach (var nd in traits.WithInterface<INotifyDamage>()) foreach (var nd in traits.WithInterface<INotifyDamage>())
nd.Damaged(this, DamageState.Half); nd.Damaged(this, DamageState.Half);
} }
} }
} }
} }

View File

@@ -166,8 +166,8 @@ namespace OpenRa.Game
{ {
var oresw = new Stopwatch(); var oresw = new Stopwatch();
map.GrowOre(SharedRandom); map.GrowOre(SharedRandom);
OreTime = oresw.ElapsedTime(); OreTime = oresw.ElapsedTime();
oreTicks = oreFrequency; oreTicks = oreFrequency;
} }
world.Tick(); world.Tick();
UnitInfluence.Tick(); UnitInfluence.Tick();
@@ -324,7 +324,7 @@ namespace OpenRa.Game
unit = new Actor(name, (1 / 24f * producer.CenterLocation).ToInt2(), player); unit = new Actor(name, (1 / 24f * producer.CenterLocation).ToInt2(), player);
var mobile = unit.traits.Get<Mobile>(); var mobile = unit.traits.Get<Mobile>();
mobile.facing = 128; mobile.facing = 128;
mobile.QueueActivity(new Traits.Activities.Move(unit.Location + new int2(0, 3))); mobile.QueueActivity(new Traits.Activities.Move(unit.Location + new int2(0, 3), 1));
} }
world.Add( unit ); world.Add( unit );

View File

@@ -54,7 +54,7 @@ namespace OpenRa.Game
WorldRenderer.ShowUnitPaths = settings.GetValue("pathdebug", false); WorldRenderer.ShowUnitPaths = settings.GetValue("pathdebug", false);
Game.Replay = settings.GetValue("replay", ""); Game.Replay = settings.GetValue("replay", "");
Game.Initialize(settings.GetValue("map", "scg11eb.ini"), renderer, new int2(ClientSize), Game.Initialize(settings.GetValue("map", "scm12ea.ini"), renderer, new int2(ClientSize),
settings.GetValue("player", 1)); settings.GetValue("player", 1));
SequenceProvider.ForcePrecache(); SequenceProvider.ForcePrecache();

View File

@@ -66,12 +66,12 @@ namespace OpenRa.Game
return ret; return ret;
} }
List<int2> FindUnitPath( int2 unitLocation, Func<int2,double> estimator, UnitMovementType umt ) public List<int2> FindUnitPath( int2 unitLocation, Func<int2,double> estimator, UnitMovementType umt )
{ {
return FindUnitPath( new[] { unitLocation }, estimator, umt ); return FindUnitPath( new[] { unitLocation }, estimator, umt );
} }
List<int2> FindUnitPath(IEnumerable<int2> startLocations, Func<int2, double> estimator, UnitMovementType umt) public List<int2> FindUnitPath( IEnumerable<int2> startLocations, Func<int2, double> estimator, UnitMovementType umt )
{ {
var cellInfo = InitCellInfo(); var cellInfo = InitCellInfo();
var queue = new PriorityQueue<PathDistance>(); var queue = new PriorityQueue<PathDistance>();
@@ -109,6 +109,9 @@ namespace OpenRa.Game
continue; continue;
if( checkForBlock && Game.UnitInfluence.GetUnitAt( newHere ) != null ) if( checkForBlock && Game.UnitInfluence.GetUnitAt( newHere ) != null )
continue; continue;
var est = estimator( newHere );
if( est == double.PositiveInfinity )
continue;
double cellCost = ( ( d.X * d.Y != 0 ) ? 1.414213563 : 1.0 ) * passableCost[(int)umt][ newHere.X, newHere.Y ]; double cellCost = ( ( d.X * d.Y != 0 ) ? 1.414213563 : 1.0 ) * passableCost[(int)umt][ newHere.X, newHere.Y ];
double newCost = cellInfo[ here.X, here.Y ].MinCost + cellCost; double newCost = cellInfo[ here.X, here.Y ].MinCost + cellCost;
@@ -119,7 +122,7 @@ namespace OpenRa.Game
cellInfo[ newHere.X, newHere.Y ].Path = here; cellInfo[ newHere.X, newHere.Y ].Path = here;
cellInfo[ newHere.X, newHere.Y ].MinCost = newCost; cellInfo[ newHere.X, newHere.Y ].MinCost = newCost;
queue.Add( new PathDistance( newCost + estimator( newHere ), newHere ) ); queue.Add( new PathDistance( newCost + est, newHere ) );
} }
} }

View File

@@ -1,7 +1,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text; using OpenRa.Game.Traits.Activities;
namespace OpenRa.Game.Traits namespace OpenRa.Game.Traits
{ {
@@ -14,7 +14,9 @@ namespace OpenRa.Game.Traits
w => w =>
{ {
var harvester = new Actor("harv", self.Location + new int2(1, 2), self.Owner); var harvester = new Actor("harv", self.Location + new int2(1, 2), self.Owner);
harvester.traits.Get<Mobile>().facing = 64; var mobile = harvester.traits.Get<Mobile>();
mobile.facing = 64;
mobile.InternalSetActivity( new Harvest() );
w.Add(harvester); w.Add(harvester);
}); });
} }

View File

@@ -21,8 +21,9 @@ namespace OpenRa.Game.Traits.Activities
harv.gemsCarried = 0; harv.gemsCarried = 0;
harv.oreCarried = 0; harv.oreCarried = 0;
if( NextActivity == null )
NextActivity = new Harvest();
mobile.InternalSetActivity(NextActivity); mobile.InternalSetActivity(NextActivity);
/* todo: return to the ore patch */
return; return;
} }

View File

@@ -12,17 +12,27 @@ namespace OpenRa.Game.Traits.Activities
public void Tick(Actor self, Mobile mobile) public void Tick(Actor self, Mobile mobile)
{ {
if( NextActivity != null )
{
mobile.InternalSetActivity( NextActivity );
NextActivity.Tick( self, mobile );
return;
}
var harv = self.traits.Get<Harvester>(); var harv = self.traits.Get<Harvester>();
var isGem = false; var isGem = false;
if (!harv.IsFull && if (!harv.IsFull &&
Game.map.ContainsResource(self.Location) && Game.map.ContainsResource(self.Location) &&
Game.map.Harvest(self.Location, out isGem)) Game.map.Harvest(self.Location, out isGem))
{ {
var harvestAnim = "harvest" + Util.QuantizeFacing(mobile.facing, 8); var harvestAnim = "harvest" + Util.QuantizeFacing(mobile.facing, 8);
var renderUnit = self.traits.WithInterface<RenderUnit>().First(); /* better have one of these! */ var renderUnit = self.traits.WithInterface<RenderUnit>().First(); /* better have one of these! */
if (harvestAnim != renderUnit.anim.CurrentSequence.Name) if( harvestAnim != renderUnit.anim.CurrentSequence.Name )
renderUnit.PlayCustomAnimation(self, harvestAnim, () => isHarvesting = false); {
isHarvesting = true;
renderUnit.PlayCustomAnimation( self, harvestAnim, () => isHarvesting = false );
}
harv.AcceptResource(isGem); harv.AcceptResource(isGem);
return; return;
} }
@@ -35,23 +45,19 @@ namespace OpenRa.Game.Traits.Activities
PlanMoreHarvesting(self, mobile); PlanMoreHarvesting(self, mobile);
} }
/* maybe this doesnt really belong here, since it's the /* maybe this doesnt really belong here, since it's the
* same as what UnitOrders has to do for an explicit return */ * same as what UnitOrders has to do for an explicit return */
void PlanReturnToBase(Actor self, Mobile mobile) void PlanReturnToBase(Actor self, Mobile mobile)
{ {
/* find a proc */ /* find a proc */
var proc = ChooseReturnLocation(self); var proc = ChooseReturnLocation(self);
if (proc == null) if( proc != null )
{ {
Cancel(self, mobile); /* is this a sane way to cancel? */ mobile.QueueActivity( new Move( proc.Location + new int2( 1, 2 ), 0 ) );
return; mobile.QueueActivity( new Turn( 64 ) );
mobile.QueueActivity( new DeliverOre() );
} }
mobile.QueueActivity(new Move(proc.Location + new int2(1, 2)));
mobile.QueueActivity(new Turn(64));
mobile.QueueActivity(new DeliverOre());
mobile.InternalSetActivity(NextActivity); mobile.InternalSetActivity(NextActivity);
} }
@@ -72,12 +78,24 @@ namespace OpenRa.Game.Traits.Activities
/* find a nearby patch */ /* find a nearby patch */
/* todo: add the queries we need to support this! */ /* todo: add the queries we need to support this! */
var path = Game.PathFinder.FindUnitPath( self.Location, loc =>
{
if( Game.UnitInfluence.GetUnitAt( loc ) != null ) return double.PositiveInfinity;
return Game.map.ContainsResource( loc ) ? 0 : 1;
}, UnitMovementType.Wheel )
.TakeWhile( a => a != self.Location )
.ToList();
if( path.Count != 0 )
{
mobile.QueueActivity( new Move( path ) );
mobile.QueueActivity( new Harvest() );
}
mobile.InternalSetActivity(NextActivity); mobile.InternalSetActivity(NextActivity);
} }
public void Cancel(Actor self, Mobile mobile) public void Cancel(Actor self, Mobile mobile)
{ {
mobile.InternalSetActivity(null); /* bob: anything else required? */
} }
} }
} }

View File

@@ -10,17 +10,19 @@ namespace OpenRa.Game.Traits.Activities
public Activity NextActivity { get; set; } public Activity NextActivity { get; set; }
int2? destination; int2? destination;
int nearEnough;
public List<int2> path; public List<int2> path;
Func<Actor, Mobile, List<int2>> getPath; Func<Actor, Mobile, List<int2>> getPath;
MovePart move; MovePart move;
public Move( int2 destination ) public Move( int2 destination, int nearEnough )
{ {
this.getPath = ( self, mobile ) => Game.PathFinder.FindUnitPath( this.getPath = ( self, mobile ) => Game.PathFinder.FindUnitPath(
self.Location, destination, self.Location, destination,
mobile.GetMovementType() ); mobile.GetMovementType() );
this.destination = destination; this.destination = destination;
this.nearEnough = nearEnough;
} }
public Move( Actor target, int range ) public Move( Actor target, int range )
@@ -29,6 +31,13 @@ namespace OpenRa.Game.Traits.Activities
self.Location, target.Location, self.Location, target.Location,
mobile.GetMovementType(), range ); mobile.GetMovementType(), range );
this.destination = null; this.destination = null;
this.nearEnough = range;
}
public Move( List<int2> path )
{
this.path = path;
this.destination = path[ 0 ];
} }
static bool CanEnterCell( int2 c, Actor self ) static bool CanEnterCell( int2 c, Actor self )
@@ -98,7 +107,7 @@ namespace OpenRa.Game.Traits.Activities
var nextCell = path[ path.Count - 1 ]; var nextCell = path[ path.Count - 1 ];
if( !CanEnterCell( nextCell, self ) ) if( !CanEnterCell( nextCell, self ) )
{ {
if( ( mobile.toCell - destination.Value ).LengthSquared <= 8 ) if( ( mobile.toCell - destination.Value ).LengthSquared <= nearEnough )
{ {
path.Clear(); path.Clear();
return null; return null;
@@ -120,6 +129,11 @@ namespace OpenRa.Game.Traits.Activities
if( path.Count == 0 ) if( path.Count == 0 )
return null; return null;
nextCell = path[ path.Count - 1 ]; nextCell = path[ path.Count - 1 ];
if( !CanEnterCell( nextCell, self ) )
{
path.Clear();
return null;
}
} }
path.RemoveAt( path.Count - 1 ); path.RemoveAt( path.Count - 1 );
return nextCell; return nextCell;

View File

@@ -29,7 +29,7 @@ namespace OpenRa.Game.Traits
&& underCursor.traits.Contains<AcceptsOre>() && !IsEmpty) && underCursor.traits.Contains<AcceptsOre>() && !IsEmpty)
return OpenRa.Game.Order.DeliverOre(self, underCursor); return OpenRa.Game.Order.DeliverOre(self, underCursor);
if (underCursor == null && Game.map.ContainsResource(xy) && !IsFull) if (underCursor == null && Game.map.ContainsResource(xy))
return OpenRa.Game.Order.Harvest(self, xy); return OpenRa.Game.Order.Harvest(self, xy);
return null; return null;

View File

@@ -12,8 +12,9 @@ namespace OpenRa.Game.Traits
{ {
public Actor self; public Actor self;
public int2 fromCell; int2 __fromCell;
public int2 toCell { get { return self.Location; } set { self.Location = value; } } public int2 fromCell { get { return __fromCell; } set { Game.UnitInfluence.Remove( this ); __fromCell = value; Game.UnitInfluence.Add( this ); } }
public int2 toCell { get { return self.Location; } set { Game.UnitInfluence.Remove( this ); self.Location = value; Game.UnitInfluence.Add( this ); } }
public int facing; public int facing;
public int Voice = Game.CosmeticRandom.Next(2); public int Voice = Game.CosmeticRandom.Next(2);

View File

@@ -47,6 +47,14 @@ namespace OpenRa.Game
} }
} }
[System.Diagnostics.Conditional( "SANITY_CHECKS" )]
void SanityCheckAdd( Mobile a )
{
foreach( var c in a.OccupiedCells() )
if( influence[c.X, c.Y] != null )
throw new InvalidOperationException( "UIM: Sanity check failed (Add)" );
}
public Actor GetUnitAt( int2 a ) public Actor GetUnitAt( int2 a )
{ {
return influence[ a.X, a.Y ]; return influence[ a.X, a.Y ];
@@ -54,6 +62,7 @@ namespace OpenRa.Game
public void Add(Mobile a) public void Add(Mobile a)
{ {
SanityCheckAdd(a);
foreach (var c in a.OccupiedCells()) foreach (var c in a.OccupiedCells())
influence[c.X, c.Y] = a.self; influence[c.X, c.Y] = a.self;
} }

View File

@@ -16,7 +16,7 @@ namespace OpenRa.Game
{ {
var mobile = order.Subject.traits.Get<Mobile>(); var mobile = order.Subject.traits.Get<Mobile>();
mobile.Cancel( order.Subject ); mobile.Cancel( order.Subject );
mobile.QueueActivity( new Traits.Activities.Move( order.TargetLocation ) ); mobile.QueueActivity( new Traits.Activities.Move( order.TargetLocation, 8 ) );
var attackBase = order.Subject.traits.WithInterface<AttackBase>().FirstOrDefault(); var attackBase = order.Subject.traits.WithInterface<AttackBase>().FirstOrDefault();
if( attackBase != null ) if( attackBase != null )
@@ -57,7 +57,7 @@ namespace OpenRa.Game
{ {
var mobile = order.Subject.traits.Get<Mobile>(); var mobile = order.Subject.traits.Get<Mobile>();
mobile.Cancel(order.Subject); mobile.Cancel(order.Subject);
mobile.QueueActivity(new Traits.Activities.Move(order.TargetActor.Location + new int2(1, 2))); mobile.QueueActivity(new Traits.Activities.Move(order.TargetActor.Location + new int2(1, 2), 0));
mobile.QueueActivity(new Traits.Activities.Turn(64)); mobile.QueueActivity(new Traits.Activities.Turn(64));
mobile.QueueActivity(new Traits.Activities.DeliverOre()); mobile.QueueActivity(new Traits.Activities.DeliverOre());
break; break;
@@ -66,7 +66,7 @@ namespace OpenRa.Game
{ {
var mobile = order.Subject.traits.Get<Mobile>(); var mobile = order.Subject.traits.Get<Mobile>();
mobile.Cancel(order.Subject); mobile.Cancel(order.Subject);
mobile.QueueActivity(new Traits.Activities.Move(order.TargetLocation)); mobile.QueueActivity(new Traits.Activities.Move(order.TargetLocation, 0));
mobile.QueueActivity(new Traits.Activities.Harvest() ); mobile.QueueActivity(new Traits.Activities.Harvest() );
break; break;
} }