Merge pull request #5685 from pchote/race-restriction
Add capturable technology support. Fixes #2129
This commit is contained in:
@@ -45,6 +45,7 @@ namespace OpenRA
|
||||
public CPos ExtraLocation;
|
||||
public uint ExtraData;
|
||||
public bool IsImmediate;
|
||||
public bool SuppressVisualFeedback;
|
||||
|
||||
public Player Player { get { return Subject.Owner; } }
|
||||
|
||||
|
||||
@@ -237,7 +237,7 @@ namespace OpenRA.Network
|
||||
return;
|
||||
|
||||
var targetPlayer = order.Player.World.Players.FirstOrDefault(p => p.InternalName == order.TargetString);
|
||||
var newStance = (Stance)order.TargetLocation.X;
|
||||
var newStance = (Stance)order.ExtraData;
|
||||
|
||||
SetPlayerStance(world, order.Player, targetPlayer, newStance);
|
||||
|
||||
|
||||
@@ -152,14 +152,14 @@ namespace OpenRA.Widgets
|
||||
if (o == null)
|
||||
continue;
|
||||
|
||||
if (!flashed)
|
||||
if (!flashed && !o.SuppressVisualFeedback)
|
||||
{
|
||||
if (o.TargetActor != null)
|
||||
{
|
||||
world.AddFrameEndTask(w => w.Add(new FlashTarget(o.TargetActor)));
|
||||
flashed = true;
|
||||
}
|
||||
else if (o.Subject != world.LocalPlayer.PlayerActor && o.TargetLocation != CPos.Zero) // TODO: this filters building placement, but also suppport powers :(
|
||||
else if (o.TargetLocation != CPos.Zero)
|
||||
{
|
||||
world.AddFrameEndTask(w => w.Add(new MoveFlash(worldRenderer.Position(worldRenderer.Viewport.ViewToWorldPx(mi.Location)), world)));
|
||||
flashed = true;
|
||||
|
||||
@@ -32,7 +32,7 @@ namespace OpenRA.Mods.Cnc
|
||||
public ProductionAirdrop(ProductionAirdropInfo info, Actor self)
|
||||
: base(info, self) { }
|
||||
|
||||
public override bool Produce(Actor self, ActorInfo producee)
|
||||
public override bool Produce(Actor self, ActorInfo producee, string raceVariant)
|
||||
{
|
||||
var owner = self.Owner;
|
||||
|
||||
@@ -69,7 +69,8 @@ namespace OpenRA.Mods.Cnc
|
||||
|
||||
foreach (var cargo in self.TraitsImplementing<INotifyDelivery>())
|
||||
cargo.Delivered(self);
|
||||
self.World.AddFrameEndTask(ww => DoProduction(self, producee, exit));
|
||||
|
||||
self.World.AddFrameEndTask(ww => DoProduction(self, producee, exit, raceVariant));
|
||||
Sound.PlayNotification(self.World.Map.Rules, self.Owner, "Speech", info.ReadyAudio, self.Owner.Country.Race);
|
||||
}));
|
||||
|
||||
|
||||
@@ -41,10 +41,10 @@ namespace OpenRA.Mods.Cnc.Widgets
|
||||
// Find an actor with a queue
|
||||
var producer = world.Selection.Actors.FirstOrDefault(a => a.IsInWorld
|
||||
&& a.World.LocalPlayer == a.Owner
|
||||
&& a.HasTrait<ProductionQueue>());
|
||||
&& a.TraitsImplementing<ProductionQueue>().Any(q => q.Enabled));
|
||||
|
||||
if (producer != null)
|
||||
tabsWidget.Value.CurrentQueue = producer.TraitsImplementing<ProductionQueue>().First();
|
||||
tabsWidget.Value.CurrentQueue = producer.TraitsImplementing<ProductionQueue>().First(q => q.Enabled);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,7 +54,7 @@ namespace OpenRA.Mods.Cnc.Widgets.Logic
|
||||
|
||||
nameLabel.GetText = () => tooltip.Name;
|
||||
|
||||
var prereqs = buildable.Prerequisites.Select(a => ActorName(mapRules, a));
|
||||
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;
|
||||
|
||||
|
||||
@@ -81,7 +81,7 @@ namespace OpenRA.Mods.Cnc.Widgets
|
||||
|
||||
public override void Tick()
|
||||
{
|
||||
if (CurrentQueue != null && !CurrentQueue.self.IsInWorld)
|
||||
if (CurrentQueue != null && !CurrentQueue.Actor.IsInWorld)
|
||||
CurrentQueue = null;
|
||||
|
||||
if (CurrentQueue != null)
|
||||
@@ -125,20 +125,20 @@ namespace OpenRA.Mods.Cnc.Widgets
|
||||
if (first != null && first.Done && actor.Traits.Contains<BuildingInfo>())
|
||||
{
|
||||
Sound.Play(TabClick);
|
||||
World.OrderGenerator = new PlaceBuildingOrderGenerator(CurrentQueue.self, icon.Name);
|
||||
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.self, icon.Name, false));
|
||||
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.self, icon.Name,
|
||||
World.IssueOrder(Order.StartProduction(CurrentQueue.Actor, icon.Name,
|
||||
Game.GetModifierKeys().HasModifier(Modifiers.Shift) ? 5 : 1));
|
||||
}
|
||||
else
|
||||
@@ -155,13 +155,13 @@ namespace OpenRA.Mods.Cnc.Widgets
|
||||
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.self, icon.Name,
|
||||
World.IssueOrder(Order.CancelProduction(CurrentQueue.Actor, icon.Name,
|
||||
Game.GetModifierKeys().HasModifier(Modifiers.Shift) ? 5 : 1));
|
||||
}
|
||||
else
|
||||
{
|
||||
Sound.PlayNotification(World.Map.Rules, World.LocalPlayer, "Speech", CurrentQueue.Info.OnHoldAudio, World.LocalPlayer.Country.Race);
|
||||
World.IssueOrder(Order.PauseProduction(CurrentQueue.self, icon.Name, true));
|
||||
World.IssueOrder(Order.PauseProduction(CurrentQueue.Actor, icon.Name, true));
|
||||
}
|
||||
}
|
||||
else
|
||||
|
||||
@@ -196,7 +196,7 @@ namespace OpenRA.Mods.Cnc.Widgets
|
||||
if (a.HasTrait<ProductionQueue>())
|
||||
{
|
||||
var allQueues = a.World.ActorsWithTrait<ProductionQueue>()
|
||||
.Where(p => p.Actor.Owner == p.Actor.World.LocalPlayer && p.Actor.IsInWorld)
|
||||
.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)
|
||||
|
||||
@@ -68,12 +68,12 @@ namespace OpenRA.Mods.RA.Render
|
||||
var production = self.TraitOrDefault<Production>();
|
||||
|
||||
var perBuildingQueues = self.TraitsImplementing<ProductionQueue>();
|
||||
queue = perBuildingQueues.FirstOrDefault(q => production.Info.Produces.Contains(q.Info.Type));
|
||||
queue = perBuildingQueues.FirstOrDefault(q => q.Enabled && production.Info.Produces.Contains(q.Info.Type));
|
||||
|
||||
if (queue == null)
|
||||
{
|
||||
var perPlayerQueues = self.Owner.PlayerActor.TraitsImplementing<ProductionQueue>();
|
||||
queue = perPlayerQueues.FirstOrDefault(q => production.Info.Produces.Contains(q.Info.Type));
|
||||
queue = perPlayerQueues.FirstOrDefault(q => q.Enabled && production.Info.Produces.Contains(q.Info.Type));
|
||||
}
|
||||
|
||||
if (queue == null)
|
||||
|
||||
@@ -51,7 +51,7 @@ namespace OpenRA.Mods.RA.AI
|
||||
{
|
||||
HackyAI.BotDebug("AI: Starting production of {0}".F(item.Name));
|
||||
state = BuildState.WaitForProduction;
|
||||
ai.world.IssueOrder(Order.StartProduction(queue.self, item.Name, 1));
|
||||
ai.world.IssueOrder(Order.StartProduction(queue.Actor, item.Name, 1));
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -60,7 +60,7 @@ namespace OpenRA.Mods.RA.AI
|
||||
return;
|
||||
|
||||
if (currentBuilding.Paused)
|
||||
ai.world.IssueOrder(Order.PauseProduction(queue.self, currentBuilding.Item, false));
|
||||
ai.world.IssueOrder(Order.PauseProduction(queue.Actor, currentBuilding.Item, false));
|
||||
else if (currentBuilding.Done)
|
||||
{
|
||||
state = BuildState.WaitForFeedback;
|
||||
@@ -77,14 +77,16 @@ namespace OpenRA.Mods.RA.AI
|
||||
if (location == null)
|
||||
{
|
||||
HackyAI.BotDebug("AI: Nowhere to place {0}".F(currentBuilding.Item));
|
||||
ai.world.IssueOrder(Order.CancelProduction(queue.self, currentBuilding.Item, 1));
|
||||
ai.world.IssueOrder(Order.CancelProduction(queue.Actor, currentBuilding.Item, 1));
|
||||
}
|
||||
else
|
||||
{
|
||||
ai.world.IssueOrder(new Order("PlaceBuilding", ai.p.PlayerActor, false)
|
||||
{
|
||||
TargetLocation = location.Value,
|
||||
TargetString = currentBuilding.Item
|
||||
TargetString = currentBuilding.Item,
|
||||
TargetActor = queue.Actor,
|
||||
SuppressVisualFeedback = true
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,6 +26,9 @@ namespace OpenRA.Mods.RA.AI
|
||||
public readonly string Name = "Unnamed Bot";
|
||||
public readonly int SquadSize = 8;
|
||||
|
||||
public readonly string[] BuildingQueues = { "Building" };
|
||||
public readonly string[] DefenseQueues = { "Defense" };
|
||||
|
||||
public readonly int AssignRolesInterval = 20;
|
||||
public readonly int RushInterval = 600;
|
||||
public readonly int AttackForceInterval = 30;
|
||||
@@ -106,7 +109,7 @@ namespace OpenRA.Mods.RA.AI
|
||||
RushFuzzy rushFuzzy = new RushFuzzy();
|
||||
|
||||
Cache<Player, Enemy> aggro = new Cache<Player, Enemy>(_ => new Enemy());
|
||||
BaseBuilder[] builders;
|
||||
List<BaseBuilder> builders = new List<BaseBuilder>();
|
||||
|
||||
List<Squad> squads = new List<Squad>();
|
||||
List<Actor> unitsHangingAroundTheBase = new List<Actor>();
|
||||
@@ -144,10 +147,11 @@ namespace OpenRA.Mods.RA.AI
|
||||
playerPower = p.PlayerActor.Trait<PowerManager>();
|
||||
supportPowerMngr = p.PlayerActor.Trait<SupportPowerManager>();
|
||||
playerResource = p.PlayerActor.Trait<PlayerResources>();
|
||||
builders = new BaseBuilder[] {
|
||||
new BaseBuilder(this, "Building", q => ChooseBuildingToBuild(q, false)),
|
||||
new BaseBuilder(this, "Defense", q => ChooseBuildingToBuild(q, true))
|
||||
};
|
||||
|
||||
foreach (var building in Info.BuildingQueues)
|
||||
builders.Add(new BaseBuilder(this, building, q => ChooseBuildingToBuild(q, false)));
|
||||
foreach (var defense in Info.DefenseQueues)
|
||||
builders.Add(new BaseBuilder(this, defense, q => ChooseBuildingToBuild(q, true)));
|
||||
|
||||
random = new MersenneTwister((int)p.PlayerActor.ActorID);
|
||||
|
||||
@@ -232,8 +236,7 @@ namespace OpenRA.Mods.RA.AI
|
||||
if (!names.Any() || !names.ContainsKey(commonName))
|
||||
return null;
|
||||
|
||||
return Map.Rules.Actors.Where(k => names[commonName].Contains(k.Key) &&
|
||||
k.Value.Traits.Get<BuildableInfo>().Owner.Contains(owner.Country.Race)).Random(random).Value;
|
||||
return Map.Rules.Actors.Where(k => names[commonName].Contains(k.Key)).Random(random).Value;
|
||||
}
|
||||
|
||||
bool HasAdequatePower()
|
||||
@@ -685,7 +688,7 @@ namespace OpenRA.Mods.RA.AI
|
||||
p.PlayerName, buildings.Length);
|
||||
|
||||
foreach (var a in buildings)
|
||||
world.IssueOrder(new Order("SetRallyPoint", a.Actor, false) { TargetLocation = ChooseRallyLocationNear(a.Actor.Location) });
|
||||
world.IssueOrder(new Order("SetRallyPoint", a.Actor, false) { TargetLocation = ChooseRallyLocationNear(a.Actor.Location), SuppressVisualFeedback = true });
|
||||
}
|
||||
|
||||
// Won't work for shipyards...
|
||||
@@ -770,7 +773,7 @@ namespace OpenRA.Mods.RA.AI
|
||||
if (attackLocation == null)
|
||||
return;
|
||||
|
||||
world.IssueOrder(new Order(sp.Info.OrderName, supportPowerMngr.self, false) { TargetLocation = attackLocation.Value });
|
||||
world.IssueOrder(new Order(sp.Info.OrderName, supportPowerMngr.self, false) { TargetLocation = attackLocation.Value, SuppressVisualFeedback = true });
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -809,7 +812,7 @@ namespace OpenRA.Mods.RA.AI
|
||||
internal IEnumerable<ProductionQueue> FindQueues(string category)
|
||||
{
|
||||
return world.ActorsWithTrait<ProductionQueue>()
|
||||
.Where(a => a.Actor.Owner == p && a.Trait.Info.Type == category)
|
||||
.Where(a => a.Actor.Owner == p && a.Trait.Info.Type == category && a.Trait.Enabled)
|
||||
.Select(a => a.Trait);
|
||||
}
|
||||
|
||||
@@ -839,7 +842,7 @@ namespace OpenRA.Mods.RA.AI
|
||||
ChooseUnitToBuild(queue);
|
||||
|
||||
if (unit != null && Info.UnitsToBuild.Any(u => u.Key == unit.Name))
|
||||
world.IssueOrder(Order.StartProduction(queue.self, unit.Name, 1));
|
||||
world.IssueOrder(Order.StartProduction(queue.Actor, unit.Name, 1));
|
||||
}
|
||||
|
||||
void BuildUnit(string category, string name)
|
||||
@@ -849,7 +852,7 @@ namespace OpenRA.Mods.RA.AI
|
||||
return;
|
||||
|
||||
if (Map.Rules.Actors[name] != null)
|
||||
world.IssueOrder(Order.StartProduction(queue.self, name, 1));
|
||||
world.IssueOrder(Order.StartProduction(queue.Actor, name, 1));
|
||||
}
|
||||
|
||||
public void Damaged(Actor self, AttackInfo e)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2011 The OpenRA Developers (see AUTHORS)
|
||||
* Copyright 2007-2014 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,
|
||||
@@ -16,21 +16,23 @@ namespace OpenRA.Mods.RA.Activities
|
||||
{
|
||||
class Transform : Activity
|
||||
{
|
||||
public readonly string ToActor = null;
|
||||
public CVec Offset = new CVec(0, 0);
|
||||
public readonly string ToActor;
|
||||
public CVec Offset = CVec.Zero;
|
||||
public int Facing = 96;
|
||||
public string[] Sounds = {};
|
||||
public string[] Sounds = { };
|
||||
public int ForceHealthPercentage = 0;
|
||||
public bool SkipMakeAnims = false;
|
||||
public string Race = null;
|
||||
|
||||
public Transform(Actor self, string toActor)
|
||||
{
|
||||
this.ToActor = toActor;
|
||||
ToActor = toActor;
|
||||
}
|
||||
|
||||
public override Activity Tick( Actor self )
|
||||
public override Activity Tick(Actor self)
|
||||
{
|
||||
if (IsCanceled) return NextActivity;
|
||||
if (IsCanceled)
|
||||
return NextActivity;
|
||||
|
||||
self.World.AddFrameEndTask(w =>
|
||||
{
|
||||
@@ -48,12 +50,16 @@ namespace OpenRA.Mods.RA.Activities
|
||||
|
||||
var init = new TypeDictionary
|
||||
{
|
||||
new LocationInit( self.Location + Offset ),
|
||||
new OwnerInit( self.Owner ),
|
||||
new FacingInit( Facing ),
|
||||
new LocationInit(self.Location + Offset),
|
||||
new OwnerInit(self.Owner),
|
||||
new FacingInit(Facing),
|
||||
};
|
||||
|
||||
if (SkipMakeAnims) init.Add(new SkipMakeAnimsInit());
|
||||
if (SkipMakeAnims)
|
||||
init.Add(new SkipMakeAnimsInit());
|
||||
|
||||
if (Race != null)
|
||||
init.Add(new RaceInit(Race));
|
||||
|
||||
var health = self.TraitOrDefault<Health>();
|
||||
if (health != null)
|
||||
@@ -62,15 +68,14 @@ namespace OpenRA.Mods.RA.Activities
|
||||
? ForceHealthPercentage / 100f
|
||||
: (float)health.HP / health.MaxHP;
|
||||
|
||||
init.Add( new HealthInit(newHP) );
|
||||
init.Add(new HealthInit(newHP));
|
||||
}
|
||||
|
||||
var cargo = self.TraitOrDefault<Cargo>();
|
||||
if (cargo != null)
|
||||
init.Add( new RuntimeCargoInit( cargo.Passengers.ToArray() ) );
|
||||
|
||||
var a = w.CreateActor( ToActor, init );
|
||||
init.Add(new RuntimeCargoInit(cargo.Passengers.ToArray()));
|
||||
|
||||
var a = w.CreateActor(ToActor, init);
|
||||
foreach (var nt in self.TraitsImplementing<INotifyTransformed>())
|
||||
nt.OnTransformed(a);
|
||||
|
||||
|
||||
@@ -66,7 +66,7 @@ namespace OpenRA.Mods.RA
|
||||
public void ResolveOrder(Actor self, Order order)
|
||||
{
|
||||
if (order.OrderString == "SetUnitStance" && info.EnableStances)
|
||||
Stance = (UnitStance)order.TargetLocation.X;
|
||||
Stance = (UnitStance)order.ExtraData;
|
||||
}
|
||||
|
||||
public void Damaged(Actor self, AttackInfo e)
|
||||
|
||||
@@ -17,7 +17,7 @@ namespace OpenRA.Mods.RA
|
||||
public readonly string[] Prerequisites = { };
|
||||
public readonly string[] Owner = { };
|
||||
|
||||
public readonly string Queue;
|
||||
public readonly string[] Queue = { };
|
||||
public readonly int BuildLimit = 0;
|
||||
|
||||
// TODO: UI fluff; doesn't belong here
|
||||
|
||||
@@ -20,14 +20,20 @@ namespace OpenRA.Mods.RA
|
||||
[ActorReference]
|
||||
public readonly string HuskActor = null;
|
||||
|
||||
public object Create(ActorInitializer init) { return new LeavesHusk(this); }
|
||||
public object Create(ActorInitializer init) { return new LeavesHusk(init, this); }
|
||||
}
|
||||
|
||||
public class LeavesHusk : INotifyKilled
|
||||
{
|
||||
LeavesHuskInfo info;
|
||||
readonly LeavesHuskInfo info;
|
||||
readonly string race;
|
||||
|
||||
public LeavesHusk(LeavesHuskInfo info) { this.info = info; }
|
||||
public LeavesHusk(ActorInitializer init, LeavesHuskInfo info)
|
||||
{
|
||||
this.info = info;
|
||||
|
||||
race = init.Contains<RaceInit>() ? init.Get<RaceInit, string>() : init.self.Owner.Country.Race;
|
||||
}
|
||||
|
||||
public void Killed(Actor self, AttackInfo e)
|
||||
{
|
||||
@@ -42,6 +48,7 @@ namespace OpenRA.Mods.RA
|
||||
new LocationInit(self.Location),
|
||||
new CenterPositionInit(self.CenterPosition),
|
||||
new OwnerInit(self.Owner),
|
||||
new RaceInit(race),
|
||||
new SkipMakeAnimsInit()
|
||||
};
|
||||
|
||||
|
||||
@@ -42,8 +42,9 @@ namespace OpenRA.Mods.RA
|
||||
var bi = i.Value.Traits.GetOrDefault<BuildableInfo>();
|
||||
if (bi != null)
|
||||
foreach (var prereq in bi.Prerequisites)
|
||||
if (!providedPrereqs.Contains(prereq.Replace("!", "").Replace("~", "")))
|
||||
emitError("Buildable actor {0} has prereq {1} not provided by anything.".F(i.Key, prereq));
|
||||
if (!prereq.StartsWith("~disabled"))
|
||||
if (!providedPrereqs.Contains(prereq.Replace("!", "").Replace("~", "")))
|
||||
emitError("Buildable actor {0} has prereq {1} not provided by anything.".F(i.Key, prereq));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -517,6 +517,7 @@
|
||||
<Compile Include="Render\WithRepairOverlay.cs" />
|
||||
<Compile Include="Activities\MoveWithinRange.cs" />
|
||||
<Compile Include="Lint\CheckPlayers.cs" />
|
||||
<Compile Include="Player\ProvidesCustomPrerequisite.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\OpenRA.Game\OpenRA.Game.csproj">
|
||||
|
||||
@@ -23,7 +23,7 @@ namespace OpenRA.Mods.RA.Orders
|
||||
if (!world.ShroudObscures(xy))
|
||||
{
|
||||
world.CancelInputMode();
|
||||
yield return new Order("PlaceBeacon", world.LocalPlayer.PlayerActor, false) { TargetLocation = xy };
|
||||
yield return new Order("PlaceBeacon", world.LocalPlayer.PlayerActor, false) { TargetLocation = xy, SuppressVisualFeedback = true };
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -22,19 +22,22 @@ namespace OpenRA.Mods.RA.Orders
|
||||
readonly Actor Producer;
|
||||
readonly string Building;
|
||||
readonly BuildingInfo BuildingInfo;
|
||||
|
||||
IEnumerable<IRenderable> preview;
|
||||
Sprite buildOk, buildBlocked;
|
||||
bool initialized = false;
|
||||
|
||||
public PlaceBuildingOrderGenerator(Actor producer, string name)
|
||||
public PlaceBuildingOrderGenerator(ProductionQueue queue, string name)
|
||||
{
|
||||
Producer = producer;
|
||||
Producer = queue.Actor;
|
||||
Building = name;
|
||||
var tileset = producer.World.TileSet.Id.ToLowerInvariant();
|
||||
BuildingInfo = producer.World.Map.Rules.Actors[Building].Traits.Get<BuildingInfo>();
|
||||
|
||||
buildOk = producer.World.Map.SequenceProvider.GetSequence("overlay", "build-valid-{0}".F(tileset)).GetSprite(0);
|
||||
buildBlocked = producer.World.Map.SequenceProvider.GetSequence("overlay", "build-invalid").GetSprite(0);
|
||||
var map = Producer.World.Map;
|
||||
var tileset = Producer.World.TileSet.Id.ToLowerInvariant();
|
||||
BuildingInfo = map.Rules.Actors[Building].Traits.Get<BuildingInfo>();
|
||||
|
||||
buildOk = map.SequenceProvider.GetSequence("overlay", "build-valid-{0}".F(tileset)).GetSprite(0);
|
||||
buildBlocked = map.SequenceProvider.GetSequence("overlay", "build-invalid").GetSprite(0);
|
||||
}
|
||||
|
||||
public IEnumerable<Order> Order(World world, CPos xy, MouseInput mi)
|
||||
@@ -62,8 +65,13 @@ namespace OpenRA.Mods.RA.Orders
|
||||
}
|
||||
|
||||
var isLineBuild = world.Map.Rules.Actors[Building].Traits.Contains<LineBuildInfo>();
|
||||
yield return new Order(isLineBuild ? "LineBuild" : "PlaceBuilding",
|
||||
Producer.Owner.PlayerActor, false) { TargetLocation = topLeft, TargetString = Building };
|
||||
yield return new Order(isLineBuild ? "LineBuild" : "PlaceBuilding", Producer.Owner.PlayerActor, false)
|
||||
{
|
||||
TargetLocation = topLeft,
|
||||
TargetActor = Producer,
|
||||
TargetString = Building,
|
||||
SuppressVisualFeedback = true
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -23,22 +23,27 @@ namespace OpenRA.Mods.RA
|
||||
{
|
||||
[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.self, this); }
|
||||
public override object Create(ActorInitializer init) { return new ClassicProductionQueue(init, this); }
|
||||
}
|
||||
|
||||
public class ClassicProductionQueue : ProductionQueue, ISync
|
||||
{
|
||||
public new ClassicProductionQueueInfo Info;
|
||||
static readonly ActorInfo[] NoItems = { };
|
||||
|
||||
public ClassicProductionQueue(Actor self, ClassicProductionQueueInfo info)
|
||||
: base(self, self, info)
|
||||
readonly Actor self;
|
||||
readonly ClassicProductionQueueInfo info;
|
||||
|
||||
public ClassicProductionQueue(ActorInitializer init, ClassicProductionQueueInfo info)
|
||||
: base(init, init.self, info)
|
||||
{
|
||||
this.Info = info;
|
||||
this.self = init.self;
|
||||
this.info = info;
|
||||
}
|
||||
|
||||
[Sync] bool isActive = false;
|
||||
@@ -47,23 +52,25 @@ namespace OpenRA.Mods.RA
|
||||
{
|
||||
isActive = false;
|
||||
foreach (var x in self.World.ActorsWithTrait<Production>())
|
||||
{
|
||||
if (x.Actor.Owner == self.Owner && x.Trait.Info.Produces.Contains(Info.Type))
|
||||
{
|
||||
isActive = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
base.Tick(self);
|
||||
}
|
||||
|
||||
static ActorInfo[] None = { };
|
||||
public override IEnumerable<ActorInfo> AllItems()
|
||||
{
|
||||
return isActive ? base.AllItems() : None;
|
||||
return isActive ? base.AllItems() : NoItems;
|
||||
}
|
||||
|
||||
public override IEnumerable<ActorInfo> BuildableItems()
|
||||
{
|
||||
return isActive ? base.BuildableItems() : None;
|
||||
return isActive ? base.BuildableItems() : NoItems;
|
||||
}
|
||||
|
||||
protected override bool BuildUnit(string name)
|
||||
@@ -83,16 +90,17 @@ namespace OpenRA.Mods.RA
|
||||
|
||||
foreach (var p in producers.Where(p => !p.Actor.IsDisabled()))
|
||||
{
|
||||
if (p.Trait.Produce(p.Actor, self.World.Map.Rules.Actors[name]))
|
||||
if (p.Trait.Produce(p.Actor, self.World.Map.Rules.Actors[name], Race))
|
||||
{
|
||||
FinishProduction();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public override int GetBuildTime(String unitString)
|
||||
public override int GetBuildTime(string unitString)
|
||||
{
|
||||
var unit = self.World.Map.Rules.Actors[unitString];
|
||||
if (unit == null || !unit.Traits.Contains<BuildableInfo>())
|
||||
@@ -103,14 +111,15 @@ namespace OpenRA.Mods.RA
|
||||
|
||||
var time = (int)(unit.GetBuildTime() * Info.BuildSpeed);
|
||||
|
||||
if (Info.SpeedUp)
|
||||
if (info.SpeedUp)
|
||||
{
|
||||
var queues = unit.Traits.Get<BuildableInfo>().Queue;
|
||||
var selfsameBuildings = self.World.ActorsWithTrait<Production>()
|
||||
.Where(p => p.Trait.Info.Produces.Contains(unit.Traits.Get<BuildableInfo>().Queue))
|
||||
.Where(p => p.Actor.Owner == self.Owner).ToArray();
|
||||
.Where(p => p.Actor.Owner == self.Owner && p.Trait.Info.Produces.Intersect(queues).Any())
|
||||
.ToArray();
|
||||
|
||||
var speedModifier = selfsameBuildings.Count().Clamp(1, Info.BuildTimeSpeedReduction.Length) - 1;
|
||||
time = (time * Info.BuildTimeSpeedReduction[speedModifier]) / 100;
|
||||
var speedModifier = selfsameBuildings.Count().Clamp(1, info.BuildTimeSpeedReduction.Length) - 1;
|
||||
time = (time * info.BuildTimeSpeedReduction[speedModifier]) / 100;
|
||||
}
|
||||
|
||||
return time;
|
||||
|
||||
@@ -28,19 +28,17 @@ namespace OpenRA.Mods.RA
|
||||
{
|
||||
var prevItems = GetNumBuildables(self.Owner);
|
||||
|
||||
// Find the queue with the target actor
|
||||
var queue = w.ActorsWithTrait<ProductionQueue>()
|
||||
.Where(p => p.Actor.Owner == self.Owner &&
|
||||
p.Trait.CurrentItem() != null &&
|
||||
p.Trait.CurrentItem().Item == order.TargetString &&
|
||||
p.Trait.CurrentItem().RemainingTime == 0)
|
||||
.Select(p => p.Trait)
|
||||
.FirstOrDefault();
|
||||
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));
|
||||
|
||||
if (queue == null)
|
||||
return;
|
||||
|
||||
var unit = self.World.Map.Rules.Actors[order.TargetString];
|
||||
|
||||
var buildingInfo = unit.Traits.Get<BuildingInfo>();
|
||||
|
||||
if (order.OrderString == "LineBuild")
|
||||
@@ -52,11 +50,13 @@ namespace OpenRA.Mods.RA
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -72,7 +72,9 @@ namespace OpenRA.Mods.RA
|
||||
{
|
||||
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);
|
||||
}
|
||||
@@ -106,7 +108,7 @@ namespace OpenRA.Mods.RA
|
||||
|
||||
var producers = self.World.ActorsWithTrait<Production>()
|
||||
.Where(x => x.Actor.Owner == self.Owner
|
||||
&& x.Actor.Info.Traits.Get<ProductionInfo>().Produces.Contains(bi.Queue))
|
||||
&& x.Actor.Info.Traits.Get<ProductionInfo>().Produces.Intersect(bi.Queue).Any())
|
||||
.ToList();
|
||||
var producer = producers.Where(x => x.Actor.IsPrimaryBuilding()).Concat(producers)
|
||||
.FirstOrDefault();
|
||||
|
||||
@@ -23,205 +23,236 @@ namespace OpenRA.Mods.RA
|
||||
{
|
||||
[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 float BuildSpeed = 0.4f;
|
||||
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.self, init.self.Owner.PlayerActor, this); }
|
||||
public virtual object Create(ActorInitializer init) { return new ProductionQueue(init, init.self.Owner.PlayerActor, this); }
|
||||
}
|
||||
|
||||
public class ProductionQueue : IResolveOrder, ITick, ITechTreeElement, INotifyCapture, INotifyKilled, INotifySold, ISync, INotifyTransform
|
||||
public class ProductionQueue : IResolveOrder, ITick, ITechTreeElement, INotifyOwnerChanged, INotifyKilled, INotifySold, ISync, INotifyTransform
|
||||
{
|
||||
public readonly Actor self;
|
||||
public ProductionQueueInfo Info;
|
||||
PowerManager PlayerPower;
|
||||
public readonly ProductionQueueInfo Info;
|
||||
readonly Actor self;
|
||||
|
||||
// Will change if the owner changes
|
||||
PowerManager playerPower;
|
||||
PlayerResources playerResources;
|
||||
readonly CountryInfo Race;
|
||||
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 List<ProductionItem> Queue = new List<ProductionItem>();
|
||||
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 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; }
|
||||
|
||||
// A list of things we could possibly build, even if our race doesn't normally get it
|
||||
public Dictionary<ActorInfo, ProductionState> Produceable;
|
||||
public string Race { get; private set; }
|
||||
|
||||
public ProductionQueue( Actor self, Actor playerActor, ProductionQueueInfo info )
|
||||
public ProductionQueue(ActorInitializer init, Actor playerActor, ProductionQueueInfo info)
|
||||
{
|
||||
this.self = self;
|
||||
this.Info = info;
|
||||
self = init.self;
|
||||
Info = info;
|
||||
playerResources = playerActor.Trait<PlayerResources>();
|
||||
PlayerPower = playerActor.Trait<PowerManager>();
|
||||
playerPower = playerActor.Trait<PowerManager>();
|
||||
developerMode = playerActor.Trait<DeveloperMode>();
|
||||
|
||||
Race = self.Owner.Country;
|
||||
Produceable = InitTech(playerActor);
|
||||
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)
|
||||
if (queue.Count == 0)
|
||||
return;
|
||||
|
||||
// Refund the current item
|
||||
playerResources.GiveCash(Queue[0].TotalCost - Queue[0].RemainingCost);
|
||||
Queue.Clear();
|
||||
playerResources.GiveCash(queue[0].TotalCost - queue[0].RemainingCost);
|
||||
queue.Clear();
|
||||
}
|
||||
|
||||
public void OnCapture(Actor self, Actor captor, Player oldOwner, Player newOwner)
|
||||
public void OnOwnerChanged(Actor self, Player oldOwner, Player newOwner)
|
||||
{
|
||||
PlayerPower = newOwner.PlayerActor.Trait<PowerManager>();
|
||||
playerResources = newOwner.PlayerActor.Trait<PlayerResources>();
|
||||
ClearQueue();
|
||||
|
||||
// Produceable contains the tech from the original owner - this is desired so we don't clear it.
|
||||
Produceable = InitTech(self.Owner.PlayerActor);
|
||||
playerPower = newOwner.PlayerActor.Trait<PowerManager>();
|
||||
playerResources = newOwner.PlayerActor.Trait<PlayerResources>();
|
||||
developerMode = newOwner.PlayerActor.Trait<DeveloperMode>();
|
||||
|
||||
// Force a third(!) tech tree update to ensure that prerequisites are correct.
|
||||
// The first two updates are triggered by adding/removing the actor when
|
||||
// changing ownership, *before* the new techtree watchers have been set up.
|
||||
// This is crap.
|
||||
self.Owner.PlayerActor.Trait<TechTree>().Update();
|
||||
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(); }
|
||||
public void Selling(Actor self) {}
|
||||
public void Selling(Actor self) { }
|
||||
public void Sold(Actor self) { ClearQueue(); }
|
||||
public void OnTransform(Actor self) { ClearQueue(); }
|
||||
|
||||
Dictionary<ActorInfo, ProductionState> InitTech(Actor playerActor)
|
||||
void CacheProduceables(Actor playerActor)
|
||||
{
|
||||
var tech = new Dictionary<ActorInfo, ProductionState>();
|
||||
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 = bi.Owner.Contains(Race.Race);
|
||||
var buildable = !Info.RequireOwner || bi.Owner.Contains(Race);
|
||||
|
||||
// Checks if Prerequisites want to hide the Actor from buildQueue if they are false
|
||||
tech.Add(a, new ProductionState { Visible = buildable });
|
||||
produceable.Add(a, new ProductionState { Visible = buildable });
|
||||
|
||||
if (buildable)
|
||||
ttc.Add(a.Name, bi.Prerequisites, bi.BuildLimit, this);
|
||||
}
|
||||
|
||||
return tech;
|
||||
}
|
||||
|
||||
IEnumerable<ActorInfo> AllBuildables(string category)
|
||||
{
|
||||
return self.World.Map.Rules.Actors.Values
|
||||
.Where( x => x.Name[ 0 ] != '^' )
|
||||
.Where( x => x.Traits.Contains<BuildableInfo>() )
|
||||
.Where( x => x.Traits.Get<BuildableInfo>().Queue == category );
|
||||
}
|
||||
|
||||
public void OverrideProduction(ActorInfo type, bool buildable)
|
||||
{
|
||||
Produceable[type].Buildable = buildable;
|
||||
Produceable[type].Sticky = true;
|
||||
.Where(x =>
|
||||
x.Name[0] != '^' &&
|
||||
x.Traits.Contains<BuildableInfo>() &&
|
||||
x.Traits.Get<BuildableInfo>().Queue.Contains(category));
|
||||
}
|
||||
|
||||
public void PrerequisitesAvailable(string key)
|
||||
{
|
||||
var ps = Produceable[ self.World.Map.Rules.Actors[key] ];
|
||||
if (!ps.Sticky)
|
||||
ps.Buildable = true;
|
||||
produceable[self.World.Map.Rules.Actors[key]].Buildable = true;
|
||||
}
|
||||
|
||||
public void PrerequisitesUnavailable(string key)
|
||||
{
|
||||
var ps = Produceable[ self.World.Map.Rules.Actors[key] ];
|
||||
if (!ps.Sticky)
|
||||
ps.Buildable = false;
|
||||
produceable[self.World.Map.Rules.Actors[key]].Buildable = false;
|
||||
}
|
||||
|
||||
public void PrerequisitesItemHidden(string key)
|
||||
{
|
||||
Produceable[self.World.Map.Rules.Actors[key]].Visible = false;
|
||||
produceable[self.World.Map.Rules.Actors[key]].Visible = false;
|
||||
}
|
||||
|
||||
public void PrerequisitesItemVisible(string key)
|
||||
{
|
||||
Produceable[self.World.Map.Rules.Actors[key]].Visible = true;
|
||||
produceable[self.World.Map.Rules.Actors[key]].Visible = true;
|
||||
}
|
||||
|
||||
public ProductionItem CurrentItem()
|
||||
{
|
||||
return Queue.ElementAtOrDefault(0);
|
||||
return queue.ElementAtOrDefault(0);
|
||||
}
|
||||
|
||||
public IEnumerable<ProductionItem> AllQueued()
|
||||
{
|
||||
return Queue;
|
||||
return queue;
|
||||
}
|
||||
|
||||
public virtual IEnumerable<ActorInfo> AllItems()
|
||||
{
|
||||
if (self.World.AllowDevCommands && self.Owner.PlayerActor.Trait<DeveloperMode>().AllTech)
|
||||
return Produceable.Select(a => a.Key);
|
||||
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);
|
||||
return produceable.Where(a => a.Value.Buildable || a.Value.Visible).Select(a => a.Key);
|
||||
}
|
||||
|
||||
public virtual IEnumerable<ActorInfo> BuildableItems()
|
||||
{
|
||||
if (self.World.AllowDevCommands && self.Owner.PlayerActor.Trait<DeveloperMode>().AllTech)
|
||||
return Produceable.Select(a => a.Key);
|
||||
if (self.World.AllowDevCommands && developerMode.AllTech)
|
||||
return produceable.Select(a => a.Key);
|
||||
|
||||
return Produceable.Where(a => a.Value.Buildable).Select(a => a.Key);
|
||||
return produceable.Where(a => a.Value.Buildable).Select(a => a.Key);
|
||||
}
|
||||
|
||||
public bool CanBuild(ActorInfo actor)
|
||||
{
|
||||
return Produceable.ContainsKey(actor) && Produceable[actor].Buildable;
|
||||
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))
|
||||
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.
|
||||
playerResources.GiveCash(queue[0].TotalCost - queue[0].RemainingCost); // refund what's been paid so far.
|
||||
FinishProduction();
|
||||
}
|
||||
if (Queue.Count > 0)
|
||||
Queue[ 0 ].Tick(playerResources);
|
||||
|
||||
if (queue.Count > 0)
|
||||
queue[0].Tick(playerResources);
|
||||
}
|
||||
|
||||
public void ResolveOrder(Actor self, Order order)
|
||||
{
|
||||
switch(order.OrderString)
|
||||
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 != Info.Type)
|
||||
if (!bi.Queue.Contains(Info.Type))
|
||||
return; /* Not built by this queue */
|
||||
|
||||
var cost = unit.Traits.Contains<ValuedInfo>() ? unit.Traits.Get<ValuedInfo>().Cost : 0;
|
||||
@@ -234,7 +265,7 @@ namespace OpenRA.Mods.RA
|
||||
var fromLimit = int.MaxValue;
|
||||
if (bi.BuildLimit > 0)
|
||||
{
|
||||
var inQueue = Queue.Count(pi => pi.Item == order.TargetString);
|
||||
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);
|
||||
|
||||
@@ -243,37 +274,39 @@ namespace OpenRA.Mods.RA
|
||||
}
|
||||
|
||||
var amountToBuild = Math.Min(fromLimit, order.ExtraData);
|
||||
|
||||
for (var n = 0; n < amountToBuild; n++) // repeat count
|
||||
for (var n = 0; n < amountToBuild; n++)
|
||||
{
|
||||
var hasPlayedSound = false;
|
||||
BeginProduction(new ProductionItem(this, order.TargetString, cost, PlayerPower,
|
||||
() => self.World.AddFrameEndTask(_ =>
|
||||
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)
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
})));
|
||||
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].Paused = ( order.ExtraData != 0 );
|
||||
if (queue.Count > 0 && queue[0].Item == order.TargetString)
|
||||
queue[0].Pause(order.ExtraData != 0);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case "CancelProduction":
|
||||
{
|
||||
CancelProduction(order.TargetString, order.ExtraData);
|
||||
@@ -282,10 +315,10 @@ namespace OpenRA.Mods.RA
|
||||
}
|
||||
}
|
||||
|
||||
virtual public int GetBuildTime(String unitString)
|
||||
public virtual int GetBuildTime(string unitString)
|
||||
{
|
||||
var unit = self.World.Map.Rules.Actors[unitString];
|
||||
if (unit == null || ! unit.Traits.Contains<BuildableInfo>())
|
||||
if (unit == null || !unit.Traits.Contains<BuildableInfo>())
|
||||
return 0;
|
||||
|
||||
if (self.World.AllowDevCommands && self.Owner.PlayerActor.Trait<DeveloperMode>().FastBuild)
|
||||
@@ -293,7 +326,7 @@ namespace OpenRA.Mods.RA
|
||||
|
||||
var time = unit.GetBuildTime() * Info.BuildSpeed;
|
||||
|
||||
return (int) time;
|
||||
return (int)time;
|
||||
}
|
||||
|
||||
protected void CancelProduction(string itemName, uint numberToCancel)
|
||||
@@ -304,13 +337,13 @@ namespace OpenRA.Mods.RA
|
||||
|
||||
void CancelProductionInner(string itemName)
|
||||
{
|
||||
var lastIndex = Queue.FindLastIndex(a => a.Item == itemName);
|
||||
var lastIndex = queue.FindLastIndex(a => a.Item == itemName);
|
||||
|
||||
if (lastIndex > 0)
|
||||
Queue.RemoveAt(lastIndex);
|
||||
queue.RemoveAt(lastIndex);
|
||||
else if (lastIndex == 0)
|
||||
{
|
||||
var item = Queue[0];
|
||||
var item = queue[0];
|
||||
playerResources.GiveCash(item.TotalCost - item.RemainingCost); // refund what has been paid
|
||||
FinishProduction();
|
||||
}
|
||||
@@ -318,13 +351,13 @@ namespace OpenRA.Mods.RA
|
||||
|
||||
public void FinishProduction()
|
||||
{
|
||||
if (Queue.Count == 0) return;
|
||||
Queue.RemoveAt(0);
|
||||
if (queue.Count != 0)
|
||||
queue.RemoveAt(0);
|
||||
}
|
||||
|
||||
protected void BeginProduction(ProductionItem item)
|
||||
{
|
||||
Queue.Add(item);
|
||||
queue.Add(item);
|
||||
}
|
||||
|
||||
// Builds a unit from the actor that holds this queue (1 queue per building)
|
||||
@@ -339,11 +372,12 @@ namespace OpenRA.Mods.RA
|
||||
}
|
||||
|
||||
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]))
|
||||
if (sp != null && !self.IsDisabled() && sp.Produce(self, self.World.Map.Rules.Actors[name], Race))
|
||||
{
|
||||
FinishProduction();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -352,16 +386,16 @@ namespace OpenRA.Mods.RA
|
||||
{
|
||||
public bool Visible = false;
|
||||
public bool Buildable = false;
|
||||
public bool Sticky = false;
|
||||
}
|
||||
|
||||
public class ProductionItem
|
||||
{
|
||||
public readonly string Item;
|
||||
public readonly ProductionQueue Queue;
|
||||
readonly PowerManager pm;
|
||||
public int TotalTime;
|
||||
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
|
||||
@@ -373,9 +407,12 @@ namespace OpenRA.Mods.RA
|
||||
}
|
||||
}
|
||||
|
||||
public bool Paused = false, Done = false, Started = false;
|
||||
public Action OnComplete;
|
||||
public int slowdown = 0;
|
||||
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)
|
||||
{
|
||||
@@ -385,7 +422,6 @@ namespace OpenRA.Mods.RA
|
||||
OnComplete = onComplete;
|
||||
Queue = queue;
|
||||
this.pm = pm;
|
||||
//Log.Write("debug", "new ProductionItem: {0} time={1} cost={2}", item, time, cost);
|
||||
}
|
||||
|
||||
public void Tick(PlayerResources pr)
|
||||
@@ -393,33 +429,43 @@ namespace OpenRA.Mods.RA
|
||||
if (!Started)
|
||||
{
|
||||
var time = Queue.GetBuildTime(Item);
|
||||
if (time > 0) RemainingTime = TotalTime = time;
|
||||
if (time > 0)
|
||||
RemainingTime = TotalTime = time;
|
||||
|
||||
Started = true;
|
||||
}
|
||||
|
||||
if (Done)
|
||||
{
|
||||
if (OnComplete != null) OnComplete();
|
||||
if (OnComplete != null)
|
||||
OnComplete();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (Paused) return;
|
||||
if (Paused)
|
||||
return;
|
||||
|
||||
if (pm.PowerState != PowerState.Normal)
|
||||
{
|
||||
if (--slowdown <= 0)
|
||||
slowdown = Queue.Info.LowPowerSlowdown;
|
||||
if (--Slowdown <= 0)
|
||||
Slowdown = Queue.Info.LowPowerSlowdown;
|
||||
else
|
||||
return;
|
||||
}
|
||||
|
||||
var costThisFrame = RemainingCost / RemainingTime;
|
||||
if (costThisFrame != 0 && !pr.TakeCash(costThisFrame)) return;
|
||||
if (costThisFrame != 0 && !pr.TakeCash(costThisFrame))
|
||||
return;
|
||||
|
||||
RemainingCost -= costThisFrame;
|
||||
RemainingTime -= 1;
|
||||
if (RemainingTime > 0) return;
|
||||
if (RemainingTime > 0)
|
||||
return;
|
||||
|
||||
Done = true;
|
||||
}
|
||||
|
||||
public void Pause(bool paused) { Paused = paused; }
|
||||
}
|
||||
}
|
||||
|
||||
77
OpenRA.Mods.RA/Player/ProvidesCustomPrerequisite.cs
Executable file
77
OpenRA.Mods.RA/Player/ProvidesCustomPrerequisite.cs
Executable file
@@ -0,0 +1,77 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2014 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.Primitives;
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.Mods.RA
|
||||
{
|
||||
public class ProvidesCustomPrerequisiteInfo : ITraitInfo
|
||||
{
|
||||
[Desc("The prerequisite type that this provides")]
|
||||
public readonly string Prerequisite = null;
|
||||
|
||||
[Desc("Only grant this prerequisite for certain factions")]
|
||||
public readonly string[] Race = { };
|
||||
|
||||
[Desc("Should the prerequisite remain enabled if the owner changes?")]
|
||||
public readonly bool Sticky = true;
|
||||
public object Create(ActorInitializer init) { return new ProvidesCustomPrerequisite(init, this); }
|
||||
}
|
||||
|
||||
public class ProvidesCustomPrerequisite : ITechTreePrerequisite, INotifyOwnerChanged
|
||||
{
|
||||
ProvidesCustomPrerequisiteInfo info;
|
||||
bool enabled = true;
|
||||
|
||||
public ProvidesCustomPrerequisite(ActorInitializer init, ProvidesCustomPrerequisiteInfo info)
|
||||
{
|
||||
this.info = info;
|
||||
|
||||
if (info.Race.Any())
|
||||
{
|
||||
var race = init.self.Owner.Country.Race;
|
||||
if (init.Contains<RaceInit>())
|
||||
race = init.Get<RaceInit, string>();
|
||||
|
||||
enabled = info.Race.Contains(race);
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<string> ProvidesPrerequisites
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!enabled)
|
||||
yield break;
|
||||
|
||||
yield return info.Prerequisite;
|
||||
}
|
||||
}
|
||||
|
||||
public void OnOwnerChanged(Actor self, Player oldOwner, Player newOwner)
|
||||
{
|
||||
if (!info.Sticky && info.Race.Any())
|
||||
enabled = info.Race.Contains(self.Owner.Country.Race);
|
||||
}
|
||||
}
|
||||
|
||||
// Allows maps / transformations to specify the race variant of an actor.
|
||||
public class RaceInit : IActorInit<string>
|
||||
{
|
||||
[FieldFromYamlKey] public readonly string Race;
|
||||
|
||||
public RaceInit() { }
|
||||
public RaceInit(string race) { Race = race; }
|
||||
public string Value(World world) { return Race; }
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2013 The OpenRA Developers (see AUTHORS)
|
||||
* Copyright 2007-2014 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,
|
||||
@@ -56,6 +56,11 @@ namespace OpenRA.Mods.RA
|
||||
watchers.RemoveAll(x => x.Key == key);
|
||||
}
|
||||
|
||||
public void Remove(ITechTreeElement tte)
|
||||
{
|
||||
watchers.RemoveAll(x => x.RegisteredBy == tte);
|
||||
}
|
||||
|
||||
static Cache<string, List<Actor>> GatherOwnedPrerequisites(Player player)
|
||||
{
|
||||
var ret = new Cache<string, List<Actor>>(x => new List<Actor>());
|
||||
@@ -94,6 +99,7 @@ namespace OpenRA.Mods.RA
|
||||
class Watcher
|
||||
{
|
||||
public readonly string Key;
|
||||
public ITechTreeElement RegisteredBy { get { return watcher; } }
|
||||
|
||||
// Strings may be either actor type, or "alternate name" key
|
||||
readonly string[] prerequisites;
|
||||
@@ -155,23 +161,4 @@ namespace OpenRA.Mods.RA
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class ProvidesCustomPrerequisiteInfo : ITraitInfo
|
||||
{
|
||||
public readonly string Prerequisite;
|
||||
|
||||
public object Create(ActorInitializer init) { return new ProvidesCustomPrerequisite(this); }
|
||||
}
|
||||
|
||||
public class ProvidesCustomPrerequisite : ITechTreePrerequisite
|
||||
{
|
||||
ProvidesCustomPrerequisiteInfo info;
|
||||
|
||||
public IEnumerable<string> ProvidesPrerequisites { get { yield return info.Prerequisite; } }
|
||||
|
||||
public ProvidesCustomPrerequisite(ProvidesCustomPrerequisiteInfo info)
|
||||
{
|
||||
this.info = info;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,7 +53,7 @@ namespace OpenRA.Mods.RA
|
||||
rp = Exts.Lazy(() => self.IsDead() ? null : self.TraitOrDefault<RallyPoint>());
|
||||
}
|
||||
|
||||
public void DoProduction(Actor self, ActorInfo producee, ExitInfo exitinfo)
|
||||
public void DoProduction(Actor self, ActorInfo producee, ExitInfo exitinfo, string raceVariant)
|
||||
{
|
||||
var exit = self.Location + exitinfo.ExitCell;
|
||||
var spawn = self.CenterPosition + exitinfo.SpawnOffset;
|
||||
@@ -68,13 +68,18 @@ namespace OpenRA.Mods.RA
|
||||
|
||||
self.World.AddFrameEndTask(w =>
|
||||
{
|
||||
var newUnit = self.World.CreateActor(producee.Name, new TypeDictionary
|
||||
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)
|
||||
@@ -96,7 +101,7 @@ namespace OpenRA.Mods.RA
|
||||
});
|
||||
}
|
||||
|
||||
public virtual bool Produce(Actor self, ActorInfo producee)
|
||||
public virtual bool Produce(Actor self, ActorInfo producee, string raceVariant)
|
||||
{
|
||||
if (Reservable.IsReserved(self))
|
||||
return false;
|
||||
@@ -107,7 +112,7 @@ namespace OpenRA.Mods.RA
|
||||
|
||||
if (exit != null)
|
||||
{
|
||||
DoProduction(self, producee, exit);
|
||||
DoProduction(self, producee, exit, raceVariant);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -44,6 +44,7 @@ namespace OpenRA.Mods.RA
|
||||
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);
|
||||
|
||||
|
||||
@@ -40,8 +40,8 @@ namespace OpenRA.Mods.RA
|
||||
|
||||
public Order IssueOrder( Actor self, IOrderTargeter order, Target target, bool queued )
|
||||
{
|
||||
if( order.OrderID == "SetRallyPoint" )
|
||||
return new Order(order.OrderID, self, false) { TargetLocation = target.CenterPosition.ToCPos() };
|
||||
if (order.OrderID == "SetRallyPoint")
|
||||
return new Order(order.OrderID, self, false) { TargetLocation = target.CenterPosition.ToCPos(), SuppressVisualFeedback = true };
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -393,7 +393,7 @@ namespace OpenRA.Mods.RA.Scripting
|
||||
if (bi == null)
|
||||
return null;
|
||||
|
||||
return GetSharedQueueForCategory(player, bi.Queue);
|
||||
return bi.Queue.Select(q => GetSharedQueueForCategory(player, q)).FirstOrDefault();
|
||||
}
|
||||
|
||||
[LuaGlobal]
|
||||
@@ -402,7 +402,7 @@ namespace OpenRA.Mods.RA.Scripting
|
||||
var queue = GetSharedQueueForUnit(player, unit);
|
||||
|
||||
if (queue != null)
|
||||
queue.ResolveOrder(queue.self, Order.StartProduction(queue.self, unit, (int)amount));
|
||||
queue.ResolveOrder(queue.Actor, Order.StartProduction(queue.Actor, unit, (int)amount));
|
||||
}
|
||||
|
||||
[LuaGlobal]
|
||||
@@ -414,7 +414,8 @@ namespace OpenRA.Mods.RA.Scripting
|
||||
if (bi == null)
|
||||
return;
|
||||
|
||||
var queue = factory.TraitOrDefault<ProductionQueue>();
|
||||
var queue = factory.TraitsImplementing<ProductionQueue>()
|
||||
.FirstOrDefault(q => q.Enabled);
|
||||
|
||||
if (queue != null)
|
||||
queue.ResolveOrder(factory, Order.StartProduction(factory, unit, (int)amount));
|
||||
@@ -434,7 +435,8 @@ namespace OpenRA.Mods.RA.Scripting
|
||||
[LuaGlobal]
|
||||
public bool PerFactoryQueueIsBusy(Actor factory)
|
||||
{
|
||||
var queue = factory.TraitOrDefault<ProductionQueue>();
|
||||
var queue = factory.TraitsImplementing<ProductionQueue>()
|
||||
.FirstOrDefault(q => q.Enabled);
|
||||
|
||||
if (queue == null)
|
||||
return true;
|
||||
|
||||
@@ -28,13 +28,13 @@ namespace OpenRA.Mods.RA.Scripting
|
||||
|
||||
[ScriptActorPropertyActivity]
|
||||
[Desc("Build a unit, ignoring the production queue. The activity will wait if the exit is blocked")]
|
||||
public void Produce(string actorType)
|
||||
public void Produce(string actorType, string raceVariant = null)
|
||||
{
|
||||
ActorInfo actorInfo;
|
||||
if (!self.World.Map.Rules.Actors.TryGetValue(actorType, out actorInfo))
|
||||
throw new LuaException("Unknown actor type '{0}'".F(actorType));
|
||||
|
||||
self.QueueActivity(new WaitFor(() => p.Produce(self, actorInfo)));
|
||||
self.QueueActivity(new WaitFor(() => p.Produce(self, actorInfo, raceVariant)));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -191,7 +191,8 @@ namespace OpenRA.Mods.RA
|
||||
yield return new Order(order, manager.self, false)
|
||||
{
|
||||
TargetLocation = xy,
|
||||
ExtraLocation = sourceLocation
|
||||
ExtraLocation = sourceLocation,
|
||||
SuppressVisualFeedback = true
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -88,7 +88,7 @@ namespace OpenRA.Mods.RA
|
||||
{
|
||||
world.CancelInputMode();
|
||||
if (mi.Button == MouseButton.Left && power.UnitsInRange(xy).Any())
|
||||
yield return new Order(order, manager.self, false) { TargetLocation = xy };
|
||||
yield return new Order(order, manager.self, false) { TargetLocation = xy, SuppressVisualFeedback = true };
|
||||
}
|
||||
|
||||
public void Tick(World world)
|
||||
|
||||
@@ -246,7 +246,7 @@ namespace OpenRA.Mods.RA
|
||||
{
|
||||
world.CancelInputMode();
|
||||
if (mi.Button == expectedButton && world.Map.IsInMap(xy))
|
||||
yield return new Order(order, manager.self, false) { TargetLocation = xy };
|
||||
yield return new Order(order, manager.self, false) { TargetLocation = xy, SuppressVisualFeedback = true };
|
||||
}
|
||||
|
||||
public virtual void Tick(World world)
|
||||
|
||||
@@ -22,23 +22,25 @@ namespace OpenRA.Mods.RA
|
||||
[ActorReference] public readonly string IntoActor = null;
|
||||
public readonly int2 Offset = int2.Zero;
|
||||
public readonly int Facing = 96;
|
||||
public readonly string[] TransformSounds = {};
|
||||
public readonly string[] NoTransformSounds = {};
|
||||
public readonly string[] TransformSounds = { };
|
||||
public readonly string[] NoTransformSounds = { };
|
||||
|
||||
public virtual object Create(ActorInitializer init) { return new Transforms(init.self, this); }
|
||||
public virtual object Create(ActorInitializer init) { return new Transforms(init, this); }
|
||||
}
|
||||
|
||||
class Transforms : IIssueOrder, IResolveOrder, IOrderVoice
|
||||
{
|
||||
Actor self;
|
||||
TransformsInfo Info;
|
||||
BuildingInfo bi;
|
||||
readonly Actor self;
|
||||
readonly TransformsInfo info;
|
||||
readonly BuildingInfo bi;
|
||||
readonly string race;
|
||||
|
||||
public Transforms(Actor self, TransformsInfo info)
|
||||
public Transforms(ActorInitializer init, TransformsInfo info)
|
||||
{
|
||||
this.self = self;
|
||||
Info = info;
|
||||
self = init.self;
|
||||
this.info = info;
|
||||
bi = self.World.Map.Rules.Actors[info.IntoActor].Traits.GetOrDefault<BuildingInfo>();
|
||||
race = init.Contains<RaceInit>() ? init.Get<RaceInit, string>() : self.Owner.Country.Race;
|
||||
}
|
||||
|
||||
public string VoicePhraseForOrder(Actor self, Order order)
|
||||
@@ -52,18 +54,18 @@ namespace OpenRA.Mods.RA
|
||||
if (b != null && b.Locked)
|
||||
return false;
|
||||
|
||||
return (bi == null || self.World.CanPlaceBuilding(Info.IntoActor, bi, self.Location + (CVec)Info.Offset, self));
|
||||
return bi == null || self.World.CanPlaceBuilding(info.IntoActor, bi, self.Location + (CVec)info.Offset, self);
|
||||
}
|
||||
|
||||
public IEnumerable<IOrderTargeter> Orders
|
||||
{
|
||||
get { yield return new DeployOrderTargeter( "DeployTransform", 5, () => CanDeploy() ); }
|
||||
get { yield return new DeployOrderTargeter("DeployTransform", 5, () => CanDeploy()); }
|
||||
}
|
||||
|
||||
public Order IssueOrder( Actor self, IOrderTargeter order, Target target, bool queued )
|
||||
public Order IssueOrder(Actor self, IOrderTargeter order, Target target, bool queued)
|
||||
{
|
||||
if( order.OrderID == "DeployTransform" )
|
||||
return new Order( order.OrderID, self, queued );
|
||||
if (order.OrderID == "DeployTransform")
|
||||
return new Order(order.OrderID, self, queued);
|
||||
|
||||
return null;
|
||||
}
|
||||
@@ -74,8 +76,9 @@ namespace OpenRA.Mods.RA
|
||||
|
||||
if (!CanDeploy() || (b != null && !b.Lock()))
|
||||
{
|
||||
foreach (var s in Info.NoTransformSounds)
|
||||
foreach (var s in info.NoTransformSounds)
|
||||
Sound.PlayToPlayer(self.Owner, s);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -83,16 +86,16 @@ namespace OpenRA.Mods.RA
|
||||
self.CancelActivity();
|
||||
|
||||
if (self.HasTrait<IFacing>())
|
||||
self.QueueActivity(new Turn(Info.Facing));
|
||||
self.QueueActivity(new Turn(info.Facing));
|
||||
|
||||
var rb = self.TraitOrDefault<RenderBuilding>();
|
||||
if (rb != null && self.Info.Traits.Get<RenderBuildingInfo>().HasMakeAnimation)
|
||||
self.QueueActivity(new MakeAnimation(self, true, () => rb.PlayCustomAnim(self, "make")));
|
||||
|
||||
self.QueueActivity(new Transform(self, Info.IntoActor) { Offset = (CVec)Info.Offset, Facing = Info.Facing, Sounds = Info.TransformSounds });
|
||||
self.QueueActivity(new Transform(self, info.IntoActor) { Offset = (CVec)info.Offset, Facing = info.Facing, Sounds = info.TransformSounds, Race = race });
|
||||
}
|
||||
|
||||
public void ResolveOrder( Actor self, Order order )
|
||||
public void ResolveOrder(Actor self, Order order)
|
||||
{
|
||||
if (order.OrderString == "DeployTransform")
|
||||
DeployTransform(order.Queued);
|
||||
|
||||
@@ -94,7 +94,7 @@ namespace OpenRA.Mods.RA.Widgets
|
||||
.Where(p => p.Actor.Owner == world.LocalPlayer)
|
||||
.Select(p => p.Trait);
|
||||
|
||||
if (CurrentQueue != null && CurrentQueue.self.Destroyed)
|
||||
if (CurrentQueue != null && CurrentQueue.Actor.Destroyed)
|
||||
CurrentQueue = null;
|
||||
|
||||
foreach (var queue in queues)
|
||||
@@ -366,7 +366,7 @@ namespace OpenRA.Mods.RA.Widgets
|
||||
if (producing.Done)
|
||||
{
|
||||
if (unit.Traits.Contains<BuildingInfo>())
|
||||
world.OrderGenerator = new PlaceBuildingOrderGenerator(CurrentQueue.self, item);
|
||||
world.OrderGenerator = new PlaceBuildingOrderGenerator(CurrentQueue, item);
|
||||
else
|
||||
StartProduction(world, item);
|
||||
return;
|
||||
@@ -374,7 +374,7 @@ namespace OpenRA.Mods.RA.Widgets
|
||||
|
||||
if (producing.Paused)
|
||||
{
|
||||
world.IssueOrder(Order.PauseProduction(CurrentQueue.self, item, false));
|
||||
world.IssueOrder(Order.PauseProduction(CurrentQueue.Actor, item, false));
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -403,12 +403,12 @@ namespace OpenRA.Mods.RA.Widgets
|
||||
Sound.PlayNotification(world.Map.Rules, world.LocalPlayer, "Speech", CurrentQueue.Info.CancelledAudio, world.LocalPlayer.Country.Race);
|
||||
var numberToCancel = Game.GetModifierKeys().HasModifier(Modifiers.Shift) ? 5 : 1;
|
||||
|
||||
world.IssueOrder(Order.CancelProduction(CurrentQueue.self, item, numberToCancel));
|
||||
world.IssueOrder(Order.CancelProduction(CurrentQueue.Actor, item, numberToCancel));
|
||||
}
|
||||
else
|
||||
{
|
||||
Sound.PlayNotification(world.Map.Rules, world.LocalPlayer, "Speech", CurrentQueue.Info.OnHoldAudio, world.LocalPlayer.Country.Race);
|
||||
world.IssueOrder(Order.PauseProduction(CurrentQueue.self, item, true));
|
||||
world.IssueOrder(Order.PauseProduction(CurrentQueue.Actor, item, true));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -416,7 +416,7 @@ namespace OpenRA.Mods.RA.Widgets
|
||||
|
||||
void StartProduction(World world, string item)
|
||||
{
|
||||
world.IssueOrder(Order.StartProduction(CurrentQueue.self, item,
|
||||
world.IssueOrder(Order.StartProduction(CurrentQueue.Actor, item,
|
||||
Game.GetModifierKeys().HasModifier(Modifiers.Shift) ? 5 : 1));
|
||||
}
|
||||
|
||||
@@ -504,10 +504,10 @@ namespace OpenRA.Mods.RA.Widgets
|
||||
p += new int2(5, 35);
|
||||
if (!canBuildThis)
|
||||
{
|
||||
var prereqs = buildable.Prerequisites.Select(s => Description(world.Map.Rules, s));
|
||||
var prereqs = buildable.Prerequisites.Select(s => Description(world.Map.Rules, s)).Where(s => !s.StartsWith("~"));
|
||||
if (prereqs.Any())
|
||||
{
|
||||
Game.Renderer.Fonts["Regular"].DrawText(RequiresText.F(prereqs.Where(s => !s.StartsWith("~")).JoinWith(", ")), p.ToInt2(), Color.White);
|
||||
Game.Renderer.Fonts["Regular"].DrawText(RequiresText.F(prereqs.JoinWith(", ")), p.ToInt2(), Color.White);
|
||||
|
||||
p += new int2(0, 8);
|
||||
}
|
||||
|
||||
@@ -87,9 +87,11 @@ namespace OpenRA.Mods.RA.Widgets.Logic
|
||||
if (!p.World.LobbyInfo.GlobalSettings.FragileAlliances)
|
||||
return; // stance changes are banned
|
||||
|
||||
// HACK: Abuse of the type system here with `CPos`
|
||||
world.IssueOrder(new Order("SetStance", world.LocalPlayer.PlayerActor, false)
|
||||
{ TargetLocation = new CPos((int)ss, 0), TargetString = p.InternalName });
|
||||
{
|
||||
ExtraData = (uint)ss,
|
||||
TargetString = p.InternalName,
|
||||
});
|
||||
|
||||
bw.Text = ss.ToString();
|
||||
}
|
||||
|
||||
@@ -164,8 +164,7 @@ namespace OpenRA.Mods.RA.Widgets
|
||||
if (at != null)
|
||||
at.PredictedStance = nextStance;
|
||||
|
||||
// FIXME: Abuse of the type system here with `CPos`
|
||||
return new Order("SetUnitStance", a, false) { TargetLocation = new CPos((int)nextStance, 0) };
|
||||
return new Order("SetUnitStance", a, false) { ExtraData = (uint)nextStance };
|
||||
});
|
||||
|
||||
Game.Debug("Unit stance set to: {0}".F(nextStance));
|
||||
|
||||
@@ -36,12 +36,12 @@ namespace OpenRA.Mods.RA
|
||||
return;
|
||||
|
||||
// Queue-per-structure
|
||||
var perqueue = world.Selection.Actors.FirstOrDefault(
|
||||
a => a.IsInWorld && a.World.LocalPlayer == a.Owner && a.HasTrait<ProductionQueue>());
|
||||
var perqueue = world.Selection.Actors.FirstOrDefault(a => a.IsInWorld && a.World.LocalPlayer == a.Owner
|
||||
&& a.TraitsImplementing<ProductionQueue>().Any(q => q.Enabled));
|
||||
|
||||
if (perqueue != null)
|
||||
{
|
||||
palette.SetCurrentTab(perqueue.TraitsImplementing<ProductionQueue>().First());
|
||||
palette.SetCurrentTab(perqueue.TraitsImplementing<ProductionQueue>().First(q => q.Enabled));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -55,7 +55,7 @@ namespace OpenRA.Mods.RA
|
||||
return;
|
||||
|
||||
palette.SetCurrentTab(world.LocalPlayer.PlayerActor.TraitsImplementing<ProductionQueue>()
|
||||
.FirstOrDefault(t => types.Contains(t.Info.Type)));
|
||||
.FirstOrDefault(q => q.Enabled && types.Contains(q.Info.Type)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -434,9 +434,9 @@ production-icons: chrome.png
|
||||
building: 384,0,16,16
|
||||
building-disabled: 384,16,16,16
|
||||
building-alert: 384,32,16,16
|
||||
defense: 400,0,16,16
|
||||
defense-disabled: 400,16,16,16
|
||||
defense-alert: 400,32,16,16
|
||||
defence: 400,0,16,16
|
||||
defence-disabled: 400,16,16,16
|
||||
defence-alert: 400,32,16,16
|
||||
infantry: 416,0,16,16
|
||||
infantry-disabled: 416,16,16,16
|
||||
infantry-alert: 416,32,16,16
|
||||
|
||||
@@ -317,14 +317,14 @@ Container@PLAYER_WIDGETS:
|
||||
X: 7
|
||||
Y: 7
|
||||
ImageCollection: production-icons
|
||||
ProductionTypeButton@DEFENSE:
|
||||
ProductionTypeButton@DEFENCE:
|
||||
X: 35
|
||||
Width: 30
|
||||
Height: 30
|
||||
Key: w
|
||||
TooltipText: Defense
|
||||
TooltipText: Defence
|
||||
TooltipContainer: TOOLTIP_CONTAINER
|
||||
ProductionGroup: Defense
|
||||
ProductionGroup: Defence
|
||||
Children:
|
||||
Image@ICON:
|
||||
X: 7
|
||||
|
||||
@@ -464,36 +464,51 @@ Rules:
|
||||
^Infantry:
|
||||
MustBeDestroyed:
|
||||
PROC:
|
||||
-Buildable:
|
||||
Buildable:
|
||||
Prerequisites: ~disabled
|
||||
SILO:
|
||||
-Buildable:
|
||||
Buildable:
|
||||
Prerequisites: ~disabled
|
||||
WEAP:
|
||||
-Buildable:
|
||||
Buildable:
|
||||
Prerequisites: ~disabled
|
||||
HQ:
|
||||
-Buildable:
|
||||
Buildable:
|
||||
Prerequisites: ~disabled
|
||||
NUK2:
|
||||
-Buildable:
|
||||
Buildable:
|
||||
Prerequisites: ~disabled
|
||||
FIX:
|
||||
-Buildable:
|
||||
Buildable:
|
||||
Prerequisites: ~disabled
|
||||
HPAD:
|
||||
-Buildable:
|
||||
Buildable:
|
||||
Prerequisites: ~disabled
|
||||
EYE:
|
||||
-Buildable:
|
||||
Buildable:
|
||||
Prerequisites: ~disabled
|
||||
GUN:
|
||||
-Buildable:
|
||||
Buildable:
|
||||
Prerequisites: ~disabled
|
||||
MustBeDestroyed:
|
||||
GTWR:
|
||||
-Buildable:
|
||||
Buildable:
|
||||
Prerequisites: ~disabled
|
||||
ATWR:
|
||||
-Buildable:
|
||||
Buildable:
|
||||
Prerequisites: ~disabled
|
||||
E2:
|
||||
-Buildable:
|
||||
Buildable:
|
||||
Prerequisites: ~disabled
|
||||
E3:
|
||||
-Buildable:
|
||||
Buildable:
|
||||
Prerequisites: ~disabled
|
||||
E6:
|
||||
-Buildable:
|
||||
Buildable:
|
||||
Prerequisites: ~disabled
|
||||
RMBO:
|
||||
-Buildable:
|
||||
Buildable:
|
||||
Prerequisites: ~disabled
|
||||
BOAT:
|
||||
Health:
|
||||
HP: 1500
|
||||
|
||||
@@ -739,47 +739,68 @@ Rules:
|
||||
Player:
|
||||
-ConquestVictoryConditions:
|
||||
PROC:
|
||||
-Buildable:
|
||||
Buildable:
|
||||
Prerequisites: ~disabled
|
||||
SILO:
|
||||
-Buildable:
|
||||
Buildable:
|
||||
Prerequisites: ~disabled
|
||||
WEAP:
|
||||
-Buildable:
|
||||
Buildable:
|
||||
Prerequisites: ~disabled
|
||||
HQ:
|
||||
-Buildable:
|
||||
Buildable:
|
||||
Prerequisites: ~disabled
|
||||
NUK2:
|
||||
-Buildable:
|
||||
Buildable:
|
||||
Prerequisites: ~disabled
|
||||
FIX:
|
||||
-Buildable:
|
||||
Buildable:
|
||||
Prerequisites: ~disabled
|
||||
HPAD:
|
||||
-Buildable:
|
||||
Buildable:
|
||||
Prerequisites: ~disabled
|
||||
EYE:
|
||||
-Buildable:
|
||||
Buildable:
|
||||
Prerequisites: ~disabled
|
||||
GUN:
|
||||
-Buildable:
|
||||
Buildable:
|
||||
Prerequisites: ~disabled
|
||||
GTWR:
|
||||
-Buildable:
|
||||
Buildable:
|
||||
Prerequisites: ~disabled
|
||||
ATWR:
|
||||
-Buildable:
|
||||
Buildable:
|
||||
Prerequisites: ~disabled
|
||||
E2:
|
||||
-Buildable:
|
||||
Buildable:
|
||||
Prerequisites: ~disabled
|
||||
E3:
|
||||
-Buildable:
|
||||
Buildable:
|
||||
Prerequisites: ~disabled
|
||||
E4:
|
||||
-Buildable:
|
||||
Buildable:
|
||||
Prerequisites: ~disabled
|
||||
E5:
|
||||
-Buildable:
|
||||
Buildable:
|
||||
Prerequisites: ~disabled
|
||||
E6:
|
||||
-Buildable:
|
||||
Buildable:
|
||||
Prerequisites: ~disabled
|
||||
RMBO:
|
||||
-Buildable:
|
||||
Buildable:
|
||||
Prerequisites: ~disabled
|
||||
AFLD:
|
||||
-Buildable:
|
||||
Buildable:
|
||||
Prerequisites: ~disabled
|
||||
TMPL:
|
||||
-Buildable:
|
||||
Buildable:
|
||||
Prerequisites: ~disabled
|
||||
OBLI:
|
||||
-Buildable:
|
||||
Buildable:
|
||||
Prerequisites: ~disabled
|
||||
SAM:
|
||||
-Buildable:
|
||||
Buildable:
|
||||
Prerequisites: ~disabled
|
||||
OLDLST:
|
||||
Inherits: LST
|
||||
-WithRoof:
|
||||
|
||||
@@ -902,39 +902,55 @@ Rules:
|
||||
^Infantry:
|
||||
MustBeDestroyed:
|
||||
WEAP:
|
||||
-Buildable:
|
||||
Buildable:
|
||||
Prerequisites: ~disabled
|
||||
NUK2:
|
||||
-Buildable:
|
||||
Buildable:
|
||||
Prerequisites: ~disabled
|
||||
FIX:
|
||||
-Buildable:
|
||||
Buildable:
|
||||
Prerequisites: ~disabled
|
||||
HPAD:
|
||||
-Buildable:
|
||||
Buildable:
|
||||
Prerequisites: ~disabled
|
||||
EYE:
|
||||
-Buildable:
|
||||
Buildable:
|
||||
Prerequisites: ~disabled
|
||||
GUN:
|
||||
-Buildable:
|
||||
Buildable:
|
||||
Prerequisites: ~disabled
|
||||
ATWR:
|
||||
-Buildable:
|
||||
Buildable:
|
||||
Prerequisites: ~disabled
|
||||
E3:
|
||||
-Buildable:
|
||||
Buildable:
|
||||
Prerequisites: ~disabled
|
||||
E4:
|
||||
-Buildable:
|
||||
Buildable:
|
||||
Prerequisites: ~disabled
|
||||
E5:
|
||||
-Buildable:
|
||||
Buildable:
|
||||
Prerequisites: ~disabled
|
||||
RMBO:
|
||||
-Buildable:
|
||||
Buildable:
|
||||
Prerequisites: ~disabled
|
||||
AFLD:
|
||||
-Buildable:
|
||||
Buildable:
|
||||
Prerequisites: ~disabled
|
||||
TMPL:
|
||||
-Buildable:
|
||||
Buildable:
|
||||
Prerequisites: ~disabled
|
||||
OBLI:
|
||||
-Buildable:
|
||||
Buildable:
|
||||
Prerequisites: ~disabled
|
||||
SAM:
|
||||
-Buildable:
|
||||
Buildable:
|
||||
Prerequisites: ~disabled
|
||||
Building:
|
||||
Power: -10
|
||||
HQ:
|
||||
-Buildable:
|
||||
Buildable:
|
||||
Prerequisites: ~disabled
|
||||
NOHQ:
|
||||
RequiresPower:
|
||||
CanPowerDown:
|
||||
|
||||
@@ -10,6 +10,9 @@ Player:
|
||||
Silo: silo
|
||||
UnitsCommonNames:
|
||||
Mcv: mcv
|
||||
BuildingQueues: Building.Nod, Building.GDI
|
||||
DefenseQueues: Defence.Nod, Defence.GDI
|
||||
UnitQueues: Vehicle.Nod, Vehicle.GDI, Infantry.Nod, Infantry.GDI, Aircraft.Nod, Aircraft.GDI
|
||||
BuildingLimits:
|
||||
proc: 4
|
||||
pyle: 2
|
||||
@@ -72,6 +75,9 @@ Player:
|
||||
Silo: silo
|
||||
UnitsCommonNames:
|
||||
Mcv: mcv
|
||||
BuildingQueues: Building.Nod, Building.GDI
|
||||
DefenseQueues: Defence.Nod, Defence.GDI
|
||||
UnitQueues: Vehicle.Nod, Vehicle.GDI, Infantry.Nod, Infantry.GDI, Aircraft.Nod, Aircraft.GDI
|
||||
BuildingLimits:
|
||||
proc: 4
|
||||
pyle: 2
|
||||
@@ -134,6 +140,9 @@ Player:
|
||||
Silo: silo
|
||||
UnitsCommonNames:
|
||||
Mcv: mcv
|
||||
BuildingQueues: Building.Nod, Building.GDI
|
||||
DefenseQueues: Defence.Nod, Defence.GDI
|
||||
UnitQueues: Vehicle.Nod, Vehicle.GDI, Infantry.Nod, Infantry.GDI, Aircraft.Nod, Aircraft.GDI
|
||||
BuildingLimits:
|
||||
proc: 4
|
||||
pyle: 2
|
||||
@@ -187,4 +196,3 @@ Player:
|
||||
htnk: 50%
|
||||
orca: 10%
|
||||
SquadSize: 8
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ TRAN:
|
||||
Buildable:
|
||||
BuildPaletteOrder: 10
|
||||
Prerequisites: hpad
|
||||
Owner: gdi,nod
|
||||
Queue: Aircraft.GDI, Aircraft.Nod
|
||||
Selectable:
|
||||
Bounds: 41,41
|
||||
Helicopter:
|
||||
@@ -52,7 +52,7 @@ HELI:
|
||||
Buildable:
|
||||
BuildPaletteOrder: 20
|
||||
Prerequisites: hpad, anyhq
|
||||
Owner: nod
|
||||
Queue: Aircraft.Nod
|
||||
Selectable:
|
||||
Bounds: 30,24
|
||||
Helicopter:
|
||||
@@ -104,7 +104,7 @@ ORCA:
|
||||
Buildable:
|
||||
BuildPaletteOrder: 20
|
||||
Prerequisites: hpad, anyhq
|
||||
Owner: gdi
|
||||
Queue: Aircraft.GDI
|
||||
Selectable:
|
||||
Bounds: 30,24
|
||||
Helicopter:
|
||||
|
||||
@@ -15,8 +15,6 @@
|
||||
Voice: VehicleVoice
|
||||
TargetableUnit:
|
||||
TargetTypes: Ground, Vehicle
|
||||
Buildable:
|
||||
Queue: Vehicle
|
||||
Repairable:
|
||||
Chronoshiftable:
|
||||
Passenger:
|
||||
@@ -61,8 +59,6 @@
|
||||
Voice: VehicleVoice
|
||||
TargetableUnit:
|
||||
TargetTypes: Ground, Vehicle
|
||||
Buildable:
|
||||
Queue: Vehicle
|
||||
Repairable:
|
||||
Chronoshiftable:
|
||||
Passenger:
|
||||
@@ -106,8 +102,6 @@
|
||||
RepairBuildings: hpad
|
||||
RearmBuildings:
|
||||
LandWhenIdle: false
|
||||
Buildable:
|
||||
Queue: Aircraft
|
||||
HiddenUnderFog:
|
||||
GainsExperience:
|
||||
GivesExperience:
|
||||
@@ -153,8 +147,6 @@
|
||||
Voice: GenericVoice
|
||||
TargetableUnit:
|
||||
TargetTypes: Ground, Infantry
|
||||
Buildable:
|
||||
Queue: Infantry
|
||||
TakeCover:
|
||||
ProneSpeed: 0.6
|
||||
RenderInfantryProne:
|
||||
@@ -198,7 +190,6 @@
|
||||
|
||||
^CivInfantry:
|
||||
Inherits: ^Infantry
|
||||
-Buildable:
|
||||
-AutoTarget:
|
||||
-TakeCover:
|
||||
-RenderInfantryProne:
|
||||
@@ -237,7 +228,6 @@
|
||||
Buildable:
|
||||
Queue: Biolab
|
||||
BuildPaletteOrder: 50
|
||||
Owner: gdi, nod
|
||||
Valued:
|
||||
Cost: 1000
|
||||
Tooltip:
|
||||
@@ -369,8 +359,6 @@
|
||||
^BaseBuilding:
|
||||
Inherits: ^Building
|
||||
MustBeDestroyed:
|
||||
Buildable:
|
||||
Queue: Building
|
||||
RepairableBuilding:
|
||||
RepairPercent: 40
|
||||
RepairStep: 14
|
||||
|
||||
@@ -7,7 +7,7 @@ E1:
|
||||
Description: General-purpose infantry.\n Strong vs Infantry\n Weak vs Vehicles
|
||||
Buildable:
|
||||
BuildPaletteOrder: 10
|
||||
Owner: gdi, nod
|
||||
Queue: Infantry.GDI, Infantry.Nod
|
||||
Selectable:
|
||||
Bounds: 12,17,0,-6
|
||||
Mobile:
|
||||
@@ -31,7 +31,7 @@ E2:
|
||||
Buildable:
|
||||
BuildPaletteOrder: 40
|
||||
Prerequisites: anyhq
|
||||
Owner: gdi
|
||||
Queue: Infantry.GDI
|
||||
Selectable:
|
||||
Bounds: 12,17,0,-6
|
||||
Mobile:
|
||||
@@ -60,7 +60,7 @@ E3:
|
||||
Description: Anti-tank/Anti-aircraft infantry. \n Strong vs Tanks, Aircraft\n Weak vs Infantry
|
||||
Buildable:
|
||||
BuildPaletteOrder: 20
|
||||
Owner: nod, gdi
|
||||
Queue: Infantry.GDI, Infantry.Nod
|
||||
Selectable:
|
||||
Bounds: 12,17,0,-6
|
||||
Mobile:
|
||||
@@ -87,8 +87,8 @@ E4:
|
||||
Description: Advanced Anti-infantry unit.\n Strong vs Infantry, Buildings\n Weak vs Tanks
|
||||
Buildable:
|
||||
BuildPaletteOrder: 40
|
||||
Owner: nod
|
||||
Prerequisites: anyhq
|
||||
Queue: Infantry.Nod
|
||||
Selectable:
|
||||
Bounds: 12,17,0,-6
|
||||
Mobile:
|
||||
@@ -117,8 +117,8 @@ E5:
|
||||
Description: Advanced general-purpose infantry.\n Strong vs all Ground units
|
||||
Buildable:
|
||||
BuildPaletteOrder: 50
|
||||
Owner: nod
|
||||
Prerequisites: tmpl
|
||||
Queue: Infantry.Nod
|
||||
Selectable:
|
||||
Bounds: 12,17,0,-6
|
||||
Mobile:
|
||||
@@ -153,7 +153,7 @@ E6:
|
||||
Description: Infiltrates and captures enemy structures.\n Strong vs Nothing\n Weak vs Everything
|
||||
Buildable:
|
||||
BuildPaletteOrder: 30
|
||||
Owner: gdi,nod
|
||||
Queue: Infantry.GDI, Infantry.Nod
|
||||
Selectable:
|
||||
Bounds: 12,17,0,-6
|
||||
Mobile:
|
||||
@@ -181,8 +181,8 @@ RMBO:
|
||||
Description: Elite sniper infantry unit.\n Strong vs Infantry, Buildings\n Weak vs Vehicles
|
||||
Buildable:
|
||||
BuildPaletteOrder: 50
|
||||
Owner: gdi
|
||||
Prerequisites: eye
|
||||
Queue: Infantry.GDI
|
||||
Selectable:
|
||||
Bounds: 12,17,0,-6
|
||||
Voice: CommandoVoice
|
||||
@@ -215,7 +215,6 @@ PVICE:
|
||||
Buildable:
|
||||
Queue: Biolab
|
||||
BuildPaletteOrder: 40
|
||||
Owner: gdi, nod
|
||||
Tooltip:
|
||||
Description: Mutated abomination that spits liquid tiberium.\n Strong vs Infantry, Buildings\n Weak vs Aircraft
|
||||
DrawLineToTarget:
|
||||
|
||||
@@ -39,7 +39,7 @@ LST:
|
||||
Buildable:
|
||||
Queue: Vehicle
|
||||
BuildPaletteOrder: 1000
|
||||
Owner: None
|
||||
Prerequisites: ~disabled
|
||||
Mobile:
|
||||
Crushes: crate
|
||||
TerrainSpeeds:
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
FACT:
|
||||
Inherits: ^BaseBuilding
|
||||
Buildable:
|
||||
Queue: Building
|
||||
Queue: Building.GDI, Building.Nod
|
||||
BuildPaletteOrder: 1000
|
||||
Owner: None
|
||||
Prerequisites: ~disabled
|
||||
Valued:
|
||||
Cost: 2000
|
||||
Tooltip:
|
||||
@@ -21,30 +21,56 @@ FACT:
|
||||
Range: 10c0
|
||||
Bib:
|
||||
Production:
|
||||
Produces: Building,Defense
|
||||
Produces: Building.GDI, Buildings.Nod, Defence.GDI, Defence.Nod
|
||||
Transforms:
|
||||
IntoActor: mcv
|
||||
Offset: 1,1
|
||||
Facing: 108
|
||||
ProductionQueue@Building:
|
||||
Type: Building
|
||||
ProductionQueue@GDIBuilding:
|
||||
Type: Building.GDI
|
||||
Race: gdi
|
||||
RequireOwner: false
|
||||
Group: Building
|
||||
BuildSpeed: .4
|
||||
LowPowerSlowdown: 2
|
||||
QueuedAudio: Building
|
||||
ReadyAudio: ConstructionComplete
|
||||
ProductionQueue@Defense:
|
||||
Type: Defense
|
||||
Group: Defense
|
||||
ProductionQueue@NodBuilding:
|
||||
Type: Building.Nod
|
||||
Race: nod
|
||||
RequireOwner: false
|
||||
Group: Building
|
||||
BuildSpeed: .4
|
||||
LowPowerSlowdown: 2
|
||||
QueuedAudio: Building
|
||||
ReadyAudio: ConstructionComplete
|
||||
ProductionQueue@GDIDefense:
|
||||
Type: Defence.GDI
|
||||
Race: gdi
|
||||
RequireOwner: false
|
||||
Group: Defence
|
||||
BuildSpeed: .4
|
||||
LowPowerSlowdown: 3
|
||||
QueuedAudio: Building
|
||||
ReadyAudio: ConstructionComplete
|
||||
ProductionQueue@NodDefense:
|
||||
Type: Defence.Nod
|
||||
Race: nod
|
||||
RequireOwner: false
|
||||
Group: Defence
|
||||
BuildSpeed: .4
|
||||
LowPowerSlowdown: 3
|
||||
QueuedAudio: Building
|
||||
ReadyAudio: ConstructionComplete
|
||||
BaseBuilding:
|
||||
ProductionBar@Building:
|
||||
ProductionType: Building
|
||||
ProductionBar@Defense:
|
||||
ProductionType: Defense
|
||||
ProductionBar@BuildingGDI:
|
||||
ProductionType: Building.GDI
|
||||
ProductionBar@BuildingNod:
|
||||
ProductionType: Building.Nod
|
||||
ProductionBar@DefenceGDI:
|
||||
ProductionType: Defence.GDI
|
||||
ProductionBar@DefenceNod:
|
||||
ProductionType: Defence.Nod
|
||||
BaseProvider:
|
||||
Cooldown: 75
|
||||
Range: 14
|
||||
@@ -61,8 +87,8 @@ NUKE:
|
||||
Prerequisite: anypower
|
||||
Buildable:
|
||||
BuildPaletteOrder: 10
|
||||
Owner: gdi,nod
|
||||
Prerequisites: fact
|
||||
Queue: Building.GDI, Building.Nod
|
||||
Building:
|
||||
Power: 100
|
||||
Footprint: x_ xx
|
||||
@@ -85,7 +111,7 @@ NUK2:
|
||||
Buildable:
|
||||
BuildPaletteOrder: 30
|
||||
Prerequisites: anyhq
|
||||
Owner: gdi,nod
|
||||
Queue: Building.GDI, Building.Nod
|
||||
Building:
|
||||
Power: 200
|
||||
Footprint: xx xx
|
||||
@@ -106,7 +132,7 @@ PROC:
|
||||
Buildable:
|
||||
BuildPaletteOrder: 20
|
||||
Prerequisites: anypower
|
||||
Owner: gdi,nod
|
||||
Queue: Building.GDI, Building.Nod
|
||||
Building:
|
||||
Power: -50
|
||||
Footprint: _x_ xxx ===
|
||||
@@ -142,10 +168,9 @@ SILO:
|
||||
Name: Tiberium Silo
|
||||
Description: Stores processed Tiberium
|
||||
Buildable:
|
||||
Queue: Defense
|
||||
BuildPaletteOrder: 35
|
||||
Prerequisites: proc
|
||||
Owner: gdi,nod
|
||||
Queue: Defence.GDI, Defence.Nod
|
||||
Building:
|
||||
Power: -10
|
||||
Footprint: xx
|
||||
@@ -179,7 +204,7 @@ PYLE:
|
||||
Buildable:
|
||||
BuildPaletteOrder: 40
|
||||
Prerequisites: anypower
|
||||
Owner: gdi
|
||||
Queue: Building.GDI
|
||||
Building:
|
||||
Power: -20
|
||||
Footprint: xx xx
|
||||
@@ -197,10 +222,11 @@ PYLE:
|
||||
SpawnOffset: 298,298,0
|
||||
ExitCell: 1,1
|
||||
Production:
|
||||
Produces: Infantry
|
||||
Produces: Infantry.GDI
|
||||
ProductionQueue:
|
||||
Type: Infantry
|
||||
Type: Infantry.GDI
|
||||
Group: Infantry
|
||||
RequireOwner: false
|
||||
BuildSpeed: .4
|
||||
LowPowerSlowdown: 3
|
||||
ProductionBar:
|
||||
@@ -217,7 +243,7 @@ HAND:
|
||||
Buildable:
|
||||
BuildPaletteOrder: 40
|
||||
Prerequisites: anypower
|
||||
Owner: nod
|
||||
Queue: Building.Nod
|
||||
Building:
|
||||
Power: -20
|
||||
Footprint: __ xx xx
|
||||
@@ -232,10 +258,11 @@ HAND:
|
||||
SpawnOffset: 512,1024,0
|
||||
ExitCell: 1,2
|
||||
Production:
|
||||
Produces: Infantry
|
||||
Produces: Infantry.Nod
|
||||
ProductionQueue:
|
||||
Type: Infantry
|
||||
Type: Infantry.Nod
|
||||
Group: Infantry
|
||||
RequireOwner: false
|
||||
BuildSpeed: .4
|
||||
LowPowerSlowdown: 3
|
||||
ProductionBar:
|
||||
@@ -252,7 +279,7 @@ AFLD:
|
||||
Buildable:
|
||||
BuildPaletteOrder: 50
|
||||
Prerequisites: proc
|
||||
Owner: nod
|
||||
Queue: Building.Nod
|
||||
Building:
|
||||
Power: -30
|
||||
Footprint: xxxx xxxx
|
||||
@@ -268,11 +295,12 @@ AFLD:
|
||||
SpawnOffset: -1024,0,0
|
||||
ExitCell: 3,1
|
||||
ProductionAirdrop:
|
||||
Produces: Vehicle
|
||||
Produces: Vehicle.Nod
|
||||
WithDeliveryAnimation:
|
||||
ProductionQueue:
|
||||
Type: Vehicle
|
||||
Type: Vehicle.Nod
|
||||
Group: Vehicle
|
||||
RequireOwner: false
|
||||
BuildSpeed: .4
|
||||
LowPowerSlowdown: 3
|
||||
ReadyAudio:
|
||||
@@ -290,7 +318,7 @@ WEAP:
|
||||
Buildable:
|
||||
BuildPaletteOrder: 50
|
||||
Prerequisites: proc
|
||||
Owner: gdi
|
||||
Queue: Building.GDI
|
||||
Building:
|
||||
Power: -30
|
||||
Footprint: ___ xxx ===
|
||||
@@ -308,9 +336,10 @@ WEAP:
|
||||
SpawnOffset: -341,-341,0
|
||||
ExitCell: 0,2
|
||||
Production:
|
||||
Produces: Vehicle
|
||||
Produces: Vehicle.GDI
|
||||
ProductionQueue:
|
||||
Type: Vehicle
|
||||
Type: Vehicle.GDI
|
||||
RequireOwner: false
|
||||
Group: Vehicle
|
||||
BuildSpeed: .4
|
||||
LowPowerSlowdown: 3
|
||||
@@ -326,7 +355,7 @@ HPAD:
|
||||
Buildable:
|
||||
BuildPaletteOrder: 60
|
||||
Prerequisites: proc
|
||||
Owner: gdi,nod
|
||||
Queue: Building.GDI, Building.Nod
|
||||
Building:
|
||||
Power: -10
|
||||
Footprint: xx xx
|
||||
@@ -338,17 +367,29 @@ HPAD:
|
||||
Exit@1:
|
||||
SpawnOffset: 0,-256,0
|
||||
Production:
|
||||
Produces: Aircraft
|
||||
Produces: Aircraft.GDI, Aircraft.Nod
|
||||
Reservable:
|
||||
RepairsUnits:
|
||||
WithRepairAnimation:
|
||||
RallyPoint:
|
||||
ProductionQueue:
|
||||
Type: Aircraft
|
||||
ProductionQueue@GDI:
|
||||
Type: Aircraft.GDI
|
||||
Race: gdi
|
||||
Group: Aircraft
|
||||
RequireOwner: false
|
||||
BuildSpeed: .4
|
||||
LowPowerSlowdown: 3
|
||||
ProductionBar:
|
||||
ProductionQueue@Nod:
|
||||
Type: Aircraft.Nod
|
||||
Race: nod
|
||||
Group: Aircraft
|
||||
RequireOwner: false
|
||||
BuildSpeed: .4
|
||||
LowPowerSlowdown: 3
|
||||
ProductionBar@GDI:
|
||||
ProductionType: Aircraft.GDI
|
||||
ProductionBar@Nod:
|
||||
ProductionType: Aircraft.Nod
|
||||
|
||||
HQ:
|
||||
Inherits: ^BaseBuilding
|
||||
@@ -362,7 +403,7 @@ HQ:
|
||||
Buildable:
|
||||
BuildPaletteOrder: 70
|
||||
Prerequisites: proc
|
||||
Owner: gdi,nod
|
||||
Queue: Building.GDI, Building.Nod
|
||||
Building:
|
||||
Power: -40
|
||||
Footprint: x_ xx
|
||||
@@ -410,7 +451,7 @@ FIX:
|
||||
Buildable:
|
||||
BuildPaletteOrder: 80
|
||||
Prerequisites: vehicleproduction
|
||||
Owner: gdi,nod
|
||||
Queue: Building.GDI, Building.Nod
|
||||
Building:
|
||||
Power: -30
|
||||
Footprint: _x_ xxx _x_
|
||||
@@ -438,7 +479,7 @@ EYE:
|
||||
Buildable:
|
||||
BuildPaletteOrder: 100
|
||||
Prerequisites: anyhq
|
||||
Owner: gdi
|
||||
Queue: Building.GDI
|
||||
Building:
|
||||
Power: -200
|
||||
Footprint: x_ xx
|
||||
@@ -483,7 +524,7 @@ TMPL:
|
||||
Buildable:
|
||||
BuildPaletteOrder: 100
|
||||
Prerequisites: anyhq
|
||||
Owner: nod
|
||||
Queue: Building.Nod
|
||||
Building:
|
||||
Power: -150
|
||||
Footprint: ___ xxx xxx
|
||||
@@ -525,10 +566,9 @@ GUN:
|
||||
Name: Turret
|
||||
Description: Basic Anti-Tank base defense.\n Strong vs Tanks, vehicles\n Weak vs Infantry
|
||||
Buildable:
|
||||
Queue: Defense
|
||||
BuildPaletteOrder: 45
|
||||
Prerequisites: barracks
|
||||
Owner: gdi,nod
|
||||
Queue: Defence.GDI, Defence.Nod
|
||||
Building:
|
||||
Power: -20
|
||||
-GivesBuildableArea:
|
||||
@@ -568,10 +608,9 @@ SAM:
|
||||
Name: SAM Site
|
||||
Description: Anti-Aircraft base defense.\n Strong vs Aircraft\n Cannot target Ground units.
|
||||
Buildable:
|
||||
Queue: Defense
|
||||
BuildPaletteOrder: 50
|
||||
Prerequisites: hand
|
||||
Owner: nod
|
||||
Queue: Defence.Nod
|
||||
Building:
|
||||
Power: -20
|
||||
Footprint: xx
|
||||
@@ -608,10 +647,9 @@ OBLI:
|
||||
Name: Obelisk of Light
|
||||
Description: Advanced base defense. \nRequires power to operate.\n Strong vs all Ground units\n Cannot target Aircraft
|
||||
Buildable:
|
||||
Queue: Defense
|
||||
BuildPaletteOrder: 60
|
||||
Prerequisites: tmpl
|
||||
Owner: nod
|
||||
Queue: Defence.Nod
|
||||
Building:
|
||||
Power: -150
|
||||
Footprint: _ x
|
||||
@@ -654,10 +692,9 @@ GTWR:
|
||||
Name: Guard Tower
|
||||
Description: Basic defensive structure.\n Strong vs Infantry\n Weak vs Tanks
|
||||
Buildable:
|
||||
Queue: Defense
|
||||
BuildPaletteOrder: 40
|
||||
Prerequisites: barracks
|
||||
Owner: gdi,nod
|
||||
Queue: Defence.GDI
|
||||
Building:
|
||||
Power: -10
|
||||
-GivesBuildableArea:
|
||||
@@ -693,10 +730,9 @@ ATWR:
|
||||
Name: Advanced Guard Tower
|
||||
Description: All-purpose defensive structure.\n Strong vs Aircraft, Tanks\n Weak vs Infantry
|
||||
Buildable:
|
||||
Queue: Defense
|
||||
BuildPaletteOrder: 60
|
||||
Prerequisites: anyhq
|
||||
Owner: gdi
|
||||
Queue: Defence.GDI
|
||||
Building:
|
||||
Power: -40
|
||||
Footprint: _ x
|
||||
@@ -742,10 +778,9 @@ SBAG:
|
||||
Name: Sandbag Barrier
|
||||
Description: Stops infantry & light vehicles. \nCan be crushed by tanks.
|
||||
Buildable:
|
||||
Queue: Defense
|
||||
BuildPaletteOrder: 20
|
||||
Prerequisites: fact
|
||||
Owner: gdi
|
||||
Queue: Defence.GDI
|
||||
Health:
|
||||
HP: 100
|
||||
Armor:
|
||||
@@ -768,10 +803,9 @@ CYCL:
|
||||
Name: Chain Link Barrier
|
||||
Description: Stops infantry & light vehicles. \nCan be crushed by tanks.
|
||||
Buildable:
|
||||
Queue: Defense
|
||||
BuildPaletteOrder: 20
|
||||
Prerequisites: fact
|
||||
Owner: nod
|
||||
Queue: Defence.Nod
|
||||
Health:
|
||||
HP: 100
|
||||
Armor:
|
||||
@@ -794,10 +828,9 @@ BRIK:
|
||||
Name: Concrete Barrier
|
||||
Description: Stop units.
|
||||
Buildable:
|
||||
Queue: Defense
|
||||
BuildPaletteOrder: 30
|
||||
Prerequisites: vehicleproduction
|
||||
Owner: gdi,nod
|
||||
Queue: Defence.GDI, Defence.Nod
|
||||
Health:
|
||||
HP: 250
|
||||
Armor:
|
||||
|
||||
@@ -61,6 +61,7 @@ BIO:
|
||||
ProductionQueue:
|
||||
Type: Biolab
|
||||
Group: Infantry
|
||||
RequireOwner: false
|
||||
BuildSpeed: .4
|
||||
LowPowerSlowdown: 3
|
||||
ProductionBar:
|
||||
@@ -88,7 +89,7 @@ MISS:
|
||||
Buildable:
|
||||
Queue: Building
|
||||
BuildPaletteOrder: 1000
|
||||
Owner: None
|
||||
Prerequisites: ~disabled
|
||||
Valued:
|
||||
Cost: 2000
|
||||
Bib:
|
||||
|
||||
@@ -8,7 +8,7 @@ MCV:
|
||||
Buildable:
|
||||
BuildPaletteOrder: 100
|
||||
Prerequisites: anyhq
|
||||
Owner: gdi,nod
|
||||
Queue: Vehicle.GDI, Vehicle.Nod
|
||||
Selectable:
|
||||
Priority: 3
|
||||
Mobile:
|
||||
@@ -47,7 +47,7 @@ HARV:
|
||||
Buildable:
|
||||
BuildPaletteOrder: 10
|
||||
Prerequisites: proc
|
||||
Owner: gdi,nod
|
||||
Queue: Vehicle.GDI, Vehicle.Nod
|
||||
Selectable:
|
||||
Priority: 7
|
||||
Bounds: 36,36
|
||||
@@ -84,7 +84,7 @@ APC:
|
||||
Buildable:
|
||||
BuildPaletteOrder: 30
|
||||
Prerequisites: pyle
|
||||
Owner: gdi
|
||||
Queue: Vehicle.GDI
|
||||
Mobile:
|
||||
ROT: 8
|
||||
Speed: 128
|
||||
@@ -131,7 +131,7 @@ ARTY:
|
||||
Buildable:
|
||||
BuildPaletteOrder: 60
|
||||
Prerequisites: anyhq
|
||||
Owner: nod
|
||||
Queue: Vehicle.Nod
|
||||
Mobile:
|
||||
ROT: 2
|
||||
Speed: 85
|
||||
@@ -167,7 +167,7 @@ FTNK:
|
||||
Buildable:
|
||||
BuildPaletteOrder: 50
|
||||
Prerequisites: anyhq
|
||||
Owner: nod
|
||||
Queue: Vehicle.Nod
|
||||
Mobile:
|
||||
ROT: 7
|
||||
Speed: 113
|
||||
@@ -203,7 +203,7 @@ BGGY:
|
||||
Buildable:
|
||||
BuildPaletteOrder: 20
|
||||
Prerequisites: afld
|
||||
Owner: nod
|
||||
Queue: Vehicle.Nod
|
||||
Mobile:
|
||||
ROT: 10
|
||||
Speed: 170
|
||||
@@ -238,7 +238,7 @@ BIKE:
|
||||
Buildable:
|
||||
BuildPaletteOrder: 30
|
||||
Prerequisites: afld
|
||||
Owner: nod
|
||||
Queue: Vehicle.Nod
|
||||
Mobile:
|
||||
ROT: 10
|
||||
Speed: 213
|
||||
@@ -275,7 +275,7 @@ JEEP:
|
||||
Buildable:
|
||||
BuildPaletteOrder: 20
|
||||
Prerequisites: weap
|
||||
Owner: gdi
|
||||
Queue: Vehicle.GDI
|
||||
Mobile:
|
||||
ROT: 10
|
||||
Speed: 156
|
||||
@@ -310,7 +310,7 @@ LTNK:
|
||||
Buildable:
|
||||
BuildPaletteOrder: 40
|
||||
Prerequisites: anyhq
|
||||
Owner: nod
|
||||
Queue: Vehicle.Nod
|
||||
Mobile:
|
||||
ROT: 7
|
||||
Speed: 113
|
||||
@@ -349,7 +349,7 @@ MTNK:
|
||||
Buildable:
|
||||
BuildPaletteOrder: 40
|
||||
Prerequisites: anyhq
|
||||
Owner: gdi
|
||||
Queue: Vehicle.GDI
|
||||
Mobile:
|
||||
Speed: 85
|
||||
Health:
|
||||
@@ -389,7 +389,7 @@ HTNK:
|
||||
Buildable:
|
||||
BuildPaletteOrder: 60
|
||||
Prerequisites: eye
|
||||
Owner: gdi
|
||||
Queue: Vehicle.GDI
|
||||
Mobile:
|
||||
Crushes: wall, heavywall, crate, infantry
|
||||
Speed: 56
|
||||
@@ -442,7 +442,7 @@ MSAM:
|
||||
Buildable:
|
||||
BuildPaletteOrder: 50
|
||||
Prerequisites: anyhq
|
||||
Owner: gdi
|
||||
Queue: Vehicle.GDI
|
||||
Mobile:
|
||||
Speed: 85
|
||||
ROT: 4
|
||||
@@ -479,7 +479,7 @@ MLRS:
|
||||
Buildable:
|
||||
BuildPaletteOrder: 70
|
||||
Prerequisites: anyhq
|
||||
Owner: nod
|
||||
Queue: Vehicle.Nod
|
||||
Mobile:
|
||||
Speed: 99
|
||||
ROT: 7
|
||||
@@ -519,7 +519,7 @@ STNK:
|
||||
Buildable:
|
||||
BuildPaletteOrder: 90
|
||||
Prerequisites: tmpl
|
||||
Owner: nod
|
||||
Queue: Vehicle.Nod
|
||||
Mobile:
|
||||
ROT: 10
|
||||
Speed: 142
|
||||
|
||||
Reference in New Issue
Block a user