Move refinery and harvester logic to Mods.Common;

Remove unused interface IAcceptOreDockAction
This commit is contained in:
penev92
2015-02-06 13:21:57 +02:00
parent 6714b498af
commit 494b600ed8
16 changed files with 32 additions and 61 deletions

View File

@@ -0,0 +1,80 @@
#region Copyright & License Information
/*
* Copyright 2007-2015 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation. For more information,
* see COPYING.
*/
#endregion
using System.Drawing;
using OpenRA.Activities;
using OpenRA.Mods.Common.Traits;
using OpenRA.Traits;
namespace OpenRA.Mods.Common.Activities
{
public class DeliverResources : Activity
{
const int NextChooseTime = 100;
bool isDocking;
int chosenTicks;
public override Activity Tick(Actor self)
{
if (NextActivity != null)
return NextActivity;
var movement = self.Trait<IMove>();
var harv = self.Trait<Harvester>();
// Find the nearest best refinery if not explicitly ordered to a specific refinery:
if (harv.OwnerLinkedProc == null || !harv.OwnerLinkedProc.IsInWorld)
{
// Maybe we lost the owner-linked refinery:
harv.OwnerLinkedProc = null;
if (self.World.WorldTick - chosenTicks > NextChooseTime)
{
harv.ChooseNewProc(self, null);
chosenTicks = self.World.WorldTick;
}
}
else
{
harv.LinkProc(self, harv.OwnerLinkedProc);
}
if (harv.LinkedProc == null || !harv.LinkedProc.IsInWorld)
harv.ChooseNewProc(self, null);
if (harv.LinkedProc == null) // no procs exist; check again in 1s.
return Util.SequenceActivities(new Wait(25), this);
var proc = harv.LinkedProc;
var iao = proc.Trait<IAcceptResources>();
self.SetTargetLine(Target.FromActor(proc), Color.Green, false);
if (self.Location != proc.Location + iao.DeliveryOffset)
{
var notify = self.TraitsImplementing<INotifyHarvesterAction>();
var next = new DeliverResources();
foreach (var n in notify)
n.MovingToRefinery(self, proc.Location + iao.DeliveryOffset, next);
return Util.SequenceActivities(movement.MoveTo(proc.Location + iao.DeliveryOffset, 0), this);
}
if (!isDocking)
{
isDocking = true;
iao.OnDock(self, this);
}
return Util.SequenceActivities(new Wait(10), this);
}
// Cannot be cancelled
public override void Cancel(Actor self) { }
}
}

View File

