From edc4a8e6e7108601281f2e54922a04548322f99a Mon Sep 17 00:00:00 2001 From: Bob Date: Thu, 5 Nov 2009 13:23:23 +1300 Subject: [PATCH] Harvesting works better, and other related stuff. --- OpenRa.Game/Actor.cs | 220 ++++++++++---------- OpenRa.Game/Game.cs | 6 +- OpenRa.Game/MainWindow.cs | 2 +- OpenRa.Game/PathFinder.cs | 9 +- OpenRa.Game/Traits/AcceptsOre.cs | 6 +- OpenRa.Game/Traits/Activities/DeliverOre.cs | 3 +- OpenRa.Game/Traits/Activities/Harvest.cs | 48 +++-- OpenRa.Game/Traits/Activities/Move.cs | 18 +- OpenRa.Game/Traits/Harvester.cs | 2 +- OpenRa.Game/Traits/Mobile.cs | 5 +- OpenRa.Game/UnitInfluenceMap.cs | 9 + OpenRa.Game/UnitOrders.cs | 6 +- 12 files changed, 191 insertions(+), 143 deletions(-) diff --git a/OpenRa.Game/Actor.cs b/OpenRa.Game/Actor.cs index 30de9e587a..9c6890cc29 100755 --- a/OpenRa.Game/Actor.cs +++ b/OpenRa.Game/Actor.cs @@ -1,119 +1,119 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Text; -using IjwFramework.Types; -using OpenRa.FileFormats; -using OpenRa.Game.GameRules; -using OpenRa.Game.Graphics; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using IjwFramework.Types; +using OpenRa.FileFormats; +using OpenRa.Game.GameRules; +using OpenRa.Game.Graphics; using System.Drawing; -using OpenRa.Game.Traits; - -namespace OpenRa.Game -{ - class Actor - { - public readonly TypeDictionary traits = new TypeDictionary(); - public readonly UnitInfo unitInfo; - - public readonly uint ActorID; - public int2 Location; - public Player Owner; +using OpenRa.Game.Traits; + +namespace OpenRa.Game +{ + class Actor + { + public readonly TypeDictionary traits = new TypeDictionary(); + public readonly UnitInfo unitInfo; + + public readonly uint ActorID; + public int2 Location; + public Player Owner; public int Health; - public Actor( string name, int2 location, Player owner ) - { - ActorID = Game.world.NextAID(); - unitInfo = Rules.UnitInfo[ name ]; - Location = location; - CenterLocation = new float2( 12, 12 ) + Game.CellSize * (float2)Location; - Owner = owner; + public Actor( string name, int2 location, Player owner ) + { + ActorID = Game.world.NextAID(); + unitInfo = Rules.UnitInfo[ name ]; + Location = location; + CenterLocation = new float2( 12, 12 ) + Game.CellSize * (float2)Location; + Owner = owner; Health = unitInfo.Strength; /* todo: handle cases where this is not true! */ - - if( unitInfo.Traits != null ) - { - foreach( var traitName in unitInfo.Traits ) - { - var type = typeof( Traits.Mobile ).Assembly.GetType( typeof( Traits.Mobile ).Namespace + "." + traitName, true, false ); - var ctor = type.GetConstructor( new[] { typeof( Actor ) } ); - traits.Add( type, ctor.Invoke( new object[] { this } ) ); - } - } - else - throw new InvalidOperationException( "No Actor traits for " + unitInfo.Name - + "; add Traits= to units.ini for appropriate unit" ); - } - - public Actor( TreeReference tree, TreeCache treeRenderer ) - { - Location = new int2( tree.Location ); - traits.Add( new Traits.Tree( treeRenderer.GetImage( tree.Image ) ) ); - } - - public void Tick() - { - foreach (var tick in traits.WithInterface()) - tick.Tick(this); - } - - public float2 CenterLocation; - public float2 SelectedSize { get { return Render().First().First.size; } } - - public IEnumerable> Render() - { - return traits.WithInterface().SelectMany( x => x.Render( this ) ); - } - - public Order Order( int2 xy, bool lmb ) - { - if (Owner != Game.LocalPlayer) - return null; - - var underCursor = Game.UnitInfluence.GetUnitAt( xy ) ?? Game.BuildingInfluence.GetBuildingAt( xy ); - - return traits.WithInterface() - .Select( x => x.Order( this, xy, lmb, underCursor ) ) - .FirstOrDefault( x => x != null ); - } - - public RectangleF Bounds - { - get - { - var size = SelectedSize; - var loc = CenterLocation - 0.5f * size; - return new RectangleF(loc.X, loc.Y, size.X, size.Y); - } - } - - public bool IsDead { get { return Health <= 0; } } - - public void InflictDamage(Actor attacker, Bullet inflictor, int damage) - { - /* todo: auto-retaliate, etc */ - /* todo: death sequence for infantry based on inflictor */ - /* 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! */ - - Health -= damage; - if (Health <= 0) - { - Health = 0; - if (attacker.Owner != null) - attacker.Owner.Kills++; - - Game.world.AddFrameEndTask(w => w.Remove(this)); - - if (Owner == Game.LocalPlayer && !traits.Contains()) + + if( unitInfo.Traits != null ) + { + foreach( var traitName in unitInfo.Traits ) + { + var type = typeof( Traits.Mobile ).Assembly.GetType( typeof( Traits.Mobile ).Namespace + "." + traitName, true, false ); + var ctor = type.GetConstructor( new[] { typeof( Actor ) } ); + traits.Add( type, ctor.Invoke( new object[] { this } ) ); + } + } + else + throw new InvalidOperationException( "No Actor traits for " + unitInfo.Name + + "; add Traits= to units.ini for appropriate unit" ); + } + + public Actor( TreeReference tree, TreeCache treeRenderer ) + { + Location = new int2( tree.Location ); + traits.Add( new Traits.Tree( treeRenderer.GetImage( tree.Image ) ) ); + } + + public void Tick() + { + foreach (var tick in traits.WithInterface()) + tick.Tick(this); + } + + public float2 CenterLocation; + public float2 SelectedSize { get { return Render().First().First.size; } } + + public IEnumerable> Render() + { + return traits.WithInterface().SelectMany( x => x.Render( this ) ); + } + + public Order Order( int2 xy, bool lmb ) + { + if (Owner != Game.LocalPlayer) + return null; + + var underCursor = Game.UnitInfluence.GetUnitAt( xy ) ?? Game.BuildingInfluence.GetBuildingAt( xy ); + + return traits.WithInterface() + .Select( x => x.Order( this, xy, lmb, underCursor ) ) + .FirstOrDefault( x => x != null ); + } + + public RectangleF Bounds + { + get + { + var size = SelectedSize; + var loc = CenterLocation - 0.5f * size; + return new RectangleF(loc.X, loc.Y, size.X, size.Y); + } + } + + public bool IsDead { get { return Health <= 0; } } + + public void InflictDamage(Actor attacker, Bullet inflictor, int damage) + { + /* todo: auto-retaliate, etc */ + /* todo: death sequence for infantry based on inflictor */ + /* 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! */ + + Health -= damage; + if (Health <= 0) + { + Health = 0; + if (attacker.Owner != null) + attacker.Owner.Kills++; + + Game.world.AddFrameEndTask(w => w.Remove(this)); + + if (Owner == Game.LocalPlayer && !traits.Contains()) Game.PlaySound("unitlst1.aud", false); if (traits.Contains()) { Game.PlaySound("kaboom22.aud", false); // todo: spawn explosion sprites - } + } } var halfStrength = unitInfo.Strength * Rules.General.ConditionYellow; @@ -122,7 +122,7 @@ namespace OpenRa.Game /* we just went below half health! */ foreach (var nd in traits.WithInterface()) nd.Damaged(this, DamageState.Half); - } - } - } -} + } + } + } +} diff --git a/OpenRa.Game/Game.cs b/OpenRa.Game/Game.cs index 5a96292f88..dd2e713ecb 100644 --- a/OpenRa.Game/Game.cs +++ b/OpenRa.Game/Game.cs @@ -166,8 +166,8 @@ namespace OpenRa.Game { var oresw = new Stopwatch(); map.GrowOre(SharedRandom); - OreTime = oresw.ElapsedTime(); - oreTicks = oreFrequency; + OreTime = oresw.ElapsedTime(); + oreTicks = oreFrequency; } world.Tick(); UnitInfluence.Tick(); @@ -324,7 +324,7 @@ namespace OpenRa.Game unit = new Actor(name, (1 / 24f * producer.CenterLocation).ToInt2(), player); var mobile = unit.traits.Get(); 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 ); diff --git a/OpenRa.Game/MainWindow.cs b/OpenRa.Game/MainWindow.cs index 1de646252b..8dabc19aca 100755 --- a/OpenRa.Game/MainWindow.cs +++ b/OpenRa.Game/MainWindow.cs @@ -54,7 +54,7 @@ namespace OpenRa.Game WorldRenderer.ShowUnitPaths = settings.GetValue("pathdebug", false); 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)); SequenceProvider.ForcePrecache(); diff --git a/OpenRa.Game/PathFinder.cs b/OpenRa.Game/PathFinder.cs index c2d443bffc..5e9c85dd32 100644 --- a/OpenRa.Game/PathFinder.cs +++ b/OpenRa.Game/PathFinder.cs @@ -66,12 +66,12 @@ namespace OpenRa.Game return ret; } - List FindUnitPath( int2 unitLocation, Func estimator, UnitMovementType umt ) + public List FindUnitPath( int2 unitLocation, Func estimator, UnitMovementType umt ) { return FindUnitPath( new[] { unitLocation }, estimator, umt ); } - List FindUnitPath(IEnumerable startLocations, Func estimator, UnitMovementType umt) + public List FindUnitPath( IEnumerable startLocations, Func estimator, UnitMovementType umt ) { var cellInfo = InitCellInfo(); var queue = new PriorityQueue(); @@ -109,6 +109,9 @@ namespace OpenRa.Game continue; if( checkForBlock && Game.UnitInfluence.GetUnitAt( newHere ) != null ) 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 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 ].MinCost = newCost; - queue.Add( new PathDistance( newCost + estimator( newHere ), newHere ) ); + queue.Add( new PathDistance( newCost + est, newHere ) ); } } diff --git a/OpenRa.Game/Traits/AcceptsOre.cs b/OpenRa.Game/Traits/AcceptsOre.cs index 0ee0922f2d..596adff1cb 100644 --- a/OpenRa.Game/Traits/AcceptsOre.cs +++ b/OpenRa.Game/Traits/AcceptsOre.cs @@ -1,7 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Text; +using OpenRa.Game.Traits.Activities; namespace OpenRa.Game.Traits { @@ -14,7 +14,9 @@ namespace OpenRa.Game.Traits w => { var harvester = new Actor("harv", self.Location + new int2(1, 2), self.Owner); - harvester.traits.Get().facing = 64; + var mobile = harvester.traits.Get(); + mobile.facing = 64; + mobile.InternalSetActivity( new Harvest() ); w.Add(harvester); }); } diff --git a/OpenRa.Game/Traits/Activities/DeliverOre.cs b/OpenRa.Game/Traits/Activities/DeliverOre.cs index 7d5e2d0e5f..bf214b8432 100644 --- a/OpenRa.Game/Traits/Activities/DeliverOre.cs +++ b/OpenRa.Game/Traits/Activities/DeliverOre.cs @@ -21,8 +21,9 @@ namespace OpenRa.Game.Traits.Activities harv.gemsCarried = 0; harv.oreCarried = 0; + if( NextActivity == null ) + NextActivity = new Harvest(); mobile.InternalSetActivity(NextActivity); - /* todo: return to the ore patch */ return; } diff --git a/OpenRa.Game/Traits/Activities/Harvest.cs b/OpenRa.Game/Traits/Activities/Harvest.cs index a2d9e8d8ed..a279c0a27b 100644 --- a/OpenRa.Game/Traits/Activities/Harvest.cs +++ b/OpenRa.Game/Traits/Activities/Harvest.cs @@ -12,17 +12,27 @@ namespace OpenRa.Game.Traits.Activities public void Tick(Actor self, Mobile mobile) { + if( NextActivity != null ) + { + mobile.InternalSetActivity( NextActivity ); + NextActivity.Tick( self, mobile ); + return; + } + var harv = self.traits.Get(); var isGem = false; - if (!harv.IsFull && - Game.map.ContainsResource(self.Location) && + if (!harv.IsFull && + Game.map.ContainsResource(self.Location) && Game.map.Harvest(self.Location, out isGem)) { var harvestAnim = "harvest" + Util.QuantizeFacing(mobile.facing, 8); var renderUnit = self.traits.WithInterface().First(); /* better have one of these! */ - if (harvestAnim != renderUnit.anim.CurrentSequence.Name) - renderUnit.PlayCustomAnimation(self, harvestAnim, () => isHarvesting = false); + if( harvestAnim != renderUnit.anim.CurrentSequence.Name ) + { + isHarvesting = true; + renderUnit.PlayCustomAnimation( self, harvestAnim, () => isHarvesting = false ); + } harv.AcceptResource(isGem); return; } @@ -35,23 +45,19 @@ namespace OpenRa.Game.Traits.Activities 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 */ - void PlanReturnToBase(Actor self, Mobile mobile) + void PlanReturnToBase(Actor self, Mobile mobile) { /* find a proc */ var proc = ChooseReturnLocation(self); - if (proc == null) + if( proc != null ) { - Cancel(self, mobile); /* is this a sane way to cancel? */ - return; + mobile.QueueActivity( new Move( proc.Location + new int2( 1, 2 ), 0 ) ); + 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); } @@ -72,12 +78,24 @@ namespace OpenRa.Game.Traits.Activities /* find a nearby patch */ /* 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); } public void Cancel(Actor self, Mobile mobile) { - mobile.InternalSetActivity(null); /* bob: anything else required? */ } } } diff --git a/OpenRa.Game/Traits/Activities/Move.cs b/OpenRa.Game/Traits/Activities/Move.cs index 8a0b35c5ca..fd70f536e5 100755 --- a/OpenRa.Game/Traits/Activities/Move.cs +++ b/OpenRa.Game/Traits/Activities/Move.cs @@ -10,17 +10,19 @@ namespace OpenRa.Game.Traits.Activities public Activity NextActivity { get; set; } int2? destination; + int nearEnough; public List path; Func> getPath; MovePart move; - public Move( int2 destination ) + public Move( int2 destination, int nearEnough ) { this.getPath = ( self, mobile ) => Game.PathFinder.FindUnitPath( self.Location, destination, mobile.GetMovementType() ); this.destination = destination; + this.nearEnough = nearEnough; } public Move( Actor target, int range ) @@ -29,6 +31,13 @@ namespace OpenRa.Game.Traits.Activities self.Location, target.Location, mobile.GetMovementType(), range ); this.destination = null; + this.nearEnough = range; + } + + public Move( List path ) + { + this.path = path; + this.destination = path[ 0 ]; } static bool CanEnterCell( int2 c, Actor self ) @@ -98,7 +107,7 @@ namespace OpenRa.Game.Traits.Activities var nextCell = path[ path.Count - 1 ]; if( !CanEnterCell( nextCell, self ) ) { - if( ( mobile.toCell - destination.Value ).LengthSquared <= 8 ) + if( ( mobile.toCell - destination.Value ).LengthSquared <= nearEnough ) { path.Clear(); return null; @@ -120,6 +129,11 @@ namespace OpenRa.Game.Traits.Activities if( path.Count == 0 ) return null; nextCell = path[ path.Count - 1 ]; + if( !CanEnterCell( nextCell, self ) ) + { + path.Clear(); + return null; + } } path.RemoveAt( path.Count - 1 ); return nextCell; diff --git a/OpenRa.Game/Traits/Harvester.cs b/OpenRa.Game/Traits/Harvester.cs index 5681d1ff06..6adc363c83 100644 --- a/OpenRa.Game/Traits/Harvester.cs +++ b/OpenRa.Game/Traits/Harvester.cs @@ -29,7 +29,7 @@ namespace OpenRa.Game.Traits && underCursor.traits.Contains() && !IsEmpty) 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 null; diff --git a/OpenRa.Game/Traits/Mobile.cs b/OpenRa.Game/Traits/Mobile.cs index 8f8b08fce9..3211383b38 100644 --- a/OpenRa.Game/Traits/Mobile.cs +++ b/OpenRa.Game/Traits/Mobile.cs @@ -12,8 +12,9 @@ namespace OpenRa.Game.Traits { public Actor self; - public int2 fromCell; - public int2 toCell { get { return self.Location; } set { self.Location = value; } } + int2 __fromCell; + 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 Voice = Game.CosmeticRandom.Next(2); diff --git a/OpenRa.Game/UnitInfluenceMap.cs b/OpenRa.Game/UnitInfluenceMap.cs index 86788ec8d1..14bd9933a3 100644 --- a/OpenRa.Game/UnitInfluenceMap.cs +++ b/OpenRa.Game/UnitInfluenceMap.cs @@ -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 ) { return influence[ a.X, a.Y ]; @@ -54,6 +62,7 @@ namespace OpenRa.Game public void Add(Mobile a) { + SanityCheckAdd(a); foreach (var c in a.OccupiedCells()) influence[c.X, c.Y] = a.self; } diff --git a/OpenRa.Game/UnitOrders.cs b/OpenRa.Game/UnitOrders.cs index 953d6ff5cb..86b1434df0 100755 --- a/OpenRa.Game/UnitOrders.cs +++ b/OpenRa.Game/UnitOrders.cs @@ -16,7 +16,7 @@ namespace OpenRa.Game { var mobile = order.Subject.traits.Get(); 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().FirstOrDefault(); if( attackBase != null ) @@ -57,7 +57,7 @@ namespace OpenRa.Game { var mobile = order.Subject.traits.Get(); 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.DeliverOre()); break; @@ -66,7 +66,7 @@ namespace OpenRa.Game { var mobile = order.Subject.traits.Get(); 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() ); break; }