Chronotank teleporation by holding SHIFT or ALT and issuing a move order

This commit is contained in:
Curtis Shmyr
2014-03-22 00:04:29 -06:00
parent 21544ce235
commit 6ced7b274c
10 changed files with 280 additions and 233 deletions

View File

@@ -74,6 +74,7 @@ NEW:
Added the submarine detection ability to Submarines and Missile Subs. Added the submarine detection ability to Submarines and Missile Subs.
Increased the submarine detection range of Gunboat from 3 to 4. Increased the submarine detection range of Gunboat from 3 to 4.
Fixed Spies having an enemy color health bar when disguised as a friendly unit (occurred using the Team Health Colors setting). Fixed Spies having an enemy color health bar when disguised as a friendly unit (occurred using the Team Health Colors setting).
Chrono Tanks can now be teleported in groups by holding SHIFT or ALT and issuing a move order.
Tiberian Dawn: Tiberian Dawn:
Chinook rotors now counter-rotate. Chinook rotors now counter-rotate.
Commando can now plant C4 on bridges. Commando can now plant C4 on bridges.
@@ -129,6 +130,8 @@ NEW:
Muzzleflash definitions have moved from WithMuzzleFlash to each Armament. Only one WithMuzzleFlash is now required per actor. Muzzleflash definitions have moved from WithMuzzleFlash to each Armament. Only one WithMuzzleFlash is now required per actor.
Added an AttackGarrisoned trait that allows passengers to fire through a set of defined ports. Added an AttackGarrisoned trait that allows passengers to fire through a set of defined ports.
Maps can define initial cargo for actors by adding "Cargo: actora, actorb, ..." to the actor reference. Maps can define initial cargo for actors by adding "Cargo: actora, actorb, ..." to the actor reference.
Modified Teleport activity to use the best/closest open cell to the target destination for teleports (for ChronoshiftPower this only applies on the return trip).
Renamed ChronoshiftDeploy trait to PortableChrono.
Server: Server:
Message of the day is now shared between all mods and a default motd.txt gets created in the user directory. Message of the day is now shared between all mods and a default motd.txt gets created in the user directory.
Asset Browser: Asset Browser:

View File