@@ -0,0 +1,177 @@
#region Copyright & License Information
/*
* Copyright 2007-2015 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation. For more information,
* see COPYING.
*/
#endregion
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using OpenRA.Activities;
using OpenRA.Mods.Common.Traits;
using OpenRA.Traits;
namespace OpenRA.Mods.Common.Activities
{
public class FindResources : Activity
{
CPos? avoidCell;
public FindResources()
{
}
public FindResources(CPos avoidCell)
{
this.avoidCell = avoidCell;
}
public override Activity Tick(Actor self)
{
if (IsCanceled || NextActivity != null) return NextActivity;
var harv = self.Trait<Harvester>();
if (harv.IsFull)
return Util.SequenceActivities(new DeliverResources(), NextActivity);
var harvInfo = self.Info.Traits.Get<HarvesterInfo>();
var mobile = self.Trait<Mobile>();
var mobileInfo = self.Info.Traits.Get<MobileInfo>();
var resLayer = self.World.WorldActor.Trait<ResourceLayer>();
var territory = self.World.WorldActor.TraitOrDefault<ResourceClaimLayer>();
// Determine where to search from and how far to search:
var searchFromLoc = harv.LastOrderLocation ?? (harv.LastLinkedProc ?? harv.LinkedProc ?? self).Location;
var searchRadius = harv.LastOrderLocation.HasValue ? harvInfo.SearchFromOrderRadius : harvInfo.SearchFromProcRadius;
var searchRadiusSquared = searchRadius * searchRadius;
// Find harvestable resources nearby:
var path = self.World.WorldActor.Trait<PathFinder>().FindPath(
PathSearch.Search(self.World, mobileInfo, self, true)
.WithHeuristic(loc =>
{
// Avoid this cell:
if (avoidCell.HasValue && loc == avoidCell.Value) return 1;
// Don't harvest out of range:
var distSquared = (loc - searchFromLoc).LengthSquared;
if (distSquared > searchRadiusSquared)
return int.MaxValue;
// Get the resource at this location:
var resType = resLayer.GetResource(loc);
if (resType == null) return 1;
// Can the harvester collect this kind of resource?
if (!harvInfo.Resources.Contains(resType.Info.Name)) return 1;
if (territory != null)
{
// Another harvester has claimed this resource:
ResourceClaim claim;
if (territory.IsClaimedByAnyoneElse(self, loc, out claim)) return 1;
}
return 0;
})
.FromPoint(self.Location));
if (path.Count == 0)
{
if (!harv.IsEmpty)
return new DeliverResources();
else
{
// Get out of the way if we are:
harv.UnblockRefinery(self);
var randFrames = 125 + self.World.SharedRandom.Next(-35, 35);
if (NextActivity != null)
return Util.SequenceActivities(NextActivity, new Wait(randFrames), new FindResources());
else
return Util.SequenceActivities(new Wait(randFrames), new FindResources());
}
}
// Attempt to claim a resource as ours:
if (territory != null)
{
if (!territory.ClaimResource(self, path[0]))
return Util.SequenceActivities(new Wait(25), new FindResources());
}
// If not given a direct order, assume ordered to the first resource location we find:
if (harv.LastOrderLocation == null)
harv.LastOrderLocation = path[0];
self.SetTargetLine(Target.FromCell(self.World, path[0]), Color.Red, false);
var notify = self.TraitsImplementing<INotifyHarvesterAction>();
var next = new FindResources();
foreach (var n in notify)
n.MovingToResources(self, path[0], next);
return Util.SequenceActivities(mobile.MoveTo(path[0], 1), new HarvestResource(), new FindResources());
}
public override IEnumerable<Target> GetTargets(Actor self)
{
yield return Target.FromCell(self.World, self.Location);
}
}
public class HarvestResource : Activity
{
public override Activity Tick(Actor self)
{
var territory = self.World.WorldActor.TraitOrDefault<ResourceClaimLayer>();
if (IsCanceled)
{
if (territory != null)
territory.UnclaimByActor(self);
return NextActivity;
}
var harv = self.Trait<Harvester>();
var harvInfo = self.Info.Traits.Get<HarvesterInfo>();
harv.LastHarvestedCell = self.Location;
if (harv.IsFull)
{
if (territory != null)
territory.UnclaimByActor(self);
return NextActivity;
}
// Turn to one of the harvestable facings
if (harvInfo.HarvestFacings != 0)
{
var facing = self.Trait<IFacing>().Facing;
var desired = Util.QuantizeFacing(facing, harvInfo.HarvestFacings) * (256 / harvInfo.HarvestFacings);
if (desired != facing)
return Util.SequenceActivities(new Turn(self, desired), this);
}
var resLayer = self.World.WorldActor.Trait<ResourceLayer>();
var resource = resLayer.Harvest(self.Location);
if (resource == null)
{
if (territory != null)
territory.UnclaimByActor(self);
return NextActivity;
}
harv.AcceptResource(resource);
foreach (var t in self.TraitsImplementing<INotifyHarvesterAction>())
t.Harvested(self, resource);
return Util.SequenceActivities(new Wait(harvInfo.LoadTicksPerBale), this);
}
}
}

View File

@@ -0,0 +1,104 @@
#region Copyright & License Information
/*
* Copyright 2007-2015 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation. For more information,
* see COPYING.
*/
#endregion
using System;
using System.Collections.Generic;
using OpenRA.Activities;
using OpenRA.Mods.Common.Traits;
using OpenRA.Traits;
namespace OpenRA.Mods.Common.Activities
{
public class HarvesterDockSequence : Activity
{
protected enum State { Wait, Turn, Dock, Loop, Undock, Complete }
protected readonly Actor Refinery;
protected readonly Harvester Harv;
protected readonly int DockAngle;
protected readonly bool IsDragRequired;
protected readonly WVec DragOffset;
protected readonly int DragLength;
protected readonly WPos StartDrag;
protected readonly WPos EndDrag;
protected State dockingState;
public HarvesterDockSequence(Actor self, Actor refinery, int dockAngle, bool isDragRequired, WVec dragOffset, int dragLength)
{
dockingState = State.Turn;
Refinery = refinery;
DockAngle = dockAngle;
IsDragRequired = isDragRequired;
DragOffset = dragOffset;
DragLength = dragLength;
Harv = self.Trait<Harvester>();
StartDrag = self.CenterPosition;
EndDrag = refinery.CenterPosition + DragOffset;
}
public override Activity Tick(Actor self)
{
switch (dockingState)
{
case State.Wait:
return this;
case State.Turn:
dockingState = State.Dock;
if (IsDragRequired)
return Util.SequenceActivities(new Turn(self, DockAngle), new Drag(self, StartDrag, EndDrag, DragLength), this);
return Util.SequenceActivities(new Turn(self, DockAngle), this);
case State.Dock:
if (Refinery.IsInWorld && !Refinery.IsDead)
foreach (var nd in Refinery.TraitsImplementing<INotifyDocking>())
nd.Docked(Refinery, self);
return OnStateDock(self);
case State.Loop:
if (!Refinery.IsInWorld || Refinery.IsDead || Harv.TickUnload(self, Refinery))
dockingState = State.Undock;
return this;
case State.Undock:
return OnStateUndock(self);
case State.Complete:
if (Refinery.IsInWorld && !Refinery.IsDead)
foreach (var nd in Refinery.TraitsImplementing<INotifyDocking>())
nd.Undocked(Refinery, self);
Harv.LastLinkedProc = Harv.LinkedProc;
Harv.LinkProc(self, null);
if (IsDragRequired)
return Util.SequenceActivities(new Drag(self, EndDrag, StartDrag, DragLength), NextActivity);
return NextActivity;
}
throw new InvalidOperationException("Invalid harvester dock state");
}
public override void Cancel(Actor self)
{
dockingState = State.Undock;
base.Cancel(self);
}
public override IEnumerable<Target> GetTargets(Actor self)
{
yield return Target.FromActor(Refinery);
}
public virtual Activity OnStateDock(Actor self)
{
throw new NotImplementedException("Base class HarvesterDockSequence does not implement method OnStateDock!");
}
public virtual Activity OnStateUndock(Actor self)
{
throw new NotImplementedException("Base class HarvesterDockSequence does not implement method OnStateUndock!");
}
}
}

