D2k Starport
- OG fly path and Frigate behaviour - BulkProductionQueue - ProductionStarport trait - OG palette widget behavior for Bulk queue - Purchase button for each BulkProduction - Delivery timer for each BulkProduction
This commit is contained in:
118
OpenRA.Mods.Common/Activities/Air/DeliverBulkOrder.cs
Normal file
118
OpenRA.Mods.Common/Activities/Air/DeliverBulkOrder.cs
Normal file
@@ -0,0 +1,118 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright (c) The OpenRA Developers and Contributors
|
||||
* 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, either version 3 of
|
||||
* the License, or (at your option) any later version. For more
|
||||
* information, see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using OpenRA.Activities;
|
||||
using OpenRA.Mods.Common.Traits;
|
||||
using OpenRA.Primitives;
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.Mods.Common.Activities
|
||||
{
|
||||
public class DeliverBulkOrder : Activity
|
||||
{
|
||||
readonly Actor producer;
|
||||
readonly List<(ActorInfo Actor, int Resources, int Cash)> orderedActors;
|
||||
readonly string productionType;
|
||||
readonly BulkProductionQueue queue;
|
||||
readonly Cargo cargo;
|
||||
int delayBetweenUnloads = 0;
|
||||
|
||||
public DeliverBulkOrder(Actor transport, Actor producer, List<(ActorInfo Actor, int Resources, int Cash)> orderedActors,
|
||||
string productionType, BulkProductionQueue queue)
|
||||
{
|
||||
this.producer = producer;
|
||||
this.orderedActors = orderedActors;
|
||||
this.productionType = productionType;
|
||||
this.queue = queue;
|
||||
cargo = transport.Trait<Cargo>();
|
||||
}
|
||||
|
||||
protected override void OnFirstRun(Actor self)
|
||||
{
|
||||
var landingOffset = producer.Info.TraitInfo<ProductionBulkAirdropInfo>().LandOffset;
|
||||
QueueChild(new Land(self, Target.FromActor(producer), WDist.FromCells(0), landingOffset));
|
||||
if (cargo.Info.BeforeUnloadDelay > 0)
|
||||
QueueChild(new Wait(cargo.Info.BeforeUnloadDelay));
|
||||
}
|
||||
|
||||
protected override void OnLastRun(Actor self)
|
||||
{
|
||||
if (!producer.IsDead || producer.IsInWorld)
|
||||
foreach (var cargo in producer.TraitsImplementing<INotifyDelivery>())
|
||||
cargo.Delivered(producer);
|
||||
|
||||
if (cargo.Info.AfterUnloadDelay > 0)
|
||||
Queue(new Wait(cargo.Info.AfterUnloadDelay));
|
||||
Queue(new FlyOffMap(self, Target.FromCell(self.World, self.World.Map.ChooseClosestEdgeCell(self.Location))));
|
||||
Queue(new RemoveSelf());
|
||||
}
|
||||
|
||||
protected override void OnActorDispose(Actor self)
|
||||
{
|
||||
queue.DeliverFinished();
|
||||
}
|
||||
|
||||
public override bool Tick(Actor self)
|
||||
{
|
||||
if (!producer.IsInWorld || producer.IsDead)
|
||||
{
|
||||
// Try to find another ProductionBulkAirDrop
|
||||
var newProducer = self.World.ActorsHavingTrait<ProductionBulkAirdrop>()
|
||||
.Where(a => a.Owner == self.Owner)
|
||||
.ClosestToIgnoringPath(self);
|
||||
if (newProducer != null)
|
||||
{
|
||||
Cancel(self);
|
||||
Queue(new DeliverBulkOrder(self, newProducer, orderedActors, productionType, queue));
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
queue.DeliverFinished();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (orderedActors == null || orderedActors.Count == 0)
|
||||
{
|
||||
queue.DeliverFinished();
|
||||
return true;
|
||||
}
|
||||
|
||||
var actor = orderedActors[^1];
|
||||
var productionTrait = producer.Trait<ProductionBulkAirdrop>();
|
||||
var exit = productionTrait.PublicExit(producer, actor.Actor, productionType);
|
||||
if (exit == null)
|
||||
return false;
|
||||
|
||||
if (delayBetweenUnloads > 0)
|
||||
{
|
||||
delayBetweenUnloads--;
|
||||
return false;
|
||||
}
|
||||
|
||||
delayBetweenUnloads = cargo.Info.BetweenUnloadDelay;
|
||||
producer.World.AddFrameEndTask(ww =>
|
||||
{
|
||||
var inits = new TypeDictionary
|
||||
{
|
||||
new OwnerInit(self.Owner),
|
||||
new FactionInit(BuildableInfo.GetInitialFaction(actor.Actor, producer.Trait<ProductionBulkAirdrop>().Faction))
|
||||
};
|
||||
productionTrait.DoProduction(producer, actor.Actor, exit?.Info, productionType, inits);
|
||||
orderedActors.Remove(actor);
|
||||
});
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -27,6 +27,7 @@ namespace OpenRA.Mods.Common.Activities
|
||||
readonly Mobile mobile;
|
||||
readonly bool assignTargetOnFirstRun;
|
||||
readonly WDist unloadRange;
|
||||
int delayBetweenUnloads = 0;
|
||||
|
||||
Target destination;
|
||||
bool takeOffAfterUnload;
|
||||
@@ -96,6 +97,13 @@ namespace OpenRA.Mods.Common.Activities
|
||||
|
||||
if (cargo.CanUnload())
|
||||
{
|
||||
if (delayBetweenUnloads > 0)
|
||||
{
|
||||
delayBetweenUnloads--;
|
||||
return false;
|
||||
}
|
||||
|
||||
delayBetweenUnloads = cargo.Info.BetweenUnloadDelay;
|
||||
foreach (var inu in notifiers)
|
||||
inu.Unloading(self);
|
||||
|
||||
|
||||
@@ -59,6 +59,9 @@ namespace OpenRA.Mods.Common.Traits
|
||||
[Desc("Delay (in ticks) before continuing after unloading a passenger.")]
|
||||
public readonly int AfterUnloadDelay = 25;
|
||||
|
||||
[Desc("Delay (in ticks) before each passenger is unloaded.")]
|
||||
public readonly int BetweenUnloadDelay = 0;
|
||||
|
||||
[CursorReference]
|
||||
[Desc("Cursor to display when able to unload the passengers.")]
|
||||
public readonly string UnloadCursor = "deploy";
|
||||
|
||||
331
OpenRA.Mods.Common/Traits/Player/BulkProductionQueue.cs
Normal file
331
OpenRA.Mods.Common/Traits/Player/BulkProductionQueue.cs
Normal file
@@ -0,0 +1,331 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright (c) The OpenRA Developers and Contributors
|
||||
* 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, either version 3 of
|
||||
* the License, or (at your option) any later version. For more
|
||||
* information, see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.Mods.Common.Traits
|
||||
{
|
||||
[TraitLocation(SystemActors.Player)]
|
||||
[Desc("Attach this to the player actor (not a building!) to define a new shared build queue.",
|
||||
"Allows you to build multiple actors before delivery",
|
||||
"Will work together with the `ProductionBulkAirDrop` trait on the actor that actually does the production.",
|
||||
"You will also want to add `PrimaryBuilding` to let the user choose where new units should exit.")]
|
||||
public class BulkProductionQueueInfo : ProductionQueueInfo, Requires<TechTreeInfo>, Requires<PlayerResourcesInfo>
|
||||
{
|
||||
[Desc("Maximum order capacity.")]
|
||||
public readonly int MaxCapacity = 6;
|
||||
|
||||
[Desc("Delivery delay in ticks.")]
|
||||
public readonly int DeliveryDelay = 1500;
|
||||
|
||||
[NotificationReference("Speech")]
|
||||
[Desc("Notification played when the delivery started.")]
|
||||
public readonly string StartDeliveryNotification = null;
|
||||
|
||||
[Desc("Return funds if the delivery fails.")]
|
||||
public readonly bool RefundUndeliveredActors = true;
|
||||
|
||||
[FluentReference(optional: true)]
|
||||
[Desc("Notification displayed when the delivery started.")]
|
||||
public readonly string StartDeliveryTextNotification = null;
|
||||
|
||||
[NotificationReference("Speech")]
|
||||
[Desc("Notifications to play when the delivery actor is on its way. Last notification is played when the actors to deliver are spawned on the map.")]
|
||||
public readonly string[] DeliveryProgressNotifications = [];
|
||||
|
||||
public override object Create(ActorInitializer init) { return new BulkProductionQueue(init, this); }
|
||||
}
|
||||
|
||||
public class BulkProductionQueue : ProductionQueue
|
||||
{
|
||||
static readonly ActorInfo[] NoItems = [];
|
||||
|
||||
readonly Actor self;
|
||||
readonly BulkProductionQueueInfo info;
|
||||
|
||||
protected readonly List<(ActorInfo Actor, int Resources, int Cash)> ActorsReadyForDelivery = [];
|
||||
public int DeliveryDelay { get; private set; } = 0;
|
||||
protected int notificationInterval = 0;
|
||||
protected int notificationIndex = 0;
|
||||
|
||||
protected bool deliveryProcessStarted = false;
|
||||
|
||||
public BulkProductionQueue(ActorInitializer init, BulkProductionQueueInfo info)
|
||||
: base(init, info)
|
||||
{
|
||||
self = init.Self;
|
||||
this.info = info;
|
||||
if (info.DeliveryProgressNotifications.Length != 0)
|
||||
notificationInterval = info.DeliveryDelay / info.DeliveryProgressNotifications.Length;
|
||||
}
|
||||
|
||||
protected override void Tick(Actor self)
|
||||
{
|
||||
// PERF: Avoid LINQ.
|
||||
Enabled = false;
|
||||
var isActive = false;
|
||||
foreach (var x in self.World.ActorsWithTrait<Production>())
|
||||
{
|
||||
if (x.Trait.IsTraitDisabled)
|
||||
continue;
|
||||
|
||||
if (x.Actor.Owner != self.Owner || !x.Trait.Info.Produces.Contains(Info.Type))
|
||||
continue;
|
||||
|
||||
Enabled |= IsValidFaction;
|
||||
isActive |= !x.Trait.IsTraitPaused;
|
||||
}
|
||||
|
||||
if (!Enabled)
|
||||
{
|
||||
DeliverFinished();
|
||||
ClearQueue();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (HasDeliveryStarted() && DeliveryDelay > 0)
|
||||
{
|
||||
DeliveryDelay--;
|
||||
PlayDeliveryProgressNotifications();
|
||||
}
|
||||
else if (HasDeliveryStarted() && DeliveryDelay == 0)
|
||||
{
|
||||
PlayDeliveryProgressNotifications();
|
||||
DeliveryHasArrived();
|
||||
DeliveryDelay--;
|
||||
}
|
||||
}
|
||||
|
||||
TickInner(self, !isActive);
|
||||
}
|
||||
|
||||
public override IEnumerable<ActorInfo> AllItems()
|
||||
{
|
||||
return Enabled ? base.AllItems() : NoItems;
|
||||
}
|
||||
|
||||
public override IEnumerable<ActorInfo> BuildableItems()
|
||||
{
|
||||
return Enabled && ActorsReadyForDelivery.Count != info.MaxCapacity && !deliveryProcessStarted ? base.BuildableItems() : NoItems;
|
||||
}
|
||||
|
||||
public override TraitPair<Production> MostLikelyProducer()
|
||||
{
|
||||
var productionActor = self.World.ActorsWithTrait<Production>()
|
||||
.Where(x => x.Actor.Owner == self.Owner
|
||||
&& !x.Trait.IsTraitDisabled && x.Trait.Info.Produces.Contains(Info.Type))
|
||||
.OrderBy(x => x.Trait.IsTraitPaused)
|
||||
.ThenByDescending(x => x.Actor.Trait<PrimaryBuilding>().IsPrimary)
|
||||
.ThenByDescending(x => x.Actor.ActorID)
|
||||
.FirstOrDefault();
|
||||
|
||||
return productionActor;
|
||||
}
|
||||
|
||||
protected override bool BuildUnit(ActorInfo unit)
|
||||
{
|
||||
// Find a production structure to build this actor
|
||||
var bi = unit.TraitInfo<BuildableInfo>();
|
||||
|
||||
// Some units may request a specific production type, which is ignored if the AllTech cheat is enabled
|
||||
var type = developerMode.AllTech ? Info.Type : (bi.BuildAtProductionType ?? Info.Type);
|
||||
|
||||
var producers = self.World.ActorsWithTrait<Production>()
|
||||
.Where(x => x.Actor.Owner == self.Owner
|
||||
&& !x.Trait.IsTraitDisabled
|
||||
&& x.Trait.Info.Produces.Contains(type))
|
||||
.OrderByDescending(x => x.Actor.Trait<PrimaryBuilding>().IsPrimary)
|
||||
.OrderByDescending(x => x.Actor.Trait<PrimaryBuilding>().IsPrimary)
|
||||
.ThenByDescending(x => x.Actor.ActorID);
|
||||
var anyProducers = false;
|
||||
foreach (var p in producers)
|
||||
{
|
||||
anyProducers = true;
|
||||
if (p.Trait.IsTraitPaused)
|
||||
continue;
|
||||
if (ActorsReadyForDelivery.Count <= info.MaxCapacity)
|
||||
{
|
||||
var item = Queue.First(i => i.Done && i.Item == unit.Name);
|
||||
ActorsReadyForDelivery.Add((unit, item.ResourcesPaid, item.TotalCost - item.ResourcesPaid));
|
||||
EndProduction(item);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!anyProducers)
|
||||
CancelProduction(unit.Name, 1);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public override void ResolveOrder(Actor self, Order order)
|
||||
{
|
||||
if (!Enabled)
|
||||
return;
|
||||
|
||||
var rules = self.World.Map.Rules;
|
||||
switch (order.OrderString)
|
||||
{
|
||||
case "StartProduction":
|
||||
var unit = rules.Actors[order.TargetString];
|
||||
var bi = unit.TraitInfo<BuildableInfo>();
|
||||
|
||||
// Not built by this queue
|
||||
if (!bi.Queue.Contains(Info.Type))
|
||||
return;
|
||||
|
||||
// You can't build that
|
||||
if (BuildableItems().All(b => b.Name != order.TargetString))
|
||||
return;
|
||||
|
||||
// Check if the player is trying to build more units that they are allowed
|
||||
var fromLimit = int.MaxValue;
|
||||
if (!developerMode.AllTech)
|
||||
{
|
||||
if (Info.QueueLimit > 0)
|
||||
fromLimit = Info.QueueLimit - Queue.Count;
|
||||
|
||||
if (Info.ItemLimit > 0)
|
||||
fromLimit = Math.Min(fromLimit, Info.ItemLimit - Queue.Count(i => i.Item == order.TargetString));
|
||||
|
||||
if (bi.BuildLimit > 0)
|
||||
{
|
||||
var inQueue = Queue.Count(pi => pi.Item == order.TargetString);
|
||||
var owned = self.Owner.World.ActorsHavingTrait<Buildable>().Count(a => a.Info.Name == order.TargetString && a.Owner == self.Owner);
|
||||
fromLimit = Math.Min(fromLimit, bi.BuildLimit - (inQueue + owned));
|
||||
}
|
||||
|
||||
if (fromLimit <= 0)
|
||||
return;
|
||||
}
|
||||
|
||||
var cost = GetProductionCost(unit);
|
||||
var time = GetBuildTime(unit, bi);
|
||||
var amountToBuild = Math.Min(fromLimit, order.ExtraData);
|
||||
for (var n = 0; n < amountToBuild; n++)
|
||||
{
|
||||
if (Info.PayUpFront && cost > playerResources.GetCashAndResources())
|
||||
return;
|
||||
BeginProduction(new ProductionItem(this, order.TargetString, cost, playerPower, () => self.World.AddFrameEndTask(_ =>
|
||||
{
|
||||
// Make sure the item hasn't been invalidated between the ProductionItem ticking and this FrameEndTask running
|
||||
if (!Queue.Any(i => i.Done && i.Item == unit.Name))
|
||||
return;
|
||||
BuildUnit(unit);
|
||||
})), !order.Queued);
|
||||
}
|
||||
|
||||
break;
|
||||
case "PauseProduction":
|
||||
PauseProduction(order.TargetString, order.ExtraData != 0);
|
||||
break;
|
||||
case "CancelProduction":
|
||||
CancelProduction(order.TargetString, order.ExtraData);
|
||||
break;
|
||||
case "ReturnOrder":
|
||||
ReturnOrder(order.TargetString, order.ExtraData);
|
||||
break;
|
||||
case "PurchaseOrder":
|
||||
if (!deliveryProcessStarted && order.TargetString == info.Type)
|
||||
StartDeliveryProcess();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public void DeliverFinished()
|
||||
{
|
||||
if (info.RefundUndeliveredActors || !deliveryProcessStarted)
|
||||
ReturnOrder();
|
||||
ActorsReadyForDelivery.Clear();
|
||||
deliveryProcessStarted = false;
|
||||
}
|
||||
|
||||
public bool HasDeliveryStarted()
|
||||
{
|
||||
return deliveryProcessStarted;
|
||||
}
|
||||
|
||||
public List<(ActorInfo Actor, int Resources, int Cash)> GetActorsReadyForDelivery()
|
||||
{
|
||||
return ActorsReadyForDelivery;
|
||||
}
|
||||
|
||||
protected void StartDeliveryProcess()
|
||||
{
|
||||
ClearQueue();
|
||||
deliveryProcessStarted = true;
|
||||
DeliveryDelay = info.DeliveryDelay;
|
||||
var rules = self.World.Map.Rules;
|
||||
Game.Sound.PlayNotification(rules, self.Owner, "Speech", info.StartDeliveryNotification, self.Owner.Faction.InternalName);
|
||||
if (info.StartDeliveryTextNotification != null)
|
||||
TextNotificationsManager.AddTransientLine(self.Owner, info.StartDeliveryTextNotification);
|
||||
}
|
||||
|
||||
protected void DeliveryHasArrived()
|
||||
{
|
||||
var producers = self.World.ActorsWithTrait<ProductionBulkAirdrop>()
|
||||
.Where(x => x.Actor.Owner == self.Owner
|
||||
&& !x.Trait.IsTraitDisabled
|
||||
&& !x.Trait.IsTraitPaused
|
||||
&& x.Trait.Info.Produces.Contains(Info.Type))
|
||||
.OrderByDescending(x => x.Actor.Trait<PrimaryBuilding>().IsPrimary)
|
||||
.ThenByDescending(x => x.Actor.ActorID);
|
||||
var p = producers.FirstOrDefault();
|
||||
p.Trait?.DeliverOrder(p.Actor, ActorsReadyForDelivery, Info.Type, this);
|
||||
}
|
||||
|
||||
public void ReturnOrder(string itemName = null, uint numberToCancel = 1)
|
||||
{
|
||||
if (itemName == null)
|
||||
{
|
||||
foreach (var actorData in ActorsReadyForDelivery)
|
||||
{
|
||||
playerResources.GiveResources(actorData.Resources);
|
||||
playerResources.GiveCash(actorData.Cash);
|
||||
}
|
||||
|
||||
ActorsReadyForDelivery.Clear();
|
||||
return;
|
||||
}
|
||||
|
||||
for (var i = 0; i < numberToCancel; i++)
|
||||
{
|
||||
var actor = ActorsReadyForDelivery.LastOrDefault(actor => actor.Actor.Name == itemName);
|
||||
if (actor.Actor == null)
|
||||
break;
|
||||
playerResources.GiveResources(actor.Resources);
|
||||
playerResources.GiveCash(actor.Cash);
|
||||
ActorsReadyForDelivery.Remove(actor);
|
||||
}
|
||||
}
|
||||
|
||||
protected void PlayDeliveryProgressNotifications()
|
||||
{
|
||||
if (notificationInterval == 0)
|
||||
{
|
||||
Game.Sound.PlayNotification(self.World.Map.Rules, self.Owner, "Speech",
|
||||
info.DeliveryProgressNotifications[notificationIndex], self.Owner.Faction.InternalName);
|
||||
notificationInterval = info.DeliveryDelay / info.DeliveryProgressNotifications.Length;
|
||||
notificationIndex++;
|
||||
if (info.DeliveryProgressNotifications.Length == notificationIndex)
|
||||
{
|
||||
notificationIndex = 0;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
notificationInterval--;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -145,7 +145,7 @@ namespace OpenRA.Mods.Common.Traits
|
||||
protected Production[] productionTraits;
|
||||
|
||||
// Will change if the owner changes
|
||||
PowerManager playerPower;
|
||||
protected PowerManager playerPower;
|
||||
protected PlayerResources playerResources;
|
||||
protected DeveloperMode developerMode;
|
||||
protected TechTree techTree;
|
||||
@@ -435,7 +435,7 @@ namespace OpenRA.Mods.Common.Traits
|
||||
return true;
|
||||
}
|
||||
|
||||
public void ResolveOrder(Actor self, Order order)
|
||||
public virtual void ResolveOrder(Actor self, Order order)
|
||||
{
|
||||
if (!Enabled)
|
||||
return;
|
||||
|
||||
141
OpenRA.Mods.Common/Traits/ProductionBulkAirDrop.cs
Normal file
141
OpenRA.Mods.Common/Traits/ProductionBulkAirDrop.cs
Normal file
@@ -0,0 +1,141 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright (c) The OpenRA Developers and Contributors
|
||||
* 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, either version 3 of
|
||||
* the License, or (at your option) any later version. For more
|
||||
* information, see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using OpenRA.Mods.Common.Activities;
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.Mods.Common.Traits
|
||||
{
|
||||
[Desc("Deliver multiple units via skylift. Works with BulkProductionQueue")]
|
||||
public class ProductionBulkAirdropInfo : ProductionInfo
|
||||
{
|
||||
[NotificationReference("Speech")]
|
||||
[Desc("Speech notification to play when a unit is delivered.")]
|
||||
public readonly string ReadyNotification = "Reinforce";
|
||||
|
||||
[FluentReference(optional: true)]
|
||||
[Desc("Text notification to display when a unit is delivered.")]
|
||||
public readonly string ReadyTextNotification = null;
|
||||
|
||||
[FieldLoader.Require]
|
||||
[ActorReference(typeof(AircraftInfo))]
|
||||
[Desc("Cargo aircraft used for delivery. Must have the `" + nameof(Aircraft) + "` trait.")]
|
||||
public readonly string ActorType = null;
|
||||
|
||||
[Desc("Direction the aircraft should face to land.")]
|
||||
public readonly WAngle Facing = new(256);
|
||||
|
||||
[Desc("Offset the aircraft used for landing.")]
|
||||
public readonly WVec LandOffset = WVec.Zero;
|
||||
|
||||
public override object Create(ActorInitializer init) { return new ProductionBulkAirdrop(init, this); }
|
||||
}
|
||||
|
||||
sealed class ProductionBulkAirdrop : Production
|
||||
{
|
||||
RallyPoint rp;
|
||||
|
||||
public ProductionBulkAirdrop(ActorInitializer init, ProductionBulkAirdropInfo info)
|
||||
: base(init, info) { }
|
||||
|
||||
protected override void Created(Actor self)
|
||||
{
|
||||
base.Created(self);
|
||||
|
||||
rp = self.TraitOrDefault<RallyPoint>();
|
||||
}
|
||||
|
||||
public bool DeliverOrder(Actor producer, List<(ActorInfo Actor, int Resources, int Cash)> orderedActors, string productionType, BulkProductionQueue queue)
|
||||
{
|
||||
if (IsTraitDisabled || IsTraitPaused)
|
||||
return false;
|
||||
var info = (ProductionBulkAirdropInfo)Info;
|
||||
var owner = producer.Owner;
|
||||
var map = owner.World.Map;
|
||||
var startPos = producer.World.Map.ChooseClosestEdgeCell(producer.Location);
|
||||
var spawnFacing = producer.World.Map.FacingBetween(startPos, producer.Location, WAngle.Zero);
|
||||
|
||||
foreach (var tower in producer.TraitsImplementing<INotifyDelivery>())
|
||||
tower.IncomingDelivery(producer);
|
||||
|
||||
owner.World.AddFrameEndTask(w =>
|
||||
{
|
||||
if (!producer.IsInWorld || producer.IsDead)
|
||||
{
|
||||
// Try to find another producer
|
||||
var anotherProducer = queue.MostLikelyProducer();
|
||||
if (anotherProducer.Trait != null && anotherProducer.Trait is ProductionBulkAirdrop anotherStarport)
|
||||
anotherStarport.DeliverOrder(anotherProducer.Actor, orderedActors, productionType, queue);
|
||||
else
|
||||
queue.DeliverFinished();
|
||||
return;
|
||||
}
|
||||
|
||||
var waitTickbeforeSpawn = 15;
|
||||
var aircraftInfo = producer.World.Map.Rules.Actors[info.ActorType].TraitInfo<AircraftInfo>();
|
||||
var exit = producer.RandomExitOrDefault(producer.World, productionType);
|
||||
var exitCell = producer.Location;
|
||||
if (exit != null)
|
||||
exitCell = producer.Location + exit.Info.ExitCell;
|
||||
|
||||
var destinations = rp != null && rp.Path.Count > 0 ? rp.Path : [exitCell];
|
||||
|
||||
// Aircraft fly in under their own power.
|
||||
foreach (var orderedAircraft in orderedActors.Where(actor => actor.Actor.HasTraitInfo<AircraftInfo>()))
|
||||
{
|
||||
var altitude = orderedAircraft.Actor.TraitInfo<AircraftInfo>().CruiseAltitude;
|
||||
var aircraft = w.CreateActor(orderedAircraft.Actor.Name,
|
||||
[
|
||||
new CenterPositionInit(w.Map.CenterOfCell(startPos) + new WVec(WDist.Zero, WDist.Zero, altitude)),
|
||||
new OwnerInit(owner),
|
||||
new FacingInit(spawnFacing)
|
||||
]);
|
||||
var move = aircraft.TraitOrDefault<IMove>();
|
||||
if (move != null)
|
||||
{
|
||||
aircraft.QueueActivity(new Wait(waitTickbeforeSpawn));
|
||||
waitTickbeforeSpawn += 15;
|
||||
|
||||
// first move must be to the Producer location
|
||||
aircraft.QueueActivity(move.MoveTo(exitCell, 2, evaluateNearestMovableCell: true));
|
||||
foreach (var cell in destinations)
|
||||
aircraft.QueueActivity(move.MoveTo(cell, 2, evaluateNearestMovableCell: true));
|
||||
}
|
||||
}
|
||||
|
||||
orderedActors.RemoveAll(actor => actor.Actor.HasTraitInfo<AircraftInfo>());
|
||||
if (orderedActors.Count == 0)
|
||||
{
|
||||
queue.DeliverFinished();
|
||||
return;
|
||||
}
|
||||
|
||||
var transport = w.CreateActor(info.ActorType,
|
||||
[
|
||||
new CenterPositionInit(w.Map.CenterOfCell(startPos) + new WVec(WDist.Zero, WDist.Zero, aircraftInfo.CruiseAltitude)),
|
||||
new OwnerInit(owner),
|
||||
new FacingInit(spawnFacing)
|
||||
]);
|
||||
|
||||
transport.QueueActivity(new DeliverBulkOrder(transport, producer, orderedActors, productionType, queue));
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public Exit PublicExit(Actor self, ActorInfo producee, string productionType)
|
||||
{
|
||||
return SelectExit(self, producee, productionType);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -50,6 +50,8 @@ namespace OpenRA.Mods.Common.Widgets
|
||||
public readonly int2 IconSpriteOffset = int2.Zero;
|
||||
|
||||
public readonly float2 QueuedOffset = new(4, 2);
|
||||
|
||||
public readonly float2 BulkOffset = new(4, 2);
|
||||
public readonly TextAlign QueuedTextAlign = TextAlign.Left;
|
||||
|
||||
public readonly string ClickSound = ChromeMetrics.Get<string>("ClickSound");
|
||||
@@ -363,6 +365,24 @@ namespace OpenRA.Mods.Common.Widgets
|
||||
|
||||
bool HandleRightClick(ProductionItem item, ProductionIcon icon, int handleCount)
|
||||
{
|
||||
if (CurrentQueue is BulkProductionQueue bulkProductionQueue && !bulkProductionQueue.HasDeliveryStarted())
|
||||
{
|
||||
var readyActors = bulkProductionQueue.GetActorsReadyForDelivery();
|
||||
if (readyActors.Any(a => a.Actor.Name == icon.Name))
|
||||
{
|
||||
World.IssueOrder(
|
||||
new Order("ReturnOrder", CurrentQueue.Actor, false)
|
||||
{
|
||||
ExtraData = (uint)handleCount,
|
||||
TargetString = icon.Name
|
||||
});
|
||||
Game.Sound.PlayNotification(World.Map.Rules, World.LocalPlayer, "Sounds", ClickSound, null);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
if (item == null)
|
||||
return false;
|
||||
|
||||
@@ -390,17 +410,18 @@ namespace OpenRA.Mods.Common.Widgets
|
||||
|
||||
bool HandleMiddleClick(ProductionItem item, ProductionIcon icon, int handleCount)
|
||||
{
|
||||
if (item == null)
|
||||
return false;
|
||||
if (item != null)
|
||||
{
|
||||
// Directly cancel, skipping "on-hold"
|
||||
Game.Sound.PlayNotification(World.Map.Rules, World.LocalPlayer, "Sounds", ClickSound, null);
|
||||
Game.Sound.PlayNotification(World.Map.Rules, World.LocalPlayer, "Speech", CurrentQueue.Info.CancelledAudio, World.LocalPlayer.Faction.InternalName);
|
||||
TextNotificationsManager.AddTransientLine(World.LocalPlayer, CurrentQueue.Info.CancelledTextNotification);
|
||||
|
||||
// Directly cancel, skipping "on-hold"
|
||||
Game.Sound.PlayNotification(World.Map.Rules, World.LocalPlayer, "Sounds", ClickSound, null);
|
||||
Game.Sound.PlayNotification(World.Map.Rules, World.LocalPlayer, "Speech", CurrentQueue.Info.CancelledAudio, World.LocalPlayer.Faction.InternalName);
|
||||
TextNotificationsManager.AddTransientLine(World.LocalPlayer, CurrentQueue.Info.CancelledTextNotification);
|
||||
World.IssueOrder(Order.CancelProduction(CurrentQueue.Actor, icon.Name, handleCount));
|
||||
return true;
|
||||
}
|
||||
|
||||
World.IssueOrder(Order.CancelProduction(CurrentQueue.Actor, icon.Name, handleCount));
|
||||
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool HandleEvent(ProductionIcon icon, MouseButton btn, Modifiers modifiers)
|
||||
@@ -568,10 +589,13 @@ namespace OpenRA.Mods.Common.Widgets
|
||||
var waiting = !CurrentQueue.IsProducing(first) && !first.Done;
|
||||
if (first.Done)
|
||||
{
|
||||
if (ReadyTextStyle == ReadyTextStyleOptions.Solid || orderManager.LocalFrameNumber * worldRenderer.World.Timestep / 360 % 2 == 0)
|
||||
overlayFont.DrawTextWithContrast(ReadyText, icon.Pos + readyOffset, TextColor, Color.Black, 1);
|
||||
else if (ReadyTextStyle == ReadyTextStyleOptions.AlternatingColor)
|
||||
overlayFont.DrawTextWithContrast(ReadyText, icon.Pos + readyOffset, ReadyTextAltColor, Color.Black, 1);
|
||||
if (CurrentQueue is not BulkProductionQueue)
|
||||
{
|
||||
if (ReadyTextStyle == ReadyTextStyleOptions.Solid || orderManager.LocalFrameNumber * worldRenderer.World.Timestep / 360 % 2 == 0)
|
||||
overlayFont.DrawTextWithContrast(ReadyText, icon.Pos + readyOffset, TextColor, Color.Black, 1);
|
||||
else if (ReadyTextStyle == ReadyTextStyleOptions.AlternatingColor)
|
||||
overlayFont.DrawTextWithContrast(ReadyText, icon.Pos + readyOffset, ReadyTextAltColor, Color.Black, 1);
|
||||
}
|
||||
}
|
||||
else if (first.Paused)
|
||||
overlayFont.DrawTextWithContrast(HoldText,
|
||||
@@ -603,6 +627,14 @@ namespace OpenRA.Mods.Common.Widgets
|
||||
TextColor, Color.Black, 1);
|
||||
}
|
||||
}
|
||||
|
||||
if (CurrentQueue is BulkProductionQueue bulkProductionQueue)
|
||||
{
|
||||
var readyActors = bulkProductionQueue.GetActorsReadyForDelivery().
|
||||
Count(a => a.Actor.Name == icon.Name);
|
||||
overlayFont.DrawTextWithContrast(readyActors.ToString(NumberFormatInfo.CurrentInfo),
|
||||
icon.Pos + BulkOffset, TextColor, Color.Black, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
84
OpenRA.Mods.D2k/Widgets/Logic/PurchaseWidgetLogic.cs
Normal file
84
OpenRA.Mods.D2k/Widgets/Logic/PurchaseWidgetLogic.cs
Normal file
@@ -0,0 +1,84 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright (c) The OpenRA Developers and Contributors
|
||||
* 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, either version 3 of
|
||||
* the License, or (at your option) any later version. For more
|
||||
* information, see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using OpenRA.Mods.Common.Traits;
|
||||
using OpenRA.Mods.Common.Widgets;
|
||||
using OpenRA.Primitives;
|
||||
using OpenRA.Widgets;
|
||||
|
||||
namespace OpenRA.Mods.D2k.Widgets.Logic
|
||||
{
|
||||
public class PurchaseWidgetLogic : ChromeLogic
|
||||
{
|
||||
[FluentReference("time")]
|
||||
const string DeliveryIn = "label-deliver-in-timer";
|
||||
string time = "";
|
||||
readonly World world;
|
||||
readonly Lazy<ProductionPaletteWidget> paletteWidget;
|
||||
readonly ButtonWidget purchaseButton;
|
||||
readonly LabelWidget deliveryLabel;
|
||||
readonly Color textColor;
|
||||
|
||||
[ObjectCreator.UseCtor]
|
||||
public PurchaseWidgetLogic(Widget widget, World world)
|
||||
{
|
||||
this.world = world;
|
||||
purchaseButton = widget.Get<ButtonWidget>("PURCHASE_BUTTON");
|
||||
deliveryLabel = widget.Get<LabelWidget>("DELIVERY_IN");
|
||||
textColor = deliveryLabel.TextColor;
|
||||
var textCache = new CachedTransform<string, string>(s => FluentProvider.GetMessage(DeliveryIn, "time", time));
|
||||
deliveryLabel.GetText = () => textCache.Update(time);
|
||||
paletteWidget = Exts.Lazy(() => Ui.Root.Get("PRODUCTION_PALETTE") as ProductionPaletteWidget);
|
||||
purchaseButton.IsVisible = () => false;
|
||||
purchaseButton.OnClick = ResolveOrder;
|
||||
}
|
||||
|
||||
public override void Tick()
|
||||
{
|
||||
if (world.LocalPlayer == null)
|
||||
return;
|
||||
if (paletteWidget.Value.CurrentQueue is BulkProductionQueue bulkProduction)
|
||||
{
|
||||
purchaseButton.IsVisible = () => bulkProduction.GetActorsReadyForDelivery().
|
||||
Count != 0 && !bulkProduction.HasDeliveryStarted();
|
||||
}
|
||||
else
|
||||
{
|
||||
purchaseButton.IsVisible = () => false;
|
||||
}
|
||||
|
||||
foreach (var bulkQueue in world.LocalPlayer.PlayerActor.TraitsImplementing<BulkProductionQueue>())
|
||||
{
|
||||
if (bulkQueue.HasDeliveryStarted())
|
||||
{
|
||||
time = WidgetUtils.FormatTime(bulkQueue.DeliveryDelay, world.Timestep);
|
||||
deliveryLabel.Visible = true;
|
||||
if (bulkQueue.DeliveryDelay <= 0 && Game.LocalTick % 25 < 15)
|
||||
deliveryLabel.TextColor = Color.White;
|
||||
else
|
||||
deliveryLabel.TextColor = textColor;
|
||||
}
|
||||
else
|
||||
deliveryLabel.Visible = false;
|
||||
}
|
||||
}
|
||||
|
||||
void ResolveOrder()
|
||||
{
|
||||
world.IssueOrder(
|
||||
new Order("PurchaseOrder", paletteWidget.Value.CurrentQueue.Actor, false)
|
||||
{
|
||||
TargetString = paletteWidget.Value.CurrentQueue.Info.Type
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -17,7 +17,9 @@ sidebar:
|
||||
Regions:
|
||||
background-top: 0, 0, 226, 295
|
||||
background-iconrow: 0, 295, 226, 48
|
||||
background-bottom: 0, 343, 226, 13
|
||||
background-bottom: 0, 343, 226, 28
|
||||
purchase-button: 226, 281, 125, 29
|
||||
|
||||
|
||||
sidebar-button:
|
||||
Inherits: ^Chrome
|
||||
@@ -115,40 +117,31 @@ production-icons:
|
||||
order-icons:
|
||||
Inherits: ^Chrome
|
||||
Regions:
|
||||
debug: 10, 357, 34, 35
|
||||
debug-disabled: 10, 393, 34, 35
|
||||
debug-active: 10, 430, 34, 35
|
||||
guard: 49, 357, 34, 35
|
||||
guard-disabled: 49, 393, 34, 35
|
||||
guard-active: 49, 430, 34, 35
|
||||
repair: 88, 357, 34, 35
|
||||
repair-disabled: 88, 393, 34, 35
|
||||
repair-active: 88, 430, 34, 35
|
||||
sell: 127, 357, 34, 35
|
||||
sell-disabled: 127, 393, 34, 35
|
||||
sell-active: 127, 430, 34, 35
|
||||
options: 165, 357, 40, 38
|
||||
options-disabled: 165, 393, 40, 38
|
||||
options-active: 165, 430, 40, 38
|
||||
beacon: 209, 357, 36, 35
|
||||
beacon-disabled: 209, 393, 36, 35
|
||||
beacon-active: 209, 430, 36, 35
|
||||
power: 248, 357, 34, 35
|
||||
power-disabled: 248, 393, 36, 35
|
||||
power-active: 248, 430, 36, 35
|
||||
diplomacy: 287, 357, 36, 35
|
||||
diplomacy-disabled: 287, 393, 36, 35
|
||||
diplomacy-active: 287, 430, 36, 35
|
||||
repair: 238, 357, 34, 35
|
||||
repair-disabled: 238, 393, 34, 35
|
||||
repair-active: 238, 430, 34, 35
|
||||
sell: 277, 357, 34, 35
|
||||
sell-disabled: 277, 393, 34, 35
|
||||
sell-active: 277, 430, 34, 35
|
||||
options: 315, 357, 40, 38
|
||||
options-disabled: 315, 393, 40, 38
|
||||
options-active: 315, 430, 40, 38
|
||||
beacon: 359, 357, 36, 35
|
||||
beacon-disabled: 359, 393, 36, 35
|
||||
beacon-active: 359, 430, 36, 35
|
||||
power: 398, 357, 34, 35
|
||||
power-disabled: 398, 393, 36, 35
|
||||
power-active: 398, 430, 36, 35
|
||||
|
||||
command-button:
|
||||
Inherits: ^Chrome
|
||||
PanelRegion: 331, 427, 0, 0, 34, 41, 0, 0
|
||||
PanelRegion: 232, 312, 0, 0, 34, 41, 0, 0
|
||||
PanelSides: Center
|
||||
command-button-hover:
|
||||
Inherits: command-button
|
||||
command-button-pressed:
|
||||
Inherits: ^Chrome
|
||||
PanelRegion: 401, 427, 0, 0, 34, 41, 0, 0
|
||||
PanelRegion: 301, 312, 0, 0, 34, 41, 0, 0
|
||||
PanelSides: Center
|
||||
command-button-highlighted:
|
||||
Inherits: command-button-pressed
|
||||
@@ -158,7 +151,7 @@ command-button-highlighted-pressed:
|
||||
Inherits: command-button-pressed
|
||||
command-button-disabled:
|
||||
Inherits: ^Chrome
|
||||
PanelRegion: 366, 427, 0, 0, 34, 41, 0, 0
|
||||
PanelRegion: 266, 312, 0, 0, 34, 41, 0, 0
|
||||
PanelSides: Center
|
||||
command-button-highlighted-disabled:
|
||||
Inherits: command-button-disabled
|
||||
@@ -304,6 +297,33 @@ progressbar-bg:
|
||||
progressbar-thumb:
|
||||
Inherits: button
|
||||
|
||||
button-purchase:
|
||||
Inherits: ^Chrome
|
||||
PanelRegion: 226, 281, 0, 0, 125, 29,0 ,0
|
||||
|
||||
button-purchase-disabled:
|
||||
Inherits: button-purchase
|
||||
|
||||
button-purchase-hover:
|
||||
Inherits: ^Chrome
|
||||
PanelRegion: 226, 252, 0, 0, 125, 29,0 ,0
|
||||
|
||||
button-purchase-highlighted:
|
||||
Inherits: button-purchase-hover
|
||||
|
||||
button-purchase-highlighted-hover:
|
||||
Inherits: button-purchase-hover
|
||||
|
||||
button-purchase-pressed:
|
||||
Inherits: ^Chrome
|
||||
PanelRegion: 226, 223, 0, 0, 125, 29,0 ,0
|
||||
|
||||
button-purchase-highlighted-pressed:
|
||||
Inherits: button-purchase-pressed
|
||||
|
||||
button-purchase-highlighted-disabled:
|
||||
Inherits: button-purchase
|
||||
|
||||
button:
|
||||
Inherits: ^Dialog
|
||||
PanelRegion: 513, 1, 2, 2, 122, 122, 2, 2
|
||||
|
||||
@@ -486,6 +486,28 @@ Container@PLAYER_WIDGETS:
|
||||
ClickThrough: false
|
||||
ImageCollection: sidebar
|
||||
ImageName: background-bottom
|
||||
Children:
|
||||
Container@PURCHASE_PANEL:
|
||||
Logic: PurchaseWidgetLogic
|
||||
Children:
|
||||
Button@PURCHASE_BUTTON:
|
||||
X: PARENT_LEFT + 65
|
||||
Y: PARENT_TOP + 1
|
||||
Width: 125
|
||||
Height: 29
|
||||
Font: Bold
|
||||
Background: button-purchase
|
||||
TooltipText: purchase-panel-button-tooltip
|
||||
TooltipContainer: TOOLTIP_CONTAINER
|
||||
Label@DELIVERY_IN:
|
||||
X: PARENT_TOP + 80
|
||||
Y: PARENT_LEFT + 3
|
||||
TextColor: FFD091
|
||||
Width: 100
|
||||
Height: 22
|
||||
Align: Center
|
||||
Font: Bold
|
||||
Text: purchase-panel-label-delivery
|
||||
LogicTicker@PRODUCTION_TICKER:
|
||||
ProductionPalette@PRODUCTION_PALETTE:
|
||||
X: 39
|
||||
|
||||
@@ -57,6 +57,7 @@ label-combat-stats-buildings-killed-header = B. Killed
|
||||
label-combat-stats-buildings-dead-header = B. Lost
|
||||
label-combat-stats-army-value-header = Army Value
|
||||
label-combat-stats-vision-header = Vision
|
||||
label-deliver-in-timer = DELIVERY IN: { $time }
|
||||
|
||||
## ingame-observer.yaml, ingame-player.yaml
|
||||
label-mute-indicator = Audio Muted
|
||||
@@ -179,6 +180,9 @@ productionpalette-sidebar-production-palette =
|
||||
.ready = READY
|
||||
.hold = ON HOLD
|
||||
|
||||
purchase-panel-button-tooltip = Order selected units
|
||||
purchase-panel-label-delivery = DELIVERY IN:
|
||||
|
||||
button-production-types-building-tooltip = Buildings
|
||||
button-production-types-infantry-tooltip = Infantry
|
||||
button-production-types-vehicle-tooltip = Light Vehicles
|
||||
|
||||
@@ -24,9 +24,6 @@ carryall.reinforce:
|
||||
Cargo:
|
||||
MaxWeight: 10
|
||||
|
||||
frigate:
|
||||
Aircraft:
|
||||
LandableTerrainTypes: Sand, Rock, Transition, Spice, SpiceSand, Dune, Concrete
|
||||
|
||||
barracks.harkonnen:
|
||||
Inherits: barracks
|
||||
|
||||
@@ -86,16 +86,17 @@ frigate:
|
||||
Aircraft:
|
||||
IdleBehavior: LeaveMap
|
||||
Speed: 189
|
||||
TurnSpeed: 4
|
||||
TurnSpeed: 16
|
||||
Repulsable: False
|
||||
MaximumPitch: 20
|
||||
CruiseAltitude: 2048
|
||||
VTOL: true
|
||||
TakeOffOnCreation: false
|
||||
CanHover: true
|
||||
CanSlide: true
|
||||
-AppearsOnRadar:
|
||||
Cargo:
|
||||
MaxWeight: 20
|
||||
BetweenUnloadDelay: 15
|
||||
RejectsOrders:
|
||||
|
||||
ornithopter:
|
||||
|
||||
@@ -63,13 +63,15 @@ Player:
|
||||
CancelledAudio: Cancelled
|
||||
SpeedUp: true
|
||||
BuildTimeSpeedReduction: 100, 66, 50
|
||||
ClassicProductionQueue@Starport:
|
||||
BulkProductionQueue@Starport:
|
||||
Type: Starport
|
||||
DisplayOrder: 4
|
||||
BuildDurationModifier: 212
|
||||
QueueLimit: 6
|
||||
PayUpFront: true
|
||||
StartDeliveryNotification: OrderPlaced
|
||||
DeliveryProgressNotifications: TMinusFive, TMinusFour, TMinusThree, TMinusTwo, TMinusOne
|
||||
BlockedAudio: NoRoom
|
||||
BlockedTextNotification: notification-no-room-for-new-unit
|
||||
QueuedAudio: OrderPlaced
|
||||
OnHoldAudio: OnHold
|
||||
CancelledAudio: Cancelled
|
||||
ClassicProductionQueue@Aircraft:
|
||||
|
||||
@@ -3,6 +3,7 @@ mcv.starport:
|
||||
Buildable:
|
||||
Prerequisites: starport
|
||||
Queue: Starport
|
||||
BuildDuration: 0
|
||||
Valued:
|
||||
Cost: 2500
|
||||
-MapEditorData:
|
||||
@@ -16,6 +17,7 @@ harvester.starport:
|
||||
Buildable:
|
||||
Prerequisites: starport
|
||||
Queue: Starport
|
||||
BuildDuration: 0
|
||||
Valued:
|
||||
Cost: 1500
|
||||
-MapEditorData:
|
||||
@@ -29,6 +31,7 @@ trike.starport:
|
||||
Buildable:
|
||||
Prerequisites: starport
|
||||
Queue: Starport
|
||||
BuildDuration: 0
|
||||
Valued:
|
||||
Cost: 315
|
||||
-MapEditorData:
|
||||
@@ -42,6 +45,7 @@ quad.starport:
|
||||
Buildable:
|
||||
Prerequisites: starport
|
||||
Queue: Starport
|
||||
BuildDuration: 0
|
||||
Valued:
|
||||
Cost: 500
|
||||
-MapEditorData:
|
||||
@@ -55,6 +59,7 @@ siege_tank.starport:
|
||||
Buildable:
|
||||
Prerequisites: starport
|
||||
Queue: Starport
|
||||
BuildDuration: 0
|
||||
Valued:
|
||||
Cost: 1075
|
||||
-MapEditorData:
|
||||
@@ -68,6 +73,7 @@ missile_tank.starport:
|
||||
Buildable:
|
||||
Prerequisites: starport
|
||||
Queue: Starport
|
||||
BuildDuration: 0
|
||||
Valued:
|
||||
Cost: 1250
|
||||
-MapEditorData:
|
||||
@@ -81,6 +87,7 @@ combat_tank_a.starport:
|
||||
Buildable:
|
||||
Prerequisites: ~starport.atreides_combat
|
||||
Queue: Starport
|
||||
BuildDuration: 0
|
||||
Valued:
|
||||
Cost: 875
|
||||
-MapEditorData:
|
||||
@@ -94,6 +101,7 @@ combat_tank_h.starport:
|
||||
Buildable:
|
||||
Prerequisites: ~starport.harkonnen_combat
|
||||
Queue: Starport
|
||||
BuildDuration: 0
|
||||
Valued:
|
||||
Cost: 875
|
||||
-MapEditorData:
|
||||
@@ -107,6 +115,7 @@ combat_tank_o.starport:
|
||||
Buildable:
|
||||
Prerequisites: ~starport.ordos_combat
|
||||
Queue: Starport
|
||||
BuildDuration: 0
|
||||
Valued:
|
||||
Cost: 875
|
||||
-MapEditorData:
|
||||
@@ -120,6 +129,7 @@ carryall.starport:
|
||||
Buildable:
|
||||
Prerequisites: starport
|
||||
Queue: Starport
|
||||
BuildDuration: 0
|
||||
Valued:
|
||||
Cost: 1500
|
||||
-MapEditorData:
|
||||
|
||||
@@ -657,9 +657,7 @@ starport:
|
||||
Exit@2:
|
||||
SpawnOffset: 0,-480,0
|
||||
ExitCell: 0,2
|
||||
ProductionAirdrop:
|
||||
WaitTickBeforeProduce: 10
|
||||
WaitTickAfterProduce: 15
|
||||
ProductionBulkAirdrop:
|
||||
LandOffset: 0, -256, 0
|
||||
Produces: Starport
|
||||
ActorType: frigate
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 169 KiB After Width: | Height: | Size: 202 KiB |
Reference in New Issue
Block a user