@@ -85,7 +85,7 @@ namespace OpenRA
return ts; return ts;
} }
const int MaxRange = 50; public const int MaxRange = 50;
static List<CVec>[] TilesByDistance = InitTilesByDistance(MaxRange); static List<CVec>[] TilesByDistance = InitTilesByDistance(MaxRange);
public static string GetTerrainType(this World world, CPos cell) public static string GetTerrainType(this World world, CPos cell)

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information #region Copyright & License Information
/* /*
* Copyright 2007-2011 The OpenRA Developers (see AUTHORS) * Copyright 2007-2014 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made * 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 * available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation. For more information, * as published by the Free Software Foundation. For more information,
@@ -8,6 +8,9 @@
*/ */
#endregion #endregion
using System;
using System.Collections.Generic;
using System.Linq;
using OpenRA.Traits; using OpenRA.Traits;
using OpenRA.Mods.RA.Render; using OpenRA.Mods.RA.Render;
@@ -15,21 +18,40 @@ namespace OpenRA.Mods.RA.Activities
{ {
public class Teleport : Activity public class Teleport : Activity
{ {
CPos destination;
bool killCargo;
Actor chronosphere; Actor chronosphere;
CPos destination;
int? maximumDistance;
bool killCargo;
bool screenFlash;
string sound; string sound;
public Teleport(Actor chronosphere, CPos destination, bool killCargo, string sound) const int maxCellSearchRange = WorldUtils.MaxRange;
public Teleport(Actor chronosphere, CPos destination, int? maximumDistance, bool killCargo, bool screenFlash, string sound)
{ {
if (maximumDistance > maxCellSearchRange)
throw new InvalidOperationException("Teleport cannot be used with a maximum teleport distance greater than {0}.".F(maxCellSearchRange));
this.chronosphere = chronosphere; this.chronosphere = chronosphere;
this.destination = destination; this.destination = destination;
this.maximumDistance = maximumDistance;
this.killCargo = killCargo; this.killCargo = killCargo;
this.screenFlash = screenFlash;
this.sound = sound; this.sound = sound;
} }
public override Activity Tick(Actor self) public override Activity Tick(Actor self)
{ {
var pc = self.TraitOrDefault<PortableChrono>();
if (pc != null && !pc.CanTeleport)
return NextActivity;
var bestCell = ChooseBestDestinationCell(self, destination);
if (bestCell == null)
return NextActivity;
destination = bestCell.Value;
Sound.Play(sound, self.CenterPosition); Sound.Play(sound, self.CenterPosition);
Sound.Play(sound, destination.CenterPosition); Sound.Play(sound, destination.CenterPosition);
@@ -51,15 +73,48 @@ namespace OpenRA.Mods.RA.Activities
} }
} }
// Consume teleport charges if this wasn't triggered via chronosphere
if (chronosphere == null && pc != null)
pc.ResetChargeTime();
// Trigger screen desaturate effect // Trigger screen desaturate effect
foreach (var a in self.World.ActorsWithTrait<ChronoshiftPaletteEffect>()) if (screenFlash)
a.Trait.Enable(); foreach (var a in self.World.ActorsWithTrait<ChronoshiftPaletteEffect>())
a.Trait.Enable();
if (chronosphere != null && !chronosphere.Destroyed && chronosphere.HasTrait<RenderBuilding>()) if (chronosphere != null && !chronosphere.Destroyed && chronosphere.HasTrait<RenderBuilding>())
chronosphere.Trait<RenderBuilding>().PlayCustomAnim(chronosphere, "active"); chronosphere.Trait<RenderBuilding>().PlayCustomAnim(chronosphere, "active");
return NextActivity; return NextActivity;
} }
CPos? ChooseBestDestinationCell(Actor self, CPos destination)
{
var restrictTo = maximumDistance == null ? null : self.World.FindTilesInCircle(self.Location, maximumDistance.Value);
if (maximumDistance != null)
destination = restrictTo.OrderBy(x => (x - destination).Length).First();
var pos = self.Trait<IPositionable>();
if (pos.CanEnterCell(destination) && self.Owner.Shroud.IsExplored(destination))
return destination;
var searched = new List<CPos>();
for (int r = 1; r <= maxCellSearchRange || (maximumDistance != null && r <= maximumDistance); r++)
{
foreach (var tile in self.World.FindTilesInCircle(destination, r).Except(searched))
{
if (self.Owner.Shroud.IsExplored(tile)
&& (restrictTo == null || (restrictTo != null && restrictTo.Contains(tile)))
&& pos.CanEnterCell(tile))
return tile;
searched.Add(tile);
}
}
return null;
}
} }
public class SimpleTeleport : Activity public class SimpleTeleport : Activity

View File