View File

@@ -0,0 +1,40 @@
#region Copyright & License Information
/*
* Copyright 2007-2015 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation. For more information,
* see COPYING.
*/
#endregion
using OpenRA.Activities;
using OpenRA.Mods.Common.Traits;
namespace OpenRA.Mods.Common.Activities
{
public class SpriteHarvesterDockSequence : HarvesterDockSequence
{
readonly RenderUnit ru;
public SpriteHarvesterDockSequence(Actor self, Actor refinery, int dockAngle, bool isDragRequired, WVec dragOffset, int dragLength)
: base(self, refinery, dockAngle, isDragRequired, dragOffset, dragLength)
{
ru = self.Trait<RenderUnit>();
}
public override Activity OnStateDock(Actor self)
{
ru.PlayCustomAnimation(self, "dock", () => ru.PlayCustomAnimRepeating(self, "dock-loop"));
dockingState = State.Loop;
return this;
}
public override Activity OnStateUndock(Actor self)
{
ru.PlayCustomAnimBackwards(self, "dock", () => dockingState = State.Complete);
dockingState = State.Wait;
return this;
}
}
}

View File

@@ -85,10 +85,13 @@
<Compile Include="Activities\Air\TakeOff.cs" />
<Compile Include="Activities\Attack.cs" />
<Compile Include="Activities\CaptureActor.cs" />
<Compile Include="Activities\DeliverResources.cs" />
<Compile Include="Activities\Demolish.cs" />
<Compile Include="Activities\Enter.cs" />
<Compile Include="Activities\EnterTransport.cs" />
<Compile Include="Activities\ExternalCaptureActor.cs" />
<Compile Include="Activities\FindResources.cs" />
<Compile Include="Activities\HarvesterDockSequence.cs" />
<Compile Include="Activities\Heal.cs" />
<Compile Include="Activities\Hunt.cs" />
<Compile Include="Activities\Move\AttackMoveActivity.cs" />
@@ -104,6 +107,7 @@
<Compile Include="Activities\RepairBuilding.cs" />
<Compile Include="Activities\Sell.cs" />
<Compile Include="Activities\SimpleTeleport.cs" />
<Compile Include="Activities\SpriteHarvesterDockSequence.cs" />
<Compile Include="Activities\Transform.cs" />
<Compile Include="Activities\Turn.cs" />
<Compile Include="Activities\UnloadCargo.cs" />
@@ -235,6 +239,7 @@
<Compile Include="Traits\Buildings\LineBuildNode.cs" />
<Compile Include="Traits\Buildings\PrimaryBuilding.cs" />
<Compile Include="Traits\Buildings\RallyPoint.cs" />
<Compile Include="Traits\Buildings\Refinery.cs" />
<Compile Include="Traits\Buildings\RepairableBuilding.cs" />
<Compile Include="Traits\Buildings\RepairsUnits.cs" />
<Compile Include="Traits\Buildings\Reservable.cs" />
@@ -265,6 +270,7 @@
<Compile Include="Traits\ExternalCapturable.cs" />
<Compile Include="Traits\ExternalCapturableBar.cs" />
<Compile Include="Traits\ExternalCaptures.cs" />
<Compile Include="Traits\Harvester.cs" />
<Compile Include="Traits\IgnoresCloak.cs" />
<Compile Include="Traits\IgnoresDisguise.cs" />
<Compile Include="Traits\DetectCloaked.cs" />
@@ -310,6 +316,7 @@
<Compile Include="Traits\Player\ConquestVictoryConditions.cs" />
<Compile Include="Traits\Player\EnemyWatcher.cs" />
<Compile Include="Traits\Player\GlobalUpgradeManager.cs" />
<Compile Include="Traits\Player\HarvesterAttackNotifier.cs" />
<Compile Include="Traits\Player\MissionObjectives.cs" />
<Compile Include="Traits\Player\PlaceBeacon.cs" />
<Compile Include="Traits\Player\PlaceBuilding.cs" />
@@ -339,6 +346,7 @@
<Compile Include="Traits\Render\RenderBuildingWarFactory.cs" />
<Compile Include="Traits\Render\RenderEditorOnly.cs" />
<Compile Include="Traits\Render\RenderFlare.cs" />
<Compile Include="Traits\Render\RenderHarvester.cs" />
<Compile Include="Traits\Render\RenderInfantry.cs" />
<Compile Include="Traits\Render\RenderNameTag.cs" />
<Compile Include="Traits\Render\ProductionBar.cs" />

