Skip to content

Commit ad99211

Browse files
committed
add EffectPulsate and fix producing into cargo
1 parent 9dc26a5 commit ad99211

File tree

5 files changed

+213
-26
lines changed

5 files changed

+213
-26
lines changed

OpenRA.Mods.Common/Traits/AutoTarget.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -361,6 +361,9 @@ Target ChooseTarget(Actor self, AttackBase ab, PlayerRelationship attackStances,
361361

362362
var activePriorities = activeTargetPriorities.ToList();
363363
if (activePriorities.Count == 0)
364+
365+
\t\t\tif (self.Owner == null)
366+
\t\t\t\treturn Target.Invalid;
364367
return chosenTarget;
365368

366369
var targetsInRange = self.World.FindActorsInCircle(self.CenterPosition, scanRange)
@@ -387,6 +390,8 @@ Target ChooseTarget(Actor self, AttackBase ab, PlayerRelationship attackStances,
387390
// Check whether we can auto-target this actor
388391
targetTypes = target.Actor.GetEnabledTargetTypes();
389392

393+
\t\t\t\tif (target.Actor.Owner == null)
394+
\t\t\t\t\tcontinue;
390395
if (PreventsAutoTarget(self, target.Actor) || !target.Actor.CanBeViewedByPlayer(self.Owner))
391396
continue;
392397

@@ -475,3 +480,6 @@ public StanceInit(TraitInfo info, UnitStance value)
475480
: base(info, value) { }
476481
}
477482
}
483+
484+
485+

OpenRA.Mods.Common/Traits/Player/ClassicProductionQueue.cs

Lines changed: 7 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -83,15 +83,10 @@ public override IEnumerable<ActorInfo> BuildableItems()
8383

8484
public override TraitPair<Production> MostLikelyProducer()
8585
{
86-
var productionActor = self.World.ActorsWithTrait<Production>()
87-
.Where(x => x.Actor.Owner == self.Owner
88-
&& !x.Trait.IsTraitDisabled && x.Trait.Info.Produces.Contains(Info.Type))
89-
.OrderBy(x => x.Trait.IsTraitPaused)
90-
.ThenByDescending(x => x.Actor.IsPrimaryBuilding())
91-
.ThenByDescending(x => x.Actor.ActorID)
86+
return OrderedProducers()
87+
.ThenByDescending(p => p.Actor.IsPrimaryBuilding())
88+
.ThenByDescending(p => p.Actor.ActorID)
9289
.FirstOrDefault();
93-
94-
return productionActor;
9590
}
9691

9792
protected override bool BuildUnit(ActorInfo unit)
@@ -102,14 +97,11 @@ protected override bool BuildUnit(ActorInfo unit)
10297
// Some units may request a specific production type, which is ignored if the AllTech cheat is enabled
10398
var type = developerMode.AllTech ? Info.Type : (bi.BuildAtProductionType ?? Info.Type);
10499

105-
var producers = self.World.ActorsWithTrait<Production>()
106-
.Where(x => x.Actor.Owner == self.Owner
107-
&& !x.Trait.IsTraitDisabled
108-
&& x.Trait.Info.Produces.Contains(type))
109-
.OrderByDescending(x => x.Actor.IsPrimaryBuilding())
110-
.ThenByDescending(x => x.Actor.ActorID);
111-
112100
var anyProducers = false;
101+
var producers = OrderedProducers(type)
102+
.ThenByDescending(p => p.Actor.IsPrimaryBuilding())
103+
.ThenByDescending(p => p.Actor.ActorID);
104+
113105
foreach (var p in producers)
114106
{
115107
anyProducers = true;

OpenRA.Mods.Common/Traits/Player/ProductionQueue.cs

Lines changed: 42 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -688,24 +688,42 @@ public virtual int RemainingTimeActual(ProductionItem item)
688688
return item.RemainingTimeActual;
689689
}
690690