@@ -1,163 +0,0 @@
#region Copyright & License Information
/*
* Copyright 2007-2011 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation. For more information,
* see COPYING.
*/
#endregion
using System.Drawing;
using System.Collections.Generic;
using OpenRA.Mods.RA.Activities;
using OpenRA.Mods.RA.Orders;
using OpenRA.Traits;
using OpenRA.Graphics;
namespace OpenRA.Mods.RA
{
class ChronoshiftDeployInfo : ITraitInfo
{
public readonly int ChargeTime = 30; // seconds
public readonly int JumpDistance = 10;
public readonly string ChronoshiftSound = "chrotnk1.aud";
public object Create(ActorInitializer init) { return new ChronoshiftDeploy(init.self, this); }
}
class ChronoshiftDeploy : IIssueOrder, IResolveOrder, ITick, IPips, IOrderVoice, ISync
{
[Sync] int chargeTick = 0;
public readonly ChronoshiftDeployInfo Info;
readonly Actor self;
public ChronoshiftDeploy(Actor self, ChronoshiftDeployInfo info)
{
this.self = self;
this.Info = info;
}
public void Tick(Actor self)
{
if (chargeTick > 0)
chargeTick--;
}
public IEnumerable<IOrderTargeter> Orders
{
get { yield return new DeployOrderTargeter("ChronoshiftJump", 5, () => chargeTick <= 0); }
}
public Order IssueOrder(Actor self, IOrderTargeter order, Target target, bool queued)
{
if (order.OrderID == "ChronoshiftJump" && chargeTick <= 0)
self.World.OrderGenerator = new ChronoTankOrderGenerator(self);
return new Order("ChronoshiftJump", self, false); // Hack until we can return null
}
public void ResolveOrder(Actor self, Order order)
{
if (order.OrderString == "ChronoshiftJump")
{
if (CanJumpTo(order.TargetLocation, true))
{
self.CancelActivity();
self.QueueActivity(new Teleport(null, order.TargetLocation, true, Info.ChronoshiftSound));
chargeTick = 25 * Info.ChargeTime;
}
}
}
public string VoicePhraseForOrder(Actor self, Order order)
{
return (order.OrderString == "ChronoshiftDeploy" && chargeTick <= 0) ? "Move" : null;
}
// Display 2 pips indicating the current charge status
public IEnumerable<PipType> GetPips(Actor self)
{
const int numPips = 2;
for (int i = 0; i < numPips; i++)
{
if ((1 - chargeTick * 1.0f / (25 * Info.ChargeTime)) * numPips < i + 1)
{
yield return PipType.Transparent;
continue;
}
yield return PipType.Blue;
}
}
public bool CanJumpTo(CPos xy, bool ignoreVis)
{
var movement = self.TraitOrDefault<IPositionable>();
if (chargeTick <= 0 // Can jump
&& (self.Location - xy).Length <= Info.JumpDistance // Within jump range
&& movement.CanEnterCell(xy) // Can enter cell
&& (ignoreVis || self.Owner.Shroud.IsExplored(xy))) // Not in shroud
return true;
else
return false;
}
}
class ChronoTankOrderGenerator : IOrderGenerator
{
readonly Actor self;
public ChronoTankOrderGenerator(Actor self) { this.self = self; }
public IEnumerable<Order> Order(World world, CPos xy, MouseInput mi)
{
if (mi.Button == Game.mouseButtonPreference.Cancel)
{
world.CancelInputMode();
yield break;
}
var queued = mi.Modifiers.HasModifier(Modifiers.Shift);
var cinfo = self.Trait<ChronoshiftDeploy>();
if (cinfo.CanJumpTo(xy, false))
{
self.World.CancelInputMode();
yield return new Order("ChronoshiftJump", self, queued) { TargetLocation = xy };
}
}
public string GetCursor(World world, CPos xy, MouseInput mi)
{
if (self.IsInWorld && self.Trait<ChronoshiftDeploy>().CanJumpTo(xy,false))
return "chrono-target";
else
return "move-blocked";
}
public void Tick(World world)
{
if (!self.IsInWorld || self.IsDead())
world.CancelInputMode();
}
public IEnumerable<IRenderable> Render(WorldRenderer wr, World world) { yield break; }
public void RenderAfterWorld(WorldRenderer wr, World world)
{
if (!self.IsInWorld)
return;
if (self.Owner != self.World.LocalPlayer)
return;
wr.DrawRangeCircleWithContrast(
self.CenterPosition,
WRange.FromCells(self.Trait<ChronoshiftDeploy>().Info.JumpDistance),
Color.FromArgb(128, Color.DeepSkyBlue),
Color.FromArgb(96, Color.Black)
);
}
}
}

View File

@@ -54,9 +54,7 @@ namespace OpenRA.Mods.RA
if (--ReturnTicks == 0) if (--ReturnTicks == 0)
{ {
self.CancelActivity(); self.CancelActivity();
self.QueueActivity(new Teleport(chronosphere, Origin, null, killCargo, true, info.ChronoshiftSound));
// TODO: need a new Teleport method that will move to the closest available cell
self.QueueActivity(new Teleport(chronosphere, Origin, killCargo, info.ChronoshiftSound));
} }
} }
@@ -90,7 +88,7 @@ namespace OpenRA.Mods.RA
// Set up the teleport // Set up the teleport
self.CancelActivity(); self.CancelActivity();
self.QueueActivity(new Teleport(chronosphere, targetLocation, killCargo, info.ChronoshiftSound)); self.QueueActivity(new Teleport(chronosphere, targetLocation, null, killCargo, true, info.ChronoshiftSound));
return true; return true;
} }

View File