View File

@@ -0,0 +1,170 @@
#region Copyright & License Information
/*
* Copyright 2007-2015 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation. For more information,
* see COPYING.
*/
#endregion
using System.Collections.Generic;
using System.Linq;
using OpenRA.Activities;
using OpenRA.Mods.Common.Activities;
using OpenRA.Mods.Common.Effects;
using OpenRA.Traits;
namespace OpenRA.Mods.Common.Traits
{
public class RefineryInfo : ITraitInfo
{
[Desc("Actual harvester facing when docking, 0-255 counter-clock-wise.")]
public readonly int DockAngle = 0;
[Desc("Docking cell relative to top-left cell.")]
public readonly CVec DockOffset = CVec.Zero;
[Desc("Does the refinery require the harvester to be dragged in?")]
public readonly bool IsDragRequired = false;
[Desc("Vector by which the harvester will be dragged when docking.")]
public readonly WVec DragOffset = WVec.Zero;
[Desc("In how many steps to perform the dragging?")]
public readonly int DragLength = 0;
public readonly bool ShowTicks = true;
public readonly int TickLifetime = 30;
public readonly int TickVelocity = 2;
public readonly int TickRate = 10;
public virtual object Create(ActorInitializer init) { return new Refinery(init.Self, this); }
}
public class Refinery : ITick, IAcceptResources, INotifyKilled, INotifySold, INotifyCapture, INotifyOwnerChanged, IExplodeModifier, ISync
{
readonly Actor self;
readonly RefineryInfo info;
PlayerResources playerResources;
int currentDisplayTick = 0;
int currentDisplayValue = 0;
[Sync] public int Ore = 0;
[Sync] Actor dockedHarv = null;
[Sync] bool preventDock = false;
public bool AllowDocking { get { return !preventDock; } }
public CVec DeliveryOffset { get { return info.DockOffset; } }
public int DeliveryAngle { get { return info.DockAngle; } }
public bool IsDragRequired { get { return info.IsDragRequired; } }
public WVec DragOffset { get { return info.DragOffset; } }
public int DragLength { get { return info.DragLength; } }
public Refinery(Actor self, RefineryInfo info)
{
this.self = self;
this.info = info;
playerResources = self.Owner.PlayerActor.Trait<PlayerResources>();
currentDisplayTick = info.TickRate;
}
public virtual Activity DockSequence(Actor harv, Actor self)
{
return new SpriteHarvesterDockSequence(harv, self, DeliveryAngle, IsDragRequired, DragOffset, DragLength);
}
public IEnumerable<TraitPair<Harvester>> GetLinkedHarvesters()
{
return self.World.ActorsWithTrait<Harvester>()
.Where(a => a.Trait.LinkedProc == self);
}
public bool CanGiveResource(int amount) { return playerResources.CanGiveResources(amount); }
public void GiveResource(int amount)
{
playerResources.GiveResources(amount);
if (info.ShowTicks)
currentDisplayValue += amount;
}
void CancelDock(Actor self)
{
preventDock = true;
// Cancel the dock sequence
if (dockedHarv != null && !dockedHarv.IsDead)
dockedHarv.CancelActivity();
}
public void Tick(Actor self)
{
// Harvester was killed while unloading
if (dockedHarv != null && dockedHarv.IsDead)
{
self.Trait<RenderBuilding>().CancelCustomAnim(self);
dockedHarv = null;
}
if (info.ShowTicks && currentDisplayValue > 0 && --currentDisplayTick <= 0)
{
var temp = currentDisplayValue;
if (self.Owner.IsAlliedWith(self.World.RenderPlayer))
self.World.AddFrameEndTask(w => w.Add(new FloatingText(self.CenterPosition, self.Owner.Color.RGB, FloatingText.FormatCashTick(temp), 30)));
currentDisplayTick = info.TickRate;
currentDisplayValue = 0;
}
}
public void Killed(Actor self, AttackInfo e)
{
CancelDock(self);
foreach (var harv in GetLinkedHarvesters())
harv.Trait.UnlinkProc(harv.Actor, self);
}
public void OnDock(Actor harv, DeliverResources dockOrder)
{
if (!preventDock)
{
harv.QueueActivity(new CallFunc(() => dockedHarv = harv, false));
harv.QueueActivity(DockSequence(harv, self));
harv.QueueActivity(new CallFunc(() => dockedHarv = null, false));
}
harv.QueueActivity(new CallFunc(() => harv.Trait<Harvester>().ContinueHarvesting(harv)));
}
public void OnOwnerChanged(Actor self, Player oldOwner, Player newOwner)
{
// Unlink any harvesters
foreach (var harv in GetLinkedHarvesters())
harv.Trait.UnlinkProc(harv.Actor, self);
playerResources = newOwner.PlayerActor.Trait<PlayerResources>();
}
public void OnCapture(Actor self, Actor captor, Player oldOwner, Player newOwner)
{
// Steal any docked harv too
if (dockedHarv != null)
{
dockedHarv.ChangeOwner(newOwner);
// Relink to this refinery
dockedHarv.Trait<Harvester>().LinkProc(dockedHarv, self);
}
}
public void Selling(Actor self) { CancelDock(self); }
public void Sold(Actor self)
{
foreach (var harv in GetLinkedHarvesters())
harv.Trait.UnlinkProc(harv.Actor, self);
}
public bool ShouldExplode(Actor self) { return Ore > 0; }
}
}