691+
IEnumerable<TraitPair<Production>> EnumerateProducers(string productionType = null)
692+
{
693+
var type = productionType ?? Info.Type;
694+
695+
foreach (var local in productionTraits)
696+
if (!local.IsTraitDisabled && local.Info.Produces.Contains(type))
697+
yield return new TraitPair<Production>(Actor, local);
698+
699+
foreach (var pair in Actor.Owner.World.ActorsWithTrait<Production>())
700+
if (pair.Actor.Owner == Actor.Owner &&
701+
!pair.Trait.IsTraitDisabled &&
702+
pair.Trait.Info.Produces.Contains(type))
703+
yield return pair;
704+
}
705+
706+
protected virtual IOrderedEnumerable<TraitPair<Production>> OrderedProducers(string productionType = null)
707+
{
708+
return EnumerateProducers(productionType)
709+
.OrderByDescending(p => p.Trait.Info.ProduceIntoCargo)
710+
.ThenByDescending(p => p.Trait.Info.CargoPriority)
711+
.ThenBy(p => p.Trait.IsTraitPaused)
712+
.ThenBy(p => p.Actor == Actor ? 0 : 1);
713+
}
714+
691715
// Returns the actor/trait that is most likely (but not necessarily guaranteed) to produce something in this queue
692716
public virtual TraitPair<Production> MostLikelyProducer()
693717
{
694-
var trait = productionTraits
695-
.Where(p => !p.IsTraitDisabled && p.Info.Produces.Contains(Info.Type))
696-
.OrderBy(p => p.IsTraitPaused)
718+
return OrderedProducers()
697719
.FirstOrDefault();
698-
return new TraitPair<Production>(Actor, trait);
699720
}
700721

701722
// Builds a unit from the actor that holds this queue (1 queue per building)
702723
// Returns false if the unit can't be built
703724
protected virtual bool BuildUnit(ActorInfo unit)
704725
{
705-
var mostLikelyProducerTrait = MostLikelyProducer().Trait;
706-
707-
// Cannot produce if I'm dead or trait is disabled
708-
if (!Actor.IsInWorld || Actor.IsDead || mostLikelyProducerTrait == null)
726+
if (!Actor.IsInWorld || Actor.IsDead)
709727
{
710728
CancelProduction(unit.Name, 1);
711729
return false;
@@ -720,12 +738,25 @@ protected virtual bool BuildUnit(ActorInfo unit)
720738
var bi = BuildableInfo.GetTraitForQueue(unit, Info.Type);
721739
var type = developerMode.AllTech ? Info.Type : (bi.BuildAtProductionType ?? Info.Type);
722740
var item = Queue.First(i => i.Done && i.Item == unit.Name);
723-
if (!mostLikelyProducerTrait.IsTraitPaused && mostLikelyProducerTrait.Produce(Actor, unit, type, inits, item.TotalCost))
741+
foreach (var candidate in OrderedProducers(type))
724742
{
725-
EndProduction(item);
726-
return true;
743+
var producerActor = candidate.Actor;
744+
var producerTrait = candidate.Trait;
745+
if (producerActor == null || producerTrait == null)
746+
continue;
747+
if (producerTrait.IsTraitPaused)
748+
continue;
749+
if (!producerActor.IsInWorld || producerActor.IsDead)
750+
continue;
751+
752+
if (producerTrait.Produce(producerActor, unit, type, inits, item.TotalCost))
753+
{
754+
EndProduction(item);
755+
return true;
756+
}
727757
}
728758

759+
CancelProduction(unit.Name, 1);
729760
return false;
730761
}
731762
}

OpenRA.Mods.Common/Traits/Production.cs

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
using System;
1313
using System.Collections.Generic;
14+
using System.Linq;
1415
using OpenRA.Primitives;
1516
using OpenRA.Traits;
1617

