@@ -99,7 +99,7 @@ namespace OpenRA.Orders
|
|||||||
if (self.Owner != self.World.LocalPlayer)
|
if (self.Owner != self.World.LocalPlayer)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
if (self.Destroyed || target.Type == TargetType.Invalid)
|
if (self.Destroyed || !target.IsValidFor(self))
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
if (mi.Button == Game.mouseButtonPreference.Action)
|
if (mi.Button == Game.mouseButtonPreference.Action)
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ namespace OpenRA.Traits
|
|||||||
|
|
||||||
foreach (var target in targets)
|
foreach (var target in targets)
|
||||||
{
|
{
|
||||||
if (!target.IsValid)
|
if (target.Type == TargetType.Invalid)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
var to = wr.ScreenPxPosition(target.CenterPosition);
|
var to = wr.ScreenPxPosition(target.CenterPosition);
|
||||||
|
|||||||
@@ -161,7 +161,7 @@ namespace OpenRA.Traits
|
|||||||
var c = Color.Green;
|
var c = Color.Green;
|
||||||
|
|
||||||
var wlr = Game.Renderer.WorldLineRenderer;
|
var wlr = Game.Renderer.WorldLineRenderer;
|
||||||
foreach (var stp in targets.Where(t => t.IsValid).Select(p => wr.ScreenPxPosition(p.CenterPosition)))
|
foreach (var stp in targets.Where(t => t.Type != TargetType.Invalid).Select(p => wr.ScreenPxPosition(p.CenterPosition)))
|
||||||
{
|
{
|
||||||
wlr.DrawLine(stp + new float2(-1, -1), stp + new float2(-1, 1), c, c);
|
wlr.DrawLine(stp + new float2(-1, -1), stp + new float2(-1, 1), c, c);
|
||||||
wlr.DrawLine(stp + new float2(-1, 1), stp + new float2(1, 1), c, c);
|
wlr.DrawLine(stp + new float2(-1, 1), stp + new float2(1, 1), c, c);
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ namespace OpenRA.Traits
|
|||||||
|
|
||||||
TargetType type;
|
TargetType type;
|
||||||
Actor actor;
|
Actor actor;
|
||||||
|
ITargetable targetable;
|
||||||
FrozenActor frozen;
|
FrozenActor frozen;
|
||||||
WPos pos;
|
WPos pos;
|
||||||
int generation;
|
int generation;
|
||||||
@@ -37,17 +38,20 @@ namespace OpenRA.Traits
|
|||||||
|
|
||||||
public static Target FromActor(Actor a)
|
public static Target FromActor(Actor a)
|
||||||
{
|
{
|
||||||
|
if (a == null)
|
||||||
|
return Target.Invalid;
|
||||||
|
|
||||||
return new Target
|
return new Target
|
||||||
{
|
{
|
||||||
actor = a,
|
actor = a,
|
||||||
type = a != null ? TargetType.Actor : TargetType.Invalid,
|
targetable = a.TraitOrDefault<ITargetable>(),
|
||||||
|
type = TargetType.Actor,
|
||||||
generation = a.Generation,
|
generation = a.Generation,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Target FromFrozenActor(FrozenActor a) { return new Target { frozen = a, type = TargetType.FrozenActor }; }
|
public static Target FromFrozenActor(FrozenActor a) { return new Target { frozen = a, type = TargetType.FrozenActor }; }
|
||||||
|
|
||||||
public bool IsValid { get { return Type != TargetType.Invalid; } }
|
|
||||||
public Actor Actor { get { return actor; } }
|
public Actor Actor { get { return actor; } }
|
||||||
public FrozenActor FrozenActor { get { return frozen; } }
|
public FrozenActor FrozenActor { get { return frozen; } }
|
||||||
|
|
||||||
@@ -70,6 +74,17 @@ namespace OpenRA.Traits
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool IsValidFor(Actor targeter)
|
||||||
|
{
|
||||||
|
if (targeter == null || Type == TargetType.Invalid)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (targetable != null && !targetable.TargetableBy(actor, targeter))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
// Representative position - see Positions for the full set of targetable positions.
|
// Representative position - see Positions for the full set of targetable positions.
|
||||||
public WPos CenterPosition
|
public WPos CenterPosition
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -99,7 +99,7 @@ namespace OpenRA.Mods.RA.AI
|
|||||||
HackyAI bot;
|
HackyAI bot;
|
||||||
XRandom random;
|
XRandom random;
|
||||||
|
|
||||||
Actor target;
|
Target target;
|
||||||
StateMachine fsm;
|
StateMachine fsm;
|
||||||
|
|
||||||
//fuzzy
|
//fuzzy
|
||||||
@@ -113,7 +113,8 @@ namespace OpenRA.Mods.RA.AI
|
|||||||
this.world = bot.world;
|
this.world = bot.world;
|
||||||
this.random = bot.random;
|
this.random = bot.random;
|
||||||
this.type = type;
|
this.type = type;
|
||||||
this.target = target;
|
this.target = Traits.Target.FromActor(target);
|
||||||
|
|
||||||
fsm = new StateMachine(this);
|
fsm = new StateMachine(this);
|
||||||
|
|
||||||
switch (type)
|
switch (type)
|
||||||
@@ -144,14 +145,13 @@ namespace OpenRA.Mods.RA.AI
|
|||||||
|
|
||||||
public Actor Target
|
public Actor Target
|
||||||
{
|
{
|
||||||
get { return target; }
|
get { return target.Actor; }
|
||||||
set { target = value; }
|
set { target = Traits.Target.FromActor(value); }
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool TargetIsValid
|
public bool TargetIsValid
|
||||||
{
|
{
|
||||||
get { return (target != null && !target.IsDead() && !target.Destroyed
|
get { return target.IsValidFor(units.FirstOrDefault()) && !target.Actor.HasTrait<Husk>(); }
|
||||||
&& target.IsInWorld && !target.HasTrait<Husk>()); }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//**********************************************************************************
|
//**********************************************************************************
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ namespace OpenRA.Mods.RA.Activities
|
|||||||
public class Attack : Activity
|
public class Attack : Activity
|
||||||
{
|
{
|
||||||
protected Target Target;
|
protected Target Target;
|
||||||
ITargetable targetable;
|
|
||||||
WRange Range;
|
WRange Range;
|
||||||
bool AllowMovement;
|
bool AllowMovement;
|
||||||
|
|
||||||
@@ -33,9 +32,6 @@ namespace OpenRA.Mods.RA.Activities
|
|||||||
public Attack(Target target, WRange range, bool allowMovement)
|
public Attack(Target target, WRange range, bool allowMovement)
|
||||||
{
|
{
|
||||||
Target = target;
|
Target = target;
|
||||||
if (target.Type == TargetType.Actor)
|
|
||||||
targetable = target.Actor.TraitOrDefault<ITargetable>();
|
|
||||||
|
|
||||||
Range = range;
|
Range = range;
|
||||||
AllowMovement = allowMovement;
|
AllowMovement = allowMovement;
|
||||||
}
|
}
|
||||||
@@ -54,13 +50,11 @@ namespace OpenRA.Mods.RA.Activities
|
|||||||
return NextActivity;
|
return NextActivity;
|
||||||
|
|
||||||
var type = Target.Type;
|
var type = Target.Type;
|
||||||
if (type != TargetType.Actor && type != TargetType.Terrain)
|
if (!Target.IsValidFor(self) || type == TargetType.FrozenActor)
|
||||||
return NextActivity;
|
|
||||||
|
|
||||||
if (type == TargetType.Actor && !self.Owner.HasFogVisibility() && Target.Actor.HasTrait<Mobile>() && !self.Owner.Shroud.IsTargetable(Target.Actor))
|
|
||||||
return NextActivity;
|
return NextActivity;
|
||||||
|
|
||||||
if (targetable != null && !targetable.TargetableBy(Target.Actor, self))
|
// TODO: This is horrible, and probably wrong. Work out what it is trying to solve, then redo it properly.
|
||||||
|
if (type == TargetType.Actor && !self.Owner.HasFogVisibility() && Target.Actor.HasTrait<Mobile>() && !self.Owner.Shroud.IsTargetable(Target.Actor))
|
||||||
return NextActivity;
|
return NextActivity;
|
||||||
|
|
||||||
if (!Target.IsInRange(self.CenterPosition, Range))
|
if (!Target.IsInRange(self.CenterPosition, Range))
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ namespace OpenRA.Mods.RA.Activities
|
|||||||
|
|
||||||
var capturable = target.Actor.Trait<Capturable>();
|
var capturable = target.Actor.Trait<Capturable>();
|
||||||
|
|
||||||
if (IsCanceled || !self.IsInWorld || self.IsDead())
|
if (IsCanceled || !self.IsInWorld || self.IsDead() || !target.IsValidFor(self))
|
||||||
{
|
{
|
||||||
if (capturable.CaptureInProgress)
|
if (capturable.CaptureInProgress)
|
||||||
capturable.EndCapture();
|
capturable.EndCapture();
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ namespace OpenRA.Mods.RA.Activities
|
|||||||
|
|
||||||
public override Activity Tick(Actor self)
|
public override Activity Tick(Actor self)
|
||||||
{
|
{
|
||||||
if (IsCanceled || target.Type != TargetType.Actor)
|
if (IsCanceled || !target.IsValidFor(self))
|
||||||
return NextActivity;
|
return NextActivity;
|
||||||
|
|
||||||
self.World.AddFrameEndTask(w => w.Add(new DelayedAction(delay, () =>
|
self.World.AddFrameEndTask(w => w.Add(new DelayedAction(delay, () =>
|
||||||
|
|||||||
@@ -27,7 +27,10 @@ namespace OpenRA.Mods.RA.Activities
|
|||||||
|
|
||||||
public override Activity Tick(Actor self)
|
public override Activity Tick(Actor self)
|
||||||
{
|
{
|
||||||
if (IsCanceled || target.Type != TargetType.Actor)
|
if (IsCanceled || !target.IsValidFor(self))
|
||||||
|
return NextActivity;
|
||||||
|
|
||||||
|
if (target.Type != TargetType.Actor)
|
||||||
return NextActivity;
|
return NextActivity;
|
||||||
|
|
||||||
var targetActor = target.Actor;
|
var targetActor = target.Actor;
|
||||||
|
|||||||
@@ -27,7 +27,10 @@ namespace OpenRA.Mods.RA.Activities
|
|||||||
|
|
||||||
public override Activity Tick(Actor self)
|
public override Activity Tick(Actor self)
|
||||||
{
|
{
|
||||||
if (IsCanceled || target.Type != TargetType.Actor)
|
if (IsCanceled || !target.IsValidFor(self))
|
||||||
|
return NextActivity;
|
||||||
|
|
||||||
|
if (target.Type != TargetType.Actor)
|
||||||
return NextActivity;
|
return NextActivity;
|
||||||
|
|
||||||
if (!Util.AdjacentCells(target).Any(c => c == self.Location))
|
if (!Util.AdjacentCells(target).Any(c => c == self.Location))
|
||||||
|
|||||||
@@ -16,21 +16,23 @@ namespace OpenRA.Mods.RA.Activities
|
|||||||
public class Follow : Activity
|
public class Follow : Activity
|
||||||
{
|
{
|
||||||
Target target;
|
Target target;
|
||||||
|
Mobile mobile;
|
||||||
WRange range;
|
WRange range;
|
||||||
int nextPathTime;
|
int nextPathTime;
|
||||||
|
|
||||||
const int delayBetweenPathingAttempts = 20;
|
const int delayBetweenPathingAttempts = 20;
|
||||||
const int delaySpread = 5;
|
const int delaySpread = 5;
|
||||||
|
|
||||||
public Follow(Target target, WRange range)
|
public Follow(Actor self, Target target, WRange range)
|
||||||
{
|
{
|
||||||
this.target = target;
|
this.target = target;
|
||||||
|
mobile = self.Trait<Mobile>();
|
||||||
this.range = range;
|
this.range = range;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override Activity Tick(Actor self)
|
public override Activity Tick(Actor self)
|
||||||
{
|
{
|
||||||
if (IsCanceled || !target.IsValid)
|
if (IsCanceled || !target.IsValidFor(self))
|
||||||
return NextActivity;
|
return NextActivity;
|
||||||
|
|
||||||
if (target.IsInRange(self.CenterPosition, range) || --nextPathTime > 0)
|
if (target.IsInRange(self.CenterPosition, range) || --nextPathTime > 0)
|
||||||
@@ -39,7 +41,6 @@ namespace OpenRA.Mods.RA.Activities
|
|||||||
nextPathTime = self.World.SharedRandom.Next(delayBetweenPathingAttempts - delaySpread,
|
nextPathTime = self.World.SharedRandom.Next(delayBetweenPathingAttempts - delaySpread,
|
||||||
delayBetweenPathingAttempts + delaySpread);
|
delayBetweenPathingAttempts + delaySpread);
|
||||||
|
|
||||||
var mobile = self.Trait<Mobile>();
|
|
||||||
return Util.SequenceActivities(mobile.MoveWithinRange(target, range), this);
|
return Util.SequenceActivities(mobile.MoveWithinRange(target, range), this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,6 +22,9 @@ namespace OpenRA.Mods.RA.Activities
|
|||||||
|
|
||||||
protected override Activity InnerTick(Actor self, AttackBase attack)
|
protected override Activity InnerTick(Actor self, AttackBase attack)
|
||||||
{
|
{
|
||||||
|
if (!Target.IsValidFor(self))
|
||||||
|
return NextActivity;
|
||||||
|
|
||||||
if (Target.Type == TargetType.Actor && Target.Actor.GetDamageState() == DamageState.Undamaged)
|
if (Target.Type == TargetType.Actor && Target.Actor.GetDamageState() == DamageState.Undamaged)
|
||||||
return NextActivity;
|
return NextActivity;
|
||||||
|
|
||||||
|
|||||||
@@ -23,7 +23,10 @@ namespace OpenRA.Mods.RA.Activities
|
|||||||
|
|
||||||
public override Activity Tick(Actor self)
|
public override Activity Tick(Actor self)
|
||||||
{
|
{
|
||||||
if (IsCanceled || target.Type != TargetType.Actor)
|
if (IsCanceled || !target.IsValidFor(self))
|
||||||
|
return NextActivity;
|
||||||
|
|
||||||
|
if (target.Type != TargetType.Actor)
|
||||||
return NextActivity;
|
return NextActivity;
|
||||||
|
|
||||||
var actor = target.Actor;
|
var actor = target.Actor;
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ namespace OpenRA.Mods.RA.Activities
|
|||||||
|
|
||||||
public override Activity Tick(Actor self)
|
public override Activity Tick(Actor self)
|
||||||
{
|
{
|
||||||
if (IsCanceled || !target.IsValid)
|
if (IsCanceled || !target.IsValidFor(self))
|
||||||
return NextActivity;
|
return NextActivity;
|
||||||
|
|
||||||
var mobile = self.Trait<Mobile>();
|
var mobile = self.Trait<Mobile>();
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ namespace OpenRA.Mods.RA.Air
|
|||||||
|
|
||||||
public override Activity Tick(Actor self)
|
public override Activity Tick(Actor self)
|
||||||
{
|
{
|
||||||
if (!target.IsValid)
|
if (!target.IsValidFor(self))
|
||||||
Cancel(self);
|
Cancel(self);
|
||||||
|
|
||||||
var limitedAmmo = self.TraitOrDefault<LimitedAmmo>();
|
var limitedAmmo = self.TraitOrDefault<LimitedAmmo>();
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ namespace OpenRA.Mods.RA.Air
|
|||||||
|
|
||||||
public override Activity Tick(Actor self)
|
public override Activity Tick(Actor self)
|
||||||
{
|
{
|
||||||
if (IsCanceled || !target.IsValid)
|
if (IsCanceled || !target.IsValidFor(self))
|
||||||
return NextActivity;
|
return NextActivity;
|
||||||
|
|
||||||
var limitedAmmo = self.TraitOrDefault<LimitedAmmo>();
|
var limitedAmmo = self.TraitOrDefault<LimitedAmmo>();
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ namespace OpenRA.Mods.RA.Air
|
|||||||
|
|
||||||
public override Activity Tick(Actor self)
|
public override Activity Tick(Actor self)
|
||||||
{
|
{
|
||||||
if (!target.IsValid)
|
if (!target.IsValidFor(self))
|
||||||
Cancel(self);
|
Cancel(self);
|
||||||
|
|
||||||
if (IsCanceled)
|
if (IsCanceled)
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ namespace OpenRA.Mods.RA
|
|||||||
if (!self.IsInWorld)
|
if (!self.IsInWorld)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (!target.IsValid)
|
if (!target.IsValidFor(self))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (Armaments.All(a => a.IsReloading))
|
if (Armaments.All(a => a.IsReloading))
|
||||||
@@ -54,10 +54,6 @@ namespace OpenRA.Mods.RA
|
|||||||
if (self.IsDisabled())
|
if (self.IsDisabled())
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (target.Type == TargetType.Actor && target.Actor.HasTrait<ITargetable>() &&
|
|
||||||
!target.Actor.Trait<ITargetable>().TargetableBy(target.Actor, self))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -133,7 +129,7 @@ namespace OpenRA.Mods.RA
|
|||||||
if (order.OrderString == "Attack")
|
if (order.OrderString == "Attack")
|
||||||
{
|
{
|
||||||
var target = self.ResolveFrozenActorOrder(order, Color.Red);
|
var target = self.ResolveFrozenActorOrder(order, Color.Red);
|
||||||
if (!target.IsValid)
|
if (!target.IsValidFor(self))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
self.SetTargetLine(target, Color.Red);
|
self.SetTargetLine(target, Color.Red);
|
||||||
@@ -155,7 +151,7 @@ namespace OpenRA.Mods.RA
|
|||||||
|
|
||||||
public void AttackTarget(Target target, bool queued, bool allowMove)
|
public void AttackTarget(Target target, bool queued, bool allowMove)
|
||||||
{
|
{
|
||||||
if (!target.IsValid)
|
if (!target.IsValidFor(self))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (!queued)
|
if (!queued)
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ namespace OpenRA.Mods.RA
|
|||||||
|
|
||||||
public override Activity Tick( Actor self )
|
public override Activity Tick( Actor self )
|
||||||
{
|
{
|
||||||
if( IsCanceled || !target.IsValid )
|
if (IsCanceled || !target.IsValidFor(self))
|
||||||
return NextActivity;
|
return NextActivity;
|
||||||
|
|
||||||
self.Trait<AttackOmni>().DoAttack(self, target);
|
self.Trait<AttackOmni>().DoAttack(self, target);
|
||||||
|
|||||||
@@ -67,7 +67,8 @@ namespace OpenRA.Mods.RA
|
|||||||
|
|
||||||
public override Activity Tick( Actor self )
|
public override Activity Tick( Actor self )
|
||||||
{
|
{
|
||||||
if( IsCanceled || !target.IsValid ) return NextActivity;
|
if (IsCanceled || !target.IsValidFor(self))
|
||||||
|
return NextActivity;
|
||||||
|
|
||||||
var attack = self.Trait<AttackTesla>();
|
var attack = self.Trait<AttackTesla>();
|
||||||
if( attack.charges == 0 || !attack.CanAttack( self, target ) )
|
if( attack.charges == 0 || !attack.CanAttack( self, target ) )
|
||||||
@@ -85,7 +86,8 @@ namespace OpenRA.Mods.RA
|
|||||||
|
|
||||||
public override Activity Tick( Actor self )
|
public override Activity Tick( Actor self )
|
||||||
{
|
{
|
||||||
if( IsCanceled || !target.IsValid ) return NextActivity;
|
if (IsCanceled || !target.IsValidFor(self))
|
||||||
|
return NextActivity;
|
||||||
|
|
||||||
var attack = self.Trait<AttackTesla>();
|
var attack = self.Trait<AttackTesla>();
|
||||||
if( attack.charges == 0 ) return NextActivity;
|
if( attack.charges == 0 ) return NextActivity;
|
||||||
|
|||||||
@@ -38,7 +38,8 @@ namespace OpenRA.Mods.RA
|
|||||||
if (self.HasTrait<Building>() && !buildComplete)
|
if (self.HasTrait<Building>() && !buildComplete)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (!target.IsValid) return false;
|
if (!target.IsValidFor(self))
|
||||||
|
return false;
|
||||||
|
|
||||||
bool canAttack = false;
|
bool canAttack = false;
|
||||||
foreach (var t in turrets)
|
foreach (var t in turrets)
|
||||||
@@ -53,7 +54,7 @@ namespace OpenRA.Mods.RA
|
|||||||
{
|
{
|
||||||
base.Tick(self);
|
base.Tick(self);
|
||||||
DoAttack(self, Target);
|
DoAttack(self, Target);
|
||||||
IsAttacking = Target.IsValid;
|
IsAttacking = Target.IsValidFor(self);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override Activity GetAttackActivity(Actor self, Target newTarget, bool allowMove)
|
public override Activity GetAttackActivity(Actor self, Target newTarget, bool allowMove)
|
||||||
@@ -84,7 +85,8 @@ namespace OpenRA.Mods.RA
|
|||||||
|
|
||||||
public override Activity Tick(Actor self)
|
public override Activity Tick(Actor self)
|
||||||
{
|
{
|
||||||
if (IsCanceled || !target.IsValid) return NextActivity;
|
if (IsCanceled || !target.IsValidFor(self))
|
||||||
|
return NextActivity;
|
||||||
|
|
||||||
if (self.IsDisabled()) return this;
|
if (self.IsDisabled()) return this;
|
||||||
|
|
||||||
@@ -98,7 +100,7 @@ namespace OpenRA.Mods.RA
|
|||||||
|
|
||||||
attack.Target = target;
|
attack.Target = target;
|
||||||
if (allowMove && self.HasTrait<Mobile>() && !self.Info.Traits.Get<MobileInfo>().OnRails)
|
if (allowMove && self.HasTrait<Mobile>() && !self.Info.Traits.Get<MobileInfo>().OnRails)
|
||||||
return Util.SequenceActivities(new Follow(target, range), this);
|
return Util.SequenceActivities(new Follow(self, target, range), this);
|
||||||
}
|
}
|
||||||
|
|
||||||
return NextActivity;
|
return NextActivity;
|
||||||
|
|||||||
@@ -19,12 +19,14 @@ namespace OpenRA.Mods.RA
|
|||||||
{
|
{
|
||||||
public class CloakInfo : ITraitInfo
|
public class CloakInfo : ITraitInfo
|
||||||
{
|
{
|
||||||
public int InitialDelay = 10; // Ticks
|
public readonly int InitialDelay = 10; // Ticks
|
||||||
public int CloakDelay = 30; // Ticks
|
public readonly int CloakDelay = 30; // Ticks
|
||||||
public string CloakSound = "subshow1.aud";
|
|
||||||
public string UncloakSound = "subshow1.aud";
|
|
||||||
public readonly string Palette = "cloak";
|
|
||||||
public readonly bool UncloakOnMove = false;
|
public readonly bool UncloakOnMove = false;
|
||||||
|
public readonly bool RequiresCrate = false;
|
||||||
|
|
||||||
|
public readonly string CloakSound = "subshow1.aud";
|
||||||
|
public readonly string UncloakSound = "subshow1.aud";
|
||||||
|
public readonly string Palette = "cloak";
|
||||||
|
|
||||||
public object Create(ActorInitializer init) { return new Cloak(init.self, this); }
|
public object Create(ActorInitializer init) { return new Cloak(init.self, this); }
|
||||||
}
|
}
|
||||||
@@ -32,7 +34,8 @@ namespace OpenRA.Mods.RA
|
|||||||
public class Cloak : IRenderModifier, INotifyDamageStateChanged, INotifyAttack, ITick, IVisibilityModifier, IRadarColorModifier, ISync
|
public class Cloak : IRenderModifier, INotifyDamageStateChanged, INotifyAttack, ITick, IVisibilityModifier, IRadarColorModifier, ISync
|
||||||
{
|
{
|
||||||
[Sync] int remainingTime;
|
[Sync] int remainingTime;
|
||||||
[Sync] bool canCloak = true;
|
[Sync] bool damageDisabled;
|
||||||
|
[Sync] bool crateDisabled;
|
||||||
|
|
||||||
Actor self;
|
Actor self;
|
||||||
CloakInfo info;
|
CloakInfo info;
|
||||||
@@ -44,6 +47,7 @@ namespace OpenRA.Mods.RA
|
|||||||
this.self = self;
|
this.self = self;
|
||||||
|
|
||||||
remainingTime = info.InitialDelay;
|
remainingTime = info.InitialDelay;
|
||||||
|
crateDisabled = info.RequiresCrate;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Uncloak() { Uncloak(info.CloakDelay); }
|
public void Uncloak() { Uncloak(info.CloakDelay); }
|
||||||
@@ -62,8 +66,9 @@ namespace OpenRA.Mods.RA
|
|||||||
|
|
||||||
public void DamageStateChanged(Actor self, AttackInfo e)
|
public void DamageStateChanged(Actor self, AttackInfo e)
|
||||||
{
|
{
|
||||||
canCloak = (e.DamageState < DamageState.Critical);
|
damageDisabled = e.DamageState >= DamageState.Critical;
|
||||||
if (!canCloak) Uncloak();
|
if (damageDisabled)
|
||||||
|
Uncloak();
|
||||||
}
|
}
|
||||||
|
|
||||||
public IEnumerable<IRenderable> ModifyRender(Actor self, WorldRenderer wr, IEnumerable<IRenderable> r)
|
public IEnumerable<IRenderable> ModifyRender(Actor self, WorldRenderer wr, IEnumerable<IRenderable> r)
|
||||||
@@ -82,9 +87,12 @@ namespace OpenRA.Mods.RA
|
|||||||
|
|
||||||
public void Tick(Actor self)
|
public void Tick(Actor self)
|
||||||
{
|
{
|
||||||
if (remainingTime > 0 && canCloak)
|
if (remainingTime > 0 && !crateDisabled && !damageDisabled && --remainingTime <= 0)
|
||||||
if (--remainingTime <= 0)
|
{
|
||||||
Sound.Play(info.CloakSound, self.CenterPosition);
|
self.Generation++;
|
||||||
|
Sound.Play(info.CloakSound, self.CenterPosition);
|
||||||
|
}
|
||||||
|
|
||||||
if (self.IsDisabled())
|
if (self.IsDisabled())
|
||||||
Uncloak();
|
Uncloak();
|
||||||
|
|
||||||
@@ -95,15 +103,14 @@ namespace OpenRA.Mods.RA
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool IsVisible(Actor self, Player byPlayer)
|
public bool IsVisible(Actor self, Player viewer)
|
||||||
{
|
{
|
||||||
if (!Cloaked || self.Owner.IsAlliedWith(byPlayer))
|
if (!Cloaked || self.Owner.IsAlliedWith(viewer))
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
// TODO: Change this to be per-player? A cloak detector revealing to everyone is dumb
|
var centerPosition = self.CenterPosition;
|
||||||
return self.World.ActorsWithTrait<DetectCloaked>().Any(a =>
|
return self.World.ActorsWithTrait<DetectCloaked>().Any(a => a.Actor.Owner.IsAlliedWith(viewer) &&
|
||||||
a.Actor.Owner.Stances[self.Owner] != Stance.Ally &&
|
(centerPosition - a.Actor.CenterPosition).Length < WRange.FromCells(a.Actor.Info.Traits.Get<DetectCloakedInfo>().Range).Range);
|
||||||
(self.Location - a.Actor.Location).Length < a.Actor.Info.Traits.Get<DetectCloakedInfo>().Range);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Color RadarColorOverride(Actor self)
|
public Color RadarColorOverride(Actor self)
|
||||||
@@ -113,5 +120,12 @@ namespace OpenRA.Mods.RA
|
|||||||
c = Color.FromArgb(128, c);
|
c = Color.FromArgb(128, c);
|
||||||
return c;
|
return c;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool AcceptsCloakCrate { get { return info.RequiresCrate && crateDisabled; } }
|
||||||
|
|
||||||
|
public void ReceivedCloakCrate(Actor self)
|
||||||
|
{
|
||||||
|
crateDisabled = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,52 +16,27 @@ namespace OpenRA.Mods.RA.Crates
|
|||||||
{
|
{
|
||||||
public class CloakCrateActionInfo : CrateActionInfo
|
public class CloakCrateActionInfo : CrateActionInfo
|
||||||
{
|
{
|
||||||
public readonly int InitialDelay = 10;
|
|
||||||
public readonly int CloakDelay = 30;
|
|
||||||
public readonly string CloakSound = "subshow1.aud";
|
|
||||||
public readonly string UncloakSound = "subshow1.aud";
|
|
||||||
|
|
||||||
public override object Create(ActorInitializer init) { return new CloakCrateAction(init.self, this); }
|
public override object Create(ActorInitializer init) { return new CloakCrateAction(init.self, this); }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class CloakCrateAction : CrateAction
|
public class CloakCrateAction : CrateAction
|
||||||
{
|
{
|
||||||
CloakCrateActionInfo Info;
|
|
||||||
public CloakCrateAction(Actor self, CloakCrateActionInfo info)
|
public CloakCrateAction(Actor self, CloakCrateActionInfo info)
|
||||||
: base(self, info) { Info = info; }
|
: base(self, info) { }
|
||||||
|
|
||||||
public override int GetSelectionShares(Actor collector)
|
public override int GetSelectionShares(Actor collector)
|
||||||
{
|
{
|
||||||
return collector.HasTrait<AcceptsCloakCrate>() && !collector.HasTrait<Cloak>()
|
var cloak = collector.TraitOrDefault<Cloak>();
|
||||||
? base.GetSelectionShares(collector) : 0;
|
if (cloak == null || !cloak.AcceptsCloakCrate)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
return base.GetSelectionShares(collector);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Activate(Actor collector)
|
public override void Activate(Actor collector)
|
||||||
{
|
{
|
||||||
var cloakInfo = new CloakInfo()
|
collector.Trait<Cloak>().ReceivedCloakCrate(collector);
|
||||||
{
|
|
||||||
InitialDelay = Info.InitialDelay,
|
|
||||||
CloakDelay = Info.CloakDelay,
|
|
||||||
CloakSound = Info.CloakSound,
|
|
||||||
UncloakSound = Info.UncloakSound
|
|
||||||
};
|
|
||||||
var cloak = new Cloak(collector, cloakInfo);
|
|
||||||
|
|
||||||
collector.World.AddFrameEndTask(w =>
|
|
||||||
{
|
|
||||||
w.Remove(collector);
|
|
||||||
|
|
||||||
collector.AddTrait(cloak);
|
|
||||||
var t = collector.TraitOrDefault<TargetableUnit>();
|
|
||||||
if (t != null) t.ReceivedCloak(collector);
|
|
||||||
|
|
||||||
w.Add(collector);
|
|
||||||
});
|
|
||||||
|
|
||||||
base.Activate(collector);
|
base.Activate(collector);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class AcceptsCloakCrateInfo : TraitInfo<AcceptsCloakCrate> {}
|
|
||||||
public class AcceptsCloakCrate {}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ namespace OpenRA.Mods.RA.Effects
|
|||||||
Lazy<HiddenUnderFog> huf;
|
Lazy<HiddenUnderFog> huf;
|
||||||
Lazy<FrozenUnderFog> fuf;
|
Lazy<FrozenUnderFog> fuf;
|
||||||
Lazy<Spy> spy;
|
Lazy<Spy> spy;
|
||||||
|
Lazy<Cloak> cloak;
|
||||||
Cache<Player, GpsWatcher> watcher;
|
Cache<Player, GpsWatcher> watcher;
|
||||||
Cache<Player, FrozenActorLayer> frozen;
|
Cache<Player, FrozenActorLayer> frozen;
|
||||||
|
|
||||||
@@ -53,15 +54,15 @@ namespace OpenRA.Mods.RA.Effects
|
|||||||
huf = Lazy.New(() => self.TraitOrDefault<HiddenUnderFog>());
|
huf = Lazy.New(() => self.TraitOrDefault<HiddenUnderFog>());
|
||||||
fuf = Lazy.New(() => self.TraitOrDefault<FrozenUnderFog>());
|
fuf = Lazy.New(() => self.TraitOrDefault<FrozenUnderFog>());
|
||||||
spy = Lazy.New(() => self.TraitOrDefault<Spy>());
|
spy = Lazy.New(() => self.TraitOrDefault<Spy>());
|
||||||
|
cloak = Lazy.New(() => self.TraitOrDefault<Cloak>());
|
||||||
|
|
||||||
watcher = new Cache<Player, GpsWatcher>(p => p.PlayerActor.Trait<GpsWatcher>());
|
watcher = new Cache<Player, GpsWatcher>(p => p.PlayerActor.Trait<GpsWatcher>());
|
||||||
frozen = new Cache<Player, FrozenActorLayer>(p => p.PlayerActor.Trait<FrozenActorLayer>());
|
frozen = new Cache<Player, FrozenActorLayer>(p => p.PlayerActor.Trait<FrozenActorLayer>());
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ShouldShowIndicator()
|
bool ShouldShowIndicator()
|
||||||
{
|
{
|
||||||
// Can be granted at runtime via a crate, so can't cache
|
if (cloak.Value != null && cloak.Value.Cloaked)
|
||||||
var cloak = self.TraitOrDefault<Cloak>();
|
|
||||||
if (cloak != null && cloak.Cloaked)
|
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (spy.Value != null && spy.Value.Disguised)
|
if (spy.Value != null && spy.Value.Disguised)
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ namespace OpenRA.Mods.RA.Effects
|
|||||||
public void Tick(World world)
|
public void Tick(World world)
|
||||||
{
|
{
|
||||||
// Beam tracks target
|
// Beam tracks target
|
||||||
if (args.guidedTarget.IsValid)
|
if (args.guidedTarget.IsValidFor(args.sourceActor))
|
||||||
target = args.guidedTarget.CenterPosition;
|
target = args.guidedTarget.CenterPosition;
|
||||||
|
|
||||||
if (!doneDamage)
|
if (!doneDamage)
|
||||||
|
|||||||
@@ -120,7 +120,7 @@ namespace OpenRA.Mods.RA.Effects
|
|||||||
anim.Tick();
|
anim.Tick();
|
||||||
|
|
||||||
// Missile tracks target
|
// Missile tracks target
|
||||||
if (args.guidedTarget.IsValid)
|
if (args.guidedTarget.IsValidFor(args.sourceActor))
|
||||||
target = args.guidedTarget.CenterPosition;
|
target = args.guidedTarget.CenterPosition;
|
||||||
|
|
||||||
var dist = target + offset - pos;
|
var dist = target + offset - pos;
|
||||||
@@ -133,7 +133,7 @@ namespace OpenRA.Mods.RA.Effects
|
|||||||
desiredFacing = facing + world.SharedRandom.Next(-20, 21);
|
desiredFacing = facing + world.SharedRandom.Next(-20, 21);
|
||||||
desiredAltitude = world.SharedRandom.Next(-43, 86);
|
desiredAltitude = world.SharedRandom.Next(-43, 86);
|
||||||
}
|
}
|
||||||
else if (!args.guidedTarget.IsValid)
|
else if (!args.guidedTarget.IsValidFor(args.sourceActor))
|
||||||
desiredFacing = facing;
|
desiredFacing = facing;
|
||||||
|
|
||||||
facing = Traits.Util.TickFacing(facing, desiredFacing, info.ROT);
|
facing = Traits.Util.TickFacing(facing, desiredFacing, info.ROT);
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ namespace OpenRA.Mods.RA.Effects
|
|||||||
|
|
||||||
if (!doneDamage)
|
if (!doneDamage)
|
||||||
{
|
{
|
||||||
var pos = Args.guidedTarget.IsValid ? Args.guidedTarget.CenterPosition : Args.passiveTarget;
|
var pos = Args.guidedTarget.IsValidFor(Args.sourceActor) ? Args.guidedTarget.CenterPosition : Args.passiveTarget;
|
||||||
Combat.DoImpacts(pos, Args.sourceActor, Args.weapon, Args.firepowerModifier);
|
Combat.DoImpacts(pos, Args.sourceActor, Args.weapon, Args.firepowerModifier);
|
||||||
doneDamage = true;
|
doneDamage = true;
|
||||||
}
|
}
|
||||||
@@ -58,7 +58,7 @@ namespace OpenRA.Mods.RA.Effects
|
|||||||
{
|
{
|
||||||
if (!initialized)
|
if (!initialized)
|
||||||
{
|
{
|
||||||
var pos = Args.guidedTarget.IsValid ? Args.guidedTarget.CenterPosition : Args.passiveTarget;
|
var pos = Args.guidedTarget.IsValidFor(Args.sourceActor) ? Args.guidedTarget.CenterPosition : Args.passiveTarget;
|
||||||
zap = new TeslaZapRenderable(Args.source, 0, pos - Args.source, Info.Image, Info.BrightZaps, Info.DimZaps);
|
zap = new TeslaZapRenderable(Args.source, 0, pos - Args.source, Info.Image, Info.BrightZaps, Info.DimZaps);
|
||||||
}
|
}
|
||||||
yield return zap;
|
yield return zap;
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ namespace OpenRA.Mods.RA
|
|||||||
|
|
||||||
var range = WRange.FromCells(target.Actor.Info.Traits.Get<GuardableInfo>().Range);
|
var range = WRange.FromCells(target.Actor.Info.Traits.Get<GuardableInfo>().Range);
|
||||||
self.QueueActivity(false, new AttackMove.AttackMoveActivity(self,
|
self.QueueActivity(false, new AttackMove.AttackMoveActivity(self,
|
||||||
new Follow(target, range)));
|
new Follow(self, target, range)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -496,7 +496,7 @@ namespace OpenRA.Mods.RA.Move
|
|||||||
|
|
||||||
public bool CanTarget(Actor self, Target target, List<Actor> othersAtTarget, TargetModifiers modifiers, ref string cursor)
|
public bool CanTarget(Actor self, Target target, List<Actor> othersAtTarget, TargetModifiers modifiers, ref string cursor)
|
||||||
{
|
{
|
||||||
if (!target.IsValid)
|
if (!target.IsValidFor(self))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
var location = target.CenterPosition.ToCPos();
|
var location = target.CenterPosition.ToCPos();
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ namespace OpenRA.Mods.RA.Move
|
|||||||
{
|
{
|
||||||
this.getPath = (self, mobile) =>
|
this.getPath = (self, mobile) =>
|
||||||
{
|
{
|
||||||
if (!target.IsValid)
|
if (!target.IsValidFor(self))
|
||||||
return NoPath;
|
return NoPath;
|
||||||
|
|
||||||
return self.World.WorldActor.Trait<PathFinder>().FindUnitPathToRange(
|
return self.World.WorldActor.Trait<PathFinder>().FindUnitPathToRange(
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ namespace OpenRA.Mods.RA
|
|||||||
if (reservedFor == null)
|
if (reservedFor == null)
|
||||||
return; /* nothing to do */
|
return; /* nothing to do */
|
||||||
|
|
||||||
if (!Target.FromActor( reservedFor ).IsValid)
|
if (!Target.FromActor(reservedFor).IsValidFor(self))
|
||||||
reservedFor = null; /* not likely to arrive now. */
|
reservedFor = null; /* not likely to arrive now. */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -30,24 +30,15 @@ namespace OpenRA.Mods.RA
|
|||||||
public TargetableUnit(Actor self, TargetableUnitInfo info)
|
public TargetableUnit(Actor self, TargetableUnitInfo info)
|
||||||
{
|
{
|
||||||
this.info = info;
|
this.info = info;
|
||||||
ReceivedCloak(self);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Arbitrary units can receive cloak via a crate during gameplay
|
|
||||||
public void ReceivedCloak(Actor self)
|
|
||||||
{
|
|
||||||
cloak = self.TraitOrDefault<Cloak>();
|
cloak = self.TraitOrDefault<Cloak>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual bool TargetableBy(Actor self, Actor byActor)
|
public virtual bool TargetableBy(Actor self, Actor viewer)
|
||||||
{
|
{
|
||||||
if (cloak == null || !cloak.Cloaked)
|
if (cloak == null || !cloak.Cloaked)
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
if (self.Owner.IsAlliedWith(byActor.Owner))
|
return cloak.IsVisible(self, viewer.Owner);
|
||||||
return true;
|
|
||||||
|
|
||||||
return self.World.ActorsWithTrait<DetectCloaked>().Any(a => (self.Location - a.Actor.Location).Length < a.Actor.Info.Traits.Get<DetectCloakedInfo>().Range);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual string[] TargetTypes { get { return info.TargetTypes; } }
|
public virtual string[] TargetTypes { get { return info.TargetTypes; } }
|
||||||
|
|||||||
@@ -28,13 +28,18 @@
|
|||||||
DrawLineToTarget:
|
DrawLineToTarget:
|
||||||
ActorLostNotification:
|
ActorLostNotification:
|
||||||
AttackMove:
|
AttackMove:
|
||||||
AcceptsCloakCrate:
|
|
||||||
WithSmoke:
|
WithSmoke:
|
||||||
DebugMuzzlePositions:
|
DebugMuzzlePositions:
|
||||||
Guard:
|
Guard:
|
||||||
Guardable:
|
Guardable:
|
||||||
BodyOrientation:
|
BodyOrientation:
|
||||||
UpdatesPlayerStatistics:
|
UpdatesPlayerStatistics:
|
||||||
|
Cloak:
|
||||||
|
RequiresCrate: true
|
||||||
|
InitialDelay: 15
|
||||||
|
CloakDelay: 90
|
||||||
|
CloakSound: trans1.aud
|
||||||
|
UncloakSound: trans1.aud
|
||||||
|
|
||||||
^Tank:
|
^Tank:
|
||||||
AppearsOnRadar:
|
AppearsOnRadar:
|
||||||
@@ -66,7 +71,6 @@
|
|||||||
DrawLineToTarget:
|
DrawLineToTarget:
|
||||||
ActorLostNotification:
|
ActorLostNotification:
|
||||||
AttackMove:
|
AttackMove:
|
||||||
AcceptsCloakCrate:
|
|
||||||
WithSmoke:
|
WithSmoke:
|
||||||
Explodes:
|
Explodes:
|
||||||
Weapon: UnitExplodeSmall
|
Weapon: UnitExplodeSmall
|
||||||
@@ -76,6 +80,12 @@
|
|||||||
Guardable:
|
Guardable:
|
||||||
BodyOrientation:
|
BodyOrientation:
|
||||||
UpdatesPlayerStatistics:
|
UpdatesPlayerStatistics:
|
||||||
|
Cloak:
|
||||||
|
RequiresCrate: true
|
||||||
|
InitialDelay: 15
|
||||||
|
CloakDelay: 90
|
||||||
|
CloakSound: trans1.aud
|
||||||
|
UncloakSound: trans1.aud
|
||||||
|
|
||||||
^Helicopter:
|
^Helicopter:
|
||||||
AppearsOnRadar:
|
AppearsOnRadar:
|
||||||
|
|||||||
@@ -393,10 +393,6 @@ CRATE:
|
|||||||
SelectionShares: 5
|
SelectionShares: 5
|
||||||
CloakCrateAction:
|
CloakCrateAction:
|
||||||
SelectionShares: 5
|
SelectionShares: 5
|
||||||
InitialDelay: 15
|
|
||||||
CloakDelay: 90
|
|
||||||
CloakSound: trans1.aud
|
|
||||||
UncloakSound: trans1.aud
|
|
||||||
Effect: cloak
|
Effect: cloak
|
||||||
GiveMcvCrateAction:
|
GiveMcvCrateAction:
|
||||||
SelectionShares: 0
|
SelectionShares: 0
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ MCV:
|
|||||||
LeavesHusk:
|
LeavesHusk:
|
||||||
HuskActor: MCV.Husk
|
HuskActor: MCV.Husk
|
||||||
-GainsExperience:
|
-GainsExperience:
|
||||||
-AcceptsCloakCrate:
|
-Cloak:
|
||||||
Explodes:
|
Explodes:
|
||||||
Weapon: UnitExplodeSmall
|
Weapon: UnitExplodeSmall
|
||||||
EmptyWeapon: UnitExplodeSmall
|
EmptyWeapon: UnitExplodeSmall
|
||||||
@@ -519,6 +519,7 @@ STNK:
|
|||||||
RevealsShroud:
|
RevealsShroud:
|
||||||
Range: 7
|
Range: 7
|
||||||
Cloak:
|
Cloak:
|
||||||
|
RequiresCrate: false
|
||||||
InitialDelay: 90
|
InitialDelay: 90
|
||||||
CloakDelay: 90
|
CloakDelay: 90
|
||||||
CloakSound: trans1.aud
|
CloakSound: trans1.aud
|
||||||
|
|||||||
@@ -554,13 +554,6 @@ CRATE:
|
|||||||
SelectionShares: 0
|
SelectionShares: 0
|
||||||
NoBaseSelectionShares: 9001
|
NoBaseSelectionShares: 9001
|
||||||
Unit: mcvo
|
Unit: mcvo
|
||||||
CloakCrateAction:
|
|
||||||
SelectionShares: 15
|
|
||||||
InitialDelay: 15
|
|
||||||
CloakDelay: 90
|
|
||||||
CloakSound: STEALTH1.WAV
|
|
||||||
UncloakSound: STEALTH2.WAV
|
|
||||||
Effect: cloak
|
|
||||||
RenderSimple:
|
RenderSimple:
|
||||||
ProximityCaptor:
|
ProximityCaptor:
|
||||||
Types:Crate
|
Types:Crate
|
||||||
|
|||||||
Reference in New Issue
Block a user