View File

@@ -0,0 +1,476 @@
#region Copyright & License Information
/*
* Copyright 2007-2015 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation. For more information,
* see COPYING.
*/
#endregion
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using OpenRA.Activities;
using OpenRA.Mods.Common.Activities;
using OpenRA.Mods.Common.Orders;
using OpenRA.Traits;
namespace OpenRA.Mods.Common.Traits
{
public class HarvesterInfo : ITraitInfo
{
public readonly string[] DeliveryBuildings = { };
[Desc("How much resources it can carry.")]
public readonly int Capacity = 28;
public readonly int LoadTicksPerBale = 4;
[Desc("How fast it can dump it's carryage.")]
public readonly int UnloadTicksPerBale = 4;
[Desc("How many squares to show the fill level.")]
public readonly int PipCount = 7;
public readonly int HarvestFacings = 0;
[Desc("Which resources it can harvest.")]
public readonly string[] Resources = { };
[Desc("Percentage of maximum speed when fully loaded.")]
public readonly int FullyLoadedSpeed = 85;
[Desc("Initial search radius (in cells) from the refinery that created us.")]
public readonly int SearchFromProcRadius = 24;
[Desc("Search radius (in cells) from the last harvest order location to find more resources.")]
public readonly int SearchFromOrderRadius = 12;
public object Create(ActorInitializer init) { return new Harvester(init.Self, this); }
}
public class Harvester : IIssueOrder, IResolveOrder, IPips,
IExplodeModifier, IOrderVoice, ISpeedModifier, ISync,
INotifyResourceClaimLost, INotifyIdle, INotifyBlockingMove
{
readonly HarvesterInfo info;
Dictionary<ResourceTypeInfo, int> contents = new Dictionary<ResourceTypeInfo, int>();
[Sync] public Actor OwnerLinkedProc = null;
[Sync] public Actor LastLinkedProc = null;
[Sync] public Actor LinkedProc = null;
[Sync] int currentUnloadTicks;
public CPos? LastHarvestedCell = null;
public CPos? LastOrderLocation = null;
[Sync] public int ContentValue { get { return contents.Sum(c => c.Key.ValuePerUnit * c.Value); } }
bool idleSmart = true;
public Harvester(Actor self, HarvesterInfo info)
{
this.info = info;
self.QueueActivity(new CallFunc(() => ChooseNewProc(self, null)));
}
public void SetProcLines(Actor proc)
{
if (proc == null) return;
if (proc.Destroyed) return;
var linkedHarvs = proc.World.ActorsWithTrait<Harvester>()
.Where(a => a.Trait.LinkedProc == proc)
.Select(a => Target.FromActor(a.Actor))
.ToList();
proc.SetTargetLines(linkedHarvs, Color.Gold);
}
public void LinkProc(Actor self, Actor proc)
{
var oldProc = LinkedProc;
LinkedProc = proc;
SetProcLines(oldProc);
SetProcLines(proc);
}
public void UnlinkProc(Actor self, Actor proc)
{
if (LinkedProc == proc)
ChooseNewProc(self, proc);
}
public void ChooseNewProc(Actor self, Actor ignore)
{
LastLinkedProc = null;
LinkProc(self, ClosestProc(self, ignore));
}
public void ContinueHarvesting(Actor self)
{
// Move out of the refinery dock and continue harvesting:
UnblockRefinery(self);
self.QueueActivity(new FindResources());
}
bool IsAcceptableProcType(Actor proc)
{
return info.DeliveryBuildings.Length == 0 ||
info.DeliveryBuildings.Contains(proc.Info.Name);
}
Actor ClosestProc(Actor self, Actor ignore)
{
// Find all refineries and their occupancy count:
var refs = (
from r in self.World.ActorsWithTrait<IAcceptResources>()
where r.Actor != ignore && r.Actor.Owner == self.Owner && IsAcceptableProcType(r.Actor)
let linkedHarvs = self.World.ActorsWithTrait<Harvester>().Where(a => a.Trait.LinkedProc == r.Actor).Count()
select new { Location = r.Actor.Location + r.Trait.DeliveryOffset, Actor = r.Actor, Occupancy = linkedHarvs }).ToDictionary(r => r.Location);
// Start a search from each refinery's delivery location:
var mi = self.Info.Traits.Get<MobileInfo>();
var path = self.World.WorldActor.Trait<PathFinder>().FindPath(
PathSearch.FromPoints(self.World, mi, self, refs.Values.Select(r => r.Location), self.Location, false)
.WithCustomCost((loc) =>
{
if (!refs.ContainsKey(loc)) return 0;
var occupancy = refs[loc].Occupancy;
// 4 harvesters clogs up the refinery's delivery location:
if (occupancy >= 3) return int.MaxValue;
// Prefer refineries with less occupancy (multiplier is to offset distance cost):
return occupancy * 12;
}));
if (path.Count != 0)
return refs[path.Last()].Actor;
return null;
}
public bool IsFull { get { return contents.Values.Sum() == info.Capacity; } }
public bool IsEmpty { get { return contents.Values.Sum() == 0; } }
public int Fullness { get { return contents.Values.Sum() * 100 / info.Capacity; } }
public void AcceptResource(ResourceType type)
{
if (!contents.ContainsKey(type.Info)) contents[type.Info] = 1;
else contents[type.Info]++;
}
public void UnblockRefinery(Actor self)
{
// Check that we're not in a critical location and being useless (refinery drop-off):
var lastproc = LastLinkedProc ?? LinkedProc;
if (lastproc != null && !lastproc.Destroyed)
{
var deliveryLoc = lastproc.Location + lastproc.Trait<IAcceptResources>().DeliveryOffset;
if (self.Location == deliveryLoc)
{
// Get out of the way:
var mobile = self.Trait<Mobile>();
var harv = self.Trait<Harvester>();
var moveTo = harv.LastHarvestedCell ?? (deliveryLoc + new CVec(0, 4));
self.QueueActivity(mobile.MoveTo(moveTo, 1));
self.SetTargetLine(Target.FromCell(self.World, moveTo), Color.Gray, false);
var territory = self.World.WorldActor.TraitOrDefault<ResourceClaimLayer>();
if (territory != null) territory.ClaimResource(self, moveTo);
var notify = self.TraitsImplementing<INotifyHarvesterAction>();
var next = new FindResources();
foreach (var n in notify)
n.MovingToResources(self, moveTo, next);
self.QueueActivity(new FindResources());
return;
}
}
}
public void OnNotifyBlockingMove(Actor self, Actor blocking)
{
// I'm blocking someone else from moving to my location:
var act = self.GetCurrentActivity();
// If I'm just waiting around then get out of the way:
if (act is Wait)
{
self.CancelActivity();
var mobile = self.Trait<Mobile>();
var cell = self.Location;
var moveTo = mobile.NearestMoveableCell(cell, 2, 5);
self.QueueActivity(mobile.MoveTo(moveTo, 0));
self.SetTargetLine(Target.FromCell(self.World, moveTo), Color.Gray, false);
// Find more resources but not at this location:
self.QueueActivity(new FindResources(cell));
}
}
public void TickIdle(Actor self)
{
// Should we be intelligent while idle?
if (!idleSmart) return;
// Are we not empty? Deliver resources:
if (!IsEmpty)
{
self.QueueActivity(new DeliverResources());
return;
}
UnblockRefinery(self);
// Wait for a bit before becoming idle again:
self.QueueActivity(new Wait(10));
}
// Returns true when unloading is complete
public bool TickUnload(Actor self, Actor proc)
{
// Wait until the next bale is ready
if (--currentUnloadTicks > 0)
return false;
if (contents.Keys.Count > 0)
{
var type = contents.First().Key;
var iao = proc.Trait<IAcceptResources>();
if (!iao.CanGiveResource(type.ValuePerUnit))
return false;
iao.GiveResource(type.ValuePerUnit);
if (--contents[type] == 0)
contents.Remove(type);
currentUnloadTicks = info.UnloadTicksPerBale;
}
return contents.Count == 0;
}
public IEnumerable<IOrderTargeter> Orders
{
get
{
yield return new EnterAlliedActorTargeter<IAcceptResources>("Deliver", 5,
proc => IsAcceptableProcType(proc),
proc => !IsEmpty && proc.Trait<IAcceptResources>().AllowDocking);
yield return new HarvestOrderTargeter();
}
}
public Order IssueOrder(Actor self, IOrderTargeter order, Target target, bool queued)
{
if (order.OrderID == "Deliver")
return new Order(order.OrderID, self, queued) { TargetActor = target.Actor };
if (order.OrderID == "Harvest")
return new Order(order.OrderID, self, queued) { TargetLocation = self.World.Map.CellContaining(target.CenterPosition) };
return null;
}
public string VoicePhraseForOrder(Actor self, Order order)
{
return (order.OrderString == "Harvest" || (order.OrderString == "Deliver" && !IsEmpty)) ? "Move" : null;
}
public void ResolveOrder(Actor self, Order order)
{
if (order.OrderString == "Harvest")
{
// NOTE: An explicit harvest order allows the harvester to decide which refinery to deliver to.
LinkProc(self, OwnerLinkedProc = null);
idleSmart = true;
self.CancelActivity();
var mobile = self.Trait<Mobile>();
if (order.TargetLocation != CPos.Zero)
{
var loc = order.TargetLocation;
var territory = self.World.WorldActor.TraitOrDefault<ResourceClaimLayer>();
if (territory != null)
{
// Find the nearest claimable cell to the order location (useful for group-select harvest):
loc = mobile.NearestCell(loc, p => mobile.CanEnterCell(p) && territory.ClaimResource(self, p), 1, 6);
}
else
{
// Find the nearest cell to the order location (useful for group-select harvest):
var taken = new HashSet<CPos>();
loc = mobile.NearestCell(loc, p => mobile.CanEnterCell(p) && taken.Add(p), 1, 6);
}
self.QueueActivity(mobile.MoveTo(loc, 0));
self.SetTargetLine(Target.FromCell(self.World, loc), Color.Red);
var notify = self.TraitsImplementing<INotifyHarvesterAction>();
var next = new FindResources();
foreach (var n in notify)
n.MovingToResources(self, loc, next);
LastOrderLocation = loc;
}
else
{
// A bot order gives us a CPos.Zero TargetLocation, so find some good resources for him:
var loc = FindNextResourceForBot(self);
// No more resources? Oh well.
if (!loc.HasValue)
return;
self.QueueActivity(mobile.MoveTo(loc.Value, 0));
self.SetTargetLine(Target.FromCell(self.World, loc.Value), Color.Red);
LastOrderLocation = loc;
}
// This prevents harvesters returning to an empty patch when the player orders them to a new patch:
LastHarvestedCell = LastOrderLocation;
self.QueueActivity(new FindResources());
}
else if (order.OrderString == "Deliver")
{
// NOTE: An explicit deliver order forces the harvester to always deliver to this refinery.
var iao = order.TargetActor.TraitOrDefault<IAcceptResources>();
if (iao == null || !iao.AllowDocking || !IsAcceptableProcType(order.TargetActor))
return;
if (order.TargetActor != OwnerLinkedProc)
LinkProc(self, OwnerLinkedProc = order.TargetActor);
if (IsEmpty)
return;
idleSmart = true;
self.SetTargetLine(Target.FromOrder(self.World, order), Color.Green);
self.CancelActivity();
self.QueueActivity(new DeliverResources());
var notify = self.TraitsImplementing<INotifyHarvesterAction>();
var next = new DeliverResources();
foreach (var n in notify)
n.MovingToRefinery(self, order.TargetLocation, next);
}
else if (order.OrderString == "Stop" || order.OrderString == "Move")
{
var notify = self.TraitsImplementing<INotifyHarvesterAction>();
foreach (var n in notify)
n.MovementCancelled(self);
// Turn off idle smarts to obey the stop/move:
idleSmart = false;
}
}
static CPos? FindNextResourceForBot(Actor self)
{
// NOTE: This is only used for the AI to find the next available resource to harvest.
var harvInfo = self.Info.Traits.Get<HarvesterInfo>();
var mobileInfo = self.Info.Traits.Get<MobileInfo>();
var resLayer = self.World.WorldActor.Trait<ResourceLayer>();
var territory = self.World.WorldActor.TraitOrDefault<ResourceClaimLayer>();
// Find any harvestable resources:
var path = self.World.WorldActor.Trait<PathFinder>().FindPath(
PathSearch.Search(self.World, mobileInfo, self, true)
.WithHeuristic(loc =>
{
// Get the resource at this location:
var resType = resLayer.GetResource(loc);
if (resType == null) return 1;
// Can the harvester collect this kind of resource?
if (!harvInfo.Resources.Contains(resType.Info.Name)) return 1;
// Another harvester has claimed this resource:
if (territory != null)
{
ResourceClaim claim;
if (territory.IsClaimedByAnyoneElse(self, loc, out claim)) return 1;
}
return 0;
})
.FromPoint(self.Location));
if (path.Count == 0)
return (CPos?)null;
return path[0];
}
public void OnNotifyResourceClaimLost(Actor self, ResourceClaim claim, Actor claimer)
{
if (self == claimer) return;
// Our claim on a resource was stolen, find more unclaimed resources:
self.CancelActivity();
self.QueueActivity(new FindResources());
}
PipType GetPipAt(int i)
{
var n = i * info.Capacity / info.PipCount;
foreach (var rt in contents)
if (n < rt.Value)
return rt.Key.PipColor;
else
n -= rt.Value;
return PipType.Transparent;
}
public IEnumerable<PipType> GetPips(Actor self)
{
var numPips = info.PipCount;
for (var i = 0; i < numPips; i++)
yield return GetPipAt(i);
}
public bool ShouldExplode(Actor self) { return !IsEmpty; }
public int GetSpeedModifier()
{
return 100 - (100 - info.FullyLoadedSpeed) * contents.Values.Sum() / info.Capacity;
}
class HarvestOrderTargeter : IOrderTargeter
{
public string OrderID { get { return "Harvest"; } }
public int OrderPriority { get { return 10; } }
public bool IsQueued { get; protected set; }
public bool CanTarget(Actor self, Target target, List<Actor> othersAtTarget, TargetModifiers modifiers, ref string cursor)
{
if (target.Type != TargetType.Terrain)
return false;
if (modifiers.HasModifier(TargetModifiers.ForceMove))
return false;
var location = self.World.Map.CellContaining(target.CenterPosition);
// Don't leak info about resources under the shroud
if (!self.Owner.Shroud.IsExplored(location))
return false;
var res = self.World.WorldActor.Trait<ResourceLayer>().GetRenderedResource(location);
var info = self.Info.Traits.Get<HarvesterInfo>();
if (res == null || !info.Resources.Contains(res.Info.Name))
return false;
cursor = "harvest";
IsQueued = modifiers.HasModifier(TargetModifiers.ForceQueue);
return true;
}
}
}
}