@@ -26,6 +27,15 @@ public class ProductionInfo : PausableConditionalTraitInfo
2627
[Desc("When owner is changed, should the Faction be updated to the new owner's faction?")]
2728
public readonly bool UpdateFactionOnOwnerChange = false;
2829

30+
[Desc("Produce new actors directly into this actor's cargo instead of spawning them on the map.")]
31+
public readonly bool ProduceIntoCargo = false;
32+
33+
[Desc("When producing into cargo, also attempt to load into the cargo of passengers that themselves have cargo.")]
34+
public readonly bool ProduceIntoCargoOfCargo = false;
35+
36+
[Desc("Priority value when multiple cargo-producing traits compete. Higher values are preferred.")]
37+
public readonly int CargoPriority = 0;
38+
2939
public override object Create(ActorInitializer init) { return new Production(init, this); }
3040
}
3141

@@ -121,6 +131,9 @@ public virtual bool Produce(Actor self, ActorInfo producee, string productionTyp
121131
if (IsTraitDisabled || IsTraitPaused)
122132
return false;
123133

134+
if (Info.ProduceIntoCargo && TryProduceIntoCargo(self, producee, productionType, inits))
135+
return true;
136+
124137
// Pick a spawn/exit point pair
125138
var exit = SelectExit(self, producee, productionType);
126139
if (exit != null || self.OccupiesSpace == null || !producee.HasTraitInfo<IOccupySpaceInfo>())
@@ -132,6 +145,62 @@ public virtual bool Produce(Actor self, ActorInfo producee, string productionTyp
132145
return false;
133146
}
134147

148+
bool TryProduceIntoCargo(Actor self, ActorInfo producee, string productionType, TypeDictionary inits)
149+
{
150+
var passengerInfo = producee.TraitInfoOrDefault<PassengerInfo>();
151+
if (passengerInfo == null)
152+
return false;
153+
154+
var candidates = GatherCargoTargets(self, passengerInfo.Weight);
155+
if (candidates == null)
156+
return false;
157+
158+
var td = new TypeDictionary();
159+
foreach (var init in inits)
160+
td.Add(init);
161+
162+
var (transport, cargo) = candidates.Value;
163+
var newUnit = self.World.CreateActor(false, producee.Name, td);
164+
cargo.Load(transport, newUnit);
165+
166+
self.World.AddFrameEndTask(w =>
167+
{
168+
if (!self.IsDead)
169+
foreach (var t in self.TraitsImplementing<INotifyProduction>())
170+
t.UnitProduced(self, newUnit, transport.Location);
171+
172+
var notifyOthers = self.World.ActorsWithTrait<INotifyOtherProduction>();
173+
foreach (var notify in notifyOthers)
174+
notify.Trait.UnitProducedByOther(notify.Actor, self, newUnit, productionType, td);
175+
});
176+
177+
return true;
178+
}
179+
180+
(Actor Transport, Cargo Cargo)? GatherCargoTargets(Actor self, int weight)
181+
{
182+
var cargos = self.TraitsImplementing<Cargo>().ToArray();
183+
foreach (var cargo in cargos)
184+
if (cargo.HasSpace(weight))
185+
return (self, cargo);
186+
187+
if (!Info.ProduceIntoCargoOfCargo)
188+
return null;
189+
190+
foreach (var cargo in cargos)
191+
foreach (var passenger in cargo.Passengers)
192+
{
193+
if (passenger == null || passenger.IsDead)
194+
continue;
195+
196+
var passengerCargo = passenger.TraitOrDefault<Cargo>();
197+
if (passengerCargo != null && passengerCargo.HasSpace(weight))
198+
return (passenger, passengerCargo);
199+
}
200+
201+
return null;
202+
}
203+
135204
public virtual void ProduceActors(Actor self, ActorInfo producee, string productionType, TypeDictionary inits, ExitInfo exit)
136205
{
137206
var buildable = BuildableInfo.GetTraitForQueue(producee, productionType);
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
#region Copyright & License Information
2+
/*
3+
* Copyright (c) The OpenRA Developers and Contributors
4+
* This file is part of OpenRA, which is free software. It is made
5+
* available to you under the terms of the GNU General Public License
6+
* as published by the Free Software Foundation, either version 3 of
7+
* the License, or (at your option) any later version. For more
8+
* information, see COPYING.
9+
*/
10+
#endregion
11+
12+
using OpenRA.GameRules;
13+
using OpenRA.Mods.Common.Effects;
14+
using OpenRA.Primitives;
15+
using OpenRA.Traits;
16+
17+
namespace OpenRA.Mods.Common.Warheads
18+
{
19+
[Desc("Spawn a sprite that pulsates between scales during its lifetime.")]
20+
public class CreateEffectPulsateWarhead : CreateEffectWarhead
21+
{
22+
[Desc("Scale at the start of the animation.")]
23+
public readonly float StartScale = 0.1f;
24+
25+
[Desc("Scale reached at the apex of the pulse.")]
26+
public readonly float PeakScale = 1f;
27+
28+
[Desc("Scale at the end of the pulse (defaults to StartScale).")]
29+
public readonly float EndScale = 0.1f;
30+
31+
[Desc("Ticks spent scaling from StartScale to PeakScale.")]
32+
public readonly int GrowTicks = 8;
33+
34+
[Desc("Ticks to remain at PeakScale before shrinking.")]
35+
public readonly int HoldTicks = 0;
36+
37+
[Desc("Ticks spent scaling from PeakScale to EndScale.")]
38+
public readonly int ShrinkTicks = 8;
39+
40+
[Desc("If true, repeats the grow/hold/shrink cycle until the animation ends.")]
41+
public readonly bool Loop = false;
42+
43+
public override void DoImpact(in Target target, WarheadArgs args)
44+
{
45+
if (target.Type == TargetType.Invalid)
46+
return;
47+
48+
var firedBy = args.SourceActor;
49+
var pos = target.CenterPosition;
50+
var world = firedBy.World;
51+
var actorAtImpact = ImpactActors ? ActorTypeAtImpact(world, pos, firedBy) : ImpactActorType.None;
52+
53+
if (actorAtImpact == ImpactActorType.Invalid)
54+
return;
55+
56+
if (actorAtImpact == ImpactActorType.None && !IsValidAgainstTerrain(world, pos))
57+
return;
58+
59+
var explosion = Explosions.RandomOrDefault(world.LocalRandom);
60+
if (Image != null && explosion != null)
61+
{
62+
if (Inaccuracy.Length > 0)
63+
pos += WVec.FromPDF(world.SharedRandom, 2) * Inaccuracy.Length / 1024;
64+
65+
if (ForceDisplayAtGroundLevel)
66+
{
67+
var dat = world.Map.DistanceAboveTerrain(pos);
68+
pos -= new WVec(0, 0, dat.Length);
69+
}
70+
71+
var palette = ExplosionPalette;
72+
if (UsePlayerPalette)
73+
palette += firedBy.Owner.InternalName;
74+
75+
world.AddFrameEndTask(w => w.Add(new PulsatingSpriteEffect(
76+
pos, w, Image, explosion, palette,
77+
StartScale, PeakScale, EndScale,
78+
GrowTicks, HoldTicks, ShrinkTicks, Loop)));
79+
}
80+
81+
var impactSound = ImpactSounds.RandomOrDefault(world.LocalRandom);
82+
if (impactSound != null && world.LocalRandom.Next(0, 100) < ImpactSoundChance
83+
&& (AudibleThroughFog || (!world.ShroudObscures(pos) && !world.FogObscures(pos))))
84+
Game.Sound.Play(SoundType.World, impactSound, pos, Volume);
85+
}
86+
}
87+
}

0 commit comments

Comments
 (0)