Move Production to Mods.Common
This commit is contained in:
@@ -194,6 +194,7 @@
|
||||
<Compile Include="Traits\Buildings\FreeActor.cs" />
|
||||
<Compile Include="Traits\Buildings\LineBuild.cs" />
|
||||
<Compile Include="Traits\Buildings\LineBuildNode.cs" />
|
||||
<Compile Include="Traits\Buildings\PrimaryBuilding.cs" />
|
||||
<Compile Include="Traits\Buildings\RallyPoint.cs" />
|
||||
<Compile Include="Traits\Buildings\RepairableBuilding.cs" />
|
||||
<Compile Include="Traits\Buildings\RepairsUnits.cs" />
|
||||
@@ -245,11 +246,14 @@
|
||||
<Compile Include="Traits\PaletteEffects\WaterPaletteRotation.cs" />
|
||||
<Compile Include="Traits\Player\ActorGroupProxy.cs" />
|
||||
<Compile Include="Traits\Player\AllyRepair.cs" />
|
||||
<Compile Include="Traits\Player\ClassicProductionQueue.cs" />
|
||||
<Compile Include="Traits\Player\ConquestVictoryConditions.cs" />
|
||||
<Compile Include="Traits\Player\GlobalUpgradeManager.cs" />
|
||||
<Compile Include="Traits\Player\MissionObjectives.cs" />
|
||||
<Compile Include="Traits\Player\PlaceBeacon.cs" />
|
||||
<Compile Include="Traits\Player\PlaceBuilding.cs" />
|
||||
<Compile Include="Traits\Player\PlayerStatistics.cs" />
|
||||
<Compile Include="Traits\Player\ProductionQueue.cs" />
|
||||
<Compile Include="Traits\Player\ProvidesCustomPrerequisite.cs" />
|
||||
<Compile Include="Traits\Player\ProvidesTechPrerequisite.cs" />
|
||||
<Compile Include="Traits\Player\StrategicVictoryConditions.cs" />
|
||||
@@ -260,6 +264,8 @@
|
||||
<Compile Include="Traits\Power\Power.cs" />
|
||||
<Compile Include="Traits\Power\RequiresPower.cs" />
|
||||
<Compile Include="Traits\Power\ScalePowerWithHealth.cs" />
|
||||
<Compile Include="Traits\Production.cs" />
|
||||
<Compile Include="Traits\ProductionQueueFromSelection.cs" />
|
||||
<Compile Include="Traits\ProvidesRadar.cs" />
|
||||
<Compile Include="Traits\ProximityCaptor.cs" />
|
||||
<Compile Include="Traits\ProximityCapturable.cs" />
|
||||
@@ -269,10 +275,12 @@
|
||||
<Compile Include="Traits\Render\RenderBuilding.cs" />
|
||||
<Compile Include="Traits\Render\RenderBuildingCharge.cs" />
|
||||
<Compile Include="Traits\Render\RenderBuildingTurreted.cs" />
|
||||
<Compile Include="Traits\Render\RenderBuildingWarFactory.cs" />
|
||||
<Compile Include="Traits\Render\RenderEditorOnly.cs" />
|
||||
<Compile Include="Traits\Render\RenderFlare.cs" />
|
||||
<Compile Include="Traits\Render\RenderInfantry.cs" />
|
||||
<Compile Include="Traits\Render\RenderNameTag.cs" />
|
||||
<Compile Include="Traits\Render\ProductionBar.cs" />
|
||||
<Compile Include="Traits\Render\RenderSimple.cs" />
|
||||
<Compile Include="Traits\Render\RenderSprites.cs" />
|
||||
<Compile Include="Traits\Render\RenderUnit.cs" />
|
||||
@@ -381,7 +389,11 @@
|
||||
<Compile Include="Widgets\Logic\Ingame\IngameRadarDisplayLogic.cs" />
|
||||
<Compile Include="Widgets\Logic\Ingame\LoadIngamePlayerOrObserverUILogic.cs" />
|
||||
<Compile Include="Widgets\Logic\ModBrowserLogic.cs" />
|
||||
<Compile Include="Widgets\Logic\ProductionTooltipLogic.cs" />
|
||||
<Compile Include="Widgets\MenuButtonWidget.cs" />
|
||||
<Compile Include="Widgets\ProductionPaletteWidget.cs" />
|
||||
<Compile Include="Widgets\ProductionTabsWidget.cs" />
|
||||
<Compile Include="Widgets\ProductionTypeButtonWidget.cs" />
|
||||
<Compile Include="Widgets\RadarWidget.cs" />
|
||||
<Compile Include="Widgets\ResourceBarWidget.cs" />
|
||||
<Compile Include="Widgets\StrategicProgressWidget.cs" />
|
||||
|
||||
86
OpenRA.Mods.Common/Traits/Buildings/PrimaryBuilding.cs
Normal file
86
OpenRA.Mods.Common/Traits/Buildings/PrimaryBuilding.cs
Normal file
@@ -0,0 +1,86 @@
|
||||
#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.Mods.Common.Orders;
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.Mods.Common.Traits
|
||||
{
|
||||
static class PrimaryExts
|
||||
{
|
||||
public static bool IsPrimaryBuilding(this Actor a)
|
||||
{
|
||||
var pb = a.TraitOrDefault<PrimaryBuilding>();
|
||||
return pb != null && pb.IsPrimary;
|
||||
}
|
||||
}
|
||||
|
||||
[Desc("Used together with ClassicProductionQueue.")]
|
||||
public class PrimaryBuildingInfo : TraitInfo<PrimaryBuilding> { }
|
||||
|
||||
public class PrimaryBuilding : IIssueOrder, IResolveOrder, ITags
|
||||
{
|
||||
bool isPrimary = false;
|
||||
public bool IsPrimary { get { return isPrimary; } }
|
||||
|
||||
public IEnumerable<TagType> GetTags()
|
||||
{
|
||||
yield return isPrimary ? TagType.Primary : TagType.None;
|
||||
}
|
||||
|
||||
public IEnumerable<IOrderTargeter> Orders
|
||||
{
|
||||
get { yield return new DeployOrderTargeter("PrimaryProducer", 1); }
|
||||
}
|
||||
|
||||
public Order IssueOrder(Actor self, IOrderTargeter order, Target target, bool queued)
|
||||
{
|
||||
if (order.OrderID == "PrimaryProducer")
|
||||
return new Order(order.OrderID, self, false);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public void ResolveOrder(Actor self, Order order)
|
||||
{
|
||||
if (order.OrderString == "PrimaryProducer")
|
||||
SetPrimaryProducer(self, !isPrimary);
|
||||
}
|
||||
|
||||
public void SetPrimaryProducer(Actor self, bool state)
|
||||
{
|
||||
if (state == false)
|
||||
{
|
||||
isPrimary = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: THIS IS SHIT
|
||||
// Cancel existing primaries
|
||||
foreach (var p in self.Info.Traits.Get<ProductionInfo>().Produces)
|
||||
{
|
||||
var productionType = p; // benign closure hazard
|
||||
foreach (var b in self.World
|
||||
.ActorsWithTrait<PrimaryBuilding>()
|
||||
.Where(a =>
|
||||
a.Actor.Owner == self.Owner &&
|
||||
a.Trait.IsPrimary &&
|
||||
a.Actor.Info.Traits.Get<ProductionInfo>().Produces.Contains(productionType)))
|
||||
b.Trait.SetPrimaryProducer(b.Actor, false);
|
||||
}
|
||||
|
||||
isPrimary = true;
|
||||
|
||||
Sound.PlayNotification(self.World.Map.Rules, self.Owner, "Speech", "PrimaryBuildingSelected", self.Owner.Country.Race);
|
||||
}
|
||||
}
|
||||
}
|
||||
136
OpenRA.Mods.Common/Traits/Player/ClassicProductionQueue.cs
Normal file
136
OpenRA.Mods.Common/Traits/Player/ClassicProductionQueue.cs
Normal file
@@ -0,0 +1,136 @@
|
||||
#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.Traits;
|
||||
|
||||
namespace OpenRA.Mods.Common.Traits
|
||||
{
|
||||
[Desc("Attach this to the player actor (not a building!) to define a new shared build queue.",
|
||||
"Will only work together with the Production: trait on the actor that actually does the production.",
|
||||
"You will also want to add PrimaryBuildings: to let the user choose where new units should exit.")]
|
||||
public class ClassicProductionQueueInfo : ProductionQueueInfo, Requires<TechTreeInfo>, Requires<PowerManagerInfo>, Requires<PlayerResourcesInfo>
|
||||
{
|
||||
[Desc("If you build more actors of the same type,", "the same queue will get its build time lowered for every actor produced there.")]
|
||||
public readonly bool SpeedUp = false;
|
||||
|
||||
[Desc("Every time another production building of the same queue is",
|
||||
"contructed, the build times of all actors in the queue",
|
||||
"decreased by a percentage of the original time.")]
|
||||
public readonly int[] BuildTimeSpeedReduction = { 100, 85, 75, 65, 60, 55, 50 };
|
||||
|
||||
public override object Create(ActorInitializer init) { return new ClassicProductionQueue(init, this); }
|
||||
}
|
||||
|
||||
public class ClassicProductionQueue : ProductionQueue, ISync
|
||||
{
|
||||
static readonly ActorInfo[] NoItems = { };
|
||||
|
||||
readonly Actor self;
|
||||
readonly ClassicProductionQueueInfo info;
|
||||
|
||||
public ClassicProductionQueue(ActorInitializer init, ClassicProductionQueueInfo info)
|
||||
: base(init, init.Self, info)
|
||||
{
|
||||
this.self = init.Self;
|
||||
this.info = info;
|
||||
}
|
||||
|
||||
[Sync] bool isActive = false;
|
||||
|
||||
public override void Tick(Actor self)
|
||||
{
|
||||
isActive = false;
|
||||
foreach (var x in self.World.ActorsWithTrait<Production>())
|
||||
{
|
||||
if (x.Actor.Owner == self.Owner && x.Trait.Info.Produces.Contains(Info.Type))
|
||||
{
|
||||
var b = x.Actor.TraitOrDefault<Building>();
|
||||
if (b != null && b.Locked)
|
||||
continue;
|
||||
isActive = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
base.Tick(self);
|
||||
}
|
||||
|
||||
public override IEnumerable<ActorInfo> AllItems()
|
||||
{
|
||||
return isActive ? base.AllItems() : NoItems;
|
||||
}
|
||||
|
||||
public override IEnumerable<ActorInfo> BuildableItems()
|
||||
{
|
||||
return isActive ? base.BuildableItems() : NoItems;
|
||||
}
|
||||
|
||||
protected override bool BuildUnit(string name)
|
||||
{
|
||||
// Find a production structure to build this actor
|
||||
var ai = self.World.Map.Rules.Actors[name];
|
||||
var bi = ai.Traits.GetOrDefault<BuildableInfo>();
|
||||
|
||||
// Some units may request a specific production type, which is ignored if the AllTech cheat is enabled
|
||||
var type = bi == null || developerMode.AllTech ? Info.Type : bi.BuildAtProductionType ?? Info.Type;
|
||||
|
||||
var producers = self.World.ActorsWithTrait<Production>()
|
||||
.Where(x => x.Actor.Owner == self.Owner
|
||||
&& x.Trait.Info.Produces.Contains(type))
|
||||
.OrderByDescending(x => x.Actor.IsPrimaryBuilding())
|
||||
.ThenByDescending(x => x.Actor.ActorID);
|
||||
|
||||
if (!producers.Any())
|
||||
{
|
||||
CancelProduction(name, 1);
|
||||
return true;
|
||||
}
|
||||
|
||||
foreach (var p in producers.Where(p => !p.Actor.IsDisabled()))
|
||||
{
|
||||
if (p.Trait.Produce(p.Actor, ai, Race))
|
||||
{
|
||||
FinishProduction();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public override int GetBuildTime(string unitString)
|
||||
{
|
||||
var ai = self.World.Map.Rules.Actors[unitString];
|
||||
var bi = ai.Traits.GetOrDefault<BuildableInfo>();
|
||||
if (bi == null)
|
||||
return 0;
|
||||
|
||||
if (self.World.AllowDevCommands && self.Owner.PlayerActor.Trait<DeveloperMode>().FastBuild)
|
||||
return 0;
|
||||
|
||||
var time = (int)(ai.GetBuildTime() * Info.BuildSpeed);
|
||||
|
||||
if (info.SpeedUp)
|
||||
{
|
||||
var type = bi.BuildAtProductionType ?? info.Type;
|
||||
|
||||
var selfsameBuildingsCount = self.World.ActorsWithTrait<Production>()
|
||||
.Count(p => p.Actor.Owner == self.Owner && p.Trait.Info.Produces.Contains(type));
|
||||
|
||||
var speedModifier = selfsameBuildingsCount.Clamp(1, info.BuildTimeSpeedReduction.Length) - 1;
|
||||
time = (time * info.BuildTimeSpeedReduction[speedModifier]) / 100;
|
||||
}
|
||||
|
||||
return time;
|
||||
}
|
||||
}
|
||||
}
|
||||
131
OpenRA.Mods.Common/Traits/Player/PlaceBuilding.cs
Normal file
131
OpenRA.Mods.Common/Traits/Player/PlaceBuilding.cs
Normal file
@@ -0,0 +1,131 @@
|
||||
#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.Effects;
|
||||
using OpenRA.Primitives;
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.Mods.Common.Traits
|
||||
{
|
||||
[Desc("Allows to execute build orders.", " Attach this to the player actor.")]
|
||||
class PlaceBuildingInfo : TraitInfo<PlaceBuilding> { }
|
||||
|
||||
class PlaceBuilding : IResolveOrder
|
||||
{
|
||||
public void ResolveOrder(Actor self, Order order)
|
||||
{
|
||||
if (order.OrderString == "PlaceBuilding" || order.OrderString == "LineBuild")
|
||||
{
|
||||
self.World.AddFrameEndTask(w =>
|
||||
{
|
||||
var prevItems = GetNumBuildables(self.Owner);
|
||||
|
||||
if (order.TargetActor.IsDead)
|
||||
return;
|
||||
|
||||
var unit = self.World.Map.Rules.Actors[order.TargetString];
|
||||
var queue = order.TargetActor.TraitsImplementing<ProductionQueue>()
|
||||
.FirstOrDefault(q => q.CanBuild(unit) && q.CurrentItem() != null && q.CurrentItem().Item == order.TargetString && q.CurrentItem().RemainingTime == 0);
|
||||
|
||||
if (queue == null)
|
||||
return;
|
||||
|
||||
var buildingInfo = unit.Traits.Get<BuildingInfo>();
|
||||
|
||||
if (order.OrderString == "LineBuild")
|
||||
{
|
||||
var playSounds = true;
|
||||
foreach (var t in BuildingUtils.GetLineBuildCells(w, order.TargetLocation, order.TargetString, buildingInfo))
|
||||
{
|
||||
var building = w.CreateActor(order.TargetString, new TypeDictionary
|
||||
{
|
||||
new LocationInit(t),
|
||||
new OwnerInit(order.Player),
|
||||
new RaceInit(queue.Race)
|
||||
});
|
||||
|
||||
if (playSounds)
|
||||
foreach (var s in buildingInfo.BuildSounds)
|
||||
Sound.PlayToPlayer(order.Player, s, building.CenterPosition);
|
||||
|
||||
playSounds = false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!self.World.CanPlaceBuilding(order.TargetString, buildingInfo, order.TargetLocation, null)
|
||||
|| !buildingInfo.IsCloseEnoughToBase(self.World, order.Player, order.TargetString, order.TargetLocation))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var building = w.CreateActor(order.TargetString, new TypeDictionary
|
||||
{
|
||||
new LocationInit(order.TargetLocation),
|
||||
new OwnerInit(order.Player),
|
||||
new RaceInit(queue.Race),
|
||||
});
|
||||
|
||||
foreach (var s in buildingInfo.BuildSounds)
|
||||
Sound.PlayToPlayer(order.Player, s, building.CenterPosition);
|
||||
}
|
||||
|
||||
PlayBuildAnim(self, unit);
|
||||
|
||||
queue.FinishProduction();
|
||||
|
||||
if (buildingInfo.RequiresBaseProvider)
|
||||
{
|
||||
// May be null if the build anywhere cheat is active
|
||||
// BuildingInfo.IsCloseEnoughToBase has already verified that this is a valid build location
|
||||
var producer = buildingInfo.FindBaseProvider(w, self.Owner, order.TargetLocation);
|
||||
if (producer != null)
|
||||
producer.Trait<BaseProvider>().BeginCooldown();
|
||||
}
|
||||
|
||||
if (GetNumBuildables(self.Owner) > prevItems)
|
||||
w.Add(new DelayedAction(10,
|
||||
() => Sound.PlayNotification(self.World.Map.Rules, order.Player, "Speech", "NewOptions", order.Player.Country.Race)));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// finds a construction yard (or equivalent) and runs its "build" animation.
|
||||
static void PlayBuildAnim(Actor self, ActorInfo unit)
|
||||
{
|
||||
var bi = unit.Traits.GetOrDefault<BuildableInfo>();
|
||||
if (bi == null)
|
||||
return;
|
||||
|
||||
var producers = self.World.ActorsWithTrait<Production>()
|
||||
.Where(x => x.Actor.Owner == self.Owner
|
||||
&& x.Actor.Info.Traits.Get<ProductionInfo>().Produces.Intersect(bi.Queue).Any())
|
||||
.ToList();
|
||||
var producer = producers.Where(x => x.Actor.IsPrimaryBuilding()).Concat(producers)
|
||||
.FirstOrDefault();
|
||||
|
||||
if (producer.Actor == null)
|
||||
return;
|
||||
|
||||
foreach (var nbp in producer.Actor.TraitsImplementing<INotifyBuildingPlaced>())
|
||||
nbp.BuildingPlaced(producer.Actor);
|
||||
}
|
||||
|
||||
static int GetNumBuildables(Player p)
|
||||
{
|
||||
if (p != p.World.LocalPlayer) return 0; // this only matters for local players.
|
||||
|
||||
return p.World.ActorsWithTrait<ProductionQueue>()
|
||||
.Where(a => a.Actor.Owner == p)
|
||||
.SelectMany(a => a.Trait.BuildableItems()).Distinct().Count();
|
||||
}
|
||||
}
|
||||
}
|
||||
475
OpenRA.Mods.Common/Traits/Player/ProductionQueue.cs
Normal file
475
OpenRA.Mods.Common/Traits/Player/ProductionQueue.cs
Normal file
@@ -0,0 +1,475 @@
|
||||
#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 System.Linq;
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.Mods.Common.Traits
|
||||
{
|
||||
[Desc("Attach this to an actor (usually a building) to let it produce units or construct buildings.",
|
||||
"If one builds another actor of this type, he will get a separate queue to create two actors",
|
||||
"at the same time. Will only work together with the Production: trait.")]
|
||||
public class ProductionQueueInfo : ITraitInfo
|
||||
{
|
||||
[Desc("What kind of production will be added (e.g. Building, Infantry, Vehicle, ...)")]
|
||||
public readonly string Type = null;
|
||||
|
||||
[Desc("Group queues from separate buildings together into the same tab.")]
|
||||
public readonly string Group = null;
|
||||
|
||||
[Desc("Filter buildable items based on their Owner.")]
|
||||
public readonly bool RequireOwner = true;
|
||||
|
||||
[Desc("Only enable this queue for certain factions")]
|
||||
public readonly string[] Race = { };
|
||||
|
||||
[Desc("Should the prerequisite remain enabled if the owner changes?")]
|
||||
public readonly bool Sticky = true;
|
||||
|
||||
[Desc("This value is used to translate the unit cost into build time.")]
|
||||
public readonly float BuildSpeed = 0.4f;
|
||||
|
||||
[Desc("The build time is multiplied with this value on low power.")]
|
||||
public readonly int LowPowerSlowdown = 3;
|
||||
|
||||
[Desc("Notification played when production is complete.",
|
||||
"The filename of the audio is defined per faction in notifications.yaml.")]
|
||||
public readonly string ReadyAudio = "UnitReady";
|
||||
|
||||
[Desc("Notification played when you can't train another unit",
|
||||
"when the build limit exceeded or the exit is jammed.",
|
||||
"The filename of the audio is defined per faction in notifications.yaml.")]
|
||||
public readonly string BlockedAudio = "NoBuild";
|
||||
|
||||
[Desc("Notification played when user clicks on the build palette icon.",
|
||||
"The filename of the audio is defined per faction in notifications.yaml.")]
|
||||
public readonly string QueuedAudio = "Training";
|
||||
|
||||
[Desc("Notification played when player right-clicks on the build palette icon.",
|
||||
"The filename of the audio is defined per faction in notifications.yaml.")]
|
||||
public readonly string OnHoldAudio = "OnHold";
|
||||
|
||||
[Desc("Notification played when player right-clicks on a build palette icon that is already on hold.",
|
||||
"The filename of the audio is defined per faction in notifications.yaml.")]
|
||||
public readonly string CancelledAudio = "Cancelled";
|
||||
|
||||
public virtual object Create(ActorInitializer init) { return new ProductionQueue(init, init.Self.Owner.PlayerActor, this); }
|
||||
}
|
||||
|
||||
public class ProductionQueue : IResolveOrder, ITick, ITechTreeElement, INotifyOwnerChanged, INotifyKilled, INotifySold, ISync, INotifyTransform
|
||||
{
|
||||
public readonly ProductionQueueInfo Info;
|
||||
readonly Actor self;
|
||||
|
||||
// Will change if the owner changes
|
||||
PowerManager playerPower;
|
||||
PlayerResources playerResources;
|
||||
protected DeveloperMode developerMode;
|
||||
|
||||
// A list of things we could possibly build
|
||||
Dictionary<ActorInfo, ProductionState> produceable;
|
||||
List<ProductionItem> queue = new List<ProductionItem>();
|
||||
|
||||
// A list of things we are currently building
|
||||
public Actor Actor { get { return self; } }
|
||||
|
||||
[Sync] public int QueueLength { get { return queue.Count; } }
|
||||
[Sync] public int CurrentRemainingCost { get { return QueueLength == 0 ? 0 : queue[0].RemainingCost; } }
|
||||
[Sync] public int CurrentRemainingTime { get { return QueueLength == 0 ? 0 : queue[0].RemainingTime; } }
|
||||
[Sync] public int CurrentSlowdown { get { return QueueLength == 0 ? 0 : queue[0].Slowdown; } }
|
||||
[Sync] public bool CurrentPaused { get { return QueueLength != 0 && queue[0].Paused; } }
|
||||
[Sync] public bool CurrentDone { get { return QueueLength != 0 && queue[0].Done; } }
|
||||
[Sync] public bool Enabled { get; private set; }
|
||||
|
||||
public string Race { get; private set; }
|
||||
|
||||
public ProductionQueue(ActorInitializer init, Actor playerActor, ProductionQueueInfo info)
|
||||
{
|
||||
self = init.Self;
|
||||
Info = info;
|
||||
playerResources = playerActor.Trait<PlayerResources>();
|
||||
playerPower = playerActor.Trait<PowerManager>();
|
||||
developerMode = playerActor.Trait<DeveloperMode>();
|
||||
|
||||
Race = init.Contains<RaceInit>() ? init.Get<RaceInit, string>() : self.Owner.Country.Race;
|
||||
Enabled = !info.Race.Any() || info.Race.Contains(Race);
|
||||
|
||||
CacheProduceables(playerActor);
|
||||
}
|
||||
|
||||
void ClearQueue()
|
||||
{
|
||||
if (queue.Count == 0)
|
||||
return;
|
||||
|
||||
// Refund the current item
|
||||
playerResources.GiveCash(queue[0].TotalCost - queue[0].RemainingCost);
|
||||
queue.Clear();
|
||||
}
|
||||
|
||||
public void OnOwnerChanged(Actor self, Player oldOwner, Player newOwner)
|
||||
{
|
||||
ClearQueue();
|
||||
|
||||
playerPower = newOwner.PlayerActor.Trait<PowerManager>();
|
||||
playerResources = newOwner.PlayerActor.Trait<PlayerResources>();
|
||||
developerMode = newOwner.PlayerActor.Trait<DeveloperMode>();
|
||||
|
||||
if (!Info.Sticky)
|
||||
{
|
||||
Race = self.Owner.Country.Race;
|
||||
Enabled = !Info.Race.Any() || Info.Race.Contains(Race);
|
||||
}
|
||||
|
||||
// Regenerate the produceables and tech tree state
|
||||
oldOwner.PlayerActor.Trait<TechTree>().Remove(this);
|
||||
CacheProduceables(newOwner.PlayerActor);
|
||||
newOwner.PlayerActor.Trait<TechTree>().Update();
|
||||
}
|
||||
|
||||
public void Killed(Actor killed, AttackInfo e) { if (killed == self) { ClearQueue(); Enabled = false; } }
|
||||
public void Selling(Actor self) { ClearQueue(); Enabled = false; }
|
||||
public void Sold(Actor self) { }
|
||||
|
||||
public void BeforeTransform(Actor self) { ClearQueue(); Enabled = false; }
|
||||
public void OnTransform(Actor self) { }
|
||||
public void AfterTransform(Actor self) { }
|
||||
|
||||
void CacheProduceables(Actor playerActor)
|
||||
{
|
||||
produceable = new Dictionary<ActorInfo, ProductionState>();
|
||||
if (!Enabled)
|
||||
return;
|
||||
|
||||
var ttc = playerActor.Trait<TechTree>();
|
||||
|
||||
foreach (var a in AllBuildables(Info.Type))
|
||||
{
|
||||
var bi = a.Traits.Get<BuildableInfo>();
|
||||
|
||||
// Can our race build this by satisfying normal prerequisites?
|
||||
var buildable = !Info.RequireOwner || bi.Owner.Contains(Race);
|
||||
|
||||
// Checks if Prerequisites want to hide the Actor from buildQueue if they are false
|
||||
produceable.Add(a, new ProductionState { Visible = buildable });
|
||||
|
||||
if (buildable)
|
||||
ttc.Add(a.Name, bi.Prerequisites, bi.BuildLimit, this);
|
||||
}
|
||||
}
|
||||
|
||||
IEnumerable<ActorInfo> AllBuildables(string category)
|
||||
{
|
||||
return self.World.Map.Rules.Actors.Values
|
||||
.Where(x =>
|
||||
x.Name[0] != '^' &&
|
||||
x.Traits.Contains<BuildableInfo>() &&
|
||||
x.Traits.Get<BuildableInfo>().Queue.Contains(category));
|
||||
}
|
||||
|
||||
public void PrerequisitesAvailable(string key)
|
||||
{
|
||||
produceable[self.World.Map.Rules.Actors[key]].Buildable = true;
|
||||
}
|
||||
|
||||
public void PrerequisitesUnavailable(string key)
|
||||
{
|
||||
produceable[self.World.Map.Rules.Actors[key]].Buildable = false;
|
||||
}
|
||||
|
||||
public void PrerequisitesItemHidden(string key)
|
||||
{
|
||||
produceable[self.World.Map.Rules.Actors[key]].Visible = false;
|
||||
}
|
||||
|
||||
public void PrerequisitesItemVisible(string key)
|
||||
{
|
||||
produceable[self.World.Map.Rules.Actors[key]].Visible = true;
|
||||
}
|
||||
|
||||
public ProductionItem CurrentItem()
|
||||
{
|
||||
return queue.ElementAtOrDefault(0);
|
||||
}
|
||||
|
||||
public IEnumerable<ProductionItem> AllQueued()
|
||||
{
|
||||
return queue;
|
||||
}
|
||||
|
||||
public virtual IEnumerable<ActorInfo> AllItems()
|
||||
{
|
||||
if (self.World.AllowDevCommands && developerMode.AllTech)
|
||||
return produceable.Select(a => a.Key);
|
||||
|
||||
return produceable.Where(a => a.Value.Buildable || a.Value.Visible).Select(a => a.Key);
|
||||
}
|
||||
|
||||
public virtual IEnumerable<ActorInfo> BuildableItems()
|
||||
{
|
||||
if (!Enabled)
|
||||
return Enumerable.Empty<ActorInfo>();
|
||||
if (self.World.AllowDevCommands && developerMode.AllTech)
|
||||
return produceable.Select(a => a.Key);
|
||||
|
||||
return produceable.Where(a => a.Value.Buildable).Select(a => a.Key);
|
||||
}
|
||||
|
||||
public bool CanBuild(ActorInfo actor)
|
||||
{
|
||||
ProductionState ps;
|
||||
if (!produceable.TryGetValue(actor, out ps))
|
||||
return false;
|
||||
|
||||
return ps.Buildable || (self.World.AllowDevCommands && developerMode.AllTech);
|
||||
}
|
||||
|
||||
public virtual void Tick(Actor self)
|
||||
{
|
||||
while (queue.Count > 0 && BuildableItems().All(b => b.Name != queue[0].Item))
|
||||
{
|
||||
playerResources.GiveCash(queue[0].TotalCost - queue[0].RemainingCost); // refund what's been paid so far.
|
||||
FinishProduction();
|
||||
}
|
||||
|
||||
if (queue.Count > 0)
|
||||
queue[0].Tick(playerResources);
|
||||
}
|
||||
|
||||
public void ResolveOrder(Actor self, Order order)
|
||||
{
|
||||
if (!Enabled)
|
||||
return;
|
||||
|
||||
switch (order.OrderString)
|
||||
{
|
||||
case "StartProduction":
|
||||
{
|
||||
var unit = self.World.Map.Rules.Actors[order.TargetString];
|
||||
var bi = unit.Traits.Get<BuildableInfo>();
|
||||
if (!bi.Queue.Contains(Info.Type))
|
||||
return; /* Not built by this queue */
|
||||
|
||||
var cost = unit.Traits.Contains<ValuedInfo>() ? unit.Traits.Get<ValuedInfo>().Cost : 0;
|
||||
var time = GetBuildTime(order.TargetString);
|
||||
|
||||
if (BuildableItems().All(b => b.Name != order.TargetString))
|
||||
return; /* you can't build that!! */
|
||||
|
||||
// Check if the player is trying to build more units that they are allowed
|
||||
var fromLimit = int.MaxValue;
|
||||
if (bi.BuildLimit > 0)
|
||||
{
|
||||
var inQueue = queue.Count(pi => pi.Item == order.TargetString);
|
||||
var owned = self.Owner.World.ActorsWithTrait<Buildable>().Count(a => a.Actor.Info.Name == order.TargetString && a.Actor.Owner == self.Owner);
|
||||
fromLimit = bi.BuildLimit - (inQueue + owned);
|
||||
|
||||
if (fromLimit <= 0)
|
||||
return;
|
||||
}
|
||||
|
||||
var amountToBuild = Math.Min(fromLimit, order.ExtraData);
|
||||
for (var n = 0; n < amountToBuild; n++)
|
||||
{
|
||||
var hasPlayedSound = false;
|
||||
BeginProduction(new ProductionItem(this, order.TargetString, cost, playerPower, () => self.World.AddFrameEndTask(_ =>
|
||||
{
|
||||
var isBuilding = unit.Traits.Contains<BuildingInfo>();
|
||||
if (isBuilding && !hasPlayedSound)
|
||||
{
|
||||
hasPlayedSound = Sound.PlayNotification(self.World.Map.Rules, self.Owner, "Speech", Info.ReadyAudio, self.Owner.Country.Race);
|
||||
}
|
||||
else if (!isBuilding)
|
||||
{
|
||||
if (BuildUnit(order.TargetString))
|
||||
Sound.PlayNotification(self.World.Map.Rules, self.Owner, "Speech", Info.ReadyAudio, self.Owner.Country.Race);
|
||||
else if (!hasPlayedSound && time > 0)
|
||||
{
|
||||
hasPlayedSound = Sound.PlayNotification(self.World.Map.Rules, self.Owner, "Speech", Info.BlockedAudio, self.Owner.Country.Race);
|
||||
}
|
||||
}
|
||||
})));
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case "PauseProduction":
|
||||
{
|
||||
if (queue.Count > 0 && queue[0].Item == order.TargetString)
|
||||
queue[0].Pause(order.ExtraData != 0);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case "CancelProduction":
|
||||
{
|
||||
CancelProduction(order.TargetString, order.ExtraData);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public virtual int GetBuildTime(string unitString)
|
||||
{
|
||||
var unit = self.World.Map.Rules.Actors[unitString];
|
||||
if (unit == null || !unit.Traits.Contains<BuildableInfo>())
|
||||
return 0;
|
||||
|
||||
if (self.World.AllowDevCommands && self.Owner.PlayerActor.Trait<DeveloperMode>().FastBuild)
|
||||
return 0;
|
||||
|
||||
var time = unit.GetBuildTime() * Info.BuildSpeed;
|
||||
|
||||
return (int)time;
|
||||
}
|
||||
|
||||
protected void CancelProduction(string itemName, uint numberToCancel)
|
||||
{
|
||||
for (var i = 0; i < numberToCancel; i++)
|
||||
CancelProductionInner(itemName);
|
||||
}
|
||||
|
||||
void CancelProductionInner(string itemName)
|
||||
{
|
||||
var lastIndex = queue.FindLastIndex(a => a.Item == itemName);
|
||||
|
||||
if (lastIndex > 0)
|
||||
queue.RemoveAt(lastIndex);
|
||||
else if (lastIndex == 0)
|
||||
{
|
||||
var item = queue[0];
|
||||
playerResources.GiveCash(item.TotalCost - item.RemainingCost); // refund what has been paid
|
||||
FinishProduction();
|
||||
}
|
||||
}
|
||||
|
||||
public void FinishProduction()
|
||||
{
|
||||
if (queue.Count != 0)
|
||||
queue.RemoveAt(0);
|
||||
}
|
||||
|
||||
protected void BeginProduction(ProductionItem item)
|
||||
{
|
||||
queue.Add(item);
|
||||
}
|
||||
|
||||
// Builds a unit from the actor that holds this queue (1 queue per building)
|
||||
// Returns false if the unit can't be built
|
||||
protected virtual bool BuildUnit(string name)
|
||||
{
|
||||
// Cannot produce if i'm dead
|
||||
if (!self.IsInWorld || self.IsDead)
|
||||
{
|
||||
CancelProduction(name, 1);
|
||||
return true;
|
||||
}
|
||||
|
||||
var sp = self.TraitsImplementing<Production>().FirstOrDefault(p => p.Info.Produces.Contains(Info.Type));
|
||||
if (sp != null && !self.IsDisabled() && sp.Produce(self, self.World.Map.Rules.Actors[name], Race))
|
||||
{
|
||||
FinishProduction();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public class ProductionState
|
||||
{
|
||||
public bool Visible = false;
|
||||
public bool Buildable = false;
|
||||
}
|
||||
|
||||
public class ProductionItem
|
||||
{
|
||||
public readonly string Item;
|
||||
public readonly ProductionQueue Queue;
|
||||
public readonly int TotalCost;
|
||||
public readonly Action OnComplete;
|
||||
|
||||
public int TotalTime { get; private set; }
|
||||
public int RemainingTime { get; private set; }
|
||||
public int RemainingCost { get; private set; }
|
||||
public int RemainingTimeActual
|
||||
{
|
||||
get
|
||||
{
|
||||
return (pm.PowerState == PowerState.Normal) ? RemainingTime :
|
||||
RemainingTime * Queue.Info.LowPowerSlowdown;
|
||||
}
|
||||
}
|
||||
|
||||
public bool Paused { get; private set; }
|
||||
public bool Done { get; private set; }
|
||||
public bool Started { get; private set; }
|
||||
public int Slowdown { get; private set; }
|
||||
|
||||
readonly PowerManager pm;
|
||||
|
||||
public ProductionItem(ProductionQueue queue, string item, int cost, PowerManager pm, Action onComplete)
|
||||
{
|
||||
Item = item;
|
||||
RemainingTime = TotalTime = 1;
|
||||
RemainingCost = TotalCost = cost;
|
||||
OnComplete = onComplete;
|
||||
Queue = queue;
|
||||
this.pm = pm;
|
||||
}
|
||||
|
||||
public void Tick(PlayerResources pr)
|
||||
{
|
||||
if (!Started)
|
||||
{
|
||||
var time = Queue.GetBuildTime(Item);
|
||||
if (time > 0)
|
||||
RemainingTime = TotalTime = time;
|
||||
|
||||
Started = true;
|
||||
}
|
||||
|
||||
if (Done)
|
||||
{
|
||||
if (OnComplete != null)
|
||||
OnComplete();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (Paused)
|
||||
return;
|
||||
|
||||
if (pm.PowerState != PowerState.Normal)
|
||||
{
|
||||
if (--Slowdown <= 0)
|
||||
Slowdown = Queue.Info.LowPowerSlowdown;
|
||||
else
|
||||
return;
|
||||
}
|
||||
|
||||
var costThisFrame = RemainingCost / RemainingTime;
|
||||
if (costThisFrame != 0 && !pr.TakeCash(costThisFrame))
|
||||
return;
|
||||
|
||||
RemainingCost -= costThisFrame;
|
||||
RemainingTime -= 1;
|
||||
if (RemainingTime > 0)
|
||||
return;
|
||||
|
||||
Done = true;
|
||||
}
|
||||
|
||||
public void Pause(bool paused) { Paused = paused; }
|
||||
}
|
||||
}
|
||||
126
OpenRA.Mods.Common/Traits/Production.cs
Normal file
126
OpenRA.Mods.Common/Traits/Production.cs
Normal file
@@ -0,0 +1,126 @@
|
||||
#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.Drawing;
|
||||
using System.Linq;
|
||||
using OpenRA.Activities;
|
||||
using OpenRA.Mods.Common.Activities;
|
||||
using OpenRA.Primitives;
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.Mods.Common.Traits
|
||||
{
|
||||
[Desc("This unit has access to build queues.")]
|
||||
public class ProductionInfo : ITraitInfo
|
||||
{
|
||||
[Desc("e.g. Infantry, Vehicles, Aircraft, Buildings")]
|
||||
public readonly string[] Produces = { };
|
||||
|
||||
public virtual object Create(ActorInitializer init) { return new Production(this, init.Self); }
|
||||
}
|
||||
|
||||
public class Production
|
||||
{
|
||||
Lazy<RallyPoint> rp;
|
||||
|
||||
public ProductionInfo Info;
|
||||
public Production(ProductionInfo info, Actor self)
|
||||
{
|
||||
Info = info;
|
||||
rp = Exts.Lazy(() => self.IsDead ? null : self.TraitOrDefault<RallyPoint>());
|
||||
}
|
||||
|
||||
public void DoProduction(Actor self, ActorInfo producee, ExitInfo exitinfo, string raceVariant)
|
||||
{
|
||||
var exit = self.Location + exitinfo.ExitCell;
|
||||
var spawn = self.CenterPosition + exitinfo.SpawnOffset;
|
||||
var to = self.World.Map.CenterOfCell(exit);
|
||||
|
||||
var fi = producee.Traits.GetOrDefault<IFacingInfo>();
|
||||
var initialFacing = exitinfo.Facing < 0 ? Util.GetFacing(to - spawn, fi == null ? 0 : fi.GetInitialFacing()) : exitinfo.Facing;
|
||||
|
||||
var exitLocation = rp.Value != null ? rp.Value.Location : exit;
|
||||
var target = Target.FromCell(self.World, exitLocation);
|
||||
|
||||
self.World.AddFrameEndTask(w =>
|
||||
{
|
||||
var td = new TypeDictionary
|
||||
{
|
||||
new OwnerInit(self.Owner),
|
||||
new LocationInit(exit),
|
||||
new CenterPositionInit(spawn),
|
||||
new FacingInit(initialFacing)
|
||||
};
|
||||
|
||||
if (raceVariant != null)
|
||||
td.Add(new RaceInit(raceVariant));
|
||||
|
||||
var newUnit = self.World.CreateActor(producee.Name, td);
|
||||
|
||||
var move = newUnit.TraitOrDefault<IMove>();
|
||||
if (move != null)
|
||||
{
|
||||
if (exitinfo.MoveIntoWorld)
|
||||
{
|
||||
newUnit.QueueActivity(move.MoveIntoWorld(newUnit, exit));
|
||||
newUnit.QueueActivity(new AttackMoveActivity(
|
||||
newUnit, move.MoveTo(exitLocation, 1)));
|
||||
}
|
||||
}
|
||||
|
||||
newUnit.SetTargetLine(target, rp.Value != null ? Color.Red : Color.Green, false);
|
||||
|
||||
if (!self.IsDead)
|
||||
foreach (var t in self.TraitsImplementing<INotifyProduction>())
|
||||
t.UnitProduced(self, newUnit, exit);
|
||||
|
||||
var notifyOthers = self.World.ActorsWithTrait<INotifyOtherProduction>();
|
||||
foreach (var notify in notifyOthers)
|
||||
notify.Trait.UnitProducedByOther(notify.Actor, self, newUnit);
|
||||
|
||||
var bi = newUnit.Info.Traits.GetOrDefault<BuildableInfo>();
|
||||
if (bi != null && bi.InitialActivity != null)
|
||||
newUnit.QueueActivity(Game.CreateObject<Activity>(bi.InitialActivity));
|
||||
|
||||
foreach (var t in newUnit.TraitsImplementing<INotifyBuildComplete>())
|
||||
t.BuildingComplete(newUnit);
|
||||
});
|
||||
}
|
||||
|
||||
public virtual bool Produce(Actor self, ActorInfo producee, string raceVariant)
|
||||
{
|
||||
if (Reservable.IsReserved(self))
|
||||
return false;
|
||||
|
||||
// pick a spawn/exit point pair
|
||||
var exit = self.Info.Traits.WithInterface<ExitInfo>().Shuffle(self.World.SharedRandom)
|
||||
.FirstOrDefault(e => CanUseExit(self, producee, e));
|
||||
|
||||
if (exit != null)
|
||||
{
|
||||
DoProduction(self, producee, exit, raceVariant);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool CanUseExit(Actor self, ActorInfo producee, ExitInfo s)
|
||||
{
|
||||
var mobileInfo = producee.Traits.GetOrDefault<MobileInfo>();
|
||||
|
||||
self.NotifyBlocker(self.Location + s.ExitCell);
|
||||
|
||||
return mobileInfo == null ||
|
||||
mobileInfo.CanEnterCell(self.World, self, self.Location + s.ExitCell, self);
|
||||
}
|
||||
}
|
||||
}
|
||||
73
OpenRA.Mods.Common/Traits/ProductionQueueFromSelection.cs
Normal file
73
OpenRA.Mods.Common/Traits/ProductionQueueFromSelection.cs
Normal file
@@ -0,0 +1,73 @@
|
||||
#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.Linq;
|
||||
using OpenRA.Mods.Common.Widgets;
|
||||
using OpenRA.Traits;
|
||||
using OpenRA.Widgets;
|
||||
|
||||
namespace OpenRA.Mods.Common.Traits
|
||||
{
|
||||
class ProductionQueueFromSelectionInfo : ITraitInfo
|
||||
{
|
||||
public string ProductionTabsWidget = null;
|
||||
public string ProductionPaletteWidget = null;
|
||||
|
||||
public object Create(ActorInitializer init) { return new ProductionQueueFromSelection(init.World, this); }
|
||||
}
|
||||
|
||||
class ProductionQueueFromSelection : INotifySelection
|
||||
{
|
||||
readonly World world;
|
||||
readonly Lazy<ProductionTabsWidget> tabsWidget;
|
||||
readonly Lazy<ProductionPaletteWidget> paletteWidget;
|
||||
|
||||
public ProductionQueueFromSelection(World world, ProductionQueueFromSelectionInfo info)
|
||||
{
|
||||
this.world = world;
|
||||
|
||||
tabsWidget = Exts.Lazy(() => Ui.Root.GetOrNull(info.ProductionTabsWidget) as ProductionTabsWidget);
|
||||
paletteWidget = Exts.Lazy(() => Ui.Root.GetOrNull(info.ProductionPaletteWidget) as ProductionPaletteWidget);
|
||||
}
|
||||
|
||||
public void SelectionChanged()
|
||||
{
|
||||
// Disable for spectators
|
||||
if (world.LocalPlayer == null)
|
||||
return;
|
||||
|
||||
// Queue-per-actor
|
||||
var queue = world.Selection.Actors
|
||||
.Where(a => a.IsInWorld && a.World.LocalPlayer == a.Owner)
|
||||
.SelectMany(a => a.TraitsImplementing<ProductionQueue>())
|
||||
.FirstOrDefault(q => q.Enabled);
|
||||
|
||||
// Queue-per-player
|
||||
if (queue == null)
|
||||
{
|
||||
var types = world.Selection.Actors.Where(a => a.IsInWorld && a.World.LocalPlayer == a.Owner)
|
||||
.SelectMany(a => a.TraitsImplementing<Production>())
|
||||
.SelectMany(t => t.Info.Produces);
|
||||
|
||||
queue = world.LocalPlayer.PlayerActor.TraitsImplementing<ProductionQueue>()
|
||||
.FirstOrDefault(q => q.Enabled && types.Contains(q.Info.Type));
|
||||
}
|
||||
|
||||
if (queue == null)
|
||||
return;
|
||||
|
||||
if (tabsWidget.Value != null)
|
||||
tabsWidget.Value.CurrentQueue = queue;
|
||||
else if (paletteWidget.Value != null)
|
||||
paletteWidget.Value.CurrentQueue = queue;
|
||||
}
|
||||
}
|
||||
}
|
||||
79
OpenRA.Mods.Common/Traits/Render/ProductionBar.cs
Normal file
79
OpenRA.Mods.Common/Traits/Render/ProductionBar.cs
Normal file
@@ -0,0 +1,79 @@
|
||||
#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.Drawing;
|
||||
using System.Linq;
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.Mods.Common.Traits
|
||||
{
|
||||
[Desc("Visualizes the remaining build time of actor produced here.")]
|
||||
class ProductionBarInfo : ITraitInfo
|
||||
{
|
||||
[Desc("Production queue type, for actors with multiple queues.")]
|
||||
public readonly string ProductionType = null;
|
||||
|
||||
public readonly Color Color = Color.SkyBlue;
|
||||
|
||||
public object Create(ActorInitializer init) { return new ProductionBar(init.Self, this); }
|
||||
}
|
||||
|
||||
class ProductionBar : ISelectionBar, ITick
|
||||
{
|
||||
readonly ProductionBarInfo info;
|
||||
readonly Actor self;
|
||||
ProductionQueue queue;
|
||||
float value;
|
||||
|
||||
public ProductionBar(Actor self, ProductionBarInfo info)
|
||||
{
|
||||
this.self = self;
|
||||
this.info = info;
|
||||
}
|
||||
|
||||
public void Tick(Actor self)
|
||||
{
|
||||
if (queue == null)
|
||||
{
|
||||
var type = info.ProductionType ?? self.Trait<Production>().Info.Produces.First();
|
||||
|
||||
// Per-actor queue
|
||||
// Note: this includes disabled queues, as each bar must bind to exactly one queue.
|
||||
queue = self.TraitsImplementing<ProductionQueue>()
|
||||
.FirstOrDefault(q => type == null || type == q.Info.Type);
|
||||
|
||||
if (queue == null)
|
||||
{
|
||||
// No queues available - check for classic production queues
|
||||
queue = self.Owner.PlayerActor.TraitsImplementing<ProductionQueue>()
|
||||
.FirstOrDefault(q => type == null || type == q.Info.Type);
|
||||
}
|
||||
|
||||
if (queue == null)
|
||||
throw new InvalidOperationException("No queues available for production type '{0}'".F(type));
|
||||
}
|
||||
|
||||
var current = queue.CurrentItem();
|
||||
value = current != null ? 1 - (float)current.RemainingCost / current.TotalCost : 0;
|
||||
}
|
||||
|
||||
public float GetValue()
|
||||
{
|
||||
// only people we like should see our production status.
|
||||
if (!self.Owner.IsAlliedWith(self.World.RenderPlayer))
|
||||
return 0;
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
public Color GetColor() { return info.Color; }
|
||||
}
|
||||
}
|
||||
96
OpenRA.Mods.Common/Traits/Render/RenderBuildingWarFactory.cs
Normal file
96
OpenRA.Mods.Common/Traits/Render/RenderBuildingWarFactory.cs
Normal file
@@ -0,0 +1,96 @@
|
||||
#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.Graphics;
|
||||
using OpenRA.Mods.Common.Graphics;
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.Mods.Common.Traits
|
||||
{
|
||||
class RenderBuildingWarFactoryInfo : RenderBuildingInfo
|
||||
{
|
||||
public override object Create(ActorInitializer init) { return new RenderBuildingWarFactory(init, this); }
|
||||
|
||||
public override IEnumerable<IActorPreview> RenderPreviewSprites(ActorPreviewInitializer init, RenderSpritesInfo rs, string image, int facings, PaletteReference p)
|
||||
{
|
||||
foreach (var orig in base.RenderPreviewSprites(init, rs, image, facings, p))
|
||||
yield return orig;
|
||||
|
||||
// Show additional roof overlay
|
||||
var anim = new Animation(init.World, image, () => 0);
|
||||
anim.PlayRepeating("idle-top");
|
||||
|
||||
var bi = init.Actor.Traits.Get<BuildingInfo>();
|
||||
var offset = FootprintUtils.CenterOffset(init.World, bi).Y + 512;
|
||||
yield return new SpriteActorPreview(anim, WVec.Zero, offset, p, rs.Scale);
|
||||
}
|
||||
}
|
||||
|
||||
class RenderBuildingWarFactory : RenderBuilding, INotifyBuildComplete, ITick, INotifyProduction, INotifySold, ISync
|
||||
{
|
||||
Animation roof;
|
||||
[Sync] bool isOpen;
|
||||
[Sync] CPos openExit;
|
||||
bool buildComplete;
|
||||
|
||||
public RenderBuildingWarFactory(ActorInitializer init, RenderBuildingInfo info)
|
||||
: base(init, info)
|
||||
{
|
||||
roof = new Animation(init.World, GetImage(init.Self));
|
||||
var bi = init.Self.Info.Traits.Get<BuildingInfo>();
|
||||
|
||||
// Additional 512 units move from center -> top of cell
|
||||
var offset = FootprintUtils.CenterOffset(init.World, bi).Y + 512;
|
||||
Add("roof", new AnimationWithOffset(roof, null,
|
||||
() => !buildComplete, offset));
|
||||
}
|
||||
|
||||
public override void BuildingComplete(Actor self)
|
||||
{
|
||||
roof.Play(NormalizeSequence(self,
|
||||
self.GetDamageState() > DamageState.Heavy ? "damaged-idle-top" : "idle-top"));
|
||||
buildComplete = true;
|
||||
}
|
||||
|
||||
public override void Tick(Actor self)
|
||||
{
|
||||
base.Tick(self);
|
||||
if (isOpen && !self.World.ActorMap.GetUnitsAt(openExit).Any(a => a != self))
|
||||
{
|
||||
isOpen = false;
|
||||
roof.PlayBackwardsThen(NormalizeSequence(self, "build-top"),
|
||||
() => roof.Play(NormalizeSequence(self, "idle-top")));
|
||||
}
|
||||
}
|
||||
|
||||
public override void DamageStateChanged(Actor self, AttackInfo e)
|
||||
{
|
||||
if (roof.CurrentSequence != null)
|
||||
{
|
||||
if (e.DamageState >= DamageState.Heavy)
|
||||
roof.ReplaceAnim("damaged-" + roof.CurrentSequence.Name);
|
||||
else
|
||||
roof.ReplaceAnim(roof.CurrentSequence.Name.Replace("damaged-", ""));
|
||||
}
|
||||
|
||||
base.DamageStateChanged(self, e);
|
||||
}
|
||||
|
||||
public void UnitProduced(Actor self, Actor other, CPos exit)
|
||||
{
|
||||
roof.PlayThen(NormalizeSequence(self, "build-top"), () => { isOpen = true; openExit = exit; });
|
||||
}
|
||||
|
||||
public void Selling(Actor self) { Remove("roof"); }
|
||||
public void Sold(Actor self) { }
|
||||
}
|
||||
}
|
||||
122
OpenRA.Mods.Common/Widgets/Logic/ProductionTooltipLogic.cs
Normal file
122
OpenRA.Mods.Common/Widgets/Logic/ProductionTooltipLogic.cs
Normal file
@@ -0,0 +1,122 @@
|
||||
#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.Drawing;
|
||||
using System.Linq;
|
||||
using OpenRA.Mods.Common.Traits;
|
||||
using OpenRA.Traits;
|
||||
using OpenRA.Widgets;
|
||||
|
||||
namespace OpenRA.Mods.Common.Widgets.Logic
|
||||
{
|
||||
public class ProductionTooltipLogic
|
||||
{
|
||||
[ObjectCreator.UseCtor]
|
||||
public ProductionTooltipLogic(Widget widget, TooltipContainerWidget tooltipContainer, ProductionPaletteWidget palette)
|
||||
{
|
||||
var mapRules = palette.World.Map.Rules;
|
||||
var pm = palette.World.LocalPlayer.PlayerActor.Trait<PowerManager>();
|
||||
var pr = palette.World.LocalPlayer.PlayerActor.Trait<PlayerResources>();
|
||||
|
||||
widget.IsVisible = () => palette.TooltipIcon != null;
|
||||
var nameLabel = widget.Get<LabelWidget>("NAME");
|
||||
var hotkeyLabel = widget.Get<LabelWidget>("HOTKEY");
|
||||
var requiresLabel = widget.Get<LabelWidget>("REQUIRES");
|
||||
var powerLabel = widget.Get<LabelWidget>("POWER");
|
||||
var powerIcon = widget.Get<ImageWidget>("POWER_ICON");
|
||||
var timeLabel = widget.Get<LabelWidget>("TIME");
|
||||
var timeIcon = widget.Get<ImageWidget>("TIME_ICON");
|
||||
var costLabel = widget.Get<LabelWidget>("COST");
|
||||
var costIcon = widget.Get<ImageWidget>("COST_ICON");
|
||||
var descLabel = widget.Get<LabelWidget>("DESC");
|
||||
|
||||
var iconMargin = timeIcon.Bounds.X;
|
||||
|
||||
var font = Game.Renderer.Fonts[nameLabel.Font];
|
||||
var descFont = Game.Renderer.Fonts[descLabel.Font];
|
||||
var requiresFont = Game.Renderer.Fonts[requiresLabel.Font];
|
||||
ActorInfo lastActor = null;
|
||||
|
||||
tooltipContainer.BeforeRender = () =>
|
||||
{
|
||||
if (palette.TooltipIcon == null)
|
||||
return;
|
||||
|
||||
var actor = palette.TooltipIcon.Actor;
|
||||
if (actor == null || actor == lastActor)
|
||||
return;
|
||||
|
||||
var tooltip = actor.Traits.Get<TooltipInfo>();
|
||||
var buildable = actor.Traits.Get<BuildableInfo>();
|
||||
var cost = actor.Traits.Get<ValuedInfo>().Cost;
|
||||
|
||||
nameLabel.GetText = () => tooltip.Name;
|
||||
|
||||
var hotkey = palette.TooltipIcon.Hotkey;
|
||||
var nameWidth = font.Measure(tooltip.Name).X;
|
||||
var hotkeyText = "({0})".F(hotkey.DisplayString());
|
||||
var hotkeyWidth = hotkey.IsValid() ? font.Measure(hotkeyText).X + 2 * nameLabel.Bounds.X : 0;
|
||||
hotkeyLabel.GetText = () => hotkeyText;
|
||||
hotkeyLabel.Bounds.X = nameWidth + 2 * nameLabel.Bounds.X;
|
||||
hotkeyLabel.Visible = hotkey.IsValid();
|
||||
|
||||
var prereqs = buildable.Prerequisites.Select(a => ActorName(mapRules, a)).Where(s => !s.StartsWith("~"));
|
||||
var requiresString = prereqs.Any() ? requiresLabel.Text.F(prereqs.JoinWith(", ")) : "";
|
||||
requiresLabel.GetText = () => requiresString;
|
||||
|
||||
var power = actor.Traits.WithInterface<PowerInfo>().Where(i => i.UpgradeMinEnabledLevel < 1).Sum(i => i.Amount);
|
||||
var powerString = power.ToString();
|
||||
powerLabel.GetText = () => powerString;
|
||||
powerLabel.GetColor = () => ((pm.PowerProvided - pm.PowerDrained) >= -power || power > 0)
|
||||
? Color.White : Color.Red;
|
||||
powerLabel.IsVisible = () => power != 0;
|
||||
powerIcon.IsVisible = () => power != 0;
|
||||
|
||||
var lowpower = pm.PowerState != PowerState.Normal;
|
||||
var time = palette.CurrentQueue == null ? 0 : palette.CurrentQueue.GetBuildTime(actor.Name)
|
||||
* (lowpower ? palette.CurrentQueue.Info.LowPowerSlowdown : 1);
|
||||
var timeString = WidgetUtils.FormatTime(time);
|
||||
timeLabel.GetText = () => timeString;
|
||||
timeLabel.GetColor = () => lowpower ? Color.Red : Color.White;
|
||||
|
||||
var costString = cost.ToString();
|
||||
costLabel.GetText = () => costString;
|
||||
costLabel.GetColor = () => pr.DisplayCash + pr.DisplayResources >= cost
|
||||
? Color.White : Color.Red;
|
||||
|
||||
var descString = tooltip.Description.Replace("\\n", "\n");
|
||||
descLabel.GetText = () => descString;
|
||||
|
||||
var leftWidth = new[] { nameWidth + hotkeyWidth, requiresFont.Measure(requiresString).X, descFont.Measure(descString).X }.Aggregate(Math.Max);
|
||||
var rightWidth = new[] { font.Measure(powerString).X, font.Measure(timeString).X, font.Measure(costString).X }.Aggregate(Math.Max);
|
||||
|
||||
timeIcon.Bounds.X = powerIcon.Bounds.X = costIcon.Bounds.X = leftWidth + 2 * nameLabel.Bounds.X;
|
||||
timeLabel.Bounds.X = powerLabel.Bounds.X = costLabel.Bounds.X = timeIcon.Bounds.Right + iconMargin;
|
||||
widget.Bounds.Width = leftWidth + rightWidth + 3 * nameLabel.Bounds.X + timeIcon.Bounds.Width + iconMargin;
|
||||
|
||||
var leftHeight = font.Measure(tooltip.Name).Y + requiresFont.Measure(requiresString).Y + descFont.Measure(descString).Y;
|
||||
var rightHeight = font.Measure(powerString).Y + font.Measure(timeString).Y + font.Measure(costString).Y;
|
||||
widget.Bounds.Height = Math.Max(leftHeight, rightHeight) * 3 / 2 + 3 * nameLabel.Bounds.Y;
|
||||
|
||||
lastActor = actor;
|
||||
};
|
||||
}
|
||||
|
||||
static string ActorName(Ruleset rules, string a)
|
||||
{
|
||||
ActorInfo ai;
|
||||
if (rules.Actors.TryGetValue(a.ToLowerInvariant(), out ai) && ai.Traits.Contains<TooltipInfo>())
|
||||
return ai.Traits.Get<TooltipInfo>().Name;
|
||||
|
||||
return a;
|
||||
}
|
||||
}
|
||||
}
|
||||
324
OpenRA.Mods.Common/Widgets/ProductionPaletteWidget.cs
Normal file
324
OpenRA.Mods.Common/Widgets/ProductionPaletteWidget.cs
Normal file
@@ -0,0 +1,324 @@
|
||||
#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 System.Drawing;
|
||||
using System.Linq;
|
||||
using OpenRA.Graphics;
|
||||
using OpenRA.Mods.Common.Traits;
|
||||
using OpenRA.Mods.Common.Orders;
|
||||
using OpenRA.Network;
|
||||
using OpenRA.Widgets;
|
||||
|
||||
namespace OpenRA.Mods.Common.Widgets
|
||||
{
|
||||
public class ProductionIcon
|
||||
{
|
||||
public ActorInfo Actor;
|
||||
public string Name;
|
||||
public Hotkey Hotkey;
|
||||
public Sprite Sprite;
|
||||
public float2 Pos;
|
||||
public List<ProductionItem> Queued;
|
||||
}
|
||||
|
||||
public class ProductionPaletteWidget : Widget
|
||||
{
|
||||
public enum ReadyTextStyleOptions { Solid, AlternatingColor, Blinking }
|
||||
public readonly ReadyTextStyleOptions ReadyTextStyle = ReadyTextStyleOptions.AlternatingColor;
|
||||
public readonly Color ReadyTextAltColor = Color.Gold;
|
||||
public readonly int Columns = 3;
|
||||
public readonly int2 IconSize = new int2(64, 48);
|
||||
public readonly int2 IconMargin = int2.Zero;
|
||||
public readonly int2 IconSpriteOffset = int2.Zero;
|
||||
|
||||
public readonly string TabClick = null;
|
||||
public readonly string DisabledTabClick = null;
|
||||
public readonly string TooltipContainer;
|
||||
public readonly string TooltipTemplate = "PRODUCTION_TOOLTIP";
|
||||
|
||||
[Translate] public readonly string ReadyText = "";
|
||||
[Translate] public readonly string HoldText = "";
|
||||
|
||||
public int IconCount { get; private set; }
|
||||
public event Action<int, int> OnIconCountChanged = (a, b) => { };
|
||||
|
||||
public ProductionIcon TooltipIcon { get; private set; }
|
||||
public readonly World World;
|
||||
readonly OrderManager orderManager;
|
||||
|
||||
Lazy<TooltipContainerWidget> tooltipContainer;
|
||||
ProductionQueue currentQueue;
|
||||
|
||||
public ProductionQueue CurrentQueue
|
||||
{
|
||||
get { return currentQueue; }
|
||||
set { currentQueue = value; RefreshIcons(); }
|
||||
}
|
||||
|
||||
public override Rectangle EventBounds { get { return eventBounds; } }
|
||||
Dictionary<Rectangle, ProductionIcon> icons = new Dictionary<Rectangle, ProductionIcon>();
|
||||
Animation cantBuild, clock;
|
||||
Rectangle eventBounds = Rectangle.Empty;
|
||||
readonly WorldRenderer worldRenderer;
|
||||
SpriteFont overlayFont;
|
||||
float2 holdOffset, readyOffset, timeOffset, queuedOffset;
|
||||
|
||||
[ObjectCreator.UseCtor]
|
||||
public ProductionPaletteWidget(OrderManager orderManager, World world, WorldRenderer worldRenderer)
|
||||
{
|
||||
this.orderManager = orderManager;
|
||||
this.World = world;
|
||||
this.worldRenderer = worldRenderer;
|
||||
tooltipContainer = Exts.Lazy(() =>
|
||||
Ui.Root.Get<TooltipContainerWidget>(TooltipContainer));
|
||||
|
||||
cantBuild = new Animation(world, "clock");
|
||||
cantBuild.PlayFetchIndex("idle", () => 0);
|
||||
clock = new Animation(world, "clock");
|
||||
}
|
||||
|
||||
public override void Tick()
|
||||
{
|
||||
if (CurrentQueue != null && !CurrentQueue.Actor.IsInWorld)
|
||||
CurrentQueue = null;
|
||||
|
||||
if (CurrentQueue != null)
|
||||
RefreshIcons();
|
||||
}
|
||||
|
||||
public override void MouseEntered()
|
||||
{
|
||||
if (TooltipContainer != null)
|
||||
tooltipContainer.Value.SetTooltip(TooltipTemplate,
|
||||
new WidgetArgs() { { "palette", this } });
|
||||
}
|
||||
|
||||
public override void MouseExited()
|
||||
{
|
||||
if (TooltipContainer != null)
|
||||
tooltipContainer.Value.RemoveTooltip();
|
||||
}
|
||||
|
||||
public override bool HandleMouseInput(MouseInput mi)
|
||||
{
|
||||
var icon = icons.Where(i => i.Key.Contains(mi.Location))
|
||||
.Select(i => i.Value).FirstOrDefault();
|
||||
|
||||
if (mi.Event == MouseInputEvent.Move)
|
||||
TooltipIcon = icon;
|
||||
|
||||
if (icon == null)
|
||||
return false;
|
||||
|
||||
// Only support left and right clicks
|
||||
if (mi.Button != MouseButton.Left && mi.Button != MouseButton.Right)
|
||||
return false;
|
||||
|
||||
// Eat mouse-up events
|
||||
if (mi.Event != MouseInputEvent.Down)
|
||||
return true;
|
||||
|
||||
return HandleEvent(icon, mi.Button == MouseButton.Left, mi.Modifiers.HasModifier(Modifiers.Shift));
|
||||
}
|
||||
|
||||
bool HandleEvent(ProductionIcon icon, bool isLeftClick, bool handleMultiple)
|
||||
{
|
||||
var actor = World.Map.Rules.Actors[icon.Name];
|
||||
var first = icon.Queued.FirstOrDefault();
|
||||
|
||||
if (isLeftClick)
|
||||
{
|
||||
// Pick up a completed building
|
||||
if (first != null && first.Done && actor.Traits.Contains<BuildingInfo>())
|
||||
{
|
||||
Sound.Play(TabClick);
|
||||
World.OrderGenerator = new PlaceBuildingOrderGenerator(CurrentQueue, icon.Name);
|
||||
}
|
||||
else if (first != null && first.Paused)
|
||||
{
|
||||
// Resume a paused item
|
||||
Sound.Play(TabClick);
|
||||
World.IssueOrder(Order.PauseProduction(CurrentQueue.Actor, icon.Name, false));
|
||||
}
|
||||
else if (CurrentQueue.BuildableItems().Any(a => a.Name == icon.Name))
|
||||
{
|
||||
// Queue a new item
|
||||
Sound.Play(TabClick);
|
||||
Sound.PlayNotification(World.Map.Rules, World.LocalPlayer, "Speech", CurrentQueue.Info.QueuedAudio, World.LocalPlayer.Country.Race);
|
||||
World.IssueOrder(Order.StartProduction(CurrentQueue.Actor, icon.Name,
|
||||
handleMultiple ? 5 : 1));
|
||||
}
|
||||
else
|
||||
Sound.Play(DisabledTabClick);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Hold/Cancel an existing item
|
||||
if (first != null)
|
||||
{
|
||||
Sound.Play(TabClick);
|
||||
|
||||
// instant cancel of things we havent started yet and things that are finished
|
||||
if (first.Paused || first.Done || first.TotalCost == first.RemainingCost)
|
||||
{
|
||||
Sound.PlayNotification(World.Map.Rules, World.LocalPlayer, "Speech", CurrentQueue.Info.CancelledAudio, World.LocalPlayer.Country.Race);
|
||||
World.IssueOrder(Order.CancelProduction(CurrentQueue.Actor, icon.Name,
|
||||
handleMultiple ? 5 : 1));
|
||||
}
|
||||
else
|
||||
{
|
||||
Sound.PlayNotification(World.Map.Rules, World.LocalPlayer, "Speech", CurrentQueue.Info.OnHoldAudio, World.LocalPlayer.Country.Race);
|
||||
World.IssueOrder(Order.PauseProduction(CurrentQueue.Actor, icon.Name, true));
|
||||
}
|
||||
}
|
||||
else
|
||||
Sound.Play(DisabledTabClick);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public override bool HandleKeyPress(KeyInput e)
|
||||
{
|
||||
if (e.Event == KeyInputEvent.Up || CurrentQueue == null)
|
||||
return false;
|
||||
|
||||
var hotkey = Hotkey.FromKeyInput(e);
|
||||
var toBuild = icons.Values.FirstOrDefault(i => i.Hotkey == hotkey);
|
||||
return toBuild != null ? HandleEvent(toBuild, true, false) : false;
|
||||
}
|
||||
|
||||
public void RefreshIcons()
|
||||
{
|
||||
icons = new Dictionary<Rectangle, ProductionIcon>();
|
||||
if (CurrentQueue == null)
|
||||
{
|
||||
if (IconCount != 0)
|
||||
{
|
||||
OnIconCountChanged(IconCount, 0);
|
||||
IconCount = 0;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
var allBuildables = CurrentQueue.AllItems().OrderBy(a => a.Traits.Get<BuildableInfo>().BuildPaletteOrder);
|
||||
|
||||
var oldIconCount = IconCount;
|
||||
IconCount = 0;
|
||||
|
||||
var ks = Game.Settings.Keys;
|
||||
var rb = RenderBounds;
|
||||
foreach (var item in allBuildables)
|
||||
{
|
||||
var x = IconCount % Columns;
|
||||
var y = IconCount / Columns;
|
||||
var rect = new Rectangle(rb.X + x * (IconSize.X + IconMargin.X), rb.Y + y * (IconSize.Y + IconMargin.Y), IconSize.X, IconSize.Y);
|
||||
var icon = new Animation(World, RenderSimple.GetImage(item));
|
||||
icon.Play(item.Traits.Get<TooltipInfo>().Icon);
|
||||
|
||||
var pi = new ProductionIcon()
|
||||
{
|
||||
Actor = item,
|
||||
Name = item.Name,
|
||||
Hotkey = ks.GetProductionHotkey(IconCount),
|
||||
Sprite = icon.Image,
|
||||
Pos = new float2(rect.Location),
|
||||
Queued = CurrentQueue.AllQueued().Where(a => a.Item == item.Name).ToList(),
|
||||
};
|
||||
|
||||
icons.Add(rect, pi);
|
||||
IconCount++;
|
||||
}
|
||||
|
||||
eventBounds = icons.Any() ? icons.Keys.Aggregate(Rectangle.Union) : Rectangle.Empty;
|
||||
|
||||
if (oldIconCount != IconCount)
|
||||
OnIconCountChanged(oldIconCount, IconCount);
|
||||
}
|
||||
|
||||
public override void Draw()
|
||||
{
|
||||
var iconOffset = 0.5f * IconSize.ToFloat2() + IconSpriteOffset;
|
||||
|
||||
overlayFont = Game.Renderer.Fonts["TinyBold"];
|
||||
timeOffset = iconOffset - overlayFont.Measure(WidgetUtils.FormatTime(0)) / 2;
|
||||
queuedOffset = new float2(4, 2);
|
||||
holdOffset = iconOffset - overlayFont.Measure(HoldText) / 2;
|
||||
readyOffset = iconOffset - overlayFont.Measure(ReadyText) / 2;
|
||||
|
||||
if (CurrentQueue == null)
|
||||
return;
|
||||
|
||||
var buildableItems = CurrentQueue.BuildableItems();
|
||||
|
||||
// Icons
|
||||
foreach (var icon in icons.Values)
|
||||
{
|
||||
WidgetUtils.DrawSHPCentered(icon.Sprite, icon.Pos + iconOffset, worldRenderer);
|
||||
|
||||
// Build progress
|
||||
if (icon.Queued.Count > 0)
|
||||
{
|
||||
var first = icon.Queued[0];
|
||||
clock.PlayFetchIndex("idle",
|
||||
() => (first.TotalTime - first.RemainingTime)
|
||||
* (clock.CurrentSequence.Length - 1) / first.TotalTime);
|
||||
clock.Tick();
|
||||
|
||||
WidgetUtils.DrawSHPCentered(clock.Image, icon.Pos + iconOffset, worldRenderer);
|
||||
}
|
||||
else if (!buildableItems.Any(a => a.Name == icon.Name))
|
||||
WidgetUtils.DrawSHPCentered(cantBuild.Image, icon.Pos + iconOffset, worldRenderer);
|
||||
}
|
||||
|
||||
// Overlays
|
||||
foreach (var icon in icons.Values)
|
||||
{
|
||||
var total = icon.Queued.Count;
|
||||
if (total > 0)
|
||||
{
|
||||
var first = icon.Queued[0];
|
||||
var waiting = first != CurrentQueue.CurrentItem() && !first.Done;
|
||||
if (first.Done)
|
||||
{
|
||||
if (ReadyTextStyle == ReadyTextStyleOptions.Solid || orderManager.LocalFrameNumber / 9 % 2 == 0)
|
||||
overlayFont.DrawTextWithContrast(ReadyText, icon.Pos + readyOffset, Color.White, 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,
|
||||
icon.Pos + holdOffset,
|
||||
Color.White, Color.Black, 1);
|
||||
else if (!waiting)
|
||||
overlayFont.DrawTextWithContrast(WidgetUtils.FormatTime(first.RemainingTimeActual),
|
||||
icon.Pos + timeOffset,
|
||||
Color.White, Color.Black, 1);
|
||||
|
||||
if (total > 1 || waiting)
|
||||
overlayFont.DrawTextWithContrast(total.ToString(),
|
||||
icon.Pos + queuedOffset,
|
||||
Color.White, Color.Black, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override string GetCursor(int2 pos)
|
||||
{
|
||||
var icon = icons.Where(i => i.Key.Contains(pos))
|
||||
.Select(i => i.Value).FirstOrDefault();
|
||||
|
||||
return icon != null ? base.GetCursor(pos) : null;
|
||||
}
|
||||
}
|
||||
}
|
||||
295
OpenRA.Mods.Common/Widgets/ProductionTabsWidget.cs
Normal file
295
OpenRA.Mods.Common/Widgets/ProductionTabsWidget.cs
Normal file
@@ -0,0 +1,295 @@
|
||||
#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 System.Drawing;
|
||||
using System.Linq;
|
||||
using OpenRA.Graphics;
|
||||
using OpenRA.Mods.Common.Traits;
|
||||
using OpenRA.Widgets;
|
||||
|
||||
namespace OpenRA.Mods.Common.Widgets
|
||||
{
|
||||
public class ProductionTab
|
||||
{
|
||||
public string Name;
|
||||
public ProductionQueue Queue;
|
||||
}
|
||||
|
||||
public class ProductionTabGroup
|
||||
{
|
||||
public List<ProductionTab> Tabs = new List<ProductionTab>();
|
||||
public string Group;
|
||||
public int NextQueueName = 1;
|
||||
public bool Alert { get { return Tabs.Any(t => t.Queue.CurrentDone); } }
|
||||
|
||||
public void Update(IEnumerable<ProductionQueue> allQueues)
|
||||
{
|
||||
var queues = allQueues.Where(q => q.Info.Group == Group).ToList();
|
||||
var tabs = new List<ProductionTab>();
|
||||
|
||||
// Remove stale queues
|
||||
foreach (var t in Tabs)
|
||||
{
|
||||
if (!queues.Contains(t.Queue))
|
||||
continue;
|
||||
|
||||
tabs.Add(t);
|
||||
queues.Remove(t.Queue);
|
||||
}
|
||||
|
||||
// Add new queues
|
||||
foreach (var queue in queues)
|
||||
tabs.Add(new ProductionTab()
|
||||
{
|
||||
Name = (NextQueueName++).ToString(),
|
||||
Queue = queue
|
||||
});
|
||||
Tabs = tabs;
|
||||
}
|
||||
}
|
||||
|
||||
public class ProductionTabsWidget : Widget
|
||||
{
|
||||
readonly World world;
|
||||
|
||||
public readonly string PaletteWidget = null;
|
||||
public readonly string TypesContainer = null;
|
||||
public readonly string BackgroundContainer = null;
|
||||
|
||||
public readonly int TabWidth = 30;
|
||||
public readonly int ArrowWidth = 20;
|
||||
public Dictionary<string, ProductionTabGroup> Groups;
|
||||
|
||||
int contentWidth = 0;
|
||||
float listOffset = 0;
|
||||
bool leftPressed = false;
|
||||
bool rightPressed = false;
|
||||
Rectangle leftButtonRect;
|
||||
Rectangle rightButtonRect;
|
||||
Lazy<ProductionPaletteWidget> paletteWidget;
|
||||
string queueGroup;
|
||||
|
||||
[ObjectCreator.UseCtor]
|
||||
public ProductionTabsWidget(World world)
|
||||
{
|
||||
this.world = world;
|
||||
|
||||
Groups = world.Map.Rules.Actors.Values.SelectMany(a => a.Traits.WithInterface<ProductionQueueInfo>())
|
||||
.Select(q => q.Group).Distinct().ToDictionary(g => g, g => new ProductionTabGroup() { Group = g });
|
||||
|
||||
// Only visible if the production palette has icons to display
|
||||
IsVisible = () => queueGroup != null && Groups[queueGroup].Tabs.Count > 0;
|
||||
|
||||
paletteWidget = Exts.Lazy(() => Ui.Root.Get<ProductionPaletteWidget>(PaletteWidget));
|
||||
}
|
||||
|
||||
public bool SelectNextTab(bool reverse)
|
||||
{
|
||||
if (queueGroup == null)
|
||||
return true;
|
||||
|
||||
// Prioritize alerted queues
|
||||
var queues = Groups[queueGroup].Tabs.Select(t => t.Queue)
|
||||
.OrderByDescending(q => q.CurrentDone ? 1 : 0)
|
||||
.ToList();
|
||||
|
||||
if (reverse) queues.Reverse();
|
||||
|
||||
CurrentQueue = queues.SkipWhile(q => q != CurrentQueue)
|
||||
.Skip(1).FirstOrDefault() ?? queues.FirstOrDefault();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public string QueueGroup
|
||||
{
|
||||
get
|
||||
{
|
||||
return queueGroup;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
listOffset = 0;
|
||||
queueGroup = value;
|
||||
SelectNextTab(false);
|
||||
}
|
||||
}
|
||||
|
||||
public ProductionQueue CurrentQueue
|
||||
{
|
||||
get
|
||||
{
|
||||
return paletteWidget.Value.CurrentQueue;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
paletteWidget.Value.CurrentQueue = value;
|
||||
queueGroup = value != null ? value.Info.Group : null;
|
||||
|
||||
// TODO: Scroll tabs so selected queue is visible
|
||||
}
|
||||
}
|
||||
|
||||
public override void Draw()
|
||||
{
|
||||
var rb = RenderBounds;
|
||||
leftButtonRect = new Rectangle(rb.X, rb.Y, ArrowWidth, rb.Height);
|
||||
rightButtonRect = new Rectangle(rb.Right - ArrowWidth, rb.Y, ArrowWidth, rb.Height);
|
||||
|
||||
var leftDisabled = listOffset >= 0;
|
||||
var leftHover = Ui.MouseOverWidget == this && leftButtonRect.Contains(Viewport.LastMousePos);
|
||||
var rightDisabled = listOffset <= Bounds.Width - rightButtonRect.Width - leftButtonRect.Width - contentWidth;
|
||||
var rightHover = Ui.MouseOverWidget == this && rightButtonRect.Contains(Viewport.LastMousePos);
|
||||
|
||||
WidgetUtils.DrawPanel("panel-black", rb);
|
||||
ButtonWidget.DrawBackground("button", leftButtonRect, leftDisabled, leftPressed, leftHover, false);
|
||||
ButtonWidget.DrawBackground("button", rightButtonRect, rightDisabled, rightPressed, rightHover, false);
|
||||
|
||||
WidgetUtils.DrawRGBA(ChromeProvider.GetImage("scrollbar", leftPressed || leftDisabled ? "left_pressed" : "left_arrow"),
|
||||
new float2(leftButtonRect.Left + 2, leftButtonRect.Top + 2));
|
||||
WidgetUtils.DrawRGBA(ChromeProvider.GetImage("scrollbar", rightPressed || rightDisabled ? "right_pressed" : "right_arrow"),
|
||||
new float2(rightButtonRect.Left + 2, rightButtonRect.Top + 2));
|
||||
|
||||
// Draw tab buttons
|
||||
Game.Renderer.EnableScissor(new Rectangle(leftButtonRect.Right, rb.Y + 1, rightButtonRect.Left - leftButtonRect.Right - 1, rb.Height));
|
||||
var origin = new int2(leftButtonRect.Right - 1 + (int)listOffset, leftButtonRect.Y);
|
||||
var font = Game.Renderer.Fonts["TinyBold"];
|
||||
contentWidth = 0;
|
||||
|
||||
foreach (var tab in Groups[queueGroup].Tabs)
|
||||
{
|
||||
var rect = new Rectangle(origin.X + contentWidth, origin.Y, TabWidth, rb.Height);
|
||||
var hover = !leftHover && !rightHover && Ui.MouseOverWidget == this && rect.Contains(Viewport.LastMousePos);
|
||||
var baseName = tab.Queue == CurrentQueue ? "button-highlighted" : "button";
|
||||
ButtonWidget.DrawBackground(baseName, rect, false, false, hover, false);
|
||||
contentWidth += TabWidth - 1;
|
||||
|
||||
var textSize = font.Measure(tab.Name);
|
||||
var position = new int2(rect.X + (rect.Width - textSize.X) / 2, rect.Y + (rect.Height - textSize.Y) / 2);
|
||||
font.DrawTextWithContrast(tab.Name, position, tab.Queue.CurrentDone ? Color.Gold : Color.White, Color.Black, 1);
|
||||
}
|
||||
|
||||
Game.Renderer.DisableScissor();
|
||||
}
|
||||
|
||||
void Scroll(int amount)
|
||||
{
|
||||
listOffset += amount * Game.Settings.Game.UIScrollSpeed;
|
||||
listOffset = Math.Min(0, Math.Max(Bounds.Width - rightButtonRect.Width - leftButtonRect.Width - contentWidth, listOffset));
|
||||
}
|
||||
|
||||
// Is added to world.ActorAdded by the SidebarLogic handler
|
||||
public void ActorChanged(Actor a)
|
||||
{
|
||||
if (a.HasTrait<ProductionQueue>())
|
||||
{
|
||||
var allQueues = a.World.ActorsWithTrait<ProductionQueue>()
|
||||
.Where(p => p.Actor.Owner == p.Actor.World.LocalPlayer && p.Actor.IsInWorld && p.Trait.Enabled)
|
||||
.Select(p => p.Trait).ToArray();
|
||||
|
||||
foreach (var g in Groups.Values)
|
||||
g.Update(allQueues);
|
||||
|
||||
if (queueGroup == null)
|
||||
return;
|
||||
|
||||
// Queue destroyed, was last of type: switch to a new group
|
||||
if (Groups[queueGroup].Tabs.Count == 0)
|
||||
QueueGroup = Groups.Where(g => g.Value.Tabs.Count > 0)
|
||||
.Select(g => g.Key).FirstOrDefault();
|
||||
|
||||
// Queue destroyed, others of same type: switch to another tab
|
||||
else if (!Groups[queueGroup].Tabs.Select(t => t.Queue).Contains(CurrentQueue))
|
||||
SelectNextTab(false);
|
||||
}
|
||||
}
|
||||
|
||||
public override void Tick()
|
||||
{
|
||||
if (leftPressed) Scroll(1);
|
||||
if (rightPressed) Scroll(-1);
|
||||
}
|
||||
|
||||
public override bool YieldMouseFocus(MouseInput mi)
|
||||
{
|
||||
leftPressed = rightPressed = false;
|
||||
return base.YieldMouseFocus(mi);
|
||||
}
|
||||
|
||||
public override bool HandleMouseInput(MouseInput mi)
|
||||
{
|
||||
if (mi.Event == MouseInputEvent.Scroll)
|
||||
{
|
||||
Scroll(mi.ScrollDelta);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (mi.Button != MouseButton.Left)
|
||||
return true;
|
||||
|
||||
if (mi.Event == MouseInputEvent.Down && !TakeMouseFocus(mi))
|
||||
return true;
|
||||
|
||||
if (!HasMouseFocus)
|
||||
return true;
|
||||
|
||||
if (HasMouseFocus && mi.Event == MouseInputEvent.Up)
|
||||
return YieldMouseFocus(mi);
|
||||
|
||||
leftPressed = leftButtonRect.Contains(mi.Location);
|
||||
rightPressed = rightButtonRect.Contains(mi.Location);
|
||||
var leftDisabled = listOffset >= 0;
|
||||
var rightDisabled = listOffset <= Bounds.Width - rightButtonRect.Width - leftButtonRect.Width - contentWidth;
|
||||
|
||||
if (leftPressed || rightPressed)
|
||||
{
|
||||
if ((leftPressed && !leftDisabled) || (rightPressed && !rightDisabled))
|
||||
Sound.PlayNotification(world.Map.Rules, null, "Sounds", "ClickSound", null);
|
||||
else
|
||||
Sound.PlayNotification(world.Map.Rules, null, "Sounds", "ClickDisabledSound", null);
|
||||
}
|
||||
|
||||
// Check production tabs
|
||||
var offsetloc = mi.Location - new int2(leftButtonRect.Right - 1 + (int)listOffset, leftButtonRect.Y);
|
||||
if (offsetloc.X > 0 && offsetloc.X < contentWidth)
|
||||
{
|
||||
CurrentQueue = Groups[queueGroup].Tabs[offsetloc.X / (TabWidth - 1)].Queue;
|
||||
Sound.PlayNotification(world.Map.Rules, null, "Sounds", "ClickSound", null);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public override bool HandleKeyPress(KeyInput e)
|
||||
{
|
||||
if (e.Event != KeyInputEvent.Down)
|
||||
return false;
|
||||
|
||||
var hotkey = Hotkey.FromKeyInput(e);
|
||||
|
||||
if (hotkey == Game.Settings.Keys.NextProductionTabKey)
|
||||
{
|
||||
Sound.PlayNotification(world.Map.Rules, null, "Sounds", "ClickSound", null);
|
||||
return SelectNextTab(false);
|
||||
}
|
||||
else if (hotkey == Game.Settings.Keys.PreviousProductionTabKey)
|
||||
{
|
||||
Sound.PlayNotification(world.Map.Rules, null, "Sounds", "ClickSound", null);
|
||||
return SelectNextTab(true);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
30
OpenRA.Mods.Common/Widgets/ProductionTypeButtonWidget.cs
Normal file
30
OpenRA.Mods.Common/Widgets/ProductionTypeButtonWidget.cs
Normal file
@@ -0,0 +1,30 @@
|
||||
#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.Widgets;
|
||||
|
||||
namespace OpenRA.Mods.Common.Widgets
|
||||
{
|
||||
public class ProductionTypeButtonWidget : ButtonWidget
|
||||
{
|
||||
public readonly string ProductionGroup;
|
||||
public readonly string HotkeyName;
|
||||
|
||||
[ObjectCreator.UseCtor]
|
||||
public ProductionTypeButtonWidget(Ruleset modRules)
|
||||
: base(modRules) { }
|
||||
|
||||
protected ProductionTypeButtonWidget(ProductionTypeButtonWidget other)
|
||||
: base(other)
|
||||
{
|
||||
ProductionGroup = other.ProductionGroup;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user