diff --git a/OpenRA.Game/Map/Map.cs b/OpenRA.Game/Map/Map.cs index 54586196eb..fc479373e5 100644 --- a/OpenRA.Game/Map/Map.cs +++ b/OpenRA.Game/Map/Map.cs @@ -804,6 +804,19 @@ namespace OpenRA return new MPos(x, y).ToCPos(this); } + public CPos ChooseClosestEdgeCell(CPos pos) + { + var mpos = pos.ToMPos(this); + + var horizontalBound = ((mpos.U - Bounds.Left) < Bounds.Width / 2) ? Bounds.Left : Bounds.Right; + var verticalBound = ((mpos.V - Bounds.Top) < Bounds.Height / 2) ? Bounds.Top : Bounds.Bottom; + + var distX = Math.Abs(horizontalBound - mpos.U); + var distY = Math.Abs(verticalBound - mpos.V); + + return distX < distY ? new MPos(horizontalBound, mpos.V).ToCPos(this) : new MPos(mpos.U, verticalBound).ToCPos(this); + } + public CPos ChooseRandomEdgeCell(MersenneTwister rand) { var isX = rand.Next(2) == 0; diff --git a/OpenRA.Mods.Common/Traits/Buildings/FreeActor.cs b/OpenRA.Mods.Common/Traits/Buildings/FreeActor.cs index 8364dc2961..1d98270c79 100644 --- a/OpenRA.Mods.Common/Traits/Buildings/FreeActor.cs +++ b/OpenRA.Mods.Common/Traits/Buildings/FreeActor.cs @@ -19,16 +19,19 @@ namespace OpenRA.Mods.Common.Traits public class FreeActorInfo : ITraitInfo { [ActorReference] - [Desc("Name of actor (use HARV if this trait is for refineries)")] + [Desc("Name of the actor.")] public readonly string Actor = null; + [Desc("What the unit should start doing. Warning: If this is not a harvester", "it will break if you use FindResources.")] public readonly string InitialActivity = null; - [Desc("Offset relative to structure-center in 2D (e.g. 1, 2)")] + + [Desc("Offset relative to the top-left cell of the building.")] public readonly CVec SpawnOffset = CVec.Zero; + [Desc("Which direction the unit should face.")] public readonly int Facing = 0; - public object Create(ActorInitializer init) { return new FreeActor(init, this); } + public virtual object Create(ActorInitializer init) { return new FreeActor(init, this); } } public class FreeActor diff --git a/OpenRA.Mods.D2k/Activities/CarryUnit.cs b/OpenRA.Mods.D2k/Activities/CarryUnit.cs deleted file mode 100644 index 1552ab5437..0000000000 --- a/OpenRA.Mods.D2k/Activities/CarryUnit.cs +++ /dev/null @@ -1,196 +0,0 @@ -#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.Activities; -using OpenRA.Mods.Common.Traits; -using OpenRA.Mods.D2k.Traits; -using OpenRA.Mods.RA.Activities; -using OpenRA.Traits; - -namespace OpenRA.Mods.D2k.Activities -{ - public class CarryUnit : Activity - { - readonly Actor self; - readonly Actor carryable; - readonly IMove movement; - readonly Carryable c; - readonly AutoCarryall aca; - readonly Helicopter helicopter; - readonly IPositionable positionable; - readonly IFacing cFacing; // Carryable facing - readonly IFacing sFacing; // Self facing - - enum State { Intercept, LockCarryable, MoveToCarryable, Turn, Pickup, Transport, Land, Release, Takeoff, Done } - - State state; - - public CarryUnit(Actor self, Actor carryable) - { - this.self = self; - this.carryable = carryable; - movement = self.Trait(); - c = carryable.Trait(); - aca = self.Trait(); - helicopter = self.Trait(); - positionable = carryable.Trait(); - cFacing = carryable.Trait(); - sFacing = self.Trait(); - - state = State.Intercept; - } - - // Find a suitable location to drop our carryable - CPos GetLocationToDrop(CPos requestedPosition) - { - if (positionable.CanEnterCell(requestedPosition)) - return requestedPosition; - - var candidateCells = Util.AdjacentCells(self.World, Target.FromCell(self.World, requestedPosition)); - - // TODO: This will behave badly if there is no suitable drop point nearby - do - { - foreach (var c in candidateCells) - if (positionable.CanEnterCell(c)) - return c; - - // Expanding dropable cells search area - // TODO: This also includes all of the cells we have just checked - candidateCells = Util.ExpandFootprint(candidateCells, true); - } while (true); - } - - // Check if we can drop the unit at our current location. - bool CanDropHere() - { - return positionable.CanEnterCell(self.Location); - } - - public override Activity Tick(Actor self) - { - if (carryable.IsDead) - { - aca.UnreserveCarryable(); - return NextActivity; - } - - switch (state) - { - case State.Intercept: // Move towards our carryable - - state = State.LockCarryable; - return Util.SequenceActivities(movement.MoveWithinRange(Target.FromActor(carryable), WRange.FromCells(4)), this); - - case State.LockCarryable: - // Last check - if (c.StandbyForPickup(self)) - { - state = State.MoveToCarryable; - return this; - } - else - { - // We got cancelled - aca.UnreserveCarryable(); - return NextActivity; - } - - case State.MoveToCarryable: // We arrived, move on top - - if (self.Location == carryable.Location) - { - state = State.Turn; - return this; - } - else - return Util.SequenceActivities(movement.MoveTo(carryable.Location, 0), this); - - case State.Turn: // Align facing and Land - - if (sFacing.Facing != cFacing.Facing) - return Util.SequenceActivities(new Turn(self, cFacing.Facing), this); - else - { - state = State.Pickup; - return Util.SequenceActivities(new HeliLand(false), new Wait(10), this); - } - - case State.Pickup: - - // Remove our carryable from world - self.World.AddFrameEndTask(w => carryable.World.Remove(carryable)); - - aca.AttachCarryable(carryable); - state = State.Transport; - return this; - - case State.Transport: - - // Move self to destination - var targetl = GetLocationToDrop(c.Destination); - - state = State.Land; - return Util.SequenceActivities(movement.MoveTo(targetl, 0), this); - - case State.Land: - - if (!CanDropHere()) - { - state = State.Transport; - return this; - } - - if (HeliFly.AdjustAltitude(self, helicopter, helicopter.Info.LandAltitude)) - return this; - else - { - state = State.Release; - return Util.SequenceActivities(new Wait(15), this); - } - - case State.Release: - - if (!CanDropHere()) - { - state = State.Transport; - return this; - } - - positionable.SetPosition(carryable, self.Location, SubCell.FullCell); - - cFacing.Facing = sFacing.Facing; - - // Put back into world - self.World.AddFrameEndTask(w => carryable.World.Add(carryable)); - - // Unlock carryable - aca.CarryableReleased(); - c.Dropped(); - - state = State.Done; - return Util.SequenceActivities(new Wait(10), this); - - case State.Done: - - self.Trait().UnreserveCarryable(); - return NextActivity; - } - - return NextActivity; - } - - public override void Cancel(Actor self) - { - // TODO: Drop the unit at the nearest available cell - } - } -} diff --git a/OpenRA.Mods.D2k/Activities/DeliverUnit.cs b/OpenRA.Mods.D2k/Activities/DeliverUnit.cs new file mode 100644 index 0000000000..0b3fe70817 --- /dev/null +++ b/OpenRA.Mods.D2k/Activities/DeliverUnit.cs @@ -0,0 +1,140 @@ +#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.Activities; +using OpenRA.Mods.Common.Traits; +using OpenRA.Mods.D2k.Traits; +using OpenRA.Traits; + +namespace OpenRA.Mods.D2k.Activities +{ + public class DeliverUnit : Activity + { + readonly Actor self; + readonly Actor cargo; + readonly IMove movement; + readonly Carryable carryable; + readonly Carryall carryall; + readonly Helicopter helicopter; + readonly IPositionable positionable; + readonly IFacing cargoFacing; + readonly IFacing selfFacing; + + enum State { Transport, Land, Release, Done } + + State state; + + public DeliverUnit(Actor self) + { + carryall = self.Trait(); + this.self = self; + cargo = carryall.Carrying; + movement = self.Trait(); + carryable = cargo.Trait(); + helicopter = self.Trait(); + positionable = cargo.Trait(); + cargoFacing = cargo.Trait(); + selfFacing = self.Trait(); + state = State.Transport; + } + + // Find a suitable location to drop our carryable + CPos GetLocationToDrop(CPos requestedPosition) + { + if (positionable.CanEnterCell(requestedPosition)) + return requestedPosition; + + var candidateCells = Util.AdjacentCells(self.World, Target.FromCell(self.World, requestedPosition)); + + // TODO: This will behave badly if there is no suitable drop point nearby + do + { + foreach (var c in candidateCells) + if (positionable.CanEnterCell(c)) + return c; + + // Expanding dropable cells search area + // TODO: This also includes all of the cells we have just checked + candidateCells = Util.ExpandFootprint(candidateCells, true); + } while (true); + } + + // Check if we can drop the unit at our current location. + bool CanDropHere() + { + return positionable.CanEnterCell(self.Location); + } + + public override Activity Tick(Actor self) + { + if (cargo.IsDead || !carryall.IsBusy) + { + carryall.UnreserveCarryable(); + return NextActivity; + } + + switch (state) + { + case State.Transport: + var targetl = GetLocationToDrop(carryable.Destination); + state = State.Land; + return Util.SequenceActivities(movement.MoveTo(targetl, 0), this); + + case State.Land: + if (!CanDropHere()) + { + state = State.Transport; + return this; + } + + if (HeliFly.AdjustAltitude(self, helicopter, helicopter.Info.LandAltitude)) + return this; + state = State.Release; + return Util.SequenceActivities(new Wait(15), this); + + case State.Release: + if (!CanDropHere()) + { + state = State.Transport; + return this; + } + + Release(); + state = State.Done; + return Util.SequenceActivities(new Wait(10), this); + + case State.Done: + self.Trait().UnreserveCarryable(); + return NextActivity; + } + + return NextActivity; + } + + void Release() + { + positionable.SetPosition(cargo, self.Location, SubCell.FullCell); + cargoFacing.Facing = selfFacing.Facing; + + // Put back into world + self.World.AddFrameEndTask(w => cargo.World.Add(cargo)); + + // Unlock carryable + carryall.CarryableReleased(); + carryable.Dropped(); + } + + public override void Cancel(Actor self) + { + // TODO: Drop the unit at the nearest available cell + } + } +} diff --git a/OpenRA.Mods.D2k/Activities/PickupUnit.cs b/OpenRA.Mods.D2k/Activities/PickupUnit.cs new file mode 100644 index 0000000000..e7ac69f65a --- /dev/null +++ b/OpenRA.Mods.D2k/Activities/PickupUnit.cs @@ -0,0 +1,106 @@ +#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.Activities; +using OpenRA.Mods.Common.Traits; +using OpenRA.Mods.D2k.Traits; +using OpenRA.Traits; + +namespace OpenRA.Mods.D2k.Activities +{ + public class PickupUnit : Activity + { + readonly Actor cargo; + readonly IMove movement; + readonly Carryable carryable; + readonly Carryall carryall; + readonly Helicopter helicopter; + readonly IFacing cargoFacing; + readonly IFacing selfFacing; + + enum State { Intercept, LockCarryable, MoveToCarryable, Turn, Pickup, TakeOff } + + State state; + + public PickupUnit(Actor self, Actor cargo) + { + this.cargo = cargo; + carryable = cargo.Trait(); + cargoFacing = cargo.Trait(); + movement = self.Trait(); + carryall = self.Trait(); + helicopter = self.Trait(); + selfFacing = self.Trait(); + state = State.Intercept; + } + + public override Activity Tick(Actor self) + { + if (cargo.IsDead || !carryall.IsBusy) + { + carryall.UnreserveCarryable(); + return NextActivity; + } + + switch (state) + { + case State.Intercept: + state = State.LockCarryable; + return Util.SequenceActivities(movement.MoveWithinRange(Target.FromActor(cargo), WRange.FromCells(4)), this); + + case State.LockCarryable: + // Last check + if (carryable.StandbyForPickup(self)) + { + state = State.MoveToCarryable; + return this; + } + + // We got cancelled + carryall.UnreserveCarryable(); + return NextActivity; + + case State.MoveToCarryable: // We arrived, move on top + if (self.Location == cargo.Location) + { + state = State.Turn; + return this; + } + + return Util.SequenceActivities(movement.MoveTo(cargo.Location, 0), this); + + case State.Turn: // Align facing and Land + if (selfFacing.Facing != cargoFacing.Facing) + return Util.SequenceActivities(new Turn(self, cargoFacing.Facing), this); + state = State.Pickup; + return Util.SequenceActivities(new HeliLand(false), new Wait(10), this); + + case State.Pickup: + // Remove our carryable from world + self.World.AddFrameEndTask(w => cargo.World.Remove(cargo)); + carryall.AttachCarryable(cargo); + state = State.TakeOff; + return this; + case State.TakeOff: + if (HeliFly.AdjustAltitude(self, helicopter, helicopter.Info.CruiseAltitude)) + return this; + return NextActivity; + } + + return NextActivity; + } + + public override void Cancel(Actor self) + { + // TODO: Drop the unit at the nearest available cell + } + } +} diff --git a/OpenRA.Mods.D2k/Activities/SwallowActor.cs b/OpenRA.Mods.D2k/Activities/SwallowActor.cs index b9ee1a0a32..64371e4803 100644 --- a/OpenRA.Mods.D2k/Activities/SwallowActor.cs +++ b/OpenRA.Mods.D2k/Activities/SwallowActor.cs @@ -70,7 +70,23 @@ namespace OpenRA.Mods.D2k.Activities sandworm.IsAttacking = true; foreach (var actor in lunch) - actor.World.AddFrameEndTask(_ => actor.Destroy()); + { + var actor1 = actor; // loop variable in closure hazard + + actor.World.AddFrameEndTask(_ => + { + actor1.Destroy(); + + // Harvester insurance + if (!actor1.HasTrait()) + return; + + var insurance = actor1.Owner.PlayerActor.TraitOrDefault(); + + if (insurance != null) + actor1.World.AddFrameEndTask(__ => insurance.TryActivate()); + }); + } positionable.SetPosition(worm, targetLocation); foreach (var notify in worm.TraitsImplementing()) diff --git a/OpenRA.Mods.D2k/OpenRA.Mods.D2k.csproj b/OpenRA.Mods.D2k/OpenRA.Mods.D2k.csproj index 6d4e14284f..5981c7e3c2 100644 --- a/OpenRA.Mods.D2k/OpenRA.Mods.D2k.csproj +++ b/OpenRA.Mods.D2k/OpenRA.Mods.D2k.csproj @@ -68,14 +68,18 @@ - + + - + + + + diff --git a/OpenRA.Mods.D2k/Traits/Buildings/FreeActorWithDelivery.cs b/OpenRA.Mods.D2k/Traits/Buildings/FreeActorWithDelivery.cs new file mode 100644 index 0000000000..fd9f57ee65 --- /dev/null +++ b/OpenRA.Mods.D2k/Traits/Buildings/FreeActorWithDelivery.cs @@ -0,0 +1,110 @@ +#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.IO; +using OpenRA.Activities; +using OpenRA.Mods.Common.Activities; +using OpenRA.Mods.Common.Traits; +using OpenRA.Mods.D2k.Activities; +using OpenRA.Primitives; +using OpenRA.Traits; + +namespace OpenRA.Mods.D2k.Traits +{ + [Desc("Player receives a unit for free once the building is placed.", + "If you want more than one unit to be delivered, copy this section and assign IDs like FreeActorWithDelivery@2, ...")] + public class FreeActorWithDeliveryInfo : FreeActorInfo + { + [ActorReference] + [Desc("Name of the delivering actor. This actor must have the `Carryall` trait")] + public readonly string DeliveringActor = null; + + [Desc("Cell coordinates for spawning the delivering actor. If left blank, the closest edge cell will be chosen.")] + public readonly CPos SpawnLocation = CPos.Zero; + + [Desc("Offset relative to the top-left cell of the building.")] + public readonly CVec DeliveryOffset = CVec.Zero; + + public override object Create(ActorInitializer init) { return new FreeActorWithDelivery(init, this); } + } + + public class FreeActorWithDelivery + { + public readonly FreeActorWithDeliveryInfo Info; + + readonly Actor self; + + public FreeActorWithDelivery(ActorInitializer init, FreeActorWithDeliveryInfo info) + { + if (string.IsNullOrEmpty(info.Actor)) + throw new InvalidDataException("Actor type was not specified!"); + if (string.IsNullOrEmpty(info.DeliveringActor)) + throw new InvalidDataException("Delivering actor type was not specified!"); + + self = init.Self; + Info = info; + + DoDelivery(self.Location + info.DeliveryOffset, info.Actor, info.DeliveringActor, info.InitialActivity); + } + + public void DoDelivery(CPos location, string actorName, string carrierActorName, string clientInitialActivity) + { + Actor cargo; + Actor carrier; + + CreateActors(actorName, carrierActorName, out cargo, out carrier); + + if (clientInitialActivity != null) + cargo.QueueActivity(Game.CreateObject(clientInitialActivity)); + + cargo.Trait().Destination = location; + + carrier.Trait().AttachCarryable(cargo); + + carrier.QueueActivity(new DeliverUnit(carrier)); + carrier.QueueActivity(new HeliFly(carrier, Target.FromCell(self.World, self.World.Map.ChooseRandomEdgeCell(self.World.SharedRandom)))); + carrier.QueueActivity(new RemoveSelf()); + + self.World.AddFrameEndTask(w => self.World.Add(carrier)); + } + + void CreateActors(string actorName, string deliveringActorName, out Actor cargo, out Actor carrier) + { + // Get a carryall spawn location + var location = Info.SpawnLocation; + if (location == CPos.Zero) + location = self.World.Map.ChooseClosestEdgeCell(self.Location); + + var spawn = self.World.Map.CenterOfCell(location); + + var initialFacing = self.World.Map.FacingBetween(location, self.Location, 0); + + // If aircraft, spawn at cruise altitude + var aircraftInfo = self.World.Map.Rules.Actors[deliveringActorName.ToLower()].Traits.GetOrDefault(); + if (aircraftInfo != null) + spawn += new WVec(0, 0, aircraftInfo.CruiseAltitude.Range); + + // Create delivery actor + carrier = self.World.CreateActor(false, deliveringActorName, new TypeDictionary + { + new LocationInit(location), + new CenterPositionInit(spawn), + new OwnerInit(self.Owner), + new FacingInit(initialFacing) + }); + + // Create delivered actor + cargo = self.World.CreateActor(false, actorName, new TypeDictionary + { + new OwnerInit(self.Owner), + }); + } + } +} diff --git a/OpenRA.Mods.D2k/Traits/Buildings/ProductionFromMapEdge.cs b/OpenRA.Mods.D2k/Traits/Buildings/ProductionFromMapEdge.cs new file mode 100644 index 0000000000..6e6cb3b00c --- /dev/null +++ b/OpenRA.Mods.D2k/Traits/Buildings/ProductionFromMapEdge.cs @@ -0,0 +1,82 @@ +#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.Primitives; +using OpenRA.Traits; + +namespace OpenRA.Mods.D2k.Traits +{ + [Desc("Produce a unit on the closest map edge cell and move into the world.")] + class ProductionFromMapEdgeInfo : ProductionInfo + { + public override object Create(ActorInitializer init) { return new ProductionFromMapEdge(init, this); } + } + + class ProductionFromMapEdge : Production + { + public ProductionFromMapEdge(ActorInitializer init, ProductionInfo info) + : base(init, info) { } + + public override bool Produce(Actor self, ActorInfo producee, string raceVariant) + { + var location = self.World.Map.ChooseClosestEdgeCell(self.Location); + var pos = self.World.Map.CenterOfCell(location); + + // If aircraft, spawn at cruise altitude + var aircraftInfo = producee.Traits.GetOrDefault(); + if (aircraftInfo != null) + pos += new WVec(0, 0, aircraftInfo.CruiseAltitude.Range); + + var initialFacing = self.World.Map.FacingBetween(location, self.Location, 0); + + self.World.AddFrameEndTask(w => + { + var td = new TypeDictionary + { + new OwnerInit(self.Owner), + new LocationInit(location), + new CenterPositionInit(pos), + new FacingInit(initialFacing) + }; + + if (raceVariant != null) + td.Add(new RaceInit(raceVariant)); + + var newUnit = self.World.CreateActor(producee.Name, td); + + var move = newUnit.TraitOrDefault(); + if (move != null) + newUnit.QueueActivity(move.MoveIntoWorld(newUnit, self.Location)); + + newUnit.SetTargetLine(Target.FromCell(self.World, self.Location), Color.Green, false); + + if (!self.IsDead) + foreach (var t in self.TraitsImplementing()) + t.UnitProduced(self, newUnit, self.Location); + + var notifyOthers = self.World.ActorsWithTrait(); + foreach (var notify in notifyOthers) + notify.Trait.UnitProducedByOther(notify.Actor, self, newUnit); + + var bi = newUnit.Info.Traits.GetOrDefault(); + if (bi != null && bi.InitialActivity != null) + newUnit.QueueActivity(Game.CreateObject(bi.InitialActivity)); + + foreach (var t in newUnit.TraitsImplementing()) + t.BuildingComplete(newUnit); + }); + + return true; + } + } +} diff --git a/OpenRA.Mods.D2k/Traits/Carryable.cs b/OpenRA.Mods.D2k/Traits/Carryable.cs index fea29f56dd..4b22004407 100644 --- a/OpenRA.Mods.D2k/Traits/Carryable.cs +++ b/OpenRA.Mods.D2k/Traits/Carryable.cs @@ -7,16 +7,15 @@ * see COPYING. */ #endregion -using System; + using System.Linq; using OpenRA.Activities; using OpenRA.Mods.Common.Traits; -using OpenRA.Mods.D2k.Activities; using OpenRA.Traits; namespace OpenRA.Mods.D2k.Traits { - [Desc("Can be carried by units with the trait `AutoCarryall`.")] + [Desc("Can be carried by units with the trait `Carryall`.")] public class CarryableInfo : ITraitInfo { [Desc("Required distance away from destination before requesting a pickup.")] @@ -32,7 +31,7 @@ namespace OpenRA.Mods.D2k.Traits public bool Reserved { get; private set; } - // If we're locked there isnt much we can do. We'll have to wait for the carrier to finish with us. We should not move or get new orders! + // If we're locked there isn't much we can do. We'll have to wait for the carrier to finish with us. We should not move or get new orders! bool locked; public bool WantsTransport { get; private set; } @@ -62,15 +61,14 @@ namespace OpenRA.Mods.D2k.Traits Destination = destination; this.afterLandActivity = afterLandActivity; + WantsTransport = true; if (locked || Reserved) return; - WantsTransport = true; - // Inform all idle carriers - var carriers = self.World.ActorsWithTrait() - .Where(c => !c.Trait.Busy && !c.Actor.IsDead && c.Actor.Owner == self.Owner) + var carriers = self.World.ActorsWithTrait() + .Where(c => !c.Trait.IsBusy && !c.Actor.IsDead && c.Actor.Owner == self.Owner && c.Actor.IsInWorld) .OrderBy(p => (self.Location - p.Actor.Location).LengthSquared); foreach (var carrier in carriers) @@ -90,7 +88,7 @@ namespace OpenRA.Mods.D2k.Traits WantsTransport = false; afterLandActivity = null; - // TODO: We could implement something like a carrier.Trait().CancelTransportNotify(self) and call it here + // TODO: We could implement something like a carrier.Trait().CancelTransportNotify(self) and call it here } // We do not handle Harvested notification @@ -99,8 +97,8 @@ namespace OpenRA.Mods.D2k.Traits public Actor GetClosestIdleCarrier() { // Find carriers - var carriers = self.World.ActorsWithTrait() - .Where(p => p.Actor.Owner == self.Owner && !p.Trait.Busy) + var carriers = self.World.ActorsWithTrait() + .Where(p => p.Actor.Owner == self.Owner && !p.Trait.IsBusy && p.Actor.IsInWorld) .Select(h => h.Actor); return WorldUtils.ClosestTo(carriers, self); diff --git a/OpenRA.Mods.D2k/Traits/AutoCarryall.cs b/OpenRA.Mods.D2k/Traits/Carryall.cs similarity index 59% rename from OpenRA.Mods.D2k/Traits/AutoCarryall.cs rename to OpenRA.Mods.D2k/Traits/Carryall.cs index 7952689267..94f7d486b9 100644 --- a/OpenRA.Mods.D2k/Traits/AutoCarryall.cs +++ b/OpenRA.Mods.D2k/Traits/Carryall.cs @@ -7,62 +7,70 @@ * see COPYING. */ #endregion -using System; + using System.Collections.Generic; using System.Linq; using OpenRA.Graphics; using OpenRA.Mods.Common.Activities; using OpenRA.Mods.Common.Traits; using OpenRA.Mods.D2k.Activities; -using OpenRA.Mods.RA; -using OpenRA.Mods.RA.Traits; using OpenRA.Traits; namespace OpenRA.Mods.D2k.Traits { - [Desc("Automatically transports harvesters with the Carryable trait between resource fields and refineries")] - public class AutoCarryallInfo : ITraitInfo, Requires + [Desc("Automatically transports harvesters with the Carryable trait between resource fields and refineries.")] + public class CarryallInfo : ITraitInfo, Requires { - public object Create(ActorInitializer init) { return new AutoCarryall(init.Self, this); } + [Desc("Set to false when the carryall should not automatically get new jobs.")] + public readonly bool Automatic = true; + + public object Create(ActorInitializer init) { return new Carryall(init.Self, this); } } - public class AutoCarryall : INotifyBecomingIdle, INotifyKilled, ISync, IRender + public class Carryall : INotifyBecomingIdle, INotifyKilled, ISync, IRender { readonly Actor self; readonly WRange carryHeight; + readonly CarryallInfo info; // The actor we are currently carrying. - [Sync] Actor carrying; - bool isCarrying; + [Sync] public Actor Carrying { get; internal set; } + public bool IsCarrying { get; internal set; } // TODO: Use ActorPreviews so that this can support actors with multiple sprites Animation anim; - public bool Busy { get; internal set; } + public bool IsBusy { get; internal set; } - public AutoCarryall(Actor self, AutoCarryallInfo info) + public Carryall(Actor self, CarryallInfo info) { this.self = self; + this.info = info; + + IsBusy = false; + IsCarrying = false; carryHeight = self.Trait().Info.LandAltitude; } public void OnBecomingIdle(Actor self) { - FindCarryableForTransport(); + if (info.Automatic) + FindCarryableForTransport(); - if (!Busy) + if (!IsBusy) self.QueueActivity(new HeliFlyCircle(self)); } // A carryable notifying us that he'd like to be carried public bool RequestTransportNotify(Actor carryable) { - if (Busy) + if (IsBusy || !info.Automatic) return false; if (ReserveCarryable(carryable)) { - self.QueueActivity(false, new CarryUnit(self, carryable)); + self.QueueActivity(false, new PickupUnit(self, carryable)); + self.QueueActivity(true, new DeliverUnit(self)); return true; } @@ -71,29 +79,32 @@ namespace OpenRA.Mods.D2k.Traits void FindCarryableForTransport() { - // get all carryables who want transport + if (!self.IsInWorld) + return; + + // Get all carryables who want transport var carryables = self.World.ActorsWithTrait() .Where(p => - { - var actor = p.Actor; - if (actor == null) - return false; + { + var actor = p.Actor; + if (actor == null) + return false; - if (actor.Owner != self.Owner) - return false; + if (actor.Owner != self.Owner) + return false; - if (actor.IsDead) - return false; + if (actor.IsDead) + return false; - var trait = p.Trait; - if (trait.Reserved) - return false; + var trait = p.Trait; + if (trait.Reserved) + return false; - if (!trait.WantsTransport) - return false; + if (!trait.WantsTransport) + return false; - return true; - }) + return true; + }) .OrderBy(p => (self.Location - p.Actor.Location).LengthSquared); foreach (var p in carryables) @@ -101,7 +112,8 @@ namespace OpenRA.Mods.D2k.Traits // Check if its actually me who's the best candidate if (p.Trait.GetClosestIdleCarrier() == self && ReserveCarryable(p.Actor)) { - self.QueueActivity(false, new CarryUnit(self, p.Actor)); + self.QueueActivity(false, new PickupUnit(self, p.Actor)); + self.QueueActivity(true, new DeliverUnit(self)); break; } } @@ -112,8 +124,8 @@ namespace OpenRA.Mods.D2k.Traits { if (carryable.Trait().Reserve(self)) { - carrying = carryable; - Busy = true; + Carrying = carryable; + IsBusy = true; return true; } @@ -123,24 +135,26 @@ namespace OpenRA.Mods.D2k.Traits // Unreserve the carryable public void UnreserveCarryable() { - if (carrying != null) + if (Carrying != null) { - if (carrying.IsInWorld && !carrying.IsDead) - carrying.Trait().UnReserve(self); + if (Carrying.IsInWorld && !Carrying.IsDead) + Carrying.Trait().UnReserve(self); - carrying = null; + Carrying = null; } - Busy = false; + IsBusy = false; } // INotifyKilled public void Killed(Actor self, AttackInfo e) { - if (carrying != null) + if (Carrying != null) { - if (isCarrying && carrying.IsInWorld && !carrying.IsDead) - carrying.Kill(e.Attacker); + if (IsCarrying && Carrying.IsInWorld && !Carrying.IsDead) + Carrying.Kill(e.Attacker); + + Carrying = null; } UnreserveCarryable(); @@ -149,7 +163,9 @@ namespace OpenRA.Mods.D2k.Traits // Called when carryable is inside. public void AttachCarryable(Actor carryable) { - isCarrying = true; + IsBusy = true; + IsCarrying = true; + Carrying = carryable; // Create a new animation for our carryable unit var rs = carryable.Trait(); @@ -161,7 +177,7 @@ namespace OpenRA.Mods.D2k.Traits // Called when released public void CarryableReleased() { - isCarrying = false; + IsCarrying = false; anim = null; } @@ -171,7 +187,8 @@ namespace OpenRA.Mods.D2k.Traits if (anim != null && !self.World.FogObscures(self)) { anim.Tick(); - var renderables = anim.Render(self.CenterPosition + new WVec(0, 0, -carryHeight.Range), wr.Palette("player" + carrying.Owner.InternalName)); + var renderables = anim.Render(self.CenterPosition + new WVec(0, 0, -carryHeight.Range), + wr.Palette("player" + Carrying.Owner.InternalName)); foreach (var rr in renderables) yield return rr; diff --git a/OpenRA.Mods.D2k/Traits/Player/HarvesterInsurance.cs b/OpenRA.Mods.D2k/Traits/Player/HarvesterInsurance.cs new file mode 100644 index 0000000000..2174de20ca --- /dev/null +++ b/OpenRA.Mods.D2k/Traits/Player/HarvesterInsurance.cs @@ -0,0 +1,48 @@ +#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.Linq; +using OpenRA.Mods.Common.Traits; +using OpenRA.Traits; + +namespace OpenRA.Mods.D2k.Traits +{ + [Desc("A player with this trait will receive a free harvester when his last one gets eaten by a sandworm, provided he has at least one refinery.")] + public class HarvesterInsuranceInfo : ITraitInfo + { + public object Create(ActorInitializer init) { return new HarvesterInsurance(init.Self); } + } + + public class HarvesterInsurance + { + readonly Actor self; + + public HarvesterInsurance(Actor self) + { + this.self = self; + } + + public void TryActivate() + { + var harvesters = self.World.ActorsWithTrait().Where(x => x.Actor.Owner == self.Owner); + if (harvesters.Any()) + return; + + var refineries = self.World.ActorsWithTrait().Where(x => x.Actor.Owner == self.Owner); + if (!refineries.Any()) + return; + + var refinery = refineries.First().Actor; + var delivery = refinery.Trait(); + delivery.DoDelivery(refinery.Location + delivery.Info.DeliveryOffset, delivery.Info.Actor, + delivery.Info.DeliveringActor, delivery.Info.InitialActivity); + } + } +} diff --git a/mods/d2k/maps/shellmap/map.yaml b/mods/d2k/maps/shellmap/map.yaml index 47d72884ee..89f159d877 100644 --- a/mods/d2k/maps/shellmap/map.yaml +++ b/mods/d2k/maps/shellmap/map.yaml @@ -111,7 +111,6 @@ Actors: AtreidesSpiceRefinery: refinery Location: 57,58 Owner: Atreides - FreeActor: false Smudges: @@ -127,14 +126,6 @@ Rules: Maximum: 1 LuaScript: Scripts: shellmap.lua - carryall.scripted: - Helicopter: - LandWhenIdle: True - Cargo: - Types: Vehicle - WithCargo: - DisplayTypes: Vehicle - LocalOffset: 0,0,-256 rockettower: Power: Amount: 100 diff --git a/mods/d2k/maps/shellmap/shellmap.lua b/mods/d2k/maps/shellmap/shellmap.lua index 8c8ae472c9..6361538ea5 100644 --- a/mods/d2k/maps/shellmap/shellmap.lua +++ b/mods/d2k/maps/shellmap/shellmap.lua @@ -1,20 +1,3 @@ -InitializeHarvester = function(harvester) - harvester.FindResources() - Trigger.OnRemovedFromWorld(harvester, InsertHarvester) -end - -InsertHarvester = function() - local harvesters = Reinforcements.ReinforceWithTransport(atreides, "carryall.scripted", { "harvester" }, - { Entry.Location, AtreidesSpiceRefinery.Location + CVec.New(2, 3) }, { Entry.Location })[2] - - Utils.Do(harvesters, function(harvester) - Trigger.OnAddedToWorld(harvester, function() InitializeHarvester(harvester) end) - end) -end - WorldLoaded = function() - atreides = Player.GetPlayer("Atreides") - - InsertHarvester() Media.PlayMusic("score") -end +end \ No newline at end of file diff --git a/mods/d2k/rules/aircraft.yaml b/mods/d2k/rules/aircraft.yaml index e3858bd7d7..8afebf7d3f 100644 --- a/mods/d2k/rules/aircraft.yaml +++ b/mods/d2k/rules/aircraft.yaml @@ -1,4 +1,4 @@ -carryall.scripted: +carryall.reinforce: Inherits: ^Helicopter Valued: Cost: 1200 @@ -10,7 +10,7 @@ carryall.scripted: Armor: Type: Light Helicopter: - CruiseAltitude: 2048 + CruiseAltitude: 2100 InitialFacing: 0 ROT: 4 Speed: 160 @@ -18,7 +18,7 @@ carryall.scripted: RepairBuildings: repair RearmBuildings: Repulsable: False - LandAltitude: 512 + LandAltitude: 100 LandWhenIdle: False RenderUnit: Image: carryall @@ -26,16 +26,17 @@ carryall.scripted: LeavesHusk: HuskActor: carryall.husk -Selectable: + -TargetableAircraft: + Carryall: + Automatic: False carryall: - Inherits: carryall.scripted - AutoCarryall: + Inherits: carryall.reinforce + Carryall: + Automatic: True Buildable: Queue: Aircraft BuildPaletteOrder: 120 - Helicopter: - CruiseAltitude: 2100 - LandAltitude: 100 carryall.infantry: Inherits: ^Plane @@ -61,6 +62,7 @@ carryall.infantry: MaxWeight: 5 Types: Infantry -Selectable: + -TargetableAircraft: -GainsExperience: Tooltip: Name: Carryall diff --git a/mods/d2k/rules/player.yaml b/mods/d2k/rules/player.yaml index 68585dcb96..4779cbd49f 100644 --- a/mods/d2k/rules/player.yaml +++ b/mods/d2k/rules/player.yaml @@ -73,3 +73,4 @@ Player: Name: Unrestricted Prerequisites: techlevel.low, techlevel.medium, techlevel.high, techlevel.superweapons EnemyWatcher: + HarvesterInsurance: diff --git a/mods/d2k/rules/structures.yaml b/mods/d2k/rules/structures.yaml index 496819e39e..e5180043b1 100644 --- a/mods/d2k/rules/structures.yaml +++ b/mods/d2k/rules/structures.yaml @@ -194,10 +194,11 @@ refinery: Capacity: 2000 CustomSellValue: Value: 500 - FreeActor: + FreeActorWithDelivery: Actor: harvester InitialActivity: FindResources - SpawnOffset: 2,1 + DeliveryOffset: 2,2 + DeliveringActor: carryall.reinforce Facing: 160 -RenderBuilding: RenderBuildingWarFactory: @@ -668,7 +669,7 @@ hightech: Tooltip: Name: High Tech Facility Description: Unlocks advanced technology - Production: + ProductionFromMapEdge: Produces: Aircraft Exit: SpawnOffset: 0,0,728