Merge Mods.RA into Mods.Cnc

This commit is contained in:
reaperrr
2017-02-12 01:08:31 +01:00
parent cdfa3fddf8
commit 0c3a786d10
47 changed files with 98 additions and 245 deletions

View 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);
}
}
}

View 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),
}));
}
}
}

View 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;
}
}
}

View 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;
}
}
}

View 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);
}
}
}

View 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));
}
}
}

View 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));
}
}
}

View 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;
}
}
}

View 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()));
}
}
}

View File

@@ -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>

View 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;
}
}
}

View File

@@ -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);
}
}
}
}

View 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);
}
}
}

View 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));
}
}
}

View 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));
}
}
}

View 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);
}
}
}

View 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; }
}
}

View 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 { }
}

View 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); }
}
}

View 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);
}
}
}

View 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));
}
}
}

View 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;
}
}
}

View 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)));
}
}
}

View File

@@ -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]));
}
}
}

View File

@@ -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();
}
}
}

View File

@@ -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>();
}
}
}

View 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.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)
}));
}
}
}

View 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()));
}
}
}

View 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));
}
}
}
}

View 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 { }
}

View 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; }
}
}
}

View File

@@ -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));
}
}
}
}
}

View 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));
}
}
}
}

View 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;
}
}
}

View 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));
}
}
}
}

View 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);
}
}
}

View 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);
}
}
}

View 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();
}
}
}

View 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;
}
}
}
}

View 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);
}
}
}
}