Merge Mods.RA into Mods.Cnc
This commit is contained in:
60
OpenRA.Mods.Cnc/Activities/Infiltrate.cs
Normal file
60
OpenRA.Mods.Cnc/Activities/Infiltrate.cs
Normal file
@@ -0,0 +1,60 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2017 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, either version 3 of
|
||||
* the License, or (at your option) any later version. For more
|
||||
* information, see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using OpenRA.Mods.Common.Activities;
|
||||
using OpenRA.Mods.Common.Traits;
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.Mods.Cnc.Activities
|
||||
{
|
||||
class Infiltrate : Enter
|
||||
{
|
||||
readonly Actor target;
|
||||
readonly Stance validStances;
|
||||
readonly Cloak cloak;
|
||||
readonly string notification;
|
||||
readonly int experience;
|
||||
|
||||
public Infiltrate(Actor self, Actor target, EnterBehaviour enterBehaviour, Stance validStances, string notification, int experience)
|
||||
: base(self, target, enterBehaviour)
|
||||
{
|
||||
this.target = target;
|
||||
this.validStances = validStances;
|
||||
this.notification = notification;
|
||||
this.experience = experience;
|
||||
cloak = self.TraitOrDefault<Cloak>();
|
||||
}
|
||||
|
||||
protected override void OnInside(Actor self)
|
||||
{
|
||||
if (target.IsDead)
|
||||
return;
|
||||
|
||||
var stance = self.Owner.Stances[target.Owner];
|
||||
if (!validStances.HasStance(stance))
|
||||
return;
|
||||
|
||||
if (cloak != null && cloak.Info.UncloakOn.HasFlag(UncloakType.Infiltrate))
|
||||
cloak.Uncloak();
|
||||
|
||||
foreach (var t in target.TraitsImplementing<INotifyInfiltrated>())
|
||||
t.Infiltrated(target, self);
|
||||
|
||||
var exp = self.Owner.PlayerActor.TraitOrDefault<PlayerExperience>();
|
||||
if (exp != null)
|
||||
exp.GiveExperience(experience);
|
||||
|
||||
if (!string.IsNullOrEmpty(notification))
|
||||
Game.Sound.PlayNotification(self.World.Map.Rules, self.Owner, "Speech",
|
||||
notification, self.Owner.Faction.InternalName);
|
||||
}
|
||||
}
|
||||
}
|
||||
109
OpenRA.Mods.Cnc/Activities/LayMines.cs
Normal file
109
OpenRA.Mods.Cnc/Activities/LayMines.cs
Normal file
@@ -0,0 +1,109 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2017 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, either version 3 of
|
||||
* the License, or (at your option) any later version. For more
|
||||
* information, see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using OpenRA.Activities;
|
||||
using OpenRA.Mods.Cnc.Traits;
|
||||
using OpenRA.Mods.Common.Activities;
|
||||
using OpenRA.Mods.Common.Traits;
|
||||
using OpenRA.Primitives;
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.Mods.Cnc.Activities
|
||||
{
|
||||
// Assumes you have Minelayer on that unit
|
||||
public class LayMines : Activity
|
||||
{
|
||||
readonly Minelayer minelayer;
|
||||
readonly MinelayerInfo info;
|
||||
readonly AmmoPool[] ammoPools;
|
||||
readonly IMove movement;
|
||||
readonly HashSet<string> rearmBuildings;
|
||||
|
||||
public LayMines(Actor self)
|
||||
{
|
||||
minelayer = self.TraitOrDefault<Minelayer>();
|
||||
info = self.Info.TraitInfo<MinelayerInfo>();
|
||||
ammoPools = self.TraitsImplementing<AmmoPool>().ToArray();
|
||||
movement = self.Trait<IMove>();
|
||||
rearmBuildings = info.RearmBuildings;
|
||||
}
|
||||
|
||||
public override Activity Tick(Actor self)
|
||||
{
|
||||
if (IsCanceled)
|
||||
return NextActivity;
|
||||
|
||||
if (ammoPools != null && ammoPools.Any(p => p.Info.Name == info.AmmoPoolName && !p.HasAmmo()))
|
||||
{
|
||||
// Rearm (and possibly repair) at rearm building, then back out here to refill the minefield some more
|
||||
var rearmTarget = self.World.Actors.Where(a => self.Owner.Stances[a.Owner] == Stance.Ally
|
||||
&& rearmBuildings.Contains(a.Info.Name))
|
||||
.ClosestTo(self);
|
||||
|
||||
if (rearmTarget == null)
|
||||
return new Wait(20);
|
||||
|
||||
return ActivityUtils.SequenceActivities(
|
||||
new MoveAdjacentTo(self, Target.FromActor(rearmTarget)),
|
||||
movement.MoveTo(self.World.Map.CellContaining(rearmTarget.CenterPosition), rearmTarget),
|
||||
new Rearm(self),
|
||||
new Repair(self, rearmTarget),
|
||||
this);
|
||||
}
|
||||
|
||||
if (minelayer.Minefield.Contains(self.Location) && ShouldLayMine(self, self.Location))
|
||||
{
|
||||
LayMine(self);
|
||||
return ActivityUtils.SequenceActivities(new Wait(20), this); // A little wait after placing each mine, for show
|
||||
}
|
||||
|
||||
if (minelayer.Minefield.Length > 0)
|
||||
{
|
||||
// Don't get stuck forever here
|
||||
for (var n = 0; n < 20; n++)
|
||||
{
|
||||
var p = minelayer.Minefield.Random(self.World.SharedRandom);
|
||||
if (ShouldLayMine(self, p))
|
||||
return ActivityUtils.SequenceActivities(movement.MoveTo(p, 0), this);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Return somewhere likely to be safe (near rearm building) so we're not sitting out in the minefield.
|
||||
return new Wait(20); // nothing to do here
|
||||
}
|
||||
|
||||
static bool ShouldLayMine(Actor self, CPos p)
|
||||
{
|
||||
// If there is no unit (other than me) here, we want to place a mine here
|
||||
return self.World.ActorMap.GetActorsAt(p).All(a => a == self);
|
||||
}
|
||||
|
||||
void LayMine(Actor self)
|
||||
{
|
||||
if (ammoPools != null)
|
||||
{
|
||||
var pool = ammoPools.FirstOrDefault(x => x.Info.Name == info.AmmoPoolName);
|
||||
if (pool == null)
|
||||
return;
|
||||
pool.TakeAmmo();
|
||||
}
|
||||
|
||||
self.World.AddFrameEndTask(
|
||||
w => w.CreateActor(info.Mine, new TypeDictionary
|
||||
{
|
||||
new LocationInit(self.Location),
|
||||
new OwnerInit(self.Owner),
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
78
OpenRA.Mods.Cnc/Activities/Leap.cs
Normal file
78
OpenRA.Mods.Cnc/Activities/Leap.cs
Normal file
@@ -0,0 +1,78 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2017 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, either version 3 of
|
||||
* the License, or (at your option) any later version. For more
|
||||
* information, see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using OpenRA.Activities;
|
||||
using OpenRA.GameRules;
|
||||
using OpenRA.Mods.Common.Traits;
|
||||
using OpenRA.Mods.Common.Traits.Render;
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.Mods.Cnc.Activities
|
||||
{
|
||||
class Leap : Activity
|
||||
{
|
||||
readonly Mobile mobile;
|
||||
readonly WeaponInfo weapon;
|
||||
readonly int length;
|
||||
|
||||
WPos from;
|
||||
WPos to;
|
||||
int ticks;
|
||||
WAngle angle;
|
||||
|
||||
public Leap(Actor self, Actor target, Armament a, WDist speed, WAngle angle)
|
||||
{
|
||||
var targetMobile = target.TraitOrDefault<Mobile>();
|
||||
if (targetMobile == null)
|
||||
throw new InvalidOperationException("Leap requires a target actor with the Mobile trait");
|
||||
|
||||
this.weapon = a.Weapon;
|
||||
this.angle = angle;
|
||||
mobile = self.Trait<Mobile>();
|
||||
mobile.SetLocation(mobile.FromCell, mobile.FromSubCell, targetMobile.FromCell, targetMobile.FromSubCell);
|
||||
mobile.IsMoving = true;
|
||||
|
||||
from = self.CenterPosition;
|
||||
to = self.World.Map.CenterOfSubCell(targetMobile.FromCell, targetMobile.FromSubCell);
|
||||
length = Math.Max((to - from).Length / speed.Length, 1);
|
||||
|
||||
// HACK: why isn't this using the interface?
|
||||
self.Trait<WithInfantryBody>().Attacking(self, Target.FromActor(target), a);
|
||||
|
||||
if (weapon.Report != null && weapon.Report.Any())
|
||||
Game.Sound.Play(SoundType.World, weapon.Report.Random(self.World.SharedRandom), self.CenterPosition);
|
||||
}
|
||||
|
||||
public override Activity Tick(Actor self)
|
||||
{
|
||||
if (ticks == 0 && IsCanceled)
|
||||
return NextActivity;
|
||||
|
||||
mobile.SetVisualPosition(self, WPos.LerpQuadratic(from, to, angle, ++ticks, length));
|
||||
if (ticks >= length)
|
||||
{
|
||||
mobile.SetLocation(mobile.ToCell, mobile.ToSubCell, mobile.ToCell, mobile.ToSubCell);
|
||||
mobile.FinishedMoving(self);
|
||||
mobile.IsMoving = false;
|
||||
|
||||
self.World.ActorMap.GetActorsAt(mobile.ToCell, mobile.ToSubCell)
|
||||
.Except(new[] { self }).Where(t => weapon.IsValidAgainst(t, self))
|
||||
.Do(t => t.Kill(self));
|
||||
|
||||
return NextActivity;
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
||||
130
OpenRA.Mods.Cnc/Activities/Teleport.cs
Normal file
130
OpenRA.Mods.Cnc/Activities/Teleport.cs
Normal file
@@ -0,0 +1,130 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2017 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, either version 3 of
|
||||
* the License, or (at your option) any later version. For more
|
||||
* information, see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using OpenRA.Activities;
|
||||
using OpenRA.Mods.Cnc.Traits;
|
||||
using OpenRA.Mods.Common.Traits;
|
||||
using OpenRA.Mods.Common.Traits.Render;
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.Mods.Cnc.Activities
|
||||
{
|
||||
public interface IPreventsTeleport { bool PreventsTeleport(Actor self); }
|
||||
|
||||
public class Teleport : Activity
|
||||
{
|
||||
readonly Actor teleporter;
|
||||
readonly int? maximumDistance;
|
||||
CPos destination;
|
||||
bool killCargo;
|
||||
bool screenFlash;
|
||||
string sound;
|
||||
|
||||
public Teleport(Actor teleporter, CPos destination, int? maximumDistance, bool killCargo, bool screenFlash, string sound)
|
||||
{
|
||||
var max = teleporter.World.Map.Grid.MaximumTileSearchRange;
|
||||
if (maximumDistance > max)
|
||||
throw new InvalidOperationException("Teleport distance cannot exceed the value of MaximumTileSearchRange ({0}).".F(max));
|
||||
|
||||
this.teleporter = teleporter;
|
||||
this.destination = destination;
|
||||
this.maximumDistance = maximumDistance;
|
||||
this.killCargo = killCargo;
|
||||
this.screenFlash = screenFlash;
|
||||
this.sound = sound;
|
||||
}
|
||||
|
||||
public override Activity Tick(Actor self)
|
||||
{
|
||||
var pc = self.TraitOrDefault<PortableChrono>();
|
||||
if (teleporter == self && pc != null && !pc.CanTeleport)
|
||||
return NextActivity;
|
||||
|
||||
foreach (var condition in self.TraitsImplementing<IPreventsTeleport>())
|
||||
if (condition.PreventsTeleport(self))
|
||||
return NextActivity;
|
||||
|
||||
var bestCell = ChooseBestDestinationCell(self, destination);
|
||||
if (bestCell == null)
|
||||
return NextActivity;
|
||||
|
||||
destination = bestCell.Value;
|
||||
|
||||
Game.Sound.Play(SoundType.World, sound, self.CenterPosition);
|
||||
Game.Sound.Play(SoundType.World, sound, self.World.Map.CenterOfCell(destination));
|
||||
|
||||
self.Trait<IPositionable>().SetPosition(self, destination);
|
||||
self.Generation++;
|
||||
|
||||
if (killCargo)
|
||||
{
|
||||
var cargo = self.TraitOrDefault<Cargo>();
|
||||
if (cargo != null && teleporter != null)
|
||||
{
|
||||
while (!cargo.IsEmpty(self))
|
||||
{
|
||||
var a = cargo.Unload(self);
|
||||
|
||||
// Kill all the units that are unloaded into the void
|
||||
// Kill() handles kill and death statistics
|
||||
a.Kill(teleporter);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Consume teleport charges if this wasn't triggered via chronosphere
|
||||
if (teleporter == self && pc != null)
|
||||
pc.ResetChargeTime();
|
||||
|
||||
// Trigger screen desaturate effect
|
||||
if (screenFlash)
|
||||
foreach (var a in self.World.ActorsWithTrait<ChronoshiftPaletteEffect>())
|
||||
a.Trait.Enable();
|
||||
|
||||
if (teleporter != null && self != teleporter && !teleporter.Disposed)
|
||||
{
|
||||
var building = teleporter.TraitOrDefault<WithSpriteBody>();
|
||||
if (building != null && building.DefaultAnimation.HasSequence("active"))
|
||||
building.PlayCustomAnimation(teleporter, "active");
|
||||
}
|
||||
|
||||
return NextActivity;
|
||||
}
|
||||
|
||||
CPos? ChooseBestDestinationCell(Actor self, CPos destination)
|
||||
{
|
||||
if (teleporter == null)
|
||||
return null;
|
||||
|
||||
var restrictTo = maximumDistance == null ? null : self.World.Map.FindTilesInCircle(self.Location, maximumDistance.Value);
|
||||
|
||||
if (maximumDistance != null)
|
||||
destination = restrictTo.MinBy(x => (x - destination).LengthSquared);
|
||||
|
||||
var pos = self.Trait<IPositionable>();
|
||||
if (pos.CanEnterCell(destination) && teleporter.Owner.Shroud.IsExplored(destination))
|
||||
return destination;
|
||||
|
||||
var max = maximumDistance != null ? maximumDistance.Value : teleporter.World.Map.Grid.MaximumTileSearchRange;
|
||||
foreach (var tile in self.World.Map.FindTilesInCircle(destination, max))
|
||||
{
|
||||
if (teleporter.Owner.Shroud.IsExplored(tile)
|
||||
&& (restrictTo == null || (restrictTo != null && restrictTo.Contains(tile)))
|
||||
&& pos.CanEnterCell(tile))
|
||||
return tile;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
149
OpenRA.Mods.Cnc/Effects/GpsDot.cs
Normal file
149
OpenRA.Mods.Cnc/Effects/GpsDot.cs
Normal file
@@ -0,0 +1,149 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2017 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, either version 3 of
|
||||
* the License, or (at your option) any later version. For more
|
||||
* information, see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using OpenRA.Effects;
|
||||
using OpenRA.Graphics;
|
||||
using OpenRA.Mods.Cnc.Traits;
|
||||
using OpenRA.Mods.Common.Traits;
|
||||
using OpenRA.Primitives;
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.Mods.Cnc.Effects
|
||||
{
|
||||
[Desc("Attach this to actors to render pictograms while hidden.")]
|
||||
class GpsDotInfo : ITraitInfo
|
||||
{
|
||||
[Desc("Sprite collection for symbols.")]
|
||||
public readonly string Image = "gpsdot";
|
||||
|
||||
[Desc("Sprite used for this actor.")]
|
||||
[SequenceReference("Image")] public readonly string String = "Infantry";
|
||||
|
||||
[PaletteReference(true)] public readonly string IndicatorPalettePrefix = "player";
|
||||
|
||||
public object Create(ActorInitializer init)
|
||||
{
|
||||
return new GpsDot(init.Self, this);
|
||||
}
|
||||
}
|
||||
|
||||
class GpsDot : IEffect, IEffectAboveShroud
|
||||
{
|
||||
readonly Actor self;
|
||||
readonly GpsDotInfo info;
|
||||
readonly Animation anim;
|
||||
|
||||
readonly PlayerDictionary<DotState> dotStates;
|
||||
readonly Lazy<HiddenUnderFog> huf;
|
||||
readonly Lazy<FrozenUnderFog> fuf;
|
||||
readonly Lazy<Disguise> disguise;
|
||||
readonly Lazy<Cloak> cloak;
|
||||
readonly Cache<Player, FrozenActorLayer> frozen;
|
||||
|
||||
class DotState
|
||||
{
|
||||
public readonly GpsWatcher Gps;
|
||||
public bool IsTargetable;
|
||||
public bool ShouldRender;
|
||||
public DotState(GpsWatcher gps)
|
||||
{
|
||||
Gps = gps;
|
||||
}
|
||||
}
|
||||
|
||||
public GpsDot(Actor self, GpsDotInfo info)
|
||||
{
|
||||
this.self = self;
|
||||
this.info = info;
|
||||
anim = new Animation(self.World, info.Image);
|
||||
anim.PlayRepeating(info.String);
|
||||
|
||||
self.World.AddFrameEndTask(w => w.Add(this));
|
||||
|
||||
huf = Exts.Lazy(() => self.TraitOrDefault<HiddenUnderFog>());
|
||||
fuf = Exts.Lazy(() => self.TraitOrDefault<FrozenUnderFog>());
|
||||
disguise = Exts.Lazy(() => self.TraitOrDefault<Disguise>());
|
||||
cloak = Exts.Lazy(() => self.TraitOrDefault<Cloak>());
|
||||
|
||||
frozen = new Cache<Player, FrozenActorLayer>(p => p.PlayerActor.Trait<FrozenActorLayer>());
|
||||
dotStates = new PlayerDictionary<DotState>(self.World, player => new DotState(player.PlayerActor.Trait<GpsWatcher>()));
|
||||
}
|
||||
|
||||
public bool IsDotVisible(Player toPlayer)
|
||||
{
|
||||
return dotStates[toPlayer].IsTargetable;
|
||||
}
|
||||
|
||||
bool IsTargetableBy(Player toPlayer, out bool shouldRenderIndicator)
|
||||
{
|
||||
shouldRenderIndicator = false;
|
||||
|
||||
if (cloak.Value != null && cloak.Value.Cloaked)
|
||||
return false;
|
||||
|
||||
if (disguise.Value != null && disguise.Value.Disguised)
|
||||
return false;
|
||||
|
||||
if (huf.Value != null && !huf.Value.IsVisible(self, toPlayer)
|
||||
&& toPlayer.Shroud.IsExplored(self.CenterPosition))
|
||||
{
|
||||
var f1 = FrozenActorForPlayer(toPlayer);
|
||||
shouldRenderIndicator = f1 == null || !f1.HasRenderables;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (fuf.Value == null)
|
||||
return false;
|
||||
|
||||
var f2 = FrozenActorForPlayer(toPlayer);
|
||||
if (f2 == null)
|
||||
return false;
|
||||
|
||||
shouldRenderIndicator = !f2.HasRenderables;
|
||||
|
||||
return f2.Visible && !f2.Shrouded && toPlayer.Shroud.IsExplored(self.CenterPosition);
|
||||
}
|
||||
|
||||
FrozenActor FrozenActorForPlayer(Player player)
|
||||
{
|
||||
return frozen[player].FromID(self.ActorID);
|
||||
}
|
||||
|
||||
void IEffect.Tick(World world)
|
||||
{
|
||||
if (self.Disposed)
|
||||
world.AddFrameEndTask(w => w.Remove(this));
|
||||
|
||||
for (var playerIndex = 0; playerIndex < dotStates.Count; playerIndex++)
|
||||
{
|
||||
var state = dotStates[playerIndex];
|
||||
var shouldRender = false;
|
||||
if (self.IsInWorld && !self.IsDead)
|
||||
state.IsTargetable = (state.Gps.Granted || state.Gps.GrantedAllies) && IsTargetableBy(world.Players[playerIndex], out shouldRender);
|
||||
|
||||
state.ShouldRender = state.IsTargetable && shouldRender;
|
||||
}
|
||||
}
|
||||
|
||||
IEnumerable<IRenderable> IEffect.Render(WorldRenderer wr) { return SpriteRenderable.None; }
|
||||
|
||||
IEnumerable<IRenderable> IEffectAboveShroud.RenderAboveShroud(WorldRenderer wr)
|
||||
{
|
||||
if (self.World.RenderPlayer == null || !dotStates[self.World.RenderPlayer].ShouldRender || self.Disposed)
|
||||
return SpriteRenderable.None;
|
||||
|
||||
var palette = wr.Palette(info.IndicatorPalettePrefix + self.Owner.InternalName);
|
||||
return anim.Render(self.CenterPosition, palette);
|
||||
}
|
||||
}
|
||||
}
|
||||
57
OpenRA.Mods.Cnc/Effects/GpsSatellite.cs
Normal file
57
OpenRA.Mods.Cnc/Effects/GpsSatellite.cs
Normal file
@@ -0,0 +1,57 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2017 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, either version 3 of
|
||||
* the License, or (at your option) any later version. For more
|
||||
* information, see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System.Collections.Generic;
|
||||
using OpenRA.Effects;
|
||||
using OpenRA.Graphics;
|
||||
using OpenRA.Mods.Cnc.Traits;
|
||||
|
||||
namespace OpenRA.Mods.Cnc.Effects
|
||||
{
|
||||
class GpsSatellite : IEffect
|
||||
{
|
||||
readonly Player launcher;
|
||||
readonly Animation anim;
|
||||
readonly string palette;
|
||||
readonly int revealDelay;
|
||||
WPos pos;
|
||||
int tick;
|
||||
|
||||
public GpsSatellite(World world, WPos pos, string image, string sequence, string palette, int revealDelay, Player launcher)
|
||||
{
|
||||
this.palette = palette;
|
||||
this.pos = pos;
|
||||
this.launcher = launcher;
|
||||
this.revealDelay = revealDelay;
|
||||
|
||||
anim = new Animation(world, image);
|
||||
anim.PlayRepeating(sequence);
|
||||
}
|
||||
|
||||
public void Tick(World world)
|
||||
{
|
||||
anim.Tick();
|
||||
pos += new WVec(0, 0, 427);
|
||||
|
||||
if (++tick > revealDelay)
|
||||
{
|
||||
var watcher = launcher.PlayerActor.Trait<GpsWatcher>();
|
||||
watcher.ReachedOrbit(launcher);
|
||||
world.AddFrameEndTask(w => w.Remove(this));
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<IRenderable> Render(WorldRenderer wr)
|
||||
{
|
||||
return anim.Render(pos, wr.Palette(palette));
|
||||
}
|
||||
}
|
||||
}
|
||||
56
OpenRA.Mods.Cnc/Effects/SatelliteLaunch.cs
Normal file
56
OpenRA.Mods.Cnc/Effects/SatelliteLaunch.cs
Normal file
@@ -0,0 +1,56 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2017 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, either version 3 of
|
||||
* the License, or (at your option) any later version. For more
|
||||
* information, see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System.Collections.Generic;
|
||||
using OpenRA.Effects;
|
||||
using OpenRA.Graphics;
|
||||
using OpenRA.Mods.Cnc.Traits;
|
||||
|
||||
namespace OpenRA.Mods.Cnc.Effects
|
||||
{
|
||||
class SatelliteLaunch : IEffect
|
||||
{
|
||||
readonly GpsPowerInfo info;
|
||||
readonly Actor launcher;
|
||||
readonly Animation doors;
|
||||
readonly WPos pos;
|
||||
int frame = 0;
|
||||
|
||||
public SatelliteLaunch(Actor launcher, GpsPowerInfo info)
|
||||
{
|
||||
this.info = info;
|
||||
this.launcher = launcher;
|
||||
|
||||
doors = new Animation(launcher.World, info.DoorImage);
|
||||
doors.PlayThen(info.DoorSequence,
|
||||
() => launcher.World.AddFrameEndTask(w => w.Remove(this)));
|
||||
|
||||
pos = launcher.CenterPosition;
|
||||
}
|
||||
|
||||
public void Tick(World world)
|
||||
{
|
||||
doors.Tick();
|
||||
|
||||
if (++frame == 19)
|
||||
{
|
||||
var palette = info.SatellitePaletteIsPlayerPalette ? info.SatellitePalette + launcher.Owner.InternalName : info.SatellitePalette;
|
||||
world.AddFrameEndTask(w => w.Add(new GpsSatellite(world, pos, info.SatelliteImage, info.SatelliteSequence, palette, info.RevealDelay * 25, launcher.Owner)));
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<IRenderable> Render(WorldRenderer wr)
|
||||
{
|
||||
var palette = info.DoorPaletteIsPlayerPalette ? info.DoorPalette + launcher.Owner.InternalName : info.DoorPalette;
|
||||
return doors.Render(pos, wr.Palette(palette));
|
||||
}
|
||||
}
|
||||
}
|
||||
163
OpenRA.Mods.Cnc/Graphics/TeslaZapRenderable.cs
Normal file
163
OpenRA.Mods.Cnc/Graphics/TeslaZapRenderable.cs
Normal file
@@ -0,0 +1,163 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2017 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, either version 3 of
|
||||
* the License, or (at your option) any later version. For more
|
||||
* information, see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
using OpenRA.Graphics;
|
||||
|
||||
namespace OpenRA.Mods.Cnc.Graphics
|
||||
{
|
||||
struct TeslaZapRenderable : IRenderable, IFinalizedRenderable
|
||||
{
|
||||
static int[][] steps = new[]
|
||||
{
|
||||
new int[] { 8, 8, 4, 4, 0 },
|
||||
new int[] { -8, -8, -4, -4, 0 },
|
||||
new int[] { 8, 0, 4, 4, 1 },
|
||||
new int[] { -8, 0, -4, 4, 1 },
|
||||
new int[] { 0, 8, 4, 4, 2 },
|
||||
new int[] { 0, -8, 4, -4, 2 },
|
||||
new int[] { -8, 8, -4, 4, 3 },
|
||||
new int[] { 8, -8, 4, -4, 3 }
|
||||
};
|
||||
|
||||
readonly WPos pos;
|
||||
readonly int zOffset;
|
||||
readonly WVec length;
|
||||
readonly string image;
|
||||
readonly string palette;
|
||||
readonly string dimSequence;
|
||||
readonly string brightSequence;
|
||||
readonly int brightZaps, dimZaps;
|
||||
|
||||
WPos cachedPos;
|
||||
WVec cachedLength;
|
||||
IEnumerable<IFinalizedRenderable> cache;
|
||||
|
||||
public TeslaZapRenderable(WPos pos, int zOffset, WVec length, string image, string brightSequence, int brightZaps, string dimSequence, int dimZaps, string palette)
|
||||
{
|
||||
this.pos = pos;
|
||||
this.zOffset = zOffset;
|
||||
this.length = length;
|
||||
this.image = image;
|
||||
this.palette = palette;
|
||||
this.brightZaps = brightZaps;
|
||||
this.dimZaps = dimZaps;
|
||||
this.dimSequence = dimSequence;
|
||||
this.brightSequence = brightSequence;
|
||||
|
||||
cachedPos = WPos.Zero;
|
||||
cachedLength = WVec.Zero;
|
||||
cache = new IFinalizedRenderable[] { };
|
||||
}
|
||||
|
||||
public WPos Pos { get { return pos; } }
|
||||
public PaletteReference Palette { get { return null; } }
|
||||
public int ZOffset { get { return zOffset; } }
|
||||
public bool IsDecoration { get { return true; } }
|
||||
|
||||
public IRenderable WithPalette(PaletteReference newPalette)
|
||||
{
|
||||
return new TeslaZapRenderable(pos, zOffset, length, image, brightSequence, brightZaps, dimSequence, dimZaps, palette);
|
||||
}
|
||||
|
||||
public IRenderable WithZOffset(int newOffset) { return new TeslaZapRenderable(pos, zOffset, length, image, brightSequence, brightZaps, dimSequence, dimZaps, palette); }
|
||||
public IRenderable OffsetBy(WVec vec) { return new TeslaZapRenderable(pos + vec, zOffset, length, image, brightSequence, brightZaps, dimSequence, dimZaps, palette); }
|
||||
public IRenderable AsDecoration() { return this; }
|
||||
|
||||
public IFinalizedRenderable PrepareRender(WorldRenderer wr) { return this; }
|
||||
public void RenderDebugGeometry(WorldRenderer wr) { }
|
||||
public void Render(WorldRenderer wr)
|
||||
{
|
||||
if (wr.World.FogObscures(pos) && wr.World.FogObscures(pos + length))
|
||||
return;
|
||||
|
||||
if (!cache.Any() || length != cachedLength || pos != cachedPos)
|
||||
cache = GenerateRenderables(wr);
|
||||
|
||||
cache.Do(c => c.Render(wr));
|
||||
}
|
||||
|
||||
public Rectangle ScreenBounds(WorldRenderer wr) { return Rectangle.Empty; }
|
||||
|
||||
public IEnumerable<IFinalizedRenderable> GenerateRenderables(WorldRenderer wr)
|
||||
{
|
||||
var bright = wr.World.Map.Rules.Sequences.GetSequence(image, brightSequence);
|
||||
var dim = wr.World.Map.Rules.Sequences.GetSequence(image, dimSequence);
|
||||
|
||||
var source = wr.ScreenPosition(pos);
|
||||
var target = wr.ScreenPosition(pos + length);
|
||||
|
||||
for (var n = 0; n < dimZaps; n++)
|
||||
foreach (var z in DrawZapWandering(wr, source, target, dim, palette))
|
||||
yield return z;
|
||||
for (var n = 0; n < brightZaps; n++)
|
||||
foreach (var z in DrawZapWandering(wr, source, target, bright, palette))
|
||||
yield return z;
|
||||
}
|
||||
|
||||
static IEnumerable<IFinalizedRenderable> DrawZapWandering(WorldRenderer wr, float2 from, float2 to, ISpriteSequence s, string pal)
|
||||
{
|
||||
var z = float2.Zero; /* hack */
|
||||
var dist = to - from;
|
||||
var norm = (1f / dist.Length) * new float2(-dist.Y, dist.X);
|
||||
|
||||
var renderables = new List<IFinalizedRenderable>();
|
||||
if (Game.CosmeticRandom.Next(2) != 0)
|
||||
{
|
||||
var p1 = from + (1 / 3f) * dist + WDist.FromPDF(Game.CosmeticRandom, 2).Length * dist.Length / 4096 * norm;
|
||||
var p2 = from + (2 / 3f) * dist + WDist.FromPDF(Game.CosmeticRandom, 2).Length * dist.Length / 4096 * norm;
|
||||
|
||||
renderables.AddRange(DrawZap(wr, from, p1, s, out p1, pal));
|
||||
renderables.AddRange(DrawZap(wr, p1, p2, s, out p2, pal));
|
||||
renderables.AddRange(DrawZap(wr, p2, to, s, out z, pal));
|
||||
}
|
||||
else
|
||||
{
|
||||
var p1 = from + (1 / 2f) * dist + WDist.FromPDF(Game.CosmeticRandom, 2).Length * dist.Length / 4096 * norm;
|
||||
|
||||
renderables.AddRange(DrawZap(wr, from, p1, s, out p1, pal));
|
||||
renderables.AddRange(DrawZap(wr, p1, to, s, out z, pal));
|
||||
}
|
||||
|
||||
return renderables;
|
||||
}
|
||||
|
||||
static IEnumerable<IFinalizedRenderable> DrawZap(WorldRenderer wr, float2 from, float2 to, ISpriteSequence s, out float2 p, string palette)
|
||||
{
|
||||
var dist = to - from;
|
||||
var q = new float2(-dist.Y, dist.X);
|
||||
var c = -float2.Dot(from, q);
|
||||
var rs = new List<IFinalizedRenderable>();
|
||||
var z = from;
|
||||
var pal = wr.Palette(palette);
|
||||
|
||||
while ((to - z).X > 5 || (to - z).X < -5 || (to - z).Y > 5 || (to - z).Y < -5)
|
||||
{
|
||||
var step = steps.Where(t => (to - (z + new float2(t[0], t[1]))).LengthSquared < (to - z).LengthSquared)
|
||||
.MinBy(t => Math.Abs(float2.Dot(z + new float2(t[0], t[1]), q) + c));
|
||||
|
||||
var pos = wr.ProjectedPosition((z + new float2(step[2], step[3])).ToInt2());
|
||||
rs.Add(new SpriteRenderable(s.GetSprite(step[4]), pos, WVec.Zero, 0, pal, 1f, true).PrepareRender(wr));
|
||||
|
||||
z += new float2(step[0], step[1]);
|
||||
if (rs.Count >= 1000)
|
||||
break;
|
||||
}
|
||||
|
||||
p = z;
|
||||
|
||||
return rs;
|
||||
}
|
||||
}
|
||||
}
|
||||
249
OpenRA.Mods.Cnc/ImportRedAlertLegacyMapCommand.cs
Normal file
249
OpenRA.Mods.Cnc/ImportRedAlertLegacyMapCommand.cs
Normal file
@@ -0,0 +1,249 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2017 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, either version 3 of
|
||||
* the License, or (at your option) any later version. For more
|
||||
* information, see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using OpenRA.Mods.Common.FileFormats;
|
||||
using OpenRA.Mods.Common.UtilityCommands;
|
||||
using OpenRA.Primitives;
|
||||
|
||||
namespace OpenRA.Mods.Cnc.UtilityCommands
|
||||
{
|
||||
class ImportRedAlertLegacyMapCommand : ImportLegacyMapCommand, IUtilityCommand
|
||||
{
|
||||
// TODO: 128x128 is probably not true for "mega maps" from the expansions.
|
||||
public ImportRedAlertLegacyMapCommand() : base(128) { }
|
||||
|
||||
string IUtilityCommand.Name { get { return "--import-ra-map"; } }
|
||||
bool IUtilityCommand.ValidateArguments(string[] args) { return ValidateArguments(args); }
|
||||
|
||||
[Desc("FILENAME", "Convert a legacy Red Alert INI/MPR map to the OpenRA format.")]
|
||||
void IUtilityCommand.Run(Utility utility, string[] args) { Run(utility, args); }
|
||||
|
||||
public override void ValidateMapFormat(int format)
|
||||
{
|
||||
if (format < 2)
|
||||
{
|
||||
Console.WriteLine("ERROR: Detected NewINIFormat {0}. Are you trying to import a Tiberian Dawn map?".F(format));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Mapping from RA95 overlay index to type string
|
||||
static string[] redAlertOverlayNames =
|
||||
{
|
||||
"sbag", "cycl", "brik", "fenc", "wood",
|
||||
"gold01", "gold02", "gold03", "gold04",
|
||||
"gem01", "gem02", "gem03", "gem04",
|
||||
"v12", "v13", "v14", "v15", "v16", "v17", "v18",
|
||||
"fpls", "wcrate", "scrate", "barb", "sbag",
|
||||
};
|
||||
|
||||
static Dictionary<string, Pair<byte, byte>> overlayResourceMapping = new Dictionary<string, Pair<byte, byte>>()
|
||||
{
|
||||
// RA ore & crystals
|
||||
{ "gold01", new Pair<byte, byte>(1, 0) },
|
||||
{ "gold02", new Pair<byte, byte>(1, 1) },
|
||||
{ "gold03", new Pair<byte, byte>(1, 2) },
|
||||
{ "gold04", new Pair<byte, byte>(1, 3) },
|
||||
{ "gem01", new Pair<byte, byte>(2, 0) },
|
||||
{ "gem02", new Pair<byte, byte>(2, 1) },
|
||||
{ "gem03", new Pair<byte, byte>(2, 2) },
|
||||
{ "gem04", new Pair<byte, byte>(2, 3) },
|
||||
};
|
||||
|
||||
void UnpackTileData(MemoryStream ms)
|
||||
{
|
||||
var types = new ushort[MapSize, MapSize];
|
||||
for (var j = 0; j < MapSize; j++)
|
||||
{
|
||||
for (var i = 0; i < MapSize; i++)
|
||||
{
|
||||
var tileID = ms.ReadUInt16();
|
||||
types[i, j] = tileID == 0 ? (ushort)255 : tileID; // RAED weirdness
|
||||
}
|
||||
}
|
||||
|
||||
for (var j = 0; j < MapSize; j++)
|
||||
for (var i = 0; i < MapSize; i++)
|
||||
Map.Tiles[new CPos(i, j)] = new TerrainTile(types[i, j], ms.ReadUInt8());
|
||||
}
|
||||
|
||||
static string[] overlayActors = new string[]
|
||||
{
|
||||
// Fences
|
||||
"sbag", "cycl", "brik", "fenc", "wood", "wood",
|
||||
|
||||
// Fields
|
||||
"v12", "v13", "v14", "v15", "v16", "v17", "v18"
|
||||
};
|
||||
|
||||
void UnpackOverlayData(MemoryStream ms)
|
||||
{
|
||||
for (var j = 0; j < MapSize; j++)
|
||||
{
|
||||
for (var i = 0; i < MapSize; i++)
|
||||
{
|
||||
var o = ms.ReadUInt8();
|
||||
var res = Pair.New((byte)0, (byte)0);
|
||||
|
||||
if (o != 255 && overlayResourceMapping.ContainsKey(redAlertOverlayNames[o]))
|
||||
res = overlayResourceMapping[redAlertOverlayNames[o]];
|
||||
|
||||
var cell = new CPos(i, j);
|
||||
Map.Resources[cell] = new ResourceTile(res.First, res.Second);
|
||||
|
||||
if (o != 255 && overlayActors.Contains(redAlertOverlayNames[o]))
|
||||
{
|
||||
var ar = new ActorReference(redAlertOverlayNames[o])
|
||||
{
|
||||
new LocationInit(cell),
|
||||
new OwnerInit("Neutral")
|
||||
};
|
||||
|
||||
var actorCount = Map.ActorDefinitions.Count;
|
||||
Map.ActorDefinitions.Add(new MiniYamlNode("Actor" + actorCount++, ar.Save()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override string ParseTreeActor(string input)
|
||||
{
|
||||
return input.ToLowerInvariant();
|
||||
}
|
||||
|
||||
public override CPos ParseActorLocation(string input, int loc)
|
||||
{
|
||||
var newLoc = new CPos(loc % MapSize, loc / MapSize);
|
||||
var vectorDown = new CVec(0, 1);
|
||||
|
||||
if (input == "tsla" || input == "agun" || input == "gap" || input == "apwr" || input == "iron")
|
||||
newLoc += vectorDown;
|
||||
|
||||
return newLoc;
|
||||
}
|
||||
|
||||
public override void LoadPlayer(IniFile file, string section)
|
||||
{
|
||||
string color;
|
||||
string faction;
|
||||
switch (section)
|
||||
{
|
||||
case "Spain":
|
||||
color = "gold";
|
||||
faction = "allies";
|
||||
break;
|
||||
case "England":
|
||||
color = "green";
|
||||
faction = "allies";
|
||||
break;
|
||||
case "Ukraine":
|
||||
color = "orange";
|
||||
faction = "soviet";
|
||||
break;
|
||||
case "Germany":
|
||||
color = "black";
|
||||
faction = "allies";
|
||||
break;
|
||||
case "France":
|
||||
color = "teal";
|
||||
faction = "allies";
|
||||
break;
|
||||
case "Turkey":
|
||||
color = "salmon";
|
||||
faction = "allies";
|
||||
break;
|
||||
case "Greece":
|
||||
case "GoodGuy":
|
||||
color = "blue";
|
||||
faction = "allies";
|
||||
break;
|
||||
case "USSR":
|
||||
case "BadGuy":
|
||||
color = "red";
|
||||
faction = "soviet";
|
||||
break;
|
||||
case "Special":
|
||||
case "Neutral":
|
||||
default:
|
||||
color = "neutral";
|
||||
faction = "allies";
|
||||
break;
|
||||
}
|
||||
|
||||
SetMapPlayers(section, faction, color, file, Players, MapPlayers);
|
||||
}
|
||||
|
||||
public static MemoryStream ReadPackedSection(IniSection mapPackSection)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
for (var i = 1;; i++)
|
||||
{
|
||||
var line = mapPackSection.GetValue(i.ToString(), null);
|
||||
if (line == null)
|
||||
break;
|
||||
|
||||
sb.Append(line.Trim());
|
||||
}
|
||||
|
||||
var data = Convert.FromBase64String(sb.ToString());
|
||||
var chunks = new List<byte[]>();
|
||||
var reader = new BinaryReader(new MemoryStream(data));
|
||||
|
||||
try
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
var length = reader.ReadUInt32() & 0xdfffffff;
|
||||
var dest = new byte[8192];
|
||||
var src = reader.ReadBytes((int)length);
|
||||
|
||||
LCWCompression.DecodeInto(src, dest);
|
||||
|
||||
chunks.Add(dest);
|
||||
}
|
||||
}
|
||||
catch (EndOfStreamException) { }
|
||||
|
||||
var ms = new MemoryStream();
|
||||
foreach (var chunk in chunks)
|
||||
ms.Write(chunk, 0, chunk.Length);
|
||||
|
||||
ms.Position = 0;
|
||||
|
||||
return ms;
|
||||
}
|
||||
|
||||
public override void ReadPacks(IniFile file, string filename)
|
||||
{
|
||||
UnpackTileData(ReadPackedSection(file.GetSection("MapPack")));
|
||||
UnpackOverlayData(ReadPackedSection(file.GetSection("OverlayPack")));
|
||||
}
|
||||
|
||||
public override void ReadActors(IniFile file)
|
||||
{
|
||||
base.ReadActors(file);
|
||||
LoadActors(file, "SHIPS", Players, MapSize, Map);
|
||||
}
|
||||
|
||||
public override void SaveWaypoint(int waypointNumber, ActorReference waypointReference)
|
||||
{
|
||||
var waypointName = "waypoint" + waypointNumber;
|
||||
if (waypointNumber == 98)
|
||||
waypointName = "DefaultCameraPosition";
|
||||
Map.ActorDefinitions.Add(new MiniYamlNode(waypointName, waypointReference.Save()));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -93,6 +93,45 @@
|
||||
<Compile Include="Traits\Attack\AttackTesla.cs" />
|
||||
<Compile Include="Traits\Render\WithTeslaChargeAnimation.cs" />
|
||||
<Compile Include="Traits\Render\WithTeslaChargeOverlay.cs" />
|
||||
<Compile Include="Activities\Infiltrate.cs" />
|
||||
<Compile Include="Activities\LayMines.cs" />
|
||||
<Compile Include="Activities\Leap.cs" />
|
||||
<Compile Include="Activities\Teleport.cs" />
|
||||
<Compile Include="Effects\GpsDot.cs" />
|
||||
<Compile Include="Effects\GpsSatellite.cs" />
|
||||
<Compile Include="Effects\SatelliteLaunch.cs" />
|
||||
<Compile Include="Projectiles\TeslaZap.cs" />
|
||||
<Compile Include="Graphics\TeslaZapRenderable.cs" />
|
||||
<Compile Include="Scripting\Properties\InfiltrateProperties.cs" />
|
||||
<Compile Include="Scripting\Properties\DisguiseProperties.cs" />
|
||||
<Compile Include="Traits\Attack\AttackLeap.cs" />
|
||||
<Compile Include="Traits\Buildings\ClonesProducedUnits.cs" />
|
||||
<Compile Include="Traits\Chronoshiftable.cs" />
|
||||
<Compile Include="Traits\Cloneable.cs" />
|
||||
<Compile Include="Traits\Disguise.cs" />
|
||||
<Compile Include="Traits\FrozenUnderFogUpdatedByGps.cs" />
|
||||
<Compile Include="Traits\HarvesterHuskModifier.cs" />
|
||||
<Compile Include="Traits\Infiltration\InfiltrateForCash.cs" />
|
||||
<Compile Include="Traits\Infiltration\InfiltrateForExploration.cs" />
|
||||
<Compile Include="Traits\Infiltration\InfiltrateForPowerOutage.cs" />
|
||||
<Compile Include="Traits\Infiltration\InfiltrateForSupportPower.cs" />
|
||||
<Compile Include="Traits\Infiltration\Infiltrates.cs" />
|
||||
<Compile Include="Traits\MadTank.cs" />
|
||||
<Compile Include="Traits\Mine.cs" />
|
||||
<Compile Include="Traits\Minelayer.cs" />
|
||||
<Compile Include="Traits\PaletteEffects\ChronoshiftPaletteEffect.cs" />
|
||||
<Compile Include="Traits\PaletteEffects\LightPaletteRotator.cs" />
|
||||
<Compile Include="Traits\PortableChrono.cs" />
|
||||
<Compile Include="Traits\Render\RenderJammerCircle.cs" />
|
||||
<Compile Include="Traits\Render\WithLandingCraftAnimation.cs" />
|
||||
<Compile Include="Traits\Render\RenderShroudCircle.cs" />
|
||||
<Compile Include="Traits\SupportPowers\ChronoshiftPower.cs" />
|
||||
<Compile Include="Traits\SupportPowers\GpsPower.cs" />
|
||||
<Compile Include="Traits\GpsWatcher.cs" />
|
||||
<Compile Include="Scripting\Properties\ChronosphereProperties.cs" />
|
||||
<Compile Include="Traits\Render\WithDisguisingInfantryBody.cs" />
|
||||
<Compile Include="ImportRedAlertLegacyMapCommand.cs" />
|
||||
<Compile Include="Traits\Infiltration\InfiltrateForDecoration.cs" />
|
||||
<Compile Include="TraitsInterfaces.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
|
||||
79
OpenRA.Mods.Cnc/Projectiles/TeslaZap.cs
Normal file
79
OpenRA.Mods.Cnc/Projectiles/TeslaZap.cs
Normal file
@@ -0,0 +1,79 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2017 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, either version 3 of
|
||||
* the License, or (at your option) any later version. For more
|
||||
* information, see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System.Collections.Generic;
|
||||
using OpenRA.Effects;
|
||||
using OpenRA.GameRules;
|
||||
using OpenRA.Graphics;
|
||||
using OpenRA.Mods.Cnc.Graphics;
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.Mods.Cnc.Projectiles
|
||||
{
|
||||
public class TeslaZapInfo : IProjectileInfo
|
||||
{
|
||||
public readonly string Image = "litning";
|
||||
|
||||
[SequenceReference("Image")] public readonly string BrightSequence = "bright";
|
||||
[SequenceReference("Image")] public readonly string DimSequence = "dim";
|
||||
|
||||
[PaletteReference] public readonly string Palette = "effect";
|
||||
|
||||
public readonly int BrightZaps = 1;
|
||||
public readonly int DimZaps = 2;
|
||||
|
||||
public readonly int Duration = 2;
|
||||
|
||||
public IProjectile Create(ProjectileArgs args) { return new TeslaZap(this, args); }
|
||||
}
|
||||
|
||||
public class TeslaZap : IProjectile
|
||||
{
|
||||
readonly ProjectileArgs args;
|
||||
readonly TeslaZapInfo info;
|
||||
TeslaZapRenderable zap;
|
||||
int timeUntilRemove; // # of frames
|
||||
bool doneDamage = false;
|
||||
bool initialized = false;
|
||||
|
||||
public TeslaZap(TeslaZapInfo info, ProjectileArgs args)
|
||||
{
|
||||
this.args = args;
|
||||
this.info = info;
|
||||
this.timeUntilRemove = info.Duration;
|
||||
}
|
||||
|
||||
public void Tick(World world)
|
||||
{
|
||||
if (timeUntilRemove-- <= 0)
|
||||
world.AddFrameEndTask(w => w.Remove(this));
|
||||
|
||||
if (!doneDamage)
|
||||
{
|
||||
var pos = args.GuidedTarget.IsValidFor(args.SourceActor) ? args.GuidedTarget.CenterPosition : args.PassiveTarget;
|
||||
args.Weapon.Impact(Target.FromPos(pos), args.SourceActor, args.DamageModifiers);
|
||||
doneDamage = true;
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<IRenderable> Render(WorldRenderer wr)
|
||||
{
|
||||
if (!initialized)
|
||||
{
|
||||
var pos = args.GuidedTarget.IsValidFor(args.SourceActor) ? args.GuidedTarget.CenterPosition : args.PassiveTarget;
|
||||
zap = new TeslaZapRenderable(args.Source, 0, pos - args.Source,
|
||||
info.Image, info.BrightSequence, info.BrightZaps, info.DimSequence, info.DimZaps, info.Palette);
|
||||
}
|
||||
|
||||
yield return zap;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2017 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, either version 3 of
|
||||
* the License, or (at your option) any later version. For more
|
||||
* information, see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using Eluant;
|
||||
using OpenRA.Mods.Cnc.Traits;
|
||||
using OpenRA.Scripting;
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.Mods.Cnc.Scripting
|
||||
{
|
||||
[ScriptPropertyGroup("Support Powers")]
|
||||
public class ChronsphereProperties : ScriptActorProperties, Requires<ChronoshiftPowerInfo>
|
||||
{
|
||||
public ChronsphereProperties(ScriptContext context, Actor self)
|
||||
: base(context, self) { }
|
||||
|
||||
[Desc("Chronoshift a group of actors. A duration of 0 will teleport the actors permanently.")]
|
||||
public void Chronoshift(LuaTable unitLocationPairs, int duration = 0, bool killCargo = false)
|
||||
{
|
||||
foreach (var kv in unitLocationPairs)
|
||||
{
|
||||
Actor actor;
|
||||
CPos cell;
|
||||
using (kv.Key)
|
||||
using (kv.Value)
|
||||
{
|
||||
if (!kv.Key.TryGetClrValue(out actor) || !kv.Value.TryGetClrValue(out cell))
|
||||
throw new LuaException("Chronoshift requires a table of Actor,CPos pairs. Received {0},{1}".F(
|
||||
kv.Key.WrappedClrType().Name, kv.Value.WrappedClrType().Name));
|
||||
}
|
||||
|
||||
var cs = actor.TraitOrDefault<Chronoshiftable>();
|
||||
if (cs != null && cs.CanChronoshiftTo(actor, cell))
|
||||
cs.Teleport(actor, cell, duration, killCargo, Self);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
42
OpenRA.Mods.Cnc/Scripting/Properties/DisguiseProperties.cs
Normal file
42
OpenRA.Mods.Cnc/Scripting/Properties/DisguiseProperties.cs
Normal file
@@ -0,0 +1,42 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2017 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, either version 3 of
|
||||
* the License, or (at your option) any later version. For more
|
||||
* information, see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using OpenRA.Mods.Cnc.Traits;
|
||||
using OpenRA.Scripting;
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.Mods.Cnc.Scripting
|
||||
{
|
||||
[ScriptPropertyGroup("Ability")]
|
||||
public class DisguiseProperties : ScriptActorProperties, Requires<DisguiseInfo>
|
||||
{
|
||||
readonly Disguise disguise;
|
||||
|
||||
public DisguiseProperties(ScriptContext context, Actor self)
|
||||
: base(context, self)
|
||||
{
|
||||
disguise = Self.Trait<Disguise>();
|
||||
}
|
||||
|
||||
[Desc("Disguises as the target actor.")]
|
||||
public void DisguiseAs(Actor target)
|
||||
{
|
||||
disguise.DisguiseAs(target);
|
||||
}
|
||||
|
||||
[Desc("Disguises as the target type with the specified owner.")]
|
||||
public void DisguiseAsType(string actorType, Player newOwner)
|
||||
{
|
||||
var actorInfo = Self.World.Map.Rules.Actors[actorType];
|
||||
disguise.DisguiseAs(actorInfo, newOwner);
|
||||
}
|
||||
}
|
||||
}
|
||||
36
OpenRA.Mods.Cnc/Scripting/Properties/InfiltrateProperties.cs
Normal file
36
OpenRA.Mods.Cnc/Scripting/Properties/InfiltrateProperties.cs
Normal file
@@ -0,0 +1,36 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2017 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, either version 3 of
|
||||
* the License, or (at your option) any later version. For more
|
||||
* information, see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using OpenRA.Mods.Cnc.Activities;
|
||||
using OpenRA.Mods.Cnc.Traits;
|
||||
using OpenRA.Scripting;
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.Mods.Cnc.Scripting
|
||||
{
|
||||
[ScriptPropertyGroup("Ability")]
|
||||
public class InfiltrateProperties : ScriptActorProperties, Requires<InfiltratesInfo>
|
||||
{
|
||||
readonly InfiltratesInfo info;
|
||||
|
||||
public InfiltrateProperties(ScriptContext context, Actor self)
|
||||
: base(context, self)
|
||||
{
|
||||
info = Self.Info.TraitInfo<InfiltratesInfo>();
|
||||
}
|
||||
|
||||
[Desc("Infiltrate the target actor.")]
|
||||
public void Infiltrate(Actor target)
|
||||
{
|
||||
Self.QueueActivity(new Infiltrate(Self, target, info.EnterBehaviour, info.ValidStances, info.Notification, info.PlayerExperience));
|
||||
}
|
||||
}
|
||||
}
|
||||
57
OpenRA.Mods.Cnc/Traits/Attack/AttackLeap.cs
Normal file
57
OpenRA.Mods.Cnc/Traits/Attack/AttackLeap.cs
Normal file
@@ -0,0 +1,57 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2017 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, either version 3 of
|
||||
* the License, or (at your option) any later version. For more
|
||||
* information, see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using OpenRA.Mods.Cnc.Activities;
|
||||
using OpenRA.Mods.Common.Traits;
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.Mods.Cnc.Traits
|
||||
{
|
||||
[Desc("Dogs use this attack model.")]
|
||||
class AttackLeapInfo : AttackFrontalInfo
|
||||
{
|
||||
[Desc("Leap speed (in units/tick).")]
|
||||
public readonly WDist Speed = new WDist(426);
|
||||
|
||||
public readonly WAngle Angle = WAngle.FromDegrees(20);
|
||||
|
||||
public override object Create(ActorInitializer init) { return new AttackLeap(init.Self, this); }
|
||||
}
|
||||
|
||||
class AttackLeap : AttackFrontal
|
||||
{
|
||||
readonly AttackLeapInfo info;
|
||||
|
||||
public AttackLeap(Actor self, AttackLeapInfo info)
|
||||
: base(self, info)
|
||||
{
|
||||
this.info = info;
|
||||
}
|
||||
|
||||
public override void DoAttack(Actor self, Target target, IEnumerable<Armament> armaments = null)
|
||||
{
|
||||
if (target.Type != TargetType.Actor || !CanAttack(self, target))
|
||||
return;
|
||||
|
||||
var a = ChooseArmamentsForTarget(target, true).FirstOrDefault();
|
||||
if (a == null)
|
||||
return;
|
||||
|
||||
if (!target.IsInRange(self.CenterPosition, a.MaxRange()))
|
||||
return;
|
||||
|
||||
self.CancelActivity();
|
||||
self.QueueActivity(new Leap(self, target.Actor, a, info.Speed, info.Angle));
|
||||
}
|
||||
}
|
||||
}
|
||||
55
OpenRA.Mods.Cnc/Traits/Buildings/ClonesProducedUnits.cs
Normal file
55
OpenRA.Mods.Cnc/Traits/Buildings/ClonesProducedUnits.cs
Normal file
@@ -0,0 +1,55 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2017 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, either version 3 of
|
||||
* the License, or (at your option) any later version. For more
|
||||
* information, see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System.Collections.Generic;
|
||||
using OpenRA.Mods.Common;
|
||||
using OpenRA.Mods.Common.Traits;
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.Mods.Cnc.Traits
|
||||
{
|
||||
[Desc("Creates a free duplicate of produced units.")]
|
||||
public class ClonesProducedUnitsInfo : ITraitInfo, Requires<ProductionInfo>, Requires<ExitInfo>
|
||||
{
|
||||
[FieldLoader.Require]
|
||||
[Desc("Uses the \"Cloneable\" trait to determine whether or not we should clone a produced unit.")]
|
||||
public readonly HashSet<string> CloneableTypes = new HashSet<string>();
|
||||
|
||||
public object Create(ActorInitializer init) { return new ClonesProducedUnits(init, this); }
|
||||
}
|
||||
|
||||
public class ClonesProducedUnits : INotifyOtherProduction
|
||||
{
|
||||
readonly ClonesProducedUnitsInfo info;
|
||||
readonly Production production;
|
||||
readonly string faction;
|
||||
|
||||
public ClonesProducedUnits(ActorInitializer init, ClonesProducedUnitsInfo info)
|
||||
{
|
||||
this.info = info;
|
||||
production = init.Self.Trait<Production>();
|
||||
faction = init.Contains<FactionInit>() ? init.Get<FactionInit, string>() : init.Self.Owner.Faction.InternalName;
|
||||
}
|
||||
|
||||
public void UnitProducedByOther(Actor self, Actor producer, Actor produced)
|
||||
{
|
||||
// No recursive cloning!
|
||||
if (producer.Owner != self.Owner || producer.Info.HasTraitInfo<ClonesProducedUnitsInfo>())
|
||||
return;
|
||||
|
||||
var ci = produced.Info.TraitInfoOrDefault<CloneableInfo>();
|
||||
if (ci == null || !info.CloneableTypes.Overlaps(ci.Types))
|
||||
return;
|
||||
|
||||
production.Produce(self, produced.Info, faction);
|
||||
}
|
||||
}
|
||||
}
|
||||
166
OpenRA.Mods.Cnc/Traits/Chronoshiftable.cs
Normal file
166
OpenRA.Mods.Cnc/Traits/Chronoshiftable.cs
Normal file
@@ -0,0 +1,166 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2017 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, either version 3 of
|
||||
* the License, or (at your option) any later version. For more
|
||||
* information, see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System.Drawing;
|
||||
using OpenRA.Mods.Cnc.Activities;
|
||||
using OpenRA.Mods.Common.Traits;
|
||||
using OpenRA.Primitives;
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.Mods.Cnc.Traits
|
||||
{
|
||||
[Desc("Can be teleported via Chronoshift power.")]
|
||||
public class ChronoshiftableInfo : ITraitInfo
|
||||
{
|
||||
[Desc("Should the actor die instead of being teleported?")]
|
||||
public readonly bool ExplodeInstead = false;
|
||||
public readonly string ChronoshiftSound = "chrono2.aud";
|
||||
|
||||
[Desc("Should the actor return to its previous location after the chronoshift wore out?")]
|
||||
public readonly bool ReturnToOrigin = true;
|
||||
|
||||
[Desc("The color the bar of the 'return-to-origin' logic has.")]
|
||||
public readonly Color TimeBarColor = Color.White;
|
||||
|
||||
public object Create(ActorInitializer init) { return new Chronoshiftable(init, this); }
|
||||
}
|
||||
|
||||
public class Chronoshiftable : ITick, ISync, ISelectionBar, IDeathActorInitModifier, INotifyCreated
|
||||
{
|
||||
readonly ChronoshiftableInfo info;
|
||||
readonly Actor self;
|
||||
Actor chronosphere;
|
||||
bool killCargo;
|
||||
int duration;
|
||||
IPositionable iPositionable;
|
||||
|
||||
// Return-to-origin logic
|
||||
[Sync] public CPos Origin;
|
||||
[Sync] public int ReturnTicks = 0;
|
||||
|
||||
public Chronoshiftable(ActorInitializer init, ChronoshiftableInfo info)
|
||||
{
|
||||
this.info = info;
|
||||
self = init.Self;
|
||||
|
||||
if (init.Contains<ChronoshiftReturnInit>())
|
||||
ReturnTicks = init.Get<ChronoshiftReturnInit, int>();
|
||||
|
||||
if (init.Contains<ChronoshiftOriginInit>())
|
||||
Origin = init.Get<ChronoshiftOriginInit, CPos>();
|
||||
|
||||
if (init.Contains<ChronoshiftChronosphereInit>())
|
||||
chronosphere = init.Get<ChronoshiftChronosphereInit, Actor>();
|
||||
}
|
||||
|
||||
public void Tick(Actor self)
|
||||
{
|
||||
if (!info.ReturnToOrigin || ReturnTicks <= 0)
|
||||
return;
|
||||
|
||||
// Return to original location
|
||||
if (--ReturnTicks == 0)
|
||||
{
|
||||
self.CancelActivity();
|
||||
self.QueueActivity(new Teleport(chronosphere, Origin, null, killCargo, true, info.ChronoshiftSound));
|
||||
}
|
||||
}
|
||||
|
||||
public void Created(Actor self)
|
||||
{
|
||||
iPositionable = self.TraitOrDefault<IPositionable>();
|
||||
}
|
||||
|
||||
// Can't be used in synced code, except with ignoreVis.
|
||||
public virtual bool CanChronoshiftTo(Actor self, CPos targetLocation)
|
||||
{
|
||||
// TODO: Allow enemy units to be chronoshifted into bad terrain to kill them
|
||||
return iPositionable != null && iPositionable.CanEnterCell(targetLocation);
|
||||
}
|
||||
|
||||
public virtual bool Teleport(Actor self, CPos targetLocation, int duration, bool killCargo, Actor chronosphere)
|
||||
{
|
||||
// Some things appear chronoshiftable, but instead they just die.
|
||||
if (info.ExplodeInstead)
|
||||
{
|
||||
self.World.AddFrameEndTask(w =>
|
||||
{
|
||||
// Damage is inflicted by the chronosphere
|
||||
if (!self.Disposed)
|
||||
self.InflictDamage(chronosphere, new Damage(int.MaxValue));
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
// Set up return-to-origin info
|
||||
Origin = self.Location;
|
||||
ReturnTicks = duration;
|
||||
this.duration = duration;
|
||||
this.chronosphere = chronosphere;
|
||||
this.killCargo = killCargo;
|
||||
|
||||
// Set up the teleport
|
||||
self.CancelActivity();
|
||||
self.QueueActivity(new Teleport(chronosphere, targetLocation, null, killCargo, true, info.ChronoshiftSound));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Show the remaining time as a bar
|
||||
float ISelectionBar.GetValue()
|
||||
{
|
||||
if (!info.ReturnToOrigin)
|
||||
return 0f;
|
||||
|
||||
// Otherwise an empty bar is rendered all the time
|
||||
if (ReturnTicks == 0 || !self.Owner.IsAlliedWith(self.World.RenderPlayer))
|
||||
return 0f;
|
||||
|
||||
return (float)ReturnTicks / duration;
|
||||
}
|
||||
|
||||
Color ISelectionBar.GetColor() { return info.TimeBarColor; }
|
||||
bool ISelectionBar.DisplayWhenEmpty { get { return false; } }
|
||||
|
||||
public void ModifyDeathActorInit(Actor self, TypeDictionary init)
|
||||
{
|
||||
if (!info.ReturnToOrigin || ReturnTicks <= 0)
|
||||
return;
|
||||
|
||||
init.Add(new ChronoshiftOriginInit(Origin));
|
||||
init.Add(new ChronoshiftReturnInit(ReturnTicks));
|
||||
if (chronosphere != self)
|
||||
init.Add(new ChronoshiftChronosphereInit(chronosphere));
|
||||
}
|
||||
}
|
||||
|
||||
public class ChronoshiftReturnInit : IActorInit<int>
|
||||
{
|
||||
[FieldFromYamlKey] readonly int value = 0;
|
||||
public ChronoshiftReturnInit() { }
|
||||
public ChronoshiftReturnInit(int init) { value = init; }
|
||||
public int Value(World world) { return value; }
|
||||
}
|
||||
|
||||
public class ChronoshiftOriginInit : IActorInit<CPos>
|
||||
{
|
||||
[FieldFromYamlKey] readonly CPos value;
|
||||
public ChronoshiftOriginInit(CPos init) { value = init; }
|
||||
public CPos Value(World world) { return value; }
|
||||
}
|
||||
|
||||
public class ChronoshiftChronosphereInit : IActorInit<Actor>
|
||||
{
|
||||
readonly Actor value;
|
||||
public ChronoshiftChronosphereInit(Actor init) { value = init; }
|
||||
public Actor Value(World world) { return value; }
|
||||
}
|
||||
}
|
||||
26
OpenRA.Mods.Cnc/Traits/Cloneable.cs
Normal file
26
OpenRA.Mods.Cnc/Traits/Cloneable.cs
Normal file
@@ -0,0 +1,26 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2017 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, either version 3 of
|
||||
* the License, or (at your option) any later version. For more
|
||||
* information, see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System.Collections.Generic;
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.Mods.Cnc.Traits
|
||||
{
|
||||
[Desc("Actors with the \"ClonesProducedUnits\" trait will produce a free duplicate of me.")]
|
||||
public class CloneableInfo : TraitInfo<Cloneable>
|
||||
{
|
||||
[FieldLoader.Require]
|
||||
[Desc("This unit's cloneable type is:")]
|
||||
public readonly HashSet<string> Types = new HashSet<string>();
|
||||
}
|
||||
|
||||
public class Cloneable { }
|
||||
}
|
||||
203
OpenRA.Mods.Cnc/Traits/Disguise.cs
Normal file
203
OpenRA.Mods.Cnc/Traits/Disguise.cs
Normal file
@@ -0,0 +1,203 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2017 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, either version 3 of
|
||||
* the License, or (at your option) any later version. For more
|
||||
* information, see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
using OpenRA.Mods.Common.Orders;
|
||||
using OpenRA.Mods.Common.Traits;
|
||||
using OpenRA.Mods.Common.Traits.Render;
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.Mods.Cnc.Traits
|
||||
{
|
||||
[Desc("Overrides the default ToolTip when this actor is disguised (aids in deceiving enemy players).")]
|
||||
class DisguiseToolTipInfo : TooltipInfo, Requires<DisguiseInfo>
|
||||
{
|
||||
public override object Create(ActorInitializer init) { return new DisguiseToolTip(init.Self, this); }
|
||||
}
|
||||
|
||||
class DisguiseToolTip : ITooltip
|
||||
{
|
||||
readonly Actor self;
|
||||
readonly Disguise disguise;
|
||||
TooltipInfo info;
|
||||
|
||||
public DisguiseToolTip(Actor self, TooltipInfo info)
|
||||
{
|
||||
this.self = self;
|
||||
this.info = info;
|
||||
disguise = self.Trait<Disguise>();
|
||||
}
|
||||
|
||||
public ITooltipInfo TooltipInfo
|
||||
{
|
||||
get
|
||||
{
|
||||
return disguise.Disguised ? disguise.AsTooltipInfo : info;
|
||||
}
|
||||
}
|
||||
|
||||
public Player Owner
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!disguise.Disguised || self.Owner.IsAlliedWith(self.World.RenderPlayer))
|
||||
return self.Owner;
|
||||
|
||||
return disguise.AsPlayer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Desc("Provides access to the disguise command, which makes the actor appear to be another player's actor.")]
|
||||
class DisguiseInfo : ITraitInfo
|
||||
{
|
||||
[VoiceReference] public readonly string Voice = "Action";
|
||||
|
||||
[GrantedConditionReference]
|
||||
[Desc("The condition to grant to self while disguised.")]
|
||||
public readonly string DisguisedCondition = null;
|
||||
|
||||
public object Create(ActorInitializer init) { return new Disguise(init.Self, this); }
|
||||
}
|
||||
|
||||
class Disguise : INotifyCreated, IEffectiveOwner, IIssueOrder, IResolveOrder, IOrderVoice, IRadarColorModifier, INotifyAttack
|
||||
{
|
||||
public Player AsPlayer { get; private set; }
|
||||
public string AsSprite { get; private set; }
|
||||
public ITooltipInfo AsTooltipInfo { get; private set; }
|
||||
|
||||
public bool Disguised { get { return AsPlayer != null; } }
|
||||
public Player Owner { get { return AsPlayer; } }
|
||||
|
||||
readonly Actor self;
|
||||
readonly DisguiseInfo info;
|
||||
|
||||
ConditionManager conditionManager;
|
||||
int disguisedToken = ConditionManager.InvalidConditionToken;
|
||||
|
||||
public Disguise(Actor self, DisguiseInfo info)
|
||||
{
|
||||
this.self = self;
|
||||
this.info = info;
|
||||
}
|
||||
|
||||
void INotifyCreated.Created(Actor self)
|
||||
{
|
||||
conditionManager = self.TraitOrDefault<ConditionManager>();
|
||||
}
|
||||
|
||||
public IEnumerable<IOrderTargeter> Orders
|
||||
{
|
||||
get
|
||||
{
|
||||
yield return new TargetTypeOrderTargeter(new HashSet<string> { "Disguise" }, "Disguise", 7, "ability", true, true) { ForceAttack = false };
|
||||
}
|
||||
}
|
||||
|
||||
public Order IssueOrder(Actor self, IOrderTargeter order, Target target, bool queued)
|
||||
{
|
||||
if (order.OrderID == "Disguise")
|
||||
return new Order(order.OrderID, self, queued) { TargetActor = target.Actor };
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public void ResolveOrder(Actor self, Order order)
|
||||
{
|
||||
if (order.OrderString == "Disguise")
|
||||
{
|
||||
var target = order.TargetActor != self && order.TargetActor.IsInWorld ? order.TargetActor : null;
|
||||
DisguiseAs(target);
|
||||
}
|
||||
}
|
||||
|
||||
public string VoicePhraseForOrder(Actor self, Order order)
|
||||
{
|
||||
return order.OrderString == "Disguise" ? info.Voice : null;
|
||||
}
|
||||
|
||||
public Color RadarColorOverride(Actor self, Color color)
|
||||
{
|
||||
if (!Disguised || self.Owner.IsAlliedWith(self.World.RenderPlayer))
|
||||
return color;
|
||||
|
||||
return color = Game.Settings.Game.UsePlayerStanceColors ? AsPlayer.PlayerStanceColor(self) : AsPlayer.Color.RGB;
|
||||
}
|
||||
|
||||
public void DisguiseAs(Actor target)
|
||||
{
|
||||
var oldDisguiseSetting = Disguised;
|
||||
var oldEffectiveOwner = AsPlayer;
|
||||
|
||||
if (target != null)
|
||||
{
|
||||
// Take the image of the target's disguise, if it exists.
|
||||
// E.g., SpyA is disguised as a rifle infantry. SpyB then targets SpyA. We should use the rifle infantry image.
|
||||
var targetDisguise = target.TraitOrDefault<Disguise>();
|
||||
if (targetDisguise != null && targetDisguise.Disguised)
|
||||
{
|
||||
AsSprite = targetDisguise.AsSprite;
|
||||
AsPlayer = targetDisguise.AsPlayer;
|
||||
AsTooltipInfo = targetDisguise.AsTooltipInfo;
|
||||
}
|
||||
else
|
||||
{
|
||||
AsSprite = target.Trait<RenderSprites>().GetImage(target);
|
||||
var tooltip = target.TraitsImplementing<ITooltip>().FirstOrDefault();
|
||||
AsPlayer = tooltip.Owner;
|
||||
AsTooltipInfo = tooltip.TooltipInfo;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
AsTooltipInfo = null;
|
||||
AsPlayer = null;
|
||||
AsSprite = null;
|
||||
}
|
||||
|
||||
HandleDisguise(oldEffectiveOwner, oldDisguiseSetting);
|
||||
}
|
||||
|
||||
public void DisguiseAs(ActorInfo actorInfo, Player newOwner)
|
||||
{
|
||||
var oldDisguiseSetting = Disguised;
|
||||
var oldEffectiveOwner = AsPlayer;
|
||||
|
||||
var renderSprites = actorInfo.TraitInfoOrDefault<RenderSpritesInfo>();
|
||||
AsSprite = renderSprites == null ? null : renderSprites.GetImage(actorInfo, self.World.Map.Rules.Sequences, newOwner.Faction.InternalName);
|
||||
AsPlayer = newOwner;
|
||||
AsTooltipInfo = actorInfo.TraitInfos<TooltipInfo>().FirstOrDefault();
|
||||
|
||||
HandleDisguise(oldEffectiveOwner, oldDisguiseSetting);
|
||||
}
|
||||
|
||||
void HandleDisguise(Player oldEffectiveOwner, bool oldDisguiseSetting)
|
||||
{
|
||||
foreach (var t in self.TraitsImplementing<INotifyEffectiveOwnerChanged>())
|
||||
t.OnEffectiveOwnerChanged(self, oldEffectiveOwner, AsPlayer);
|
||||
|
||||
if (Disguised != oldDisguiseSetting && conditionManager != null)
|
||||
{
|
||||
if (Disguised && disguisedToken == ConditionManager.InvalidConditionToken && !string.IsNullOrEmpty(info.DisguisedCondition))
|
||||
disguisedToken = conditionManager.GrantCondition(self, info.DisguisedCondition);
|
||||
else if (!Disguised && disguisedToken != ConditionManager.InvalidConditionToken)
|
||||
disguisedToken = conditionManager.RevokeCondition(self, disguisedToken);
|
||||
}
|
||||
}
|
||||
|
||||
void INotifyAttack.PreparingAttack(Actor self, Target target, Armament a, Barrel barrel) { }
|
||||
|
||||
void INotifyAttack.Attacking(Actor self, Target target, Armament a, Barrel barrel) { DisguiseAs(null); }
|
||||
}
|
||||
}
|
||||
109
OpenRA.Mods.Cnc/Traits/FrozenUnderFogUpdatedByGps.cs
Normal file
109
OpenRA.Mods.Cnc/Traits/FrozenUnderFogUpdatedByGps.cs
Normal file
@@ -0,0 +1,109 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2017 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, either version 3 of
|
||||
* the License, or (at your option) any later version. For more
|
||||
* information, see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using OpenRA.Mods.Common.Traits;
|
||||
using OpenRA.Primitives;
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.Mods.Cnc.Traits
|
||||
{
|
||||
using FrozenActorAction = Action<FrozenUnderFogUpdatedByGps, FrozenActorLayer, GpsWatcher, FrozenActor>;
|
||||
|
||||
[Desc("Updates frozen actors of actors that change owners, are sold or die whilst having an active GPS power.")]
|
||||
public class FrozenUnderFogUpdatedByGpsInfo : ITraitInfo, Requires<FrozenUnderFogInfo>
|
||||
{
|
||||
public object Create(ActorInitializer init) { return new FrozenUnderFogUpdatedByGps(init); }
|
||||
}
|
||||
|
||||
public class FrozenUnderFogUpdatedByGps : INotifyOwnerChanged, INotifyActorDisposing, IOnGpsRefreshed
|
||||
{
|
||||
static readonly FrozenActorAction Refresh = (fufubg, fal, gps, fa) =>
|
||||
{
|
||||
// Refreshes the visual state of the frozen actor, so ownership changes can be seen.
|
||||
// This only makes sense if the frozen actor has already been revealed (i.e. has renderables)
|
||||
if (fa.HasRenderables)
|
||||
{
|
||||
fa.RefreshState();
|
||||
fa.NeedRenderables = true;
|
||||
}
|
||||
};
|
||||
static readonly FrozenActorAction Remove = (fufubg, fal, gps, fa) =>
|
||||
{
|
||||
// Removes the frozen actor. Once done, we no longer need to track GPS updates.
|
||||
fal.Remove(fa);
|
||||
gps.UnregisterForOnGpsRefreshed(fufubg.self, fufubg);
|
||||
};
|
||||
|
||||
class Traits
|
||||
{
|
||||
public readonly FrozenActorLayer FrozenActorLayer;
|
||||
public readonly GpsWatcher GpsWatcher;
|
||||
public Traits(Player player, FrozenUnderFogUpdatedByGps frozenUnderFogUpdatedByGps)
|
||||
{
|
||||
FrozenActorLayer = player.PlayerActor.TraitOrDefault<FrozenActorLayer>();
|
||||
GpsWatcher = player.PlayerActor.TraitOrDefault<GpsWatcher>();
|
||||
GpsWatcher.RegisterForOnGpsRefreshed(frozenUnderFogUpdatedByGps.self, frozenUnderFogUpdatedByGps);
|
||||
}
|
||||
}
|
||||
|
||||
readonly PlayerDictionary<Traits> traits;
|
||||
readonly Actor self;
|
||||
|
||||
public FrozenUnderFogUpdatedByGps(ActorInitializer init)
|
||||
{
|
||||
self = init.Self;
|
||||
traits = new PlayerDictionary<Traits>(init.World, player => new Traits(player, this));
|
||||
}
|
||||
|
||||
public void OnOwnerChanged(Actor self, Player oldOwner, Player newOwner)
|
||||
{
|
||||
ActOnFrozenActorsForAllPlayers(Refresh);
|
||||
}
|
||||
|
||||
public void Disposing(Actor self)
|
||||
{
|
||||
ActOnFrozenActorsForAllPlayers(Remove);
|
||||
}
|
||||
|
||||
public void OnGpsRefresh(Actor self, Player player)
|
||||
{
|
||||
if (self.IsDead)
|
||||
ActOnFrozenActorForPlayer(player, Remove);
|
||||
else
|
||||
ActOnFrozenActorForPlayer(player, Refresh);
|
||||
}
|
||||
|
||||
void ActOnFrozenActorsForAllPlayers(FrozenActorAction action)
|
||||
{
|
||||
for (var playerIndex = 0; playerIndex < traits.Count; playerIndex++)
|
||||
ActOnFrozenActorForTraits(traits[playerIndex], action);
|
||||
}
|
||||
|
||||
void ActOnFrozenActorForPlayer(Player player, FrozenActorAction action)
|
||||
{
|
||||
ActOnFrozenActorForTraits(traits[player], action);
|
||||
}
|
||||
|
||||
void ActOnFrozenActorForTraits(Traits t, FrozenActorAction action)
|
||||
{
|
||||
if (t.FrozenActorLayer == null || t.GpsWatcher == null ||
|
||||
!t.GpsWatcher.Granted || !t.GpsWatcher.GrantedAllies)
|
||||
return;
|
||||
|
||||
var fa = t.FrozenActorLayer.FromID(self.ActorID);
|
||||
if (fa == null)
|
||||
return;
|
||||
|
||||
action(this, t.FrozenActorLayer, t.GpsWatcher, fa);
|
||||
}
|
||||
}
|
||||
}
|
||||
119
OpenRA.Mods.Cnc/Traits/GpsWatcher.cs
Normal file
119
OpenRA.Mods.Cnc/Traits/GpsWatcher.cs
Normal file
@@ -0,0 +1,119 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2017 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, either version 3 of
|
||||
* the License, or (at your option) any later version. For more
|
||||
* information, see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using OpenRA.Effects;
|
||||
using OpenRA.Mods.Cnc.Effects;
|
||||
using OpenRA.Mods.Common.Traits;
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.Mods.Cnc.Traits
|
||||
{
|
||||
[Desc("Required for `GpsPower`. Attach this to the player actor.")]
|
||||
class GpsWatcherInfo : ITraitInfo
|
||||
{
|
||||
public object Create(ActorInitializer init) { return new GpsWatcher(init.Self.Owner); }
|
||||
}
|
||||
|
||||
interface IOnGpsRefreshed { void OnGpsRefresh(Actor self, Player player); }
|
||||
|
||||
class GpsWatcher : ISync, IFogVisibilityModifier
|
||||
{
|
||||
[Sync] public bool Launched { get; private set; }
|
||||
[Sync] public bool GrantedAllies { get; private set; }
|
||||
[Sync] public bool Granted { get; private set; }
|
||||
|
||||
// Whether this watcher has explored the terrain (by becoming Launched, or an ally becoming Launched)
|
||||
[Sync] bool explored;
|
||||
|
||||
readonly Player owner;
|
||||
|
||||
readonly List<Actor> actors = new List<Actor>();
|
||||
readonly HashSet<TraitPair<IOnGpsRefreshed>> notifyOnRefresh = new HashSet<TraitPair<IOnGpsRefreshed>>();
|
||||
|
||||
public GpsWatcher(Player owner)
|
||||
{
|
||||
this.owner = owner;
|
||||
}
|
||||
|
||||
public void GpsRemove(Actor atek)
|
||||
{
|
||||
actors.Remove(atek);
|
||||
RefreshGps(atek.Owner);
|
||||
}
|
||||
|
||||
public void GpsAdd(Actor atek)
|
||||
{
|
||||
actors.Add(atek);
|
||||
RefreshGps(atek.Owner);
|
||||
}
|
||||
|
||||
public void ReachedOrbit(Player launcher)
|
||||
{
|
||||
Launched = true;
|
||||
RefreshGps(launcher);
|
||||
}
|
||||
|
||||
public void RefreshGps(Player launcher)
|
||||
{
|
||||
RefreshGranted();
|
||||
|
||||
foreach (var i in launcher.World.ActorsWithTrait<GpsWatcher>())
|
||||
i.Trait.RefreshGranted();
|
||||
}
|
||||
|
||||
void RefreshGranted()
|
||||
{
|
||||
var wasGranted = Granted;
|
||||
var wasGrantedAllies = GrantedAllies;
|
||||
var allyWatchers = owner.World.ActorsWithTrait<GpsWatcher>().Where(kv => kv.Actor.Owner.IsAlliedWith(owner));
|
||||
|
||||
Granted = actors.Count > 0 && Launched;
|
||||
GrantedAllies = allyWatchers.Any(w => w.Trait.Granted);
|
||||
|
||||
var allyLaunched = allyWatchers.Any(w => w.Trait.Launched);
|
||||
if ((Launched || allyLaunched) && !explored)
|
||||
{
|
||||
explored = true;
|
||||
owner.Shroud.ExploreAll();
|
||||
}
|
||||
|
||||
if (wasGranted != Granted || wasGrantedAllies != GrantedAllies)
|
||||
foreach (var tp in notifyOnRefresh.ToList())
|
||||
tp.Trait.OnGpsRefresh(tp.Actor, owner);
|
||||
}
|
||||
|
||||
public bool HasFogVisibility()
|
||||
{
|
||||
return Granted || GrantedAllies;
|
||||
}
|
||||
|
||||
public bool IsVisible(Actor actor)
|
||||
{
|
||||
var gpsDot = actor.TraitOrDefault<GpsDot>();
|
||||
if (gpsDot == null)
|
||||
return false;
|
||||
|
||||
return gpsDot.IsDotVisible(owner);
|
||||
}
|
||||
|
||||
public void RegisterForOnGpsRefreshed(Actor actor, IOnGpsRefreshed toBeNotified)
|
||||
{
|
||||
notifyOnRefresh.Add(new TraitPair<IOnGpsRefreshed>(actor, toBeNotified));
|
||||
}
|
||||
|
||||
public void UnregisterForOnGpsRefreshed(Actor actor, IOnGpsRefreshed toBeNotified)
|
||||
{
|
||||
notifyOnRefresh.Remove(new TraitPair<IOnGpsRefreshed>(actor, toBeNotified));
|
||||
}
|
||||
}
|
||||
}
|
||||
39
OpenRA.Mods.Cnc/Traits/HarvesterHuskModifier.cs
Normal file
39
OpenRA.Mods.Cnc/Traits/HarvesterHuskModifier.cs
Normal file
@@ -0,0 +1,39 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2017 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, either version 3 of
|
||||
* the License, or (at your option) any later version. For more
|
||||
* information, see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using OpenRA.Mods.Common.Traits;
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.Mods.Cnc.Traits
|
||||
{
|
||||
public class HarvesterHuskModifierInfo : ITraitInfo, Requires<HarvesterInfo>
|
||||
{
|
||||
[ActorReference]
|
||||
public readonly string FullHuskActor = null;
|
||||
public readonly int FullnessThreshold = 50;
|
||||
|
||||
public object Create(ActorInitializer init) { return new HarvesterHuskModifier(this); }
|
||||
}
|
||||
|
||||
public class HarvesterHuskModifier : IHuskModifier
|
||||
{
|
||||
readonly HarvesterHuskModifierInfo info;
|
||||
public HarvesterHuskModifier(HarvesterHuskModifierInfo info)
|
||||
{
|
||||
this.info = info;
|
||||
}
|
||||
|
||||
public string HuskActor(Actor self)
|
||||
{
|
||||
return self.Trait<Harvester>().Fullness > info.FullnessThreshold ? info.FullHuskActor : null;
|
||||
}
|
||||
}
|
||||
}
|
||||
62
OpenRA.Mods.Cnc/Traits/Infiltration/InfiltrateForCash.cs
Normal file
62
OpenRA.Mods.Cnc/Traits/Infiltration/InfiltrateForCash.cs
Normal file
@@ -0,0 +1,62 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2017 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, either version 3 of
|
||||
* the License, or (at your option) any later version. For more
|
||||
* information, see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using OpenRA.Mods.Common.Effects;
|
||||
using OpenRA.Mods.Common.Traits;
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.Mods.Cnc.Traits
|
||||
{
|
||||
[Desc("This structure can be infiltrated causing funds to be stolen.")]
|
||||
class InfiltrateForCashInfo : ITraitInfo
|
||||
{
|
||||
[Desc("Percentage of the victim's resources that will be stolen.")]
|
||||
public readonly int Percentage = 100;
|
||||
|
||||
[Desc("Amount of guaranteed funds to claim when the victim does not have enough resources.",
|
||||
"When negative, the production price of the infiltrating actor will be used instead.")]
|
||||
public readonly int Minimum = -1;
|
||||
|
||||
[Desc("Maximum amount of funds which will be stolen.")]
|
||||
public readonly int Maximum = int.MaxValue;
|
||||
|
||||
[Desc("Sound the victim will hear when they get robbed.")]
|
||||
public readonly string Notification = null;
|
||||
|
||||
public object Create(ActorInitializer init) { return new InfiltrateForCash(this); }
|
||||
}
|
||||
|
||||
class InfiltrateForCash : INotifyInfiltrated
|
||||
{
|
||||
readonly InfiltrateForCashInfo info;
|
||||
|
||||
public InfiltrateForCash(InfiltrateForCashInfo info) { this.info = info; }
|
||||
|
||||
void INotifyInfiltrated.Infiltrated(Actor self, Actor infiltrator)
|
||||
{
|
||||
var targetResources = self.Owner.PlayerActor.Trait<PlayerResources>();
|
||||
var spyResources = infiltrator.Owner.PlayerActor.Trait<PlayerResources>();
|
||||
var spyValue = infiltrator.Info.TraitInfoOrDefault<ValuedInfo>();
|
||||
|
||||
var toTake = Math.Min(info.Maximum, (targetResources.Cash + targetResources.Resources) * info.Percentage / 100);
|
||||
var toGive = Math.Max(toTake, info.Minimum >= 0 ? info.Minimum : spyValue != null ? spyValue.Cost : 0);
|
||||
|
||||
targetResources.TakeCash(toTake);
|
||||
spyResources.GiveCash(toGive);
|
||||
|
||||
if (info.Notification != null)
|
||||
Game.Sound.PlayNotification(self.World.Map.Rules, self.Owner, "Speech", info.Notification, self.Owner.Faction.InternalName);
|
||||
|
||||
self.World.AddFrameEndTask(w => w.Add(new FloatingText(self.CenterPosition, infiltrator.Owner.Color.RGB, FloatingText.FormatCashTick(toGive), 30)));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2017 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, either version 3 of
|
||||
* the License, or (at your option) any later version. For more
|
||||
* information, see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using OpenRA.Mods.Common.Traits;
|
||||
using OpenRA.Mods.Common.Traits.Render;
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.Mods.Cnc.Traits
|
||||
{
|
||||
[Desc("Reveals a decoration sprite to the indicated players when infiltrated.")]
|
||||
class InfiltrateForDecorationInfo : WithDecorationInfo
|
||||
{
|
||||
public override object Create(ActorInitializer init) { return new InfiltrateForDecoration(init.Self, this); }
|
||||
}
|
||||
|
||||
class InfiltrateForDecoration : WithDecoration, INotifyInfiltrated
|
||||
{
|
||||
readonly HashSet<Player> infiltrators = new HashSet<Player>();
|
||||
|
||||
public InfiltrateForDecoration(Actor self, InfiltrateForDecorationInfo info)
|
||||
: base(self, info) { }
|
||||
|
||||
void INotifyInfiltrated.Infiltrated(Actor self, Actor infiltrator)
|
||||
{
|
||||
infiltrators.Add(infiltrator.Owner);
|
||||
}
|
||||
|
||||
protected override bool ShouldRender(Actor self)
|
||||
{
|
||||
return self.World.RenderPlayer == null || infiltrators.Any(i =>
|
||||
Info.ValidStances.HasStance(i.Stances[self.World.RenderPlayer]));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2017 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, either version 3 of
|
||||
* the License, or (at your option) any later version. For more
|
||||
* information, see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using OpenRA.Mods.Common.Traits;
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.Mods.Cnc.Traits
|
||||
{
|
||||
[Desc("Steal and reset the owner's exploration.")]
|
||||
class InfiltrateForExplorationInfo : TraitInfo<InfiltrateForExploration> { }
|
||||
|
||||
class InfiltrateForExploration : INotifyInfiltrated
|
||||
{
|
||||
void INotifyInfiltrated.Infiltrated(Actor self, Actor infiltrator)
|
||||
{
|
||||
infiltrator.Owner.Shroud.Explore(self.Owner.Shroud);
|
||||
if (!self.Owner.HasFogVisibility)
|
||||
self.Owner.Shroud.ResetExploration();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2017 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, either version 3 of
|
||||
* the License, or (at your option) any later version. For more
|
||||
* information, see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using OpenRA.Mods.Common.Traits;
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.Mods.Cnc.Traits
|
||||
{
|
||||
class InfiltrateForPowerOutageInfo : ITraitInfo
|
||||
{
|
||||
public readonly int Duration = 25 * 20;
|
||||
|
||||
public object Create(ActorInitializer init) { return new InfiltrateForPowerOutage(init.Self, this); }
|
||||
}
|
||||
|
||||
class InfiltrateForPowerOutage : INotifyOwnerChanged, INotifyInfiltrated
|
||||
{
|
||||
readonly InfiltrateForPowerOutageInfo info;
|
||||
PowerManager playerPower;
|
||||
|
||||
public InfiltrateForPowerOutage(Actor self, InfiltrateForPowerOutageInfo info)
|
||||
{
|
||||
this.info = info;
|
||||
playerPower = self.Owner.PlayerActor.Trait<PowerManager>();
|
||||
}
|
||||
|
||||
void INotifyInfiltrated.Infiltrated(Actor self, Actor infiltrator)
|
||||
{
|
||||
playerPower.TriggerPowerOutage(info.Duration);
|
||||
}
|
||||
|
||||
void INotifyOwnerChanged.OnOwnerChanged(Actor self, Player oldOwner, Player newOwner)
|
||||
{
|
||||
playerPower = self.Owner.PlayerActor.Trait<PowerManager>();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2017 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, either version 3 of
|
||||
* the License, or (at your option) any later version. For more
|
||||
* information, see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using OpenRA.Mods.Common.Traits;
|
||||
using OpenRA.Primitives;
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.Mods.Cnc.Traits
|
||||
{
|
||||
class InfiltrateForSupportPowerInfo : ITraitInfo
|
||||
{
|
||||
[ActorReference, FieldLoader.Require] public readonly string Proxy = null;
|
||||
|
||||
public object Create(ActorInitializer init) { return new InfiltrateForSupportPower(this); }
|
||||
}
|
||||
|
||||
class InfiltrateForSupportPower : INotifyInfiltrated
|
||||
{
|
||||
readonly InfiltrateForSupportPowerInfo info;
|
||||
|
||||
public InfiltrateForSupportPower(InfiltrateForSupportPowerInfo info)
|
||||
{
|
||||
this.info = info;
|
||||
}
|
||||
|
||||
void INotifyInfiltrated.Infiltrated(Actor self, Actor infiltrator)
|
||||
{
|
||||
infiltrator.World.AddFrameEndTask(w => w.CreateActor(info.Proxy, new TypeDictionary
|
||||
{
|
||||
new OwnerInit(infiltrator.Owner)
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
152
OpenRA.Mods.Cnc/Traits/Infiltration/Infiltrates.cs
Normal file
152
OpenRA.Mods.Cnc/Traits/Infiltration/Infiltrates.cs
Normal file
@@ -0,0 +1,152 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2017 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, either version 3 of
|
||||
* the License, or (at your option) any later version. For more
|
||||
* information, see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
using OpenRA.Mods.Cnc.Activities;
|
||||
using OpenRA.Mods.Common;
|
||||
using OpenRA.Mods.Common.Activities;
|
||||
using OpenRA.Mods.Common.Orders;
|
||||
using OpenRA.Mods.Common.Traits;
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.Mods.Cnc.Traits
|
||||
{
|
||||
class InfiltratesInfo : ITraitInfo
|
||||
{
|
||||
public readonly HashSet<string> Types = new HashSet<string>();
|
||||
|
||||
[VoiceReference] public readonly string Voice = "Action";
|
||||
|
||||
[Desc("What diplomatic stances can be infiltrated by this actor.")]
|
||||
public readonly Stance ValidStances = Stance.Neutral | Stance.Enemy;
|
||||
|
||||
[Desc("Behaviour when entering the structure.",
|
||||
"Possible values are Exit, Suicide, Dispose.")]
|
||||
public readonly EnterBehaviour EnterBehaviour = EnterBehaviour.Dispose;
|
||||
|
||||
[Desc("Notification to play when a building is infiltrated.")]
|
||||
public readonly string Notification = "BuildingInfiltrated";
|
||||
|
||||
[Desc("Experience to grant to the infiltrating player.")]
|
||||
public readonly int PlayerExperience = 0;
|
||||
|
||||
public object Create(ActorInitializer init) { return new Infiltrates(this); }
|
||||
}
|
||||
|
||||
class Infiltrates : IIssueOrder, IResolveOrder, IOrderVoice
|
||||
{
|
||||
readonly InfiltratesInfo info;
|
||||
|
||||
public Infiltrates(InfiltratesInfo info)
|
||||
{
|
||||
this.info = info;
|
||||
}
|
||||
|
||||
public IEnumerable<IOrderTargeter> Orders
|
||||
{
|
||||
get { yield return new InfiltrationOrderTargeter(info); }
|
||||
}
|
||||
|
||||
public Order IssueOrder(Actor self, IOrderTargeter order, Target target, bool queued)
|
||||
{
|
||||
if (order.OrderID != "Infiltrate")
|
||||
return null;
|
||||
|
||||
if (target.Type == TargetType.FrozenActor)
|
||||
return new Order(order.OrderID, self, queued) { ExtraData = target.FrozenActor.ID };
|
||||
|
||||
return new Order(order.OrderID, self, queued) { TargetActor = target.Actor };
|
||||
}
|
||||
|
||||
bool IsValidOrder(Actor self, Order order)
|
||||
{
|
||||
// Not targeting an actor
|
||||
if (order.ExtraData == 0 && order.TargetActor == null)
|
||||
return false;
|
||||
|
||||
IEnumerable<string> targetTypes;
|
||||
|
||||
if (order.ExtraData != 0)
|
||||
{
|
||||
// Targeted an actor under the fog
|
||||
var frozenLayer = self.Owner.PlayerActor.TraitOrDefault<FrozenActorLayer>();
|
||||
if (frozenLayer == null)
|
||||
return false;
|
||||
|
||||
var frozen = frozenLayer.FromID(order.ExtraData);
|
||||
if (frozen == null)
|
||||
return false;
|
||||
|
||||
targetTypes = frozen.TargetTypes;
|
||||
}
|
||||
else
|
||||
targetTypes = order.TargetActor.GetEnabledTargetTypes();
|
||||
|
||||
return info.Types.Overlaps(targetTypes);
|
||||
}
|
||||
|
||||
public string VoicePhraseForOrder(Actor self, Order order)
|
||||
{
|
||||
return order.OrderString == "Infiltrate" && IsValidOrder(self, order)
|
||||
? info.Voice : null;
|
||||
}
|
||||
|
||||
public void ResolveOrder(Actor self, Order order)
|
||||
{
|
||||
if (order.OrderString != "Infiltrate" || !IsValidOrder(self, order))
|
||||
return;
|
||||
|
||||
var target = self.ResolveFrozenActorOrder(order, Color.Red);
|
||||
if (target.Type != TargetType.Actor
|
||||
|| !info.Types.Overlaps(target.Actor.GetAllTargetTypes()))
|
||||
return;
|
||||
|
||||
if (!order.Queued)
|
||||
self.CancelActivity();
|
||||
|
||||
self.SetTargetLine(target, Color.Red);
|
||||
self.QueueActivity(new Infiltrate(self, target.Actor, info.EnterBehaviour, info.ValidStances, info.Notification, info.PlayerExperience));
|
||||
}
|
||||
}
|
||||
|
||||
class InfiltrationOrderTargeter : UnitOrderTargeter
|
||||
{
|
||||
readonly InfiltratesInfo info;
|
||||
|
||||
public InfiltrationOrderTargeter(InfiltratesInfo info)
|
||||
: base("Infiltrate", 7, "enter", true, false)
|
||||
{
|
||||
this.info = info;
|
||||
}
|
||||
|
||||
public override bool CanTargetActor(Actor self, Actor target, TargetModifiers modifiers, ref string cursor)
|
||||
{
|
||||
var stance = self.Owner.Stances[target.Owner];
|
||||
|
||||
if (!info.ValidStances.HasStance(stance))
|
||||
return false;
|
||||
|
||||
return info.Types.Overlaps(target.GetAllTargetTypes());
|
||||
}
|
||||
|
||||
public override bool CanTargetFrozenActor(Actor self, FrozenActor target, TargetModifiers modifiers, ref string cursor)
|
||||
{
|
||||
var stance = self.Owner.Stances[target.Owner];
|
||||
|
||||
if (!info.ValidStances.HasStance(stance))
|
||||
return false;
|
||||
|
||||
return info.Types.Overlaps(target.Info.TraitInfos<ITargetableInfo>().SelectMany(ti => ti.GetTargetTypes()));
|
||||
}
|
||||
}
|
||||
}
|
||||
190
OpenRA.Mods.Cnc/Traits/MadTank.cs
Normal file
190
OpenRA.Mods.Cnc/Traits/MadTank.cs
Normal file
@@ -0,0 +1,190 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2017 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, either version 3 of
|
||||
* the License, or (at your option) any later version. For more
|
||||
* information, see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
using OpenRA.Activities;
|
||||
using OpenRA.GameRules;
|
||||
using OpenRA.Mods.Cnc.Activities;
|
||||
using OpenRA.Mods.Common;
|
||||
using OpenRA.Mods.Common.Activities;
|
||||
using OpenRA.Mods.Common.Orders;
|
||||
using OpenRA.Mods.Common.Traits;
|
||||
using OpenRA.Mods.Common.Traits.Render;
|
||||
using OpenRA.Primitives;
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.Mods.Cnc.Traits
|
||||
{
|
||||
class MadTankInfo : ITraitInfo, IRulesetLoaded, Requires<ExplodesInfo>, Requires<WithFacingSpriteBodyInfo>
|
||||
{
|
||||
[SequenceReference] public readonly string ThumpSequence = "piston";
|
||||
public readonly int ThumpInterval = 8;
|
||||
[WeaponReference]
|
||||
public readonly string ThumpDamageWeapon = "MADTankThump";
|
||||
public readonly int ThumpShakeIntensity = 3;
|
||||
public readonly float2 ThumpShakeMultiplier = new float2(1, 0);
|
||||
public readonly int ThumpShakeTime = 10;
|
||||
|
||||
[Desc("Measured in ticks.")]
|
||||
public readonly int ChargeDelay = 96;
|
||||
public readonly string ChargeSound = "madchrg2.aud";
|
||||
|
||||
[Desc("Measured in ticks.")]
|
||||
public readonly int DetonationDelay = 42;
|
||||
public readonly string DetonationSound = "madexplo.aud";
|
||||
[WeaponReference]
|
||||
public readonly string DetonationWeapon = "MADTankDetonate";
|
||||
|
||||
[ActorReference]
|
||||
public readonly string DriverActor = "e1";
|
||||
|
||||
[VoiceReference] public readonly string Voice = "Action";
|
||||
|
||||
public WeaponInfo ThumpDamageWeaponInfo { get; private set; }
|
||||
public WeaponInfo DetonationWeaponInfo { get; private set; }
|
||||
|
||||
public object Create(ActorInitializer init) { return new MadTank(init.Self, this); }
|
||||
public void RulesetLoaded(Ruleset rules, ActorInfo ai)
|
||||
{
|
||||
ThumpDamageWeaponInfo = rules.Weapons[ThumpDamageWeapon.ToLowerInvariant()];
|
||||
DetonationWeaponInfo = rules.Weapons[DetonationWeapon.ToLowerInvariant()];
|
||||
}
|
||||
}
|
||||
|
||||
class MadTank : IIssueOrder, IResolveOrder, IOrderVoice, ITick, IPreventsTeleport
|
||||
{
|
||||
readonly Actor self;
|
||||
readonly MadTankInfo info;
|
||||
readonly WithFacingSpriteBody wfsb;
|
||||
readonly ScreenShaker screenShaker;
|
||||
bool deployed;
|
||||
int tick;
|
||||
|
||||
public MadTank(Actor self, MadTankInfo info)
|
||||
{
|
||||
this.self = self;
|
||||
this.info = info;
|
||||
wfsb = self.Trait<WithFacingSpriteBody>();
|
||||
screenShaker = self.World.WorldActor.Trait<ScreenShaker>();
|
||||
}
|
||||
|
||||
public void Tick(Actor self)
|
||||
{
|
||||
if (!deployed)
|
||||
return;
|
||||
|
||||
if (++tick >= info.ThumpInterval)
|
||||
{
|
||||
if (info.ThumpDamageWeapon != null)
|
||||
{
|
||||
// Use .FromPos since this weapon needs to affect more than just the MadTank actor
|
||||
info.ThumpDamageWeaponInfo.Impact(Target.FromPos(self.CenterPosition), self, Enumerable.Empty<int>());
|
||||
}
|
||||
|
||||
screenShaker.AddEffect(info.ThumpShakeTime, self.CenterPosition, info.ThumpShakeIntensity, info.ThumpShakeMultiplier);
|
||||
tick = 0;
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<IOrderTargeter> Orders
|
||||
{
|
||||
get
|
||||
{
|
||||
yield return new TargetTypeOrderTargeter(new HashSet<string> { "DetonateAttack" }, "DetonateAttack", 5, "attack", true, false) { ForceAttack = false };
|
||||
yield return new DeployOrderTargeter("Detonate", 5);
|
||||
}
|
||||
}
|
||||
|
||||
public Order IssueOrder(Actor self, IOrderTargeter order, Target target, bool queued)
|
||||
{
|
||||
if (order.OrderID != "DetonateAttack" && order.OrderID != "Detonate")
|
||||
return null;
|
||||
|
||||
if (target.Type == TargetType.FrozenActor)
|
||||
return new Order(order.OrderID, self, queued) { ExtraData = target.FrozenActor.ID };
|
||||
|
||||
return new Order(order.OrderID, self, queued) { TargetActor = target.Actor };
|
||||
}
|
||||
|
||||
public string VoicePhraseForOrder(Actor self, Order order)
|
||||
{
|
||||
return info.Voice;
|
||||
}
|
||||
|
||||
void Detonate()
|
||||
{
|
||||
self.World.AddFrameEndTask(w =>
|
||||
{
|
||||
if (info.DetonationWeapon != null)
|
||||
{
|
||||
// Use .FromPos since this actor is killed. Cannot use Target.FromActor
|
||||
info.DetonationWeaponInfo.Impact(Target.FromPos(self.CenterPosition), self, Enumerable.Empty<int>());
|
||||
}
|
||||
|
||||
self.Kill(self);
|
||||
});
|
||||
}
|
||||
|
||||
void EjectDriver()
|
||||
{
|
||||
var driver = self.World.CreateActor(info.DriverActor.ToLowerInvariant(), new TypeDictionary
|
||||
{
|
||||
new LocationInit(self.Location),
|
||||
new OwnerInit(self.Owner)
|
||||
});
|
||||
var driverMobile = driver.TraitOrDefault<Mobile>();
|
||||
if (driverMobile != null)
|
||||
driverMobile.Nudge(driver, driver, true);
|
||||
}
|
||||
|
||||
public bool PreventsTeleport(Actor self) { return deployed; }
|
||||
|
||||
void StartDetonationSequence()
|
||||
{
|
||||
if (deployed)
|
||||
return;
|
||||
|
||||
self.World.AddFrameEndTask(w => EjectDriver());
|
||||
if (info.ThumpSequence != null)
|
||||
wfsb.PlayCustomAnimationRepeating(self, info.ThumpSequence);
|
||||
deployed = true;
|
||||
self.QueueActivity(new Wait(info.ChargeDelay, false));
|
||||
self.QueueActivity(new CallFunc(() => Game.Sound.Play(SoundType.World, info.ChargeSound, self.CenterPosition)));
|
||||
self.QueueActivity(new Wait(info.DetonationDelay, false));
|
||||
self.QueueActivity(new CallFunc(() => Game.Sound.Play(SoundType.World, info.DetonationSound, self.CenterPosition)));
|
||||
self.QueueActivity(new CallFunc(Detonate));
|
||||
}
|
||||
|
||||
public void ResolveOrder(Actor self, Order order)
|
||||
{
|
||||
if (order.OrderString == "DetonateAttack")
|
||||
{
|
||||
var target = self.ResolveFrozenActorOrder(order, Color.Red);
|
||||
if (target.Type != TargetType.Actor)
|
||||
return;
|
||||
|
||||
if (!order.Queued)
|
||||
self.CancelActivity();
|
||||
|
||||
self.SetTargetLine(target, Color.Red);
|
||||
self.QueueActivity(new MoveAdjacentTo(self, target));
|
||||
self.QueueActivity(new CallFunc(StartDetonationSequence));
|
||||
}
|
||||
else if (order.OrderString == "Detonate")
|
||||
{
|
||||
self.CancelActivity();
|
||||
self.QueueActivity(new CallFunc(StartDetonationSequence));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
66
OpenRA.Mods.Cnc/Traits/Mine.cs
Normal file
66
OpenRA.Mods.Cnc/Traits/Mine.cs
Normal file
@@ -0,0 +1,66 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2017 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, either version 3 of
|
||||
* the License, or (at your option) any later version. For more
|
||||
* information, see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System.Collections.Generic;
|
||||
using OpenRA.Mods.Common.Traits;
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.Mods.Cnc.Traits
|
||||
{
|
||||
class MineInfo : ITraitInfo
|
||||
{
|
||||
public readonly HashSet<string> CrushClasses = new HashSet<string>();
|
||||
public readonly bool AvoidFriendly = true;
|
||||
public readonly bool BlockFriendly = true;
|
||||
public readonly HashSet<string> DetonateClasses = new HashSet<string>();
|
||||
|
||||
public object Create(ActorInitializer init) { return new Mine(this); }
|
||||
}
|
||||
|
||||
class Mine : ICrushable, INotifyCrushed
|
||||
{
|
||||
readonly MineInfo info;
|
||||
|
||||
public Mine(MineInfo info)
|
||||
{
|
||||
this.info = info;
|
||||
}
|
||||
|
||||
void INotifyCrushed.WarnCrush(Actor self, Actor crusher, HashSet<string> crushClasses) { }
|
||||
|
||||
void INotifyCrushed.OnCrush(Actor self, Actor crusher, HashSet<string> crushClasses)
|
||||
{
|
||||
if (!info.CrushClasses.Overlaps(crushClasses))
|
||||
return;
|
||||
|
||||
if (crusher.Info.HasTraitInfo<MineImmuneInfo>() || (self.Owner.Stances[crusher.Owner] == Stance.Ally && info.AvoidFriendly))
|
||||
return;
|
||||
|
||||
var mobile = crusher.TraitOrDefault<Mobile>();
|
||||
if (mobile != null && !info.DetonateClasses.Overlaps(mobile.Info.Crushes))
|
||||
return;
|
||||
|
||||
self.Kill(crusher);
|
||||
}
|
||||
|
||||
bool ICrushable.CrushableBy(Actor self, Actor crusher, HashSet<string> crushClasses)
|
||||
{
|
||||
if (info.BlockFriendly && !crusher.Info.HasTraitInfo<MineImmuneInfo>() && self.Owner.Stances[crusher.Owner] == Stance.Ally)
|
||||
return false;
|
||||
|
||||
return info.CrushClasses.Overlaps(crushClasses);
|
||||
}
|
||||
}
|
||||
|
||||
[Desc("Tag trait for stuff that should not trigger mines.")]
|
||||
class MineImmuneInfo : TraitInfo<MineImmune> { }
|
||||
class MineImmune { }
|
||||
}
|
||||
234
OpenRA.Mods.Cnc/Traits/Minelayer.cs
Normal file
234
OpenRA.Mods.Cnc/Traits/Minelayer.cs
Normal file
@@ -0,0 +1,234 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2017 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, either version 3 of
|
||||
* the License, or (at your option) any later version. For more
|
||||
* information, see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using OpenRA.Graphics;
|
||||
using OpenRA.Mods.Cnc.Activities;
|
||||
using OpenRA.Mods.Common.Orders;
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.Mods.Cnc.Traits
|
||||
{
|
||||
public class MinelayerInfo : ITraitInfo
|
||||
{
|
||||
[ActorReference] public readonly string Mine = "minv";
|
||||
[ActorReference] public readonly HashSet<string> RearmBuildings = new HashSet<string> { "fix" };
|
||||
|
||||
public readonly string AmmoPoolName = "primary";
|
||||
|
||||
public readonly WDist MinefieldDepth = new WDist(1536);
|
||||
|
||||
public object Create(ActorInitializer init) { return new Minelayer(init.Self); }
|
||||
}
|
||||
|
||||
public class Minelayer : IIssueOrder, IResolveOrder, IRenderAboveShroudWhenSelected, ISync
|
||||
{
|
||||
/* TODO: [Sync] when sync can cope with arrays! */
|
||||
public CPos[] Minefield = null;
|
||||
readonly Sprite tile;
|
||||
[Sync] CPos minefieldStart;
|
||||
|
||||
public Minelayer(Actor self)
|
||||
{
|
||||
var tileset = self.World.Map.Tileset.ToLowerInvariant();
|
||||
tile = self.World.Map.Rules.Sequences.GetSequence("overlay", "build-valid-{0}".F(tileset)).GetSprite(0);
|
||||
}
|
||||
|
||||
IEnumerable<IOrderTargeter> IIssueOrder.Orders
|
||||
{
|
||||
get
|
||||
{
|
||||
yield return new BeginMinefieldOrderTargeter();
|
||||
yield return new DeployOrderTargeter("PlaceMine", 5);
|
||||
}
|
||||
}
|
||||
|
||||
Order IIssueOrder.IssueOrder(Actor self, IOrderTargeter order, Target target, bool queued)
|
||||
{
|
||||
switch (order.OrderID)
|
||||
{
|
||||
case "BeginMinefield":
|
||||
var start = self.World.Map.CellContaining(target.CenterPosition);
|
||||
if (self.World.OrderGenerator is MinefieldOrderGenerator)
|
||||
((MinefieldOrderGenerator)self.World.OrderGenerator).AddMinelayer(self, start);
|
||||
else
|
||||
self.World.OrderGenerator = new MinefieldOrderGenerator(self, start);
|
||||
|
||||
return new Order("BeginMinefield", self, false) { TargetLocation = start };
|
||||
case "PlaceMine":
|
||||
return new Order("PlaceMine", self, false) { TargetLocation = self.Location };
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
void IResolveOrder.ResolveOrder(Actor self, Order order)
|
||||
{
|
||||
if (order.OrderString == "BeginMinefield")
|
||||
minefieldStart = order.TargetLocation;
|
||||
|
||||
if (order.OrderString == "PlaceMine")
|
||||
{
|
||||
minefieldStart = order.TargetLocation;
|
||||
Minefield = new CPos[] { order.TargetLocation };
|
||||
self.CancelActivity();
|
||||
self.QueueActivity(new LayMines(self));
|
||||
}
|
||||
|
||||
if (order.OrderString == "PlaceMinefield")
|
||||
{
|
||||
var movement = self.Trait<IPositionable>();
|
||||
|
||||
Minefield = GetMinefieldCells(minefieldStart, order.TargetLocation,
|
||||
self.Info.TraitInfo<MinelayerInfo>().MinefieldDepth)
|
||||
.Where(p => movement.CanEnterCell(p, null, false)).ToArray();
|
||||
|
||||
self.CancelActivity();
|
||||
self.QueueActivity(new LayMines(self));
|
||||
}
|
||||
}
|
||||
|
||||
static IEnumerable<CPos> GetMinefieldCells(CPos start, CPos end, WDist depth)
|
||||
{
|
||||
var mins = new CPos(Math.Min(start.X, end.X), Math.Min(start.Y, end.Y));
|
||||
var maxs = new CPos(Math.Max(start.X, end.X), Math.Max(start.Y, end.Y));
|
||||
|
||||
/* TODO: proper endcaps, if anyone cares (which won't happen unless depth is large) */
|
||||
|
||||
var p = end - start;
|
||||
var q = new float2(p.Y, -p.X);
|
||||
q = (start != end) ? (1 / q.Length) * q : new float2(1, 0);
|
||||
var c = -float2.Dot(q, new float2(start.X, start.Y));
|
||||
|
||||
/* return all points such that |ax + by + c| < depth */
|
||||
|
||||
for (var i = mins.X; i <= maxs.X; i++)
|
||||
for (var j = mins.Y; j <= maxs.Y; j++)
|
||||
if (Math.Abs(q.X * i + q.Y * j + c) * 1024 < depth.Length)
|
||||
yield return new CPos(i, j);
|
||||
}
|
||||
|
||||
IEnumerable<IRenderable> IRenderAboveShroudWhenSelected.RenderAboveShroud(Actor self, WorldRenderer wr)
|
||||
{
|
||||
if (self.Owner != self.World.LocalPlayer || Minefield == null)
|
||||
yield break;
|
||||
|
||||
var pal = wr.Palette(TileSet.TerrainPaletteInternalName);
|
||||
foreach (var c in Minefield)
|
||||
yield return new SpriteRenderable(tile, self.World.Map.CenterOfCell(c),
|
||||
WVec.Zero, -511, pal, 1f, true);
|
||||
}
|
||||
|
||||
class MinefieldOrderGenerator : IOrderGenerator
|
||||
{
|
||||
readonly List<Actor> minelayers;
|
||||
readonly Sprite tileOk;
|
||||
readonly Sprite tileBlocked;
|
||||
readonly CPos minefieldStart;
|
||||
|
||||
public MinefieldOrderGenerator(Actor a, CPos xy)
|
||||
{
|
||||
minelayers = new List<Actor>() { a };
|
||||
minefieldStart = xy;
|
||||
|
||||
var tileset = a.World.Map.Tileset.ToLowerInvariant();
|
||||
tileOk = a.World.Map.Rules.Sequences.GetSequence("overlay", "build-valid-{0}".F(tileset)).GetSprite(0);
|
||||
tileBlocked = a.World.Map.Rules.Sequences.GetSequence("overlay", "build-invalid").GetSprite(0);
|
||||
}
|
||||
|
||||
public void AddMinelayer(Actor a, CPos xy)
|
||||
{
|
||||
minelayers.Add(a);
|
||||
}
|
||||
|
||||
public IEnumerable<Order> Order(World world, CPos cell, int2 worldPixel, MouseInput mi)
|
||||
{
|
||||
if (mi.Button == Game.Settings.Game.MouseButtonPreference.Cancel)
|
||||
{
|
||||
world.CancelInputMode();
|
||||
yield break;
|
||||
}
|
||||
|
||||
var underCursor = world.ScreenMap.ActorsAt(mi)
|
||||
.Where(a => !world.FogObscures(a))
|
||||
.MaxByOrDefault(a => a.Info.HasTraitInfo<SelectableInfo>()
|
||||
? a.Info.TraitInfo<SelectableInfo>().Priority : int.MinValue);
|
||||
|
||||
if (mi.Button == Game.Settings.Game.MouseButtonPreference.Action && underCursor == null)
|
||||
{
|
||||
minelayers.First().World.CancelInputMode();
|
||||
foreach (var minelayer in minelayers)
|
||||
yield return new Order("PlaceMinefield", minelayer, false) { TargetLocation = cell };
|
||||
}
|
||||
}
|
||||
|
||||
public void Tick(World world)
|
||||
{
|
||||
minelayers.RemoveAll(minelayer => !minelayer.IsInWorld || minelayer.IsDead);
|
||||
if (!minelayers.Any())
|
||||
world.CancelInputMode();
|
||||
}
|
||||
|
||||
CPos lastMousePos;
|
||||
public IEnumerable<IRenderable> Render(WorldRenderer wr, World world) { yield break; }
|
||||
public IEnumerable<IRenderable> RenderAboveShroud(WorldRenderer wr, World world)
|
||||
{
|
||||
var minelayer = minelayers.FirstOrDefault(m => m.IsInWorld && !m.IsDead);
|
||||
if (minelayer == null)
|
||||
yield break;
|
||||
|
||||
// We get the biggest depth so we cover all cells that mines could be placed on.
|
||||
var minefield = GetMinefieldCells(minefieldStart, lastMousePos,
|
||||
minelayers.Max(m => m.Info.TraitInfo<MinelayerInfo>().MinefieldDepth));
|
||||
|
||||
var movement = minelayer.Trait<IPositionable>();
|
||||
var pal = wr.Palette(TileSet.TerrainPaletteInternalName);
|
||||
foreach (var c in minefield)
|
||||
{
|
||||
var tile = movement.CanEnterCell(c, null, false) && !world.ShroudObscures(c) ? tileOk : tileBlocked;
|
||||
yield return new SpriteRenderable(tile, world.Map.CenterOfCell(c),
|
||||
WVec.Zero, -511, pal, 1f, true);
|
||||
}
|
||||
}
|
||||
|
||||
public string GetCursor(World world, CPos cell, int2 worldPixel, MouseInput mi)
|
||||
{
|
||||
lastMousePos = cell; return "ability"; /* TODO */
|
||||
}
|
||||
}
|
||||
|
||||
class BeginMinefieldOrderTargeter : IOrderTargeter
|
||||
{
|
||||
public string OrderID { get { return "BeginMinefield"; } }
|
||||
public int OrderPriority { get { return 5; } }
|
||||
public bool TargetOverridesSelection(TargetModifiers modifiers) { return true; }
|
||||
|
||||
public bool CanTarget(Actor self, Target target, List<Actor> othersAtTarget, ref TargetModifiers modifiers, ref string cursor)
|
||||
{
|
||||
if (target.Type != TargetType.Terrain)
|
||||
return false;
|
||||
|
||||
var location = self.World.Map.CellContaining(target.CenterPosition);
|
||||
if (!self.World.Map.Contains(location))
|
||||
return false;
|
||||
|
||||
cursor = "ability";
|
||||
IsQueued = modifiers.HasModifier(TargetModifiers.ForceQueue);
|
||||
|
||||
return !othersAtTarget.Any() && modifiers.HasModifier(TargetModifiers.ForceAttack);
|
||||
}
|
||||
|
||||
public bool IsQueued { get; protected set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2017 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, either version 3 of
|
||||
* the License, or (at your option) any later version. For more
|
||||
* information, see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System.Drawing;
|
||||
using OpenRA.Graphics;
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.Mods.Cnc.Traits
|
||||
{
|
||||
[Desc("Apply palette full screen rotations during chronoshifts. Add this to the world actor.")]
|
||||
public class ChronoshiftPaletteEffectInfo : ITraitInfo
|
||||
{
|
||||
[Desc("Measured in ticks.")]
|
||||
public readonly int ChronoEffectLength = 60;
|
||||
|
||||
public object Create(ActorInitializer init) { return new ChronoshiftPaletteEffect(this); }
|
||||
}
|
||||
|
||||
public class ChronoshiftPaletteEffect : IPaletteModifier, ITick
|
||||
{
|
||||
readonly ChronoshiftPaletteEffectInfo info;
|
||||
int remainingFrames;
|
||||
|
||||
public ChronoshiftPaletteEffect(ChronoshiftPaletteEffectInfo info)
|
||||
{
|
||||
this.info = info;
|
||||
}
|
||||
|
||||
public void Enable()
|
||||
{
|
||||
remainingFrames = info.ChronoEffectLength;
|
||||
}
|
||||
|
||||
public void Tick(Actor self)
|
||||
{
|
||||
if (remainingFrames > 0)
|
||||
remainingFrames--;
|
||||
}
|
||||
|
||||
public void AdjustPalette(IReadOnlyDictionary<string, MutablePalette> palettes)
|
||||
{
|
||||
if (remainingFrames == 0)
|
||||
return;
|
||||
|
||||
var frac = (float)remainingFrames / info.ChronoEffectLength;
|
||||
|
||||
foreach (var pal in palettes)
|
||||
{
|
||||
for (var x = 0; x < Palette.Size; x++)
|
||||
{
|
||||
var orig = pal.Value.GetColor(x);
|
||||
var lum = (int)(255 * orig.GetBrightness());
|
||||
var desat = Color.FromArgb(orig.A, lum, lum, lum);
|
||||
pal.Value.SetColor(x, Exts.ColorLerp(frac, orig, desat));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
56
OpenRA.Mods.Cnc/Traits/PaletteEffects/LightPaletteRotator.cs
Normal file
56
OpenRA.Mods.Cnc/Traits/PaletteEffects/LightPaletteRotator.cs
Normal file
@@ -0,0 +1,56 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2017 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, either version 3 of
|
||||
* the License, or (at your option) any later version. For more
|
||||
* information, see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System.Collections.Generic;
|
||||
using OpenRA.Graphics;
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.Mods.Cnc.Traits
|
||||
{
|
||||
[Desc("Palette effect used for blinking \"animations\" on actors.")]
|
||||
class LightPaletteRotatorInfo : ITraitInfo
|
||||
{
|
||||
public readonly HashSet<string> ExcludePalettes = new HashSet<string>();
|
||||
|
||||
public object Create(ActorInitializer init) { return new LightPaletteRotator(this); }
|
||||
}
|
||||
|
||||
class LightPaletteRotator : ITick, IPaletteModifier
|
||||
{
|
||||
readonly LightPaletteRotatorInfo info;
|
||||
float t = 0;
|
||||
|
||||
public LightPaletteRotator(LightPaletteRotatorInfo info)
|
||||
{
|
||||
this.info = info;
|
||||
}
|
||||
|
||||
public void Tick(Actor self)
|
||||
{
|
||||
t += .5f;
|
||||
}
|
||||
|
||||
public void AdjustPalette(IReadOnlyDictionary<string, MutablePalette> palettes)
|
||||
{
|
||||
foreach (var pal in palettes)
|
||||
{
|
||||
if (info.ExcludePalettes.Contains(pal.Key))
|
||||
continue;
|
||||
|
||||
var rotate = (int)t % 18;
|
||||
if (rotate > 9)
|
||||
rotate = 18 - rotate;
|
||||
|
||||
pal.Value.SetColor(0x67, pal.Value.GetColor(230 + rotate));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
223
OpenRA.Mods.Cnc/Traits/PortableChrono.cs
Normal file
223
OpenRA.Mods.Cnc/Traits/PortableChrono.cs
Normal file
@@ -0,0 +1,223 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2017 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, either version 3 of
|
||||
* the License, or (at your option) any later version. For more
|
||||
* information, see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using OpenRA.Graphics;
|
||||
using OpenRA.Mods.Cnc.Activities;
|
||||
using OpenRA.Mods.Common.Graphics;
|
||||
using OpenRA.Mods.Common.Orders;
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.Mods.Cnc.Traits
|
||||
{
|
||||
class PortableChronoInfo : ITraitInfo
|
||||
{
|
||||
[Desc("Cooldown in ticks until the unit can teleport.")]
|
||||
public readonly int ChargeDelay = 500;
|
||||
|
||||
[Desc("Can the unit teleport only a certain distance?")]
|
||||
public readonly bool HasDistanceLimit = true;
|
||||
|
||||
[Desc("The maximum distance in cells this unit can teleport (only used if HasDistanceLimit = true).")]
|
||||
public readonly int MaxDistance = 12;
|
||||
|
||||
[Desc("Sound to play when teleporting.")]
|
||||
public readonly string ChronoshiftSound = "chrotnk1.aud";
|
||||
|
||||
[Desc("Cursor to display when able to deploy the actor.")]
|
||||
public readonly string DeployCursor = "deploy";
|
||||
|
||||
[Desc("Cursor to display when unable to deploy the actor.")]
|
||||
public readonly string DeployBlockedCursor = "deploy-blocked";
|
||||
|
||||
[Desc("Cursor to display when targeting a teleport location.")]
|
||||
public readonly string TargetCursor = "chrono-target";
|
||||
|
||||
[Desc("Cursor to display when the targeted location is blocked.")]
|
||||
public readonly string TargetBlockedCursor = "move-blocked";
|
||||
|
||||
[VoiceReference] public readonly string Voice = "Action";
|
||||
|
||||
public object Create(ActorInitializer init) { return new PortableChrono(this); }
|
||||
}
|
||||
|
||||
class PortableChrono : IIssueOrder, IResolveOrder, ITick, ISelectionBar, IOrderVoice, ISync
|
||||
{
|
||||
[Sync] int chargeTick = 0;
|
||||
public readonly PortableChronoInfo Info;
|
||||
|
||||
public PortableChrono(PortableChronoInfo info)
|
||||
{
|
||||
Info = info;
|
||||
}
|
||||
|
||||
public void Tick(Actor self)
|
||||
{
|
||||
if (chargeTick > 0)
|
||||
chargeTick--;
|
||||
}
|
||||
|
||||
public IEnumerable<IOrderTargeter> Orders
|
||||
{
|
||||
get
|
||||
{
|
||||
yield return new PortableChronoOrderTargeter(Info.TargetCursor);
|
||||
yield return new DeployOrderTargeter("PortableChronoDeploy", 5,
|
||||
() => CanTeleport ? Info.DeployCursor : Info.DeployBlockedCursor);
|
||||
}
|
||||
}
|
||||
|
||||
public Order IssueOrder(Actor self, IOrderTargeter order, Target target, bool queued)
|
||||
{
|
||||
if (order.OrderID == "PortableChronoDeploy" && CanTeleport)
|
||||
self.World.OrderGenerator = new PortableChronoOrderGenerator(self, Info);
|
||||
|
||||
if (order.OrderID == "PortableChronoTeleport")
|
||||
return new Order(order.OrderID, self, queued) { TargetLocation = self.World.Map.CellContaining(target.CenterPosition) };
|
||||
|
||||
return new Order(order.OrderID, self, queued) { TargetActor = target.Actor };
|
||||
}
|
||||
|
||||
public void ResolveOrder(Actor self, Order order)
|
||||
{
|
||||
if (order.OrderString == "PortableChronoTeleport" && CanTeleport)
|
||||
{
|
||||
var maxDistance = Info.HasDistanceLimit ? Info.MaxDistance : (int?)null;
|
||||
self.CancelActivity();
|
||||
self.QueueActivity(new Teleport(self, order.TargetLocation, maxDistance, true, false, Info.ChronoshiftSound));
|
||||
}
|
||||
}
|
||||
|
||||
public string VoicePhraseForOrder(Actor self, Order order)
|
||||
{
|
||||
return order.OrderString == "PortableChronoTeleport" && CanTeleport ? Info.Voice : null;
|
||||
}
|
||||
|
||||
public void ResetChargeTime()
|
||||
{
|
||||
chargeTick = Info.ChargeDelay;
|
||||
}
|
||||
|
||||
public bool CanTeleport
|
||||
{
|
||||
get { return chargeTick <= 0; }
|
||||
}
|
||||
|
||||
float ISelectionBar.GetValue()
|
||||
{
|
||||
return (float)(Info.ChargeDelay - chargeTick) / Info.ChargeDelay;
|
||||
}
|
||||
|
||||
Color ISelectionBar.GetColor() { return Color.Magenta; }
|
||||
bool ISelectionBar.DisplayWhenEmpty { get { return false; } }
|
||||
}
|
||||
|
||||
class PortableChronoOrderTargeter : IOrderTargeter
|
||||
{
|
||||
readonly string targetCursor;
|
||||
|
||||
public PortableChronoOrderTargeter(string targetCursor)
|
||||
{
|
||||
this.targetCursor = targetCursor;
|
||||
}
|
||||
|
||||
public string OrderID { get { return "PortableChronoTeleport"; } }
|
||||
public int OrderPriority { get { return 5; } }
|
||||
public bool IsQueued { get; protected set; }
|
||||
public bool TargetOverridesSelection(TargetModifiers modifiers) { return true; }
|
||||
|
||||
public bool CanTarget(Actor self, Target target, List<Actor> othersAtTarget, ref TargetModifiers modifiers, ref string cursor)
|
||||
{
|
||||
// TODO: When target modifiers are configurable this needs to be revisited
|
||||
if (modifiers.HasModifier(TargetModifiers.ForceMove) || modifiers.HasModifier(TargetModifiers.ForceQueue))
|
||||
{
|
||||
var xy = self.World.Map.CellContaining(target.CenterPosition);
|
||||
|
||||
IsQueued = modifiers.HasModifier(TargetModifiers.ForceQueue);
|
||||
|
||||
if (self.IsInWorld && self.Owner.Shroud.IsExplored(xy))
|
||||
{
|
||||
cursor = targetCursor;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
class PortableChronoOrderGenerator : IOrderGenerator
|
||||
{
|
||||
readonly Actor self;
|
||||
readonly PortableChronoInfo info;
|
||||
|
||||
public PortableChronoOrderGenerator(Actor self, PortableChronoInfo info)
|
||||
{
|
||||
this.self = self;
|
||||
this.info = info;
|
||||
}
|
||||
|
||||
public IEnumerable<Order> Order(World world, CPos cell, int2 worldPixel, MouseInput mi)
|
||||
{
|
||||
if (mi.Button == Game.Settings.Game.MouseButtonPreference.Cancel)
|
||||
{
|
||||
world.CancelInputMode();
|
||||
yield break;
|
||||
}
|
||||
|
||||
if (self.IsInWorld && self.Location != cell
|
||||
&& self.Trait<PortableChrono>().CanTeleport && self.Owner.Shroud.IsExplored(cell))
|
||||
{
|
||||
world.CancelInputMode();
|
||||
yield return new Order("PortableChronoTeleport", self, mi.Modifiers.HasModifier(Modifiers.Shift)) { TargetLocation = cell };
|
||||
}
|
||||
}
|
||||
|
||||
public void Tick(World world)
|
||||
{
|
||||
if (!self.IsInWorld || self.IsDead)
|
||||
world.CancelInputMode();
|
||||
}
|
||||
|
||||
public IEnumerable<IRenderable> Render(WorldRenderer wr, World world)
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
|
||||
public IEnumerable<IRenderable> RenderAboveShroud(WorldRenderer wr, World world)
|
||||
{
|
||||
if (!self.IsInWorld || self.Owner != self.World.LocalPlayer)
|
||||
yield break;
|
||||
|
||||
if (!self.Trait<PortableChrono>().Info.HasDistanceLimit)
|
||||
yield break;
|
||||
|
||||
yield return new RangeCircleRenderable(
|
||||
self.CenterPosition,
|
||||
WDist.FromCells(self.Trait<PortableChrono>().Info.MaxDistance),
|
||||
0,
|
||||
Color.FromArgb(128, Color.LawnGreen),
|
||||
Color.FromArgb(96, Color.Black));
|
||||
}
|
||||
|
||||
public string GetCursor(World world, CPos cell, int2 worldPixel, MouseInput mi)
|
||||
{
|
||||
if (self.IsInWorld && self.Location != cell
|
||||
&& self.Trait<PortableChrono>().CanTeleport && self.Owner.Shroud.IsExplored(cell))
|
||||
return info.TargetCursor;
|
||||
else
|
||||
return info.TargetBlockedCursor;
|
||||
}
|
||||
}
|
||||
}
|
||||
64
OpenRA.Mods.Cnc/Traits/Render/RenderJammerCircle.cs
Normal file
64
OpenRA.Mods.Cnc/Traits/Render/RenderJammerCircle.cs
Normal file
@@ -0,0 +1,64 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2017 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, either version 3 of
|
||||
* the License, or (at your option) any later version. For more
|
||||
* information, see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using OpenRA.Graphics;
|
||||
using OpenRA.Mods.Common.Graphics;
|
||||
using OpenRA.Mods.Common.Traits;
|
||||
using OpenRA.Mods.Common.Traits.Radar;
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.Mods.Cnc.Traits
|
||||
{
|
||||
// TODO: remove all the Render*Circle duplication
|
||||
class RenderJammerCircleInfo : TraitInfo<RenderJammerCircle>, IPlaceBuildingDecorationInfo
|
||||
{
|
||||
public IEnumerable<IRenderable> Render(WorldRenderer wr, World w, ActorInfo ai, WPos centerPosition)
|
||||
{
|
||||
var jamsMissiles = ai.TraitInfoOrDefault<JamsMissilesInfo>();
|
||||
if (jamsMissiles != null)
|
||||
{
|
||||
yield return new RangeCircleRenderable(
|
||||
centerPosition,
|
||||
jamsMissiles.Range,
|
||||
0,
|
||||
Color.FromArgb(128, Color.Red),
|
||||
Color.FromArgb(96, Color.Black));
|
||||
}
|
||||
|
||||
foreach (var a in w.ActorsWithTrait<RenderJammerCircle>())
|
||||
if (a.Actor.Owner.IsAlliedWith(w.RenderPlayer))
|
||||
foreach (var r in a.Trait.RenderAboveShroud(a.Actor, wr))
|
||||
yield return r;
|
||||
}
|
||||
}
|
||||
|
||||
class RenderJammerCircle : IRenderAboveShroudWhenSelected
|
||||
{
|
||||
public IEnumerable<IRenderable> RenderAboveShroud(Actor self, WorldRenderer wr)
|
||||
{
|
||||
if (!self.Owner.IsAlliedWith(self.World.RenderPlayer))
|
||||
yield break;
|
||||
|
||||
var jamsMissiles = self.Info.TraitInfoOrDefault<JamsMissilesInfo>();
|
||||
if (jamsMissiles != null)
|
||||
{
|
||||
yield return new RangeCircleRenderable(
|
||||
self.CenterPosition,
|
||||
jamsMissiles.Range,
|
||||
0,
|
||||
Color.FromArgb(128, Color.Red),
|
||||
Color.FromArgb(96, Color.Black));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
60
OpenRA.Mods.Cnc/Traits/Render/RenderShroudCircle.cs
Normal file
60
OpenRA.Mods.Cnc/Traits/Render/RenderShroudCircle.cs
Normal file
@@ -0,0 +1,60 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2017 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, either version 3 of
|
||||
* the License, or (at your option) any later version. For more
|
||||
* information, see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
using OpenRA.Graphics;
|
||||
using OpenRA.Mods.Common.Graphics;
|
||||
using OpenRA.Mods.Common.Traits;
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.Mods.Cnc.Traits
|
||||
{
|
||||
class RenderShroudCircleInfo : TraitInfo<RenderShroudCircle>, IPlaceBuildingDecorationInfo
|
||||
{
|
||||
public IEnumerable<IRenderable> Render(WorldRenderer wr, World w, ActorInfo ai, WPos centerPosition)
|
||||
{
|
||||
var localRange = new RangeCircleRenderable(
|
||||
centerPosition,
|
||||
ai.TraitInfo<CreatesShroudInfo>().Range,
|
||||
0,
|
||||
Color.FromArgb(128, Color.Cyan),
|
||||
Color.FromArgb(96, Color.Black));
|
||||
|
||||
var otherRanges = w.ActorsWithTrait<RenderShroudCircle>()
|
||||
.SelectMany(a => a.Trait.RangeCircleRenderables(a.Actor, wr));
|
||||
|
||||
return otherRanges.Append(localRange);
|
||||
}
|
||||
}
|
||||
|
||||
class RenderShroudCircle : IRenderAboveShroudWhenSelected
|
||||
{
|
||||
public IEnumerable<IRenderable> RangeCircleRenderables(Actor self, WorldRenderer wr)
|
||||
{
|
||||
if (!self.Owner.IsAlliedWith(self.World.RenderPlayer))
|
||||
yield break;
|
||||
|
||||
yield return new RangeCircleRenderable(
|
||||
self.CenterPosition,
|
||||
self.Info.TraitInfo<CreatesShroudInfo>().Range,
|
||||
0,
|
||||
Color.FromArgb(128, Color.Cyan),
|
||||
Color.FromArgb(96, Color.Black));
|
||||
}
|
||||
|
||||
IEnumerable<IRenderable> IRenderAboveShroudWhenSelected.RenderAboveShroud(Actor self, WorldRenderer wr)
|
||||
{
|
||||
return RangeCircleRenderables(self, wr);
|
||||
}
|
||||
}
|
||||
}
|
||||
53
OpenRA.Mods.Cnc/Traits/Render/WithDisguisingInfantryBody.cs
Normal file
53
OpenRA.Mods.Cnc/Traits/Render/WithDisguisingInfantryBody.cs
Normal file
@@ -0,0 +1,53 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2017 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, either version 3 of
|
||||
* the License, or (at your option) any later version. For more
|
||||
* information, see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using OpenRA.Mods.Common.Traits;
|
||||
using OpenRA.Mods.Common.Traits.Render;
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.Mods.Cnc.Traits.Render
|
||||
{
|
||||
class WithDisguisingInfantryBodyInfo : WithInfantryBodyInfo, Requires<DisguiseInfo>
|
||||
{
|
||||
public override object Create(ActorInitializer init) { return new WithDisguisingInfantryBody(init, this); }
|
||||
}
|
||||
|
||||
class WithDisguisingInfantryBody : WithInfantryBody
|
||||
{
|
||||
readonly WithDisguisingInfantryBodyInfo info;
|
||||
readonly Disguise disguise;
|
||||
readonly RenderSprites rs;
|
||||
string intendedSprite;
|
||||
|
||||
public WithDisguisingInfantryBody(ActorInitializer init, WithDisguisingInfantryBodyInfo info)
|
||||
: base(init, info)
|
||||
{
|
||||
this.info = info;
|
||||
rs = init.Self.Trait<RenderSprites>();
|
||||
disguise = init.Self.Trait<Disguise>();
|
||||
intendedSprite = disguise.AsSprite;
|
||||
}
|
||||
|
||||
public override void Tick(Actor self)
|
||||
{
|
||||
if (disguise.AsSprite != intendedSprite)
|
||||
{
|
||||
intendedSprite = disguise.AsSprite;
|
||||
var sequence = DefaultAnimation.GetRandomExistingSequence(info.StandSequences, Game.CosmeticRandom);
|
||||
if (sequence != null)
|
||||
DefaultAnimation.ChangeImage(intendedSprite ?? rs.GetImage(self), sequence);
|
||||
rs.UpdatePalette();
|
||||
}
|
||||
|
||||
base.Tick(self);
|
||||
}
|
||||
}
|
||||
}
|
||||
87
OpenRA.Mods.Cnc/Traits/Render/WithLandingCraftAnimation.cs
Normal file
87
OpenRA.Mods.Cnc/Traits/Render/WithLandingCraftAnimation.cs
Normal file
@@ -0,0 +1,87 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2017 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, either version 3 of
|
||||
* the License, or (at your option) any later version. For more
|
||||
* information, see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using OpenRA.Mods.Common.Traits;
|
||||
using OpenRA.Mods.Common.Traits.Render;
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.Mods.Cnc.Traits.Render
|
||||
{
|
||||
public class WithLandingCraftAnimationInfo : ITraitInfo, Requires<IMoveInfo>, Requires<WithSpriteBodyInfo>, Requires<CargoInfo>
|
||||
{
|
||||
public readonly HashSet<string> OpenTerrainTypes = new HashSet<string> { "Clear" };
|
||||
[SequenceReference] public readonly string OpenSequence = "open";
|
||||
[SequenceReference] public readonly string CloseSequence = "close";
|
||||
[SequenceReference] public readonly string UnloadSequence = "unload";
|
||||
|
||||
public object Create(ActorInitializer init) { return new WithLandingCraftAnimation(init, this); }
|
||||
}
|
||||
|
||||
public class WithLandingCraftAnimation : ITick
|
||||
{
|
||||
readonly WithLandingCraftAnimationInfo info;
|
||||
readonly Actor self;
|
||||
readonly Cargo cargo;
|
||||
readonly IMove move;
|
||||
readonly WithSpriteBody wsb;
|
||||
bool open;
|
||||
|
||||
public WithLandingCraftAnimation(ActorInitializer init, WithLandingCraftAnimationInfo info)
|
||||
{
|
||||
this.info = info;
|
||||
self = init.Self;
|
||||
cargo = self.Trait<Cargo>();
|
||||
move = self.Trait<IMove>();
|
||||
wsb = init.Self.Trait<WithSpriteBody>();
|
||||
}
|
||||
|
||||
public bool ShouldBeOpen()
|
||||
{
|
||||
if (move.IsMoving || self.CenterPosition.Z > 0)
|
||||
return false;
|
||||
|
||||
return cargo.CurrentAdjacentCells.Any(c => self.World.Map.Contains(c)
|
||||
&& info.OpenTerrainTypes.Contains(self.World.Map.GetTerrainInfo(c).Type));
|
||||
}
|
||||
|
||||
void Open()
|
||||
{
|
||||
if (open || !wsb.DefaultAnimation.HasSequence(info.OpenSequence))
|
||||
return;
|
||||
|
||||
open = true;
|
||||
wsb.PlayCustomAnimation(self, info.OpenSequence, () =>
|
||||
{
|
||||
if (wsb.DefaultAnimation.HasSequence(info.UnloadSequence))
|
||||
wsb.PlayCustomAnimationRepeating(self, info.UnloadSequence);
|
||||
});
|
||||
}
|
||||
|
||||
void Close()
|
||||
{
|
||||
if (!open || !wsb.DefaultAnimation.HasSequence(info.CloseSequence))
|
||||
return;
|
||||
|
||||
open = false;
|
||||
wsb.PlayCustomAnimation(self, info.CloseSequence);
|
||||
}
|
||||
|
||||
public void Tick(Actor self)
|
||||
{
|
||||
if (ShouldBeOpen())
|
||||
Open();
|
||||
else
|
||||
Close();
|
||||
}
|
||||
}
|
||||
}
|
||||
314
OpenRA.Mods.Cnc/Traits/SupportPowers/ChronoshiftPower.cs
Normal file
314
OpenRA.Mods.Cnc/Traits/SupportPowers/ChronoshiftPower.cs
Normal file
@@ -0,0 +1,314 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2017 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, either version 3 of
|
||||
* the License, or (at your option) any later version. For more
|
||||
* information, see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
using OpenRA.Graphics;
|
||||
using OpenRA.Mods.Cnc.Activities;
|
||||
using OpenRA.Mods.Common.Graphics;
|
||||
using OpenRA.Mods.Common.Traits;
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.Mods.Cnc.Traits
|
||||
{
|
||||
class ChronoshiftPowerInfo : SupportPowerInfo
|
||||
{
|
||||
[Desc("Target actor selection radius in cells.")]
|
||||
public readonly int Range = 1;
|
||||
|
||||
[Desc("Seconds until returning after teleportation.")]
|
||||
public readonly int Duration = 30;
|
||||
|
||||
[PaletteReference] public readonly string TargetOverlayPalette = TileSet.TerrainPaletteInternalName;
|
||||
|
||||
public readonly string OverlaySpriteGroup = "overlay";
|
||||
[SequenceReference("OverlaySpriteGroup", true)] public readonly string ValidTileSequencePrefix = "target-valid-";
|
||||
[SequenceReference("OverlaySpriteGroup")] public readonly string InvalidTileSequence = "target-invalid";
|
||||
[SequenceReference("OverlaySpriteGroup")] public readonly string SourceTileSequence = "target-select";
|
||||
|
||||
public readonly bool KillCargo = true;
|
||||
|
||||
[Desc("Cursor sequence to use when selecting targets for the chronoshift.")]
|
||||
public readonly string SelectionCursor = "chrono-select";
|
||||
|
||||
[Desc("Cursor sequence to use when targeting an area for the chronoshift.")]
|
||||
public readonly string TargetCursor = "chrono-target";
|
||||
|
||||
[Desc("Cursor sequence to use when the targeted area is blocked.")]
|
||||
public readonly string TargetBlockedCursor = "move-blocked";
|
||||
|
||||
public override object Create(ActorInitializer init) { return new ChronoshiftPower(init.Self, this); }
|
||||
}
|
||||
|
||||
class ChronoshiftPower : SupportPower
|
||||
{
|
||||
public ChronoshiftPower(Actor self, ChronoshiftPowerInfo info) : base(self, info) { }
|
||||
|
||||
public override void SelectTarget(Actor self, string order, SupportPowerManager manager)
|
||||
{
|
||||
Game.Sound.PlayToPlayer(SoundType.UI, manager.Self.Owner, Info.SelectTargetSound);
|
||||
self.World.OrderGenerator = new SelectChronoshiftTarget(Self.World, order, manager, this);
|
||||
}
|
||||
|
||||
public override void Activate(Actor self, Order order, SupportPowerManager manager)
|
||||
{
|
||||
base.Activate(self, order, manager);
|
||||
|
||||
foreach (var target in UnitsInRange(order.ExtraLocation))
|
||||
{
|
||||
var cs = target.Trait<Chronoshiftable>();
|
||||
var targetCell = target.Location + (order.TargetLocation - order.ExtraLocation);
|
||||
var cpi = Info as ChronoshiftPowerInfo;
|
||||
|
||||
if (self.Owner.Shroud.IsExplored(targetCell) && cs.CanChronoshiftTo(target, targetCell))
|
||||
cs.Teleport(target, targetCell, cpi.Duration * 25, cpi.KillCargo, self);
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<Actor> UnitsInRange(CPos xy)
|
||||
{
|
||||
var range = ((ChronoshiftPowerInfo)Info).Range;
|
||||
var tiles = Self.World.Map.FindTilesInCircle(xy, range);
|
||||
var units = new HashSet<Actor>();
|
||||
foreach (var t in tiles)
|
||||
units.UnionWith(Self.World.ActorMap.GetActorsAt(t));
|
||||
|
||||
return units.Where(a => a.Info.HasTraitInfo<ChronoshiftableInfo>() &&
|
||||
!a.TraitsImplementing<IPreventsTeleport>().Any(condition => condition.PreventsTeleport(a)));
|
||||
}
|
||||
|
||||
public bool SimilarTerrain(CPos xy, CPos sourceLocation)
|
||||
{
|
||||
if (!Self.Owner.Shroud.IsExplored(xy))
|
||||
return false;
|
||||
|
||||
var range = ((ChronoshiftPowerInfo)Info).Range;
|
||||
var sourceTiles = Self.World.Map.FindTilesInCircle(xy, range);
|
||||
var destTiles = Self.World.Map.FindTilesInCircle(sourceLocation, range);
|
||||
|
||||
using (var se = sourceTiles.GetEnumerator())
|
||||
using (var de = destTiles.GetEnumerator())
|
||||
while (se.MoveNext() && de.MoveNext())
|
||||
{
|
||||
var a = se.Current;
|
||||
var b = de.Current;
|
||||
|
||||
if (!Self.Owner.Shroud.IsExplored(a) || !Self.Owner.Shroud.IsExplored(b))
|
||||
return false;
|
||||
|
||||
if (Self.World.Map.GetTerrainIndex(a) != Self.World.Map.GetTerrainIndex(b))
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
class SelectChronoshiftTarget : IOrderGenerator
|
||||
{
|
||||
readonly ChronoshiftPower power;
|
||||
readonly int range;
|
||||
readonly Sprite tile;
|
||||
readonly SupportPowerManager manager;
|
||||
readonly string order;
|
||||
|
||||
public SelectChronoshiftTarget(World world, string order, SupportPowerManager manager, ChronoshiftPower power)
|
||||
{
|
||||
// Clear selection if using Left-Click Orders
|
||||
if (Game.Settings.Game.UseClassicMouseStyle)
|
||||
manager.Self.World.Selection.Clear();
|
||||
|
||||
this.manager = manager;
|
||||
this.order = order;
|
||||
this.power = power;
|
||||
|
||||
var info = (ChronoshiftPowerInfo)power.Info;
|
||||
range = info.Range;
|
||||
tile = world.Map.Rules.Sequences.GetSequence(info.OverlaySpriteGroup, info.SourceTileSequence).GetSprite(0);
|
||||
}
|
||||
|
||||
public IEnumerable<Order> Order(World world, CPos cell, int2 worldPixel, MouseInput mi)
|
||||
{
|
||||
world.CancelInputMode();
|
||||
if (mi.Button == MouseButton.Left)
|
||||
world.OrderGenerator = new SelectDestination(world, order, manager, power, cell);
|
||||
|
||||
yield break;
|
||||
}
|
||||
|
||||
public void Tick(World world)
|
||||
{
|
||||
// Cancel the OG if we can't use the power
|
||||
if (!manager.Powers.ContainsKey(order))
|
||||
world.CancelInputMode();
|
||||
}
|
||||
|
||||
public IEnumerable<IRenderable> RenderAboveShroud(WorldRenderer wr, World world)
|
||||
{
|
||||
var xy = wr.Viewport.ViewToWorld(Viewport.LastMousePos);
|
||||
var targetUnits = power.UnitsInRange(xy).Where(a => !world.FogObscures(a));
|
||||
|
||||
foreach (var unit in targetUnits)
|
||||
if (manager.Self.Owner.CanTargetActor(unit))
|
||||
yield return new SelectionBoxRenderable(unit, Color.Red);
|
||||
}
|
||||
|
||||
public IEnumerable<IRenderable> Render(WorldRenderer wr, World world)
|
||||
{
|
||||
var xy = wr.Viewport.ViewToWorld(Viewport.LastMousePos);
|
||||
var tiles = world.Map.FindTilesInCircle(xy, range);
|
||||
var palette = wr.Palette(((ChronoshiftPowerInfo)power.Info).TargetOverlayPalette);
|
||||
foreach (var t in tiles)
|
||||
yield return new SpriteRenderable(tile, wr.World.Map.CenterOfCell(t), WVec.Zero, -511, palette, 1f, true);
|
||||
}
|
||||
|
||||
public string GetCursor(World world, CPos cell, int2 worldPixel, MouseInput mi)
|
||||
{
|
||||
return ((ChronoshiftPowerInfo)power.Info).SelectionCursor;
|
||||
}
|
||||
}
|
||||
|
||||
class SelectDestination : IOrderGenerator
|
||||
{
|
||||
readonly ChronoshiftPower power;
|
||||
readonly CPos sourceLocation;
|
||||
readonly int range;
|
||||
readonly Sprite validTile, invalidTile, sourceTile;
|
||||
readonly SupportPowerManager manager;
|
||||
readonly string order;
|
||||
|
||||
public SelectDestination(World world, string order, SupportPowerManager manager, ChronoshiftPower power, CPos sourceLocation)
|
||||
{
|
||||
this.manager = manager;
|
||||
this.order = order;
|
||||
this.power = power;
|
||||
this.sourceLocation = sourceLocation;
|
||||
|
||||
var info = (ChronoshiftPowerInfo)power.Info;
|
||||
range = info.Range;
|
||||
|
||||
var tileset = world.Map.Tileset.ToLowerInvariant();
|
||||
validTile = world.Map.Rules.Sequences.GetSequence(info.OverlaySpriteGroup, info.ValidTileSequencePrefix + tileset).GetSprite(0);
|
||||
invalidTile = world.Map.Rules.Sequences.GetSequence(info.OverlaySpriteGroup, info.InvalidTileSequence).GetSprite(0);
|
||||
sourceTile = world.Map.Rules.Sequences.GetSequence(info.OverlaySpriteGroup, info.SourceTileSequence).GetSprite(0);
|
||||
}
|
||||
|
||||
public IEnumerable<Order> Order(World world, CPos cell, int2 worldPixel, MouseInput mi)
|
||||
{
|
||||
if (mi.Button == MouseButton.Right)
|
||||
{
|
||||
world.CancelInputMode();
|
||||
yield break;
|
||||
}
|
||||
|
||||
var ret = OrderInner(cell).FirstOrDefault();
|
||||
if (ret == null)
|
||||
yield break;
|
||||
|
||||
world.CancelInputMode();
|
||||
yield return ret;
|
||||
}
|
||||
|
||||
IEnumerable<Order> OrderInner(CPos xy)
|
||||
{
|
||||
// Cannot chronoshift into unexplored location
|
||||
if (IsValidTarget(xy))
|
||||
yield return new Order(order, manager.Self, false)
|
||||
{
|
||||
TargetLocation = xy,
|
||||
ExtraLocation = sourceLocation,
|
||||
SuppressVisualFeedback = true
|
||||
};
|
||||
}
|
||||
|
||||
public void Tick(World world)
|
||||
{
|
||||
// Cancel the OG if we can't use the power
|
||||
if (!manager.Powers.ContainsKey(order))
|
||||
world.CancelInputMode();
|
||||
}
|
||||
|
||||
public IEnumerable<IRenderable> RenderAboveShroud(WorldRenderer wr, World world)
|
||||
{
|
||||
var xy = wr.Viewport.ViewToWorld(Viewport.LastMousePos);
|
||||
var palette = wr.Palette(power.Info.IconPalette);
|
||||
|
||||
// Destination tiles
|
||||
foreach (var t in world.Map.FindTilesInCircle(xy, range))
|
||||
{
|
||||
var tile = manager.Self.Owner.Shroud.IsExplored(t) ? validTile : invalidTile;
|
||||
yield return new SpriteRenderable(tile, wr.World.Map.CenterOfCell(t), WVec.Zero, -511, palette, 1f, true);
|
||||
}
|
||||
|
||||
// Unit previews
|
||||
foreach (var unit in power.UnitsInRange(sourceLocation))
|
||||
{
|
||||
if (manager.Self.Owner.CanTargetActor(unit))
|
||||
{
|
||||
var targetCell = unit.Location + (xy - sourceLocation);
|
||||
var canEnter = manager.Self.Owner.Shroud.IsExplored(targetCell) &&
|
||||
unit.Trait<Chronoshiftable>().CanChronoshiftTo(unit, targetCell);
|
||||
var tile = canEnter ? validTile : invalidTile;
|
||||
yield return new SpriteRenderable(tile, wr.World.Map.CenterOfCell(targetCell), WVec.Zero, -511, palette, 1f, true);
|
||||
}
|
||||
|
||||
var offset = world.Map.CenterOfCell(xy) - world.Map.CenterOfCell(sourceLocation);
|
||||
if (manager.Self.Owner.CanTargetActor(unit))
|
||||
foreach (var r in unit.Render(wr))
|
||||
yield return r.OffsetBy(offset);
|
||||
}
|
||||
|
||||
foreach (var unit in power.UnitsInRange(sourceLocation))
|
||||
if (manager.Self.Owner.CanTargetActor(unit))
|
||||
yield return new SelectionBoxRenderable(unit, Color.Red);
|
||||
}
|
||||
|
||||
public IEnumerable<IRenderable> Render(WorldRenderer wr, World world)
|
||||
{
|
||||
var palette = wr.Palette(power.Info.IconPalette);
|
||||
|
||||
// Source tiles
|
||||
foreach (var t in world.Map.FindTilesInCircle(sourceLocation, range))
|
||||
yield return new SpriteRenderable(sourceTile, wr.World.Map.CenterOfCell(t), WVec.Zero, -511, palette, 1f, true);
|
||||
}
|
||||
|
||||
bool IsValidTarget(CPos xy)
|
||||
{
|
||||
var canTeleport = false;
|
||||
foreach (var unit in power.UnitsInRange(sourceLocation))
|
||||
{
|
||||
var targetCell = unit.Location + (xy - sourceLocation);
|
||||
if (manager.Self.Owner.Shroud.IsExplored(targetCell) && unit.Trait<Chronoshiftable>().CanChronoshiftTo(unit, targetCell))
|
||||
{
|
||||
canTeleport = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!canTeleport)
|
||||
{
|
||||
// Check the terrain types. This will allow chronoshifts to occur on empty terrain to terrain of
|
||||
// a similar type. This also keeps the cursor from changing in non-visible property, alerting the
|
||||
// chronoshifter of enemy unit presence
|
||||
canTeleport = power.SimilarTerrain(sourceLocation, xy);
|
||||
}
|
||||
|
||||
return canTeleport;
|
||||
}
|
||||
|
||||
public string GetCursor(World world, CPos cell, int2 worldPixel, MouseInput mi)
|
||||
{
|
||||
var powerInfo = (ChronoshiftPowerInfo)power.Info;
|
||||
return IsValidTarget(cell) ? powerInfo.TargetCursor : powerInfo.TargetBlockedCursor;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
120
OpenRA.Mods.Cnc/Traits/SupportPowers/GpsPower.cs
Normal file
120
OpenRA.Mods.Cnc/Traits/SupportPowers/GpsPower.cs
Normal file
@@ -0,0 +1,120 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2017 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, either version 3 of
|
||||
* the License, or (at your option) any later version. For more
|
||||
* information, see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using OpenRA.Effects;
|
||||
using OpenRA.Mods.Cnc.Effects;
|
||||
using OpenRA.Mods.Common.Traits;
|
||||
using OpenRA.Mods.Common.Traits.Radar;
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.Mods.Cnc.Traits
|
||||
{
|
||||
[Desc("Requires `GpsWatcher` on the player actor.")]
|
||||
class GpsPowerInfo : SupportPowerInfo
|
||||
{
|
||||
public readonly int RevealDelay = 0;
|
||||
|
||||
public readonly string DoorImage = "atek";
|
||||
[SequenceReference("DoorImage")] public readonly string DoorSequence = "active";
|
||||
|
||||
[Desc("Palette to use for rendering the launch animation")]
|
||||
[PaletteReference("DoorPaletteIsPlayerPalette")] public readonly string DoorPalette = "player";
|
||||
|
||||
[Desc("Custom palette is a player palette BaseName")]
|
||||
public readonly bool DoorPaletteIsPlayerPalette = true;
|
||||
|
||||
public readonly string SatelliteImage = "sputnik";
|
||||
[SequenceReference("SatelliteImage")] public readonly string SatelliteSequence = "idle";
|
||||
|
||||
[Desc("Palette to use for rendering the satellite projectile")]
|
||||
[PaletteReference("SatellitePaletteIsPlayerPalette")] public readonly string SatellitePalette = "player";
|
||||
|
||||
[Desc("Custom palette is a player palette BaseName")]
|
||||
public readonly bool SatellitePaletteIsPlayerPalette = true;
|
||||
|
||||
[Desc("Requires an actor with an online `ProvidesRadar` to show GPS dots.")]
|
||||
public readonly bool RequiresActiveRadar = true;
|
||||
|
||||
public override object Create(ActorInitializer init) { return new GpsPower(init.Self, this); }
|
||||
}
|
||||
|
||||
class GpsPower : SupportPower, INotifyKilled, INotifySold, INotifyOwnerChanged, ITick
|
||||
{
|
||||
readonly Actor self;
|
||||
readonly GpsPowerInfo info;
|
||||
GpsWatcher owner;
|
||||
|
||||
public GpsPower(Actor self, GpsPowerInfo info)
|
||||
: base(self, info)
|
||||
{
|
||||
this.self = self;
|
||||
this.info = info;
|
||||
owner = self.Owner.PlayerActor.Trait<GpsWatcher>();
|
||||
owner.GpsAdd(self);
|
||||
}
|
||||
|
||||
public override void Charged(Actor self, string key)
|
||||
{
|
||||
self.Owner.PlayerActor.Trait<SupportPowerManager>().Powers[key].Activate(new Order());
|
||||
}
|
||||
|
||||
public override void Activate(Actor self, Order order, SupportPowerManager manager)
|
||||
{
|
||||
base.Activate(self, order, manager);
|
||||
|
||||
self.World.AddFrameEndTask(w =>
|
||||
{
|
||||
Game.Sound.PlayToPlayer(SoundType.World, self.Owner, Info.LaunchSound);
|
||||
Game.Sound.PlayNotification(self.World.Map.Rules, self.Owner, "Speech",
|
||||
Info.LaunchSpeechNotification, self.Owner.Faction.InternalName);
|
||||
|
||||
w.Add(new SatelliteLaunch(self, info));
|
||||
});
|
||||
}
|
||||
|
||||
void INotifyKilled.Killed(Actor self, AttackInfo e) { RemoveGps(self); }
|
||||
|
||||
void INotifySold.Selling(Actor self) { }
|
||||
void INotifySold.Sold(Actor self) { RemoveGps(self); }
|
||||
|
||||
void RemoveGps(Actor self)
|
||||
{
|
||||
// Extra function just in case something needs to be added later
|
||||
owner.GpsRemove(self);
|
||||
}
|
||||
|
||||
void INotifyOwnerChanged.OnOwnerChanged(Actor self, Player oldOwner, Player newOwner)
|
||||
{
|
||||
RemoveGps(self);
|
||||
owner = newOwner.PlayerActor.Trait<GpsWatcher>();
|
||||
owner.GpsAdd(self);
|
||||
}
|
||||
|
||||
bool NoActiveRadar { get { return !self.World.ActorsHavingTrait<ProvidesRadar>(r => !r.IsTraitDisabled).Any(a => a.Owner == self.Owner); } }
|
||||
bool wasDisabled;
|
||||
|
||||
void ITick.Tick(Actor self)
|
||||
{
|
||||
if (!wasDisabled && (self.IsDisabled() || (info.RequiresActiveRadar && NoActiveRadar)))
|
||||
{
|
||||
wasDisabled = true;
|
||||
RemoveGps(self);
|
||||
}
|
||||
else if (wasDisabled && !self.IsDisabled() && !(info.RequiresActiveRadar && NoActiveRadar))
|
||||
{
|
||||
wasDisabled = false;
|
||||
owner.GpsAdd(self);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user