View File

@@ -0,0 +1,69 @@
#region Copyright & License Information
/*
* Copyright 2007-2015 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation. For more information,
* see COPYING.
*/
#endregion
using System.Drawing;
using OpenRA.Traits;
namespace OpenRA.Mods.Common.Traits
{
[Desc("Plays an audio notification and shows a radar ping when a harvester is attacked.",
"Attach this to the player actor.")]
public class HarvesterAttackNotifierInfo : ITraitInfo
{
[Desc("Minimum duration (in seconds) between notification events.")]
public readonly int NotifyInterval = 30;
public readonly Color RadarPingColor = Color.Red;
[Desc("Length of time (in ticks) to display a location ping in the minimap.")]
public readonly int RadarPingDuration = 10 * 25;
[Desc("The audio notification type to play.")]
public string Notification = "HarvesterAttack";
public object Create(ActorInitializer init) { return new HarvesterAttackNotifier(init.Self, this); }
}
public class HarvesterAttackNotifier : INotifyDamage
{
readonly RadarPings radarPings;
readonly HarvesterAttackNotifierInfo info;
int lastAttackTime;
public HarvesterAttackNotifier(Actor self, HarvesterAttackNotifierInfo info)
{
radarPings = self.World.WorldActor.TraitOrDefault<RadarPings>();
this.info = info;
lastAttackTime = -info.NotifyInterval * 25;
}
public void Damaged(Actor self, AttackInfo e)
{
// only track last hit against our base
if (!self.HasTrait<Harvester>())
return;
// don't track self-damage
if (e.Attacker != null && e.Attacker.Owner == self.Owner)
return;
if (self.World.WorldTick - lastAttackTime > info.NotifyInterval * 25)
{
Sound.PlayNotification(self.World.Map.Rules, self.Owner, "Speech", info.Notification, self.Owner.Country.Race);
if (radarPings != null)
radarPings.Add(() => self.Owner == self.World.LocalPlayer, self.CenterPosition, info.RadarPingColor, info.RadarPingDuration);
}
lastAttackTime = self.World.WorldTick;
}
}
}