@@ -190,7 +190,6 @@
<Compile Include="ExternalCaptures.cs" /> <Compile Include="ExternalCaptures.cs" />
<Compile Include="Cargo.cs" /> <Compile Include="Cargo.cs" />
<Compile Include="CashTrickler.cs" /> <Compile Include="CashTrickler.cs" />
<Compile Include="ChronoshiftDeploy.cs" />
<Compile Include="ChronoshiftPaletteEffect.cs" /> <Compile Include="ChronoshiftPaletteEffect.cs" />
<Compile Include="Chronoshiftable.cs" /> <Compile Include="Chronoshiftable.cs" />
<Compile Include="Cloak.cs" /> <Compile Include="Cloak.cs" />
@@ -293,6 +292,7 @@
<Compile Include="Player\ClassicProductionQueue.cs" /> <Compile Include="Player\ClassicProductionQueue.cs" />
<Compile Include="Player\PlaceBuilding.cs" /> <Compile Include="Player\PlaceBuilding.cs" />
<Compile Include="Player\ProductionQueue.cs" /> <Compile Include="Player\ProductionQueue.cs" />
<Compile Include="PortableChrono.cs" />
<Compile Include="World\RadarPings.cs" /> <Compile Include="World\RadarPings.cs" />
<Compile Include="Player\TechTree.cs" /> <Compile Include="Player\TechTree.cs" />
<Compile Include="PlayerExts.cs" /> <Compile Include="PlayerExts.cs" />
@@ -453,7 +453,6 @@
<Compile Include="Widgets\Logic\SimpleTooltipLogic.cs" /> <Compile Include="Widgets\Logic\SimpleTooltipLogic.cs" />
<Compile Include="World\DomainIndex.cs" /> <Compile Include="World\DomainIndex.cs" />
<Compile Include="MPStartUnits.cs" /> <Compile Include="MPStartUnits.cs" />
<Compile Include="Orders\SetChronoTankDestination.cs" />
<Compile Include="Widgets\Logic\WorldTooltipLogic.cs" /> <Compile Include="Widgets\Logic\WorldTooltipLogic.cs" />
<Compile Include="TeslaZapRenderable.cs" /> <Compile Include="TeslaZapRenderable.cs" />
<Compile Include="Buildings\Bib.cs" /> <Compile Include="Buildings\Bib.cs" />

View File

@@ -1,56 +0,0 @@
#region Copyright & License Information
/*
* Copyright 2007-2011 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation. For more information,
* see COPYING.
*/
#endregion
using System.Collections.Generic;
using System.Drawing;
using OpenRA.Graphics;
using OpenRA.Traits;
namespace OpenRA.Mods.RA.Orders
{
class SetChronoTankDestination : IOrderGenerator
{
public readonly Actor self;
public SetChronoTankDestination(Actor self)
{
this.self = self;
}
public IEnumerable<Order> Order(World world, CPos xy, MouseInput mi)
{
if (mi.Button == MouseButton.Left)
{
world.CancelInputMode();
yield break;
}
var queued = mi.Modifiers.HasModifier(Modifiers.Shift);
if (world.LocalPlayer.Shroud.IsExplored(xy))
yield return new Order("ChronoshiftSelf", self, queued) { TargetLocation = xy };
}
public void Tick( World world ) { }
public IEnumerable<IRenderable> Render(WorldRenderer wr, World world) { yield break; }
public void RenderAfterWorld( WorldRenderer wr, World world )
{
wr.DrawSelectionBox(self, Color.White);
}
public string GetCursor(World world, CPos xy, MouseInput mi)
{
if (!world.LocalPlayer.Shroud.IsExplored(xy))
return "move-blocked";
var movement = self.TraitOrDefault<IPositionable>();
return (movement.CanEnterCell(xy)) ? "chrono-target" : "move-blocked";
}
}
}

View File

@@ -0,0 +1,201 @@
#region Copyright & License Information
/*
* Copyright 2007-2014 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation. For more information,
* see COPYING.
*/
#endregion
using System.Collections.Generic;
using System.Drawing;
using OpenRA.Mods.RA.Activities;
using OpenRA.Mods.RA.Orders;
using OpenRA.FileFormats;
using OpenRA.Traits;
using OpenRA.Graphics;
namespace OpenRA.Mods.RA
{
class PortableChronoInfo : ITraitInfo
{
[Desc("Cooldown in seconds until the unit can teleport.")]
public readonly int ChargeTime = 30;
[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 = 10;
[Desc("Sound to play when teleporting.")]
public readonly string ChronoshiftSound = "chrotnk1.aud";
public object Create(ActorInitializer init) { return new PortableChrono(this); }
}
class PortableChrono : IIssueOrder, IResolveOrder, ITick, IPips, IOrderVoice, ISync
{
[Sync] int chargeTick = 0;
public readonly PortableChronoInfo Info;
public PortableChrono(PortableChronoInfo info)
{
this.Info = info;
}
public void Tick(Actor self)
{
if (chargeTick > 0)
chargeTick--;
}
public IEnumerable<IOrderTargeter> Orders
{
get
{
yield return new PortableChronoOrderTargeter();
yield return new DeployOrderTargeter("PortableChronoDeploy", 5, () => CanTeleport);
}
}
public Order IssueOrder(Actor self, IOrderTargeter order, Target target, bool queued)
{
if (order.OrderID == "PortableChronoDeploy" && CanTeleport)
self.World.OrderGenerator = new PortableChronoOrderGenerator(self);
if (order.OrderID == "PortableChronoTeleport")
return new Order(order.OrderID, self, queued) { TargetLocation = target.CenterPosition.ToCPos() };
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(null, order.TargetLocation, maxDistance, true, false, Info.ChronoshiftSound));
}
}
public string VoicePhraseForOrder(Actor self, Order order)
{
return order.OrderString == "PortableChronoTeleport" && CanTeleport ? "Move" : null;
}
public void ResetChargeTime()
{
chargeTick = 25 * Info.ChargeTime;
}
public bool CanTeleport
{
get { return chargeTick <= 0; }
}
// Display 2 pips indicating the current charge status
public IEnumerable<PipType> GetPips(Actor self)
{
const int numPips = 2;
for (int i = 0; i < numPips; i++)
{
if ((1 - chargeTick * 1.0f / (25 * Info.ChargeTime)) * numPips < i + 1)
{
yield return PipType.Transparent;
continue;
}
yield return PipType.Blue;
}
}
}
class PortableChronoOrderTargeter : IOrderTargeter
{
public string OrderID { get { return "PortableChronoTeleport"; } }
public int OrderPriority { get { return 5; } }
public bool IsQueued { get; protected set; }
public bool CanTarget(Actor self, Target target, List<Actor> othersAtTarget, 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 = target.CenterPosition.ToCPos();
IsQueued = modifiers.HasModifier(TargetModifiers.ForceQueue);
if (self.IsInWorld && self.Owner.Shroud.IsExplored(xy))
{
cursor = "chrono-target";
return true;
}
return false;
}
return false;
}
}
class PortableChronoOrderGenerator : IOrderGenerator
{
readonly Actor self;
public PortableChronoOrderGenerator(Actor self)
{
this.self = self;
}
public IEnumerable<Order> Order(World world, CPos xy, MouseInput mi)
{
if (mi.Button == Game.mouseButtonPreference.Cancel)
{
world.CancelInputMode();
yield break;
}
if (self.IsInWorld && self.CenterPosition.ToCPos() != xy
&& self.Trait<PortableChrono>().CanTeleport && self.Owner.Shroud.IsExplored(xy))
{
world.CancelInputMode();
yield return new Order("PortableChronoTeleport", self, mi.Modifiers.HasModifier(Modifiers.Shift)) { TargetLocation = xy };
}
}
public void Tick(World world)
{
if (!self.IsInWorld || self.IsDead())
world.CancelInputMode();
}
public IEnumerable<IRenderable> Render(WorldRenderer wr, World world)
{
yield break;
}
public void RenderAfterWorld(WorldRenderer wr, World world)
{
if (!self.IsInWorld || self.Owner != self.World.LocalPlayer)
return;
if (!self.Trait<PortableChrono>().Info.HasDistanceLimit)
return;
wr.DrawRangeCircleWithContrast(
self.CenterPosition,
WRange.FromCells(self.Trait<PortableChrono>().Info.MaxDistance),
Color.FromArgb(128, Color.LawnGreen),
Color.FromArgb(96, Color.Black)
);
}
public string GetCursor(World world, CPos xy, MouseInput mi)
{
if (self.IsInWorld && self.CenterPosition.ToCPos() != xy
&& self.Trait<PortableChrono>().CanTeleport && self.Owner.Shroud.IsExplored(xy))
return "chrono-target";
else
return "move-blocked";
}
}
}

View File

@@ -222,6 +222,16 @@ namespace OpenRA.Utility
} }
} }
// ChronoshiftDeploy was replaced with PortableChrono
if (engineVersion < 20140321)
{
if (depth == 1 && node.Key == "ChronoshiftDeploy")
node.Key = "PortableChrono";
if (depth == 2 && parentKey == "PortableChrono" && node.Key == "JumpDistance")
node.Key = "MaxDistance";
}
UpgradeActorRules(engineVersion, ref node.Value.Nodes, node, depth + 1); UpgradeActorRules(engineVersion, ref node.Value.Nodes, node, depth + 1);
} }
} }

View File

@@ -693,7 +693,7 @@ CTNK:
LocalOffset: 0,171,0 LocalOffset: 0,171,0
LocalYaw: -100 LocalYaw: -100
AttackFrontal: AttackFrontal:
ChronoshiftDeploy: PortableChrono:
QTNK: QTNK:
Inherits: ^Tank Inherits: ^Tank