View File

@@ -0,0 +1,60 @@
#region Copyright & License Information
/*
* Copyright 2007-2015 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation. For more information,
* see COPYING.
*/
#endregion
using OpenRA.Activities;
using OpenRA.Graphics;
using OpenRA.Traits;
namespace OpenRA.Mods.Common.Traits
{
class RenderHarvesterInfo : RenderUnitInfo, Requires<HarvesterInfo>
{
public readonly string[] ImagesByFullness = { "harv" };
public override object Create(ActorInitializer init) { return new RenderHarvester(init.Self, this); }
}
class RenderHarvester : RenderUnit, INotifyHarvesterAction
{
Harvester harv;
RenderHarvesterInfo info;
public RenderHarvester(Actor self, RenderHarvesterInfo info)
: base(self)
{
this.info = info;
harv = self.Trait<Harvester>();
// HACK: Force images to be loaded up-front
foreach (var image in info.ImagesByFullness)
new Animation(self.World, image);
}
public override void Tick(Actor self)
{
var desiredState = harv.Fullness * (info.ImagesByFullness.Length - 1) / 100;
var desiredImage = info.ImagesByFullness[desiredState];
if (DefaultAnimation.Name != desiredImage)
DefaultAnimation.ChangeImage(desiredImage, "idle");
base.Tick(self);
}
public void Harvested(Actor self, ResourceType resource)
{
if (DefaultAnimation.CurrentSequence.Name != "harvest")
PlayCustomAnim(self, "harvest");
}
public void MovingToResources(Actor self, CPos targetCell, Activity next) { }
public void MovingToRefinery(Actor self, CPos targetCell, Activity next) { }
public void MovementCancelled(Actor self) { }
}
}

View File

@@ -11,6 +11,7 @@
using System.Collections.Generic;
using OpenRA.Activities;
using OpenRA.Graphics;
using OpenRA.Mods.Common.Activities;
using OpenRA.Mods.Common.Graphics;
using OpenRA.Traits;
@@ -61,4 +62,13 @@ namespace OpenRA.Mods.Common.Traits
}
public interface INotifyTransform { void BeforeTransform(Actor self); void OnTransform(Actor self); void AfterTransform(Actor toActor); }
public interface IAcceptResources
{
void OnDock(Actor harv, DeliverResources dockOrder);
void GiveResource(int amount);
bool CanGiveResource(int amount);
CVec DeliveryOffset { get; }
bool AllowDocking { get; }
}
}