Merge branch 'master' of git://github.com/chrisforbes/OpenRA
This commit is contained in:
@@ -9,9 +9,9 @@ namespace OpenRa.FileFormats
|
||||
static List<IFolder> mountedFolders = new List<IFolder>();
|
||||
static List<IFolder> temporaryMounts = new List<IFolder>();
|
||||
|
||||
public static void MountDefault( bool useAftermath )
|
||||
public static void MountDefaultPackages()
|
||||
{
|
||||
FileSystem.Mount( new Folder( "./" ) );
|
||||
FileSystem.Mount(new Folder("./"));
|
||||
if( File.Exists( "main.mix" ) )
|
||||
FileSystem.Mount( new Package( "main.mix" ) );
|
||||
FileSystem.Mount( new Package( "redalert.mix" ) );
|
||||
@@ -23,11 +23,12 @@ namespace OpenRa.FileFormats
|
||||
FileSystem.Mount( new Package( "speech.mix" ) );
|
||||
FileSystem.Mount( new Package( "allies.mix" ) );
|
||||
FileSystem.Mount( new Package( "russian.mix" ) );
|
||||
if( useAftermath )
|
||||
{
|
||||
FileSystem.Mount( new Package( "expand2.mix" ) );
|
||||
FileSystem.Mount( new Package( "hires1.mix" ) );
|
||||
}
|
||||
}
|
||||
|
||||
public static void MountAftermathPackages()
|
||||
{
|
||||
FileSystem.Mount( new Package( "expand2.mix" ) );
|
||||
FileSystem.Mount( new Package( "hires1.mix" ) );
|
||||
}
|
||||
|
||||
public static void Mount(IFolder folder)
|
||||
@@ -73,5 +74,20 @@ namespace OpenRa.FileFormats
|
||||
|
||||
throw new FileNotFoundException( string.Format( "File not found: {0}", filename ), filename );
|
||||
}
|
||||
|
||||
public static bool Exists(string filename)
|
||||
{
|
||||
foreach (var folder in mountedFolders)
|
||||
{
|
||||
var s = folder.GetContent(filename);
|
||||
if (s != null)
|
||||
{
|
||||
s.Dispose();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,6 +71,9 @@ namespace OpenRa.Game
|
||||
{
|
||||
get
|
||||
{
|
||||
if (Info != null && Info.SelectionSize != null)
|
||||
return new float2(Info.SelectionSize[0], Info.SelectionSize[1]);
|
||||
|
||||
var firstSprite = Render().FirstOrDefault();
|
||||
if (firstSprite.Sprite == null) return float2.Zero;
|
||||
return firstSprite.Sprite.size;
|
||||
@@ -107,6 +110,8 @@ namespace OpenRa.Game
|
||||
{
|
||||
var size = SelectedSize;
|
||||
var loc = CenterLocation - 0.5f * size;
|
||||
if (Info != null && Info.SelectionSize != null && Info.SelectionSize.Length > 2)
|
||||
loc += new float2(Info.SelectionSize[2], Info.SelectionSize[3]);
|
||||
|
||||
if (useAltitude)
|
||||
{
|
||||
|
||||
@@ -7,17 +7,26 @@ using OpenRa.Game.GameRules;
|
||||
using OpenRa.Game.Graphics;
|
||||
using OpenRa.Game.Orders;
|
||||
using OpenRa.Game.Support;
|
||||
using OpenRa.Game.Traits;
|
||||
|
||||
namespace OpenRa.Game
|
||||
{
|
||||
class Chrome : IHandleInput
|
||||
{
|
||||
readonly Renderer renderer;
|
||||
readonly LineRenderer lineRenderer;
|
||||
readonly Sheet specialBin;
|
||||
readonly SpriteRenderer chromeRenderer;
|
||||
readonly Sprite specialBinSprite;
|
||||
readonly Sprite moneyBinSprite;
|
||||
readonly Sprite tooltipSprite;
|
||||
readonly Sprite powerIndicatorSprite;
|
||||
readonly Sprite powerLevelTopSprite;
|
||||
readonly Sprite powerLevelBottomSprite;
|
||||
|
||||
readonly Animation repairButton;
|
||||
readonly Animation sellButton;
|
||||
|
||||
readonly SpriteRenderer buildPaletteRenderer;
|
||||
readonly Animation cantBuild;
|
||||
readonly Animation ready;
|
||||
@@ -37,17 +46,36 @@ namespace OpenRa.Game
|
||||
// Positioning of chrome elements
|
||||
// Build palette
|
||||
paletteColumns = 4;
|
||||
paletteOrigin = new int2(Game.viewport.Width - paletteColumns * 64 - 9, 240 - 9);
|
||||
paletteOrigin = new int2(Game.viewport.Width - paletteColumns * 64 - 9 - 20, 240 - 9);
|
||||
|
||||
this.renderer = r;
|
||||
specialBin = new Sheet(renderer, "specialbin.png");
|
||||
chromeRenderer = new SpriteRenderer(renderer, true, renderer.RgbaSpriteShader);
|
||||
lineRenderer = new LineRenderer(renderer);
|
||||
buildPaletteRenderer = new SpriteRenderer(renderer, true);
|
||||
|
||||
specialBinSprite = new Sprite(specialBin, new Rectangle(0, 0, 32, 192), TextureChannel.Alpha);
|
||||
moneyBinSprite = new Sprite(specialBin, new Rectangle(512 - 320, 0, 320, 32), TextureChannel.Alpha);
|
||||
tooltipSprite = new Sprite(specialBin, new Rectangle(0, 288, 272, 136), TextureChannel.Alpha);
|
||||
|
||||
var powerIndicator = new Animation("power");
|
||||
powerIndicator.PlayRepeating("power-level-indicator");
|
||||
powerIndicatorSprite = powerIndicator.Image;
|
||||
|
||||
var powerTop = new Animation("powerbar");
|
||||
powerTop.PlayRepeating("powerbar-top");
|
||||
powerLevelTopSprite = powerTop.Image;
|
||||
|
||||
var powerBottom = new Animation("powerbar");
|
||||
powerBottom.PlayRepeating("powerbar-bottom");
|
||||
powerLevelBottomSprite = powerBottom.Image;
|
||||
|
||||
repairButton = new Animation("repair");
|
||||
repairButton.PlayRepeating("normal");
|
||||
|
||||
sellButton = new Animation("sell");
|
||||
sellButton.PlayRepeating("normal");
|
||||
|
||||
blank = SheetBuilder.Add(new Size(64, 48), 16);
|
||||
|
||||
sprites = groups
|
||||
@@ -59,7 +87,7 @@ namespace OpenRa.Game
|
||||
|
||||
tabSprites = groups.Select(
|
||||
(g, i) => Pair.New(g,
|
||||
Util.MakeArray(3,
|
||||
OpenRa.Game.Graphics.Util.MakeArray(3,
|
||||
n => new Sprite(specialBin,
|
||||
new Rectangle(512 - (n + 1) * 27, 64 + i * 40, 27, 40),
|
||||
TextureChannel.Alpha))))
|
||||
@@ -68,7 +96,7 @@ namespace OpenRa.Game
|
||||
cantBuild = new Animation("clock");
|
||||
cantBuild.PlayFetchIndex("idle", () => 0);
|
||||
|
||||
digitSprites = Util.MakeArray(10, a => a)
|
||||
digitSprites = OpenRa.Game.Graphics.Util.MakeArray(10, a => a)
|
||||
.Select(n => new Sprite(specialBin, new Rectangle(32 + 13 * n, 0, 13, 17), TextureChannel.Alpha)).ToList();
|
||||
|
||||
shimSprites = new[]
|
||||
@@ -106,14 +134,17 @@ namespace OpenRa.Game
|
||||
chromeRenderer.DrawSprite(moneyBinSprite, new float2(Game.viewport.Width - 320, 0), PaletteType.Chrome);
|
||||
|
||||
DrawMoney();
|
||||
|
||||
DrawPower();
|
||||
chromeRenderer.Flush();
|
||||
DrawButtons();
|
||||
|
||||
int paletteHeight = DrawBuildPalette(currentTab);
|
||||
DrawBuildTabs(paletteHeight);
|
||||
DrawChat();
|
||||
}
|
||||
|
||||
void AddButton(Rectangle r, Action<bool> b) { buttons.Add(Pair.New(r, b)); }
|
||||
|
||||
void DrawBuildTabs(int paletteHeight)
|
||||
{
|
||||
const int tabWidth = 24;
|
||||
@@ -184,7 +215,64 @@ namespace OpenRa.Game
|
||||
x -= 14;
|
||||
}
|
||||
}
|
||||
|
||||
void DrawPower()
|
||||
{
|
||||
//draw background
|
||||
float2 powerOrigin = Game.viewport.Location+new float2(Game.viewport.Width - 20, 240 - 9);
|
||||
|
||||
buildPaletteRenderer.DrawSprite(powerLevelTopSprite, powerOrigin, PaletteType.Chrome);
|
||||
buildPaletteRenderer.DrawSprite(powerLevelBottomSprite, powerOrigin + new float2(0, powerLevelTopSprite.size.Y), PaletteType.Chrome);
|
||||
buildPaletteRenderer.Flush();
|
||||
float2 top = powerOrigin + new float2(0, 15);
|
||||
float2 bottom = powerOrigin + new float2(0, powerLevelTopSprite.size.Y + powerLevelBottomSprite.size.Y) - new float2(0, 50);
|
||||
|
||||
var scale = 100;
|
||||
while(Game.LocalPlayer.PowerProvided >= scale) scale += 100;
|
||||
//draw bar
|
||||
float2 powerTop = new float2(bottom.X, bottom.Y + (top.Y - bottom.Y) * (Game.LocalPlayer.PowerProvided / (float)scale));
|
||||
|
||||
for(int i = 7; i < 11; i++)
|
||||
lineRenderer.DrawLine(bottom + new float2(i, 0), powerTop + new float2(i, 0), Color.LimeGreen, Color.LimeGreen);
|
||||
lineRenderer.Flush();
|
||||
|
||||
//draw indicator
|
||||
float2 drainedPosition = new float2(bottom.X , bottom.Y + (top.Y - bottom.Y)*(Game.LocalPlayer.PowerDrained/(float) scale));
|
||||
|
||||
buildPaletteRenderer.DrawSprite(powerIndicatorSprite, drainedPosition, PaletteType.Chrome);
|
||||
buildPaletteRenderer.Flush();
|
||||
}
|
||||
|
||||
void DrawButtons()
|
||||
{
|
||||
// Repair
|
||||
Rectangle repairRect = new Rectangle(Game.viewport.Width - 100, 5, repairButton.Image.bounds.Width, repairButton.Image.bounds.Height);
|
||||
var repairDrawPos = Game.viewport.Location + new float2(repairRect.Location);
|
||||
|
||||
var hasFact = Game.world.Actors.Any(a => a.Owner == Game.LocalPlayer && a.traits.Contains<ConstructionYard>());
|
||||
|
||||
if (Game.Settings.RepairRequiresConyard && !hasFact)
|
||||
repairButton.ReplaceAnim("disabled");
|
||||
else
|
||||
{
|
||||
repairButton.ReplaceAnim(Game.controller.orderGenerator is RepairOrderGenerator ? "pressed" : "normal");
|
||||
AddButton(repairRect, isLmb => Game.controller.ToggleInputMode<RepairOrderGenerator>());
|
||||
}
|
||||
buildPaletteRenderer.DrawSprite(repairButton.Image, repairDrawPos, PaletteType.Chrome);
|
||||
|
||||
// Sell
|
||||
Rectangle sellRect = new Rectangle(Game.viewport.Width - 60, 5,
|
||||
sellButton.Image.bounds.Width, sellButton.Image.bounds.Height);
|
||||
|
||||
var sellDrawPos = Game.viewport.Location + new float2(sellRect.Location);
|
||||
|
||||
sellButton.ReplaceAnim(Game.controller.orderGenerator is SellOrderGenerator ? "pressed" : "normal");
|
||||
|
||||
AddButton(sellRect, isLmb => Game.controller.ToggleInputMode<SellOrderGenerator>());
|
||||
buildPaletteRenderer.DrawSprite(sellButton.Image, sellDrawPos, PaletteType.Chrome);
|
||||
buildPaletteRenderer.Flush();
|
||||
}
|
||||
|
||||
void DrawChat()
|
||||
{
|
||||
var chatpos = new int2(400, Game.viewport.Height - 20);
|
||||
@@ -300,8 +388,7 @@ namespace OpenRa.Game
|
||||
overlayBits.Add(Pair.New(cantBuild.Image, drawPos));
|
||||
|
||||
var closureItem = item;
|
||||
buttons.Add(Pair.New(rect,
|
||||
(Action<bool>)(isLmb => HandleBuildPalette(closureItem, isLmb))));
|
||||
AddButton(rect, isLmb => HandleBuildPalette(closureItem, isLmb));
|
||||
if (++x == columns) { x = 0; y++; }
|
||||
}
|
||||
|
||||
@@ -310,7 +397,7 @@ namespace OpenRa.Game
|
||||
var rect = new Rectangle(origin.X + x * 64, origin.Y + 48 * y, 64, 48);
|
||||
var drawPos = Game.viewport.Location + new float2(rect.Location);
|
||||
buildPaletteRenderer.DrawSprite(blank, drawPos, PaletteType.Chrome);
|
||||
buttons.Add(Pair.New(rect, (Action<bool>)(_ => { })));
|
||||
AddButton(rect, _ => { });
|
||||
if (++x == columns) { x = 0; y++; }
|
||||
}
|
||||
|
||||
|
||||
@@ -27,6 +27,14 @@ namespace OpenRa.Game
|
||||
orderGenerator = new UnitOrderGenerator(new Actor[] { });
|
||||
}
|
||||
|
||||
public void ToggleInputMode<T>() where T : IOrderGenerator, new()
|
||||
{
|
||||
if (orderGenerator is T)
|
||||
CancelInputMode();
|
||||
else
|
||||
orderGenerator = new T();
|
||||
}
|
||||
|
||||
List<Order> recentOrders = new List<Order>();
|
||||
|
||||
void ApplyOrders(float2 xy, MouseInput mi)
|
||||
@@ -133,60 +141,14 @@ namespace OpenRa.Game
|
||||
|
||||
public Cursor ChooseCursor()
|
||||
{
|
||||
var mods = GetModifierKeys();
|
||||
|
||||
var mi = new MouseInput {
|
||||
Location = (Game.CellSize * dragEnd - Game.viewport.Location).ToInt2(),
|
||||
Button = MouseButton.Right,
|
||||
Modifiers = mods,
|
||||
IsFake = true,
|
||||
var mi = new MouseInput
|
||||
{
|
||||
Location = (Game.CellSize * MousePosition - Game.viewport.Location).ToInt2(),
|
||||
Button = MouseButton.Right,
|
||||
Modifiers = GetModifierKeys(),
|
||||
};
|
||||
|
||||
var c = orderGenerator.Order(dragEnd.ToInt2(), mi)
|
||||
.Where(o => o.Validate())
|
||||
.Select(o => CursorForOrderString(o.OrderString, o.Subject, o.TargetLocation))
|
||||
.FirstOrDefault(a => a != null);
|
||||
|
||||
return c ??
|
||||
(Game.SelectActorsInBox(Game.CellSize * dragEnd, Game.CellSize * dragEnd).Any()
|
||||
? Cursor.Select : Cursor.Default);
|
||||
}
|
||||
|
||||
Cursor CursorForOrderString( string s, Actor a, int2 location )
|
||||
{
|
||||
var movement = a.traits.WithInterface<IMovement>().FirstOrDefault();
|
||||
switch( s )
|
||||
{
|
||||
case "Attack": return Cursor.Attack;
|
||||
case "Heal": return Cursor.Heal;
|
||||
case "C4": return Cursor.C4;
|
||||
case "Move":
|
||||
if (movement.CanEnterCell(location))
|
||||
return Cursor.Move;
|
||||
else
|
||||
return Cursor.MoveBlocked;
|
||||
case "DeployMcv":
|
||||
var factBuildingInfo = (BuildingInfo)Rules.UnitInfo[ "fact" ];
|
||||
if( Game.CanPlaceBuilding( factBuildingInfo, a.Location - new int2( 1, 1 ), a, false ) )
|
||||
return Cursor.Deploy;
|
||||
else
|
||||
return Cursor.DeployBlocked;
|
||||
case "Deploy": return Cursor.Deploy;
|
||||
case "Chronoshift":
|
||||
if (movement.CanEnterCell(location))
|
||||
return Cursor.Chronoshift;
|
||||
else
|
||||
return Cursor.MoveBlocked;
|
||||
case "Enter": return Cursor.Enter;
|
||||
case "Infiltrate": return Cursor.Enter;
|
||||
case "Capture": return Cursor.Capture;
|
||||
case "Harvest": return Cursor.Attack; // TODO: special harvest cursor?
|
||||
case "PlaceBuilding": return Cursor.Default;
|
||||
case "Sell": return Cursor.Sell;
|
||||
case "NoSell": return Cursor.SellBlocked;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
return orderGenerator.GetCursor(MousePosition.ToInt2(), mi);
|
||||
}
|
||||
|
||||
Cache<int, List<Actor>> controlGroups = new Cache<int, List<Actor>>(_ => new List<Actor>());
|
||||
@@ -194,9 +156,11 @@ namespace OpenRa.Game
|
||||
public void DoControlGroup(int group, Modifiers mods)
|
||||
{
|
||||
var uog = orderGenerator as UnitOrderGenerator;
|
||||
if (uog == null) return;
|
||||
|
||||
if (mods.HasModifier(Modifiers.Ctrl))
|
||||
{
|
||||
if (uog == null || !uog.selection.Any())
|
||||
if (!uog.selection.Any())
|
||||
return;
|
||||
|
||||
controlGroups[group].Clear();
|
||||
@@ -214,7 +178,6 @@ namespace OpenRa.Game
|
||||
return;
|
||||
}
|
||||
|
||||
if (uog == null) return;
|
||||
CombineSelection(controlGroups[group], mods.HasModifier(Modifiers.Shift), false);
|
||||
}
|
||||
|
||||
|
||||
@@ -22,11 +22,13 @@ namespace OpenRa.Game
|
||||
public static Cursor Deploy { get { return new Cursor("deploy"); } }
|
||||
public static Cursor Enter { get { return new Cursor("enter"); } }
|
||||
public static Cursor DeployBlocked { get { return new Cursor("deploy-blocked"); } }
|
||||
public static Cursor Chronoshift { get { return new Cursor("chrono"); } }
|
||||
public static Cursor Chronoshift { get { return new Cursor("chrono"); } }
|
||||
public static Cursor C4 { get { return new Cursor("c4"); } }
|
||||
public static Cursor Capture { get { return new Cursor("capture"); } }
|
||||
public static Cursor Heal { get { return new Cursor("heal"); } }
|
||||
public static Cursor Sell { get { return new Cursor("sell"); } }
|
||||
public static Cursor SellBlocked { get { return new Cursor("sell-blocked"); } }
|
||||
public static Cursor Repair { get { return new Cursor("repair"); } }
|
||||
public static Cursor RepairBlocked { get { return new Cursor("repair-blocked"); } }
|
||||
}
|
||||
}
|
||||
|
||||
27
OpenRa.Game/Effects/RepairIndicator.cs
Normal file
27
OpenRa.Game/Effects/RepairIndicator.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
using System.Collections.Generic;
|
||||
using OpenRa.Game.Graphics;
|
||||
using OpenRa.Game.Traits;
|
||||
|
||||
namespace OpenRa.Game.Effects
|
||||
{
|
||||
class RepairIndicator : IEffect
|
||||
{
|
||||
int framesLeft = (int)(Rules.General.RepairRate * 25 * 60 / 2);
|
||||
Actor a;
|
||||
Animation anim = new Animation("select");
|
||||
|
||||
public RepairIndicator(Actor a) { this.a = a; anim.PlayRepeating("repair"); }
|
||||
|
||||
public void Tick()
|
||||
{
|
||||
if (--framesLeft == 0 || a.IsDead)
|
||||
Game.world.AddFrameEndTask(w => w.Remove(this));
|
||||
}
|
||||
|
||||
public IEnumerable<Renderable> Render()
|
||||
{
|
||||
yield return new Renderable(anim.Image,
|
||||
a.CenterLocation - .5f * anim.Image.size, PaletteType.Chrome);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -22,7 +22,8 @@ namespace OpenRa.Game
|
||||
public static WorldRenderer worldRenderer;
|
||||
public static Controller controller;
|
||||
public static Chrome chrome;
|
||||
|
||||
public static UserSettings Settings;
|
||||
|
||||
public static OrderManager orderManager;
|
||||
|
||||
static int localPlayerIndex;
|
||||
@@ -41,11 +42,6 @@ namespace OpenRa.Game
|
||||
public static BuildingInfluenceMap BuildingInfluence;
|
||||
public static UnitInfluenceMap UnitInfluence;
|
||||
|
||||
public static string Replay;
|
||||
|
||||
public static string NetworkHost;
|
||||
public static int NetworkPort;
|
||||
|
||||
public static bool skipMakeAnims = true;
|
||||
|
||||
static Renderer renderer;
|
||||
@@ -121,13 +117,13 @@ namespace OpenRa.Game
|
||||
|
||||
ChangeMap(mapName);
|
||||
|
||||
if (Replay != "")
|
||||
orderManager = new OrderManager(new IOrderSource[] { new ReplayOrderSource(Replay) });
|
||||
if (Settings.Replay != "")
|
||||
orderManager = new OrderManager(new IOrderSource[] { new ReplayOrderSource(Settings.Replay) });
|
||||
else
|
||||
{
|
||||
var orderSources = (string.IsNullOrEmpty(NetworkHost))
|
||||
var orderSources = (string.IsNullOrEmpty(Settings.NetworkHost))
|
||||
? new IOrderSource[] { new LocalOrderSource() }
|
||||
: new IOrderSource[] { new LocalOrderSource(), new NetworkOrderSource(new TcpClient(NetworkHost, NetworkPort)) };
|
||||
: new IOrderSource[] { new LocalOrderSource(), new NetworkOrderSource(new TcpClient(Settings.NetworkHost, Settings.NetworkPort)) };
|
||||
orderManager = new OrderManager(orderSources, "replay.rep");
|
||||
}
|
||||
}
|
||||
@@ -149,7 +145,6 @@ namespace OpenRa.Game
|
||||
}
|
||||
|
||||
static int lastTime = Environment.TickCount;
|
||||
public static int timestep = 40;
|
||||
|
||||
public static void ResetTimer()
|
||||
{
|
||||
@@ -166,11 +161,11 @@ namespace OpenRa.Game
|
||||
{
|
||||
int t = Environment.TickCount;
|
||||
int dt = t - lastTime;
|
||||
if (dt >= timestep)
|
||||
if (dt >= Settings.Timestep)
|
||||
{
|
||||
using (new PerfSample("tick_time"))
|
||||
{
|
||||
lastTime += timestep;
|
||||
lastTime += Settings.Timestep;
|
||||
UpdatePalette(world.Actors.SelectMany(
|
||||
a => a.traits.WithInterface<IPaletteModifier>()));
|
||||
orderManager.TickImmediate();
|
||||
|
||||
@@ -10,8 +10,8 @@ namespace OpenRa.Game.GameRules
|
||||
{
|
||||
foreach( var x in ini )
|
||||
{
|
||||
var field = self.GetType().GetField( x.Key );
|
||||
field.SetValue( self, GetValue( field.FieldType, x.Value ) );
|
||||
var field = self.GetType().GetField( x.Key.Trim() );
|
||||
field.SetValue( self, GetValue( field.FieldType, x.Value.Trim() ) );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ namespace OpenRa.Game
|
||||
public static InfoLoader<VoiceInfo> VoiceInfo;
|
||||
public static InfoLoader<SupportPowerInfo> SupportPowerInfo;
|
||||
public static GeneralInfo General;
|
||||
public static AftermathInfo Aftermath;
|
||||
public static AftermathInfo Aftermath;
|
||||
public static TechTree TechTree;
|
||||
public static Map Map;
|
||||
public static TileSet TileSet;
|
||||
@@ -48,7 +48,7 @@ namespace OpenRa.Game
|
||||
General = new GeneralInfo();
|
||||
FieldLoader.Load(General, AllRules.GetSection("General"));
|
||||
|
||||
Aftermath = new AftermathInfo();
|
||||
Aftermath = new AftermathInfo();
|
||||
if (useAftermath)
|
||||
FieldLoader.Load(Aftermath, AllRules.GetSection("Aftermath"));
|
||||
|
||||
|
||||
@@ -54,6 +54,7 @@ namespace OpenRa.Game.GameRules
|
||||
public readonly string LongDesc = null;
|
||||
public readonly int OrePips = 0;
|
||||
public readonly string Icon = null;
|
||||
public readonly int[] SelectionSize = null;
|
||||
|
||||
public UnitInfo(string name) { Name = name; }
|
||||
}
|
||||
|
||||
32
OpenRa.Game/GameRules/UserSettings.cs
Normal file
32
OpenRa.Game/GameRules/UserSettings.cs
Normal file
@@ -0,0 +1,32 @@
|
||||
|
||||
namespace OpenRa.Game.GameRules
|
||||
{
|
||||
class UserSettings
|
||||
{
|
||||
// Debug settings
|
||||
public readonly bool UnitDebug = false;
|
||||
public readonly bool BuildingDebug = false;
|
||||
public readonly bool PathDebug = false;
|
||||
|
||||
// Window settings
|
||||
public readonly int Width = 0;
|
||||
public readonly int Height = 0;
|
||||
public readonly bool Fullscreen = false;
|
||||
|
||||
// Internal game settings
|
||||
public readonly int Timestep = 40;
|
||||
public readonly int SheetSize = 512;
|
||||
|
||||
// External game settings
|
||||
public readonly bool UseAftermath = false;
|
||||
public readonly string NetworkHost = "";
|
||||
public readonly int NetworkPort = 0;
|
||||
public readonly string Map = "scm12ea.ini";
|
||||
public readonly int Player = 1;
|
||||
public readonly string Replay = "";
|
||||
|
||||
// Gameplay options
|
||||
public readonly bool RepairRequiresConyard = false;
|
||||
|
||||
}
|
||||
}
|
||||
@@ -82,7 +82,7 @@ namespace OpenRa.Game.Graphics
|
||||
|
||||
spriteRenderer.Flush();
|
||||
|
||||
DrawBandBox();
|
||||
DrawBandBox();
|
||||
|
||||
if (Game.controller.orderGenerator != null)
|
||||
Game.controller.orderGenerator.Render();
|
||||
|
||||
@@ -6,6 +6,8 @@ using System.Windows.Forms;
|
||||
using OpenRa.FileFormats;
|
||||
using OpenRa.Game.Graphics;
|
||||
using OpenRa.Game.Orders;
|
||||
using OpenRa.Game.GameRules;
|
||||
|
||||
|
||||
namespace OpenRa.Game
|
||||
{
|
||||
@@ -16,52 +18,72 @@ namespace OpenRa.Game
|
||||
static Size GetResolution(Settings settings)
|
||||
{
|
||||
var desktopResolution = Screen.PrimaryScreen.Bounds.Size;
|
||||
|
||||
if (Game.Settings.Width > 0 && Game.Settings.Height > 0)
|
||||
{
|
||||
desktopResolution.Width = Game.Settings.Width;
|
||||
desktopResolution.Height = Game.Settings.Height;
|
||||
}
|
||||
return new Size(
|
||||
settings.GetValue("width", desktopResolution.Width),
|
||||
settings.GetValue("height", desktopResolution.Height));
|
||||
desktopResolution.Width,
|
||||
desktopResolution.Height);
|
||||
}
|
||||
|
||||
[DllImport("user32")]
|
||||
static extern int ShowCursor([MarshalAs(UnmanagedType.Bool)] bool visible);
|
||||
|
||||
|
||||
|
||||
public MainWindow(Settings settings)
|
||||
{
|
||||
|
||||
|
||||
FormBorderStyle = FormBorderStyle.None;
|
||||
BackColor = Color.Black;
|
||||
StartPosition = FormStartPosition.Manual;
|
||||
Location = Point.Empty;
|
||||
Visible = true;
|
||||
|
||||
UiOverlay.ShowUnitDebug = settings.GetValue("udebug", false);
|
||||
UiOverlay.ShowBuildDebug = settings.GetValue("bdebug", false);
|
||||
WorldRenderer.ShowUnitPaths = settings.GetValue("pathdebug", false);
|
||||
Game.timestep = settings.GetValue("rate", 40);
|
||||
Game.Replay = settings.GetValue("replay", "");
|
||||
Game.NetworkHost = settings.GetValue("host", "");
|
||||
Game.NetworkPort = int.Parse(settings.GetValue("port", "0"));
|
||||
|
||||
var useAftermath = bool.Parse(settings.GetValue("aftermath", "false"));
|
||||
|
||||
Renderer.SheetSize = int.Parse(settings.GetValue("sheetsize", "512"));
|
||||
|
||||
|
||||
// Load user settings
|
||||
Game.Settings = new UserSettings();
|
||||
|
||||
while (!File.Exists("redalert.mix"))
|
||||
{
|
||||
var current = Directory.GetCurrentDirectory();
|
||||
if (Directory.GetDirectoryRoot(current) == current)
|
||||
throw new InvalidOperationException("Unable to load MIX files.");
|
||||
Directory.SetCurrentDirectory("..");
|
||||
|
||||
try
|
||||
{
|
||||
// settings.ini should be located with the mix files
|
||||
FileSystem.MountTemporary(new Folder("./"));
|
||||
IniFile SettingsRules = new IniFile(FileSystem.Open("settings.ini"));
|
||||
FieldLoader.Load(Game.Settings, SettingsRules.GetSection("Settings"));
|
||||
FileSystem.UnmountTemporaryPackages();
|
||||
}
|
||||
catch (FileNotFoundException) { }
|
||||
}
|
||||
|
||||
FileSystem.MountDefault(useAftermath);
|
||||
UiOverlay.ShowUnitDebug = Game.Settings.UnitDebug;
|
||||
UiOverlay.ShowBuildDebug = Game.Settings.BuildingDebug;
|
||||
WorldRenderer.ShowUnitPaths = Game.Settings.PathDebug;
|
||||
Renderer.SheetSize = Game.Settings.SheetSize;
|
||||
|
||||
bool windowed = !settings.GetValue("fullscreen", false);
|
||||
|
||||
FileSystem.MountDefaultPackages();
|
||||
|
||||
if (Game.Settings.UseAftermath)
|
||||
{
|
||||
FileSystem.MountAftermathPackages();
|
||||
}
|
||||
|
||||
bool windowed = !Game.Settings.Fullscreen;
|
||||
renderer = new Renderer(this, GetResolution(settings), windowed);
|
||||
|
||||
var controller = new Controller(() => (Modifiers)(int)ModifierKeys); /* a bit of insane input routing */
|
||||
|
||||
Game.Initialize(settings.GetValue("map", "scm12ea.ini"), renderer, new int2(ClientSize),
|
||||
settings.GetValue("player", 1), useAftermath, controller);
|
||||
Game.Initialize(Game.Settings.Map, renderer, new int2(ClientSize),
|
||||
Game.Settings.Player, Game.Settings.UseAftermath, controller);
|
||||
|
||||
ShowCursor(false);
|
||||
Game.ResetTimer();
|
||||
@@ -135,6 +157,8 @@ namespace OpenRa.Game
|
||||
Game.LocalPlayer = Game.players[(Game.LocalPlayer.Index + 1) % 4];
|
||||
if (e.KeyCode == Keys.F3)
|
||||
Game.controller.orderGenerator = new SellOrderGenerator();
|
||||
if (e.KeyCode == Keys.F4)
|
||||
Game.controller.orderGenerator = new RepairOrderGenerator();
|
||||
|
||||
if (!Game.chat.isChatting)
|
||||
if (e.KeyCode >= Keys.D0 && e.KeyCode <= Keys.D9)
|
||||
@@ -176,7 +200,6 @@ namespace OpenRa.Game
|
||||
public int2 Location;
|
||||
public MouseButton Button;
|
||||
public Modifiers Modifiers;
|
||||
public bool IsFake;
|
||||
}
|
||||
|
||||
enum MouseInputEvent { Down, Move, Up };
|
||||
|
||||
@@ -83,6 +83,7 @@
|
||||
<Compile Include="Effects\Corpse.cs" />
|
||||
<Compile Include="Effects\DelayedAction.cs" />
|
||||
<Compile Include="Effects\MoveFlash.cs" />
|
||||
<Compile Include="Effects\RepairIndicator.cs" />
|
||||
<Compile Include="Effects\Smoke.cs" />
|
||||
<Compile Include="Effects\TeslaZap.cs" />
|
||||
<Compile Include="Exts.cs" />
|
||||
@@ -91,6 +92,7 @@
|
||||
<Compile Include="GameRules\GeneralInfo.cs" />
|
||||
<Compile Include="GameRules\SupportPowerInfo.cs" />
|
||||
<Compile Include="GameRules\TechTree.cs" />
|
||||
<Compile Include="GameRules\UserSettings.cs" />
|
||||
<Compile Include="GameRules\VoiceInfo.cs" />
|
||||
<Compile Include="Effects\IEffect.cs" />
|
||||
<Compile Include="Orders\IOrderSource.cs" />
|
||||
@@ -99,7 +101,9 @@
|
||||
<Compile Include="Orders\NetworkOrderSource.cs" />
|
||||
<Compile Include="Orders\OrderIO.cs" />
|
||||
<Compile Include="Orders\OrderManager.cs" />
|
||||
<Compile Include="Orders\RepairOrderGenerator.cs" />
|
||||
<Compile Include="Orders\SellOrderGenerator.cs" />
|
||||
<Compile Include="Orders\TeleportOrderGenerator.cs" />
|
||||
<Compile Include="Ore.cs" />
|
||||
<Compile Include="PathSearch.cs" />
|
||||
<Compile Include="ProductionItem.cs" />
|
||||
@@ -178,6 +182,7 @@
|
||||
<Compile Include="Traits\Activities\Move.cs" />
|
||||
<Compile Include="Traits\Activities\Follow.cs" />
|
||||
<Compile Include="Traits\Activities\Turn.cs" />
|
||||
<Compile Include="Traits\Activities\UndeployMcv.cs" />
|
||||
<Compile Include="Traits\APMine.cs" />
|
||||
<Compile Include="Traits\ATMine.cs" />
|
||||
<Compile Include="Traits\AttackBase.cs" />
|
||||
@@ -198,6 +203,7 @@
|
||||
<Compile Include="Traits\Harvester.cs" />
|
||||
<Compile Include="Traits\Helicopter.cs" />
|
||||
<Compile Include="Traits\InvisibleToOthers.cs" />
|
||||
<Compile Include="Traits\ConstructionYard.cs" />
|
||||
<Compile Include="Traits\MineImmune.cs" />
|
||||
<Compile Include="Traits\Minelayer.cs" />
|
||||
<Compile Include="Traits\LimitedAmmo.cs" />
|
||||
|
||||
@@ -7,5 +7,6 @@ namespace OpenRa.Game
|
||||
IEnumerable<Order> Order( int2 xy, MouseInput mi );
|
||||
void Tick();
|
||||
void Render();
|
||||
Cursor GetCursor(int2 xy, MouseInput mi);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,13 +16,14 @@ namespace OpenRa.Game.Orders
|
||||
|
||||
public IEnumerable<Order> Order(int2 xy, MouseInput mi)
|
||||
{
|
||||
if (mi.IsFake)
|
||||
{
|
||||
// this order is never actually issued, but it's used for choosing a cursor
|
||||
yield return new Order("PlaceBuilding", Producer.Owner.PlayerActor, null, xy, Building.Name);
|
||||
yield break;
|
||||
}
|
||||
if (mi.Button == MouseButton.Right)
|
||||
Game.controller.CancelInputMode();
|
||||
|
||||
return InnerOrder(xy, mi);
|
||||
}
|
||||
|
||||
IEnumerable<Order> InnerOrder(int2 xy, MouseInput mi)
|
||||
{
|
||||
if (mi.Button == MouseButton.Left)
|
||||
{
|
||||
if (!Game.CanPlaceBuilding(Building, xy, null, true)
|
||||
@@ -34,8 +35,6 @@ namespace OpenRa.Game.Orders
|
||||
|
||||
yield return new Order("PlaceBuilding", Producer.Owner.PlayerActor, null, xy, Building.Name);
|
||||
}
|
||||
else
|
||||
Game.controller.CancelInputMode();
|
||||
}
|
||||
|
||||
public void Tick()
|
||||
@@ -49,5 +48,10 @@ namespace OpenRa.Game.Orders
|
||||
{
|
||||
Game.worldRenderer.uiOverlay.DrawBuildingGrid( Building );
|
||||
}
|
||||
|
||||
public Cursor GetCursor(int2 xy, MouseInput mi)
|
||||
{
|
||||
return Cursor.Default;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
58
OpenRa.Game/Orders/RepairOrderGenerator.cs
Normal file
58
OpenRa.Game/Orders/RepairOrderGenerator.cs
Normal file
@@ -0,0 +1,58 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using OpenRa.Game.GameRules;
|
||||
using OpenRa.Game.Traits;
|
||||
|
||||
namespace OpenRa.Game.Orders
|
||||
{
|
||||
class RepairOrderGenerator : IOrderGenerator
|
||||
{
|
||||
public IEnumerable<Order> Order(int2 xy, MouseInput mi)
|
||||
{
|
||||
if (mi.Button == MouseButton.Right)
|
||||
Game.controller.CancelInputMode();
|
||||
|
||||
return OrderInner(xy, mi);
|
||||
}
|
||||
|
||||
IEnumerable<Order> OrderInner(int2 xy, MouseInput mi)
|
||||
{
|
||||
if (mi.Button == MouseButton.Left)
|
||||
{
|
||||
var loc = mi.Location + Game.viewport.Location;
|
||||
var underCursor = Game.FindUnits(loc, loc)
|
||||
.Where(a => a.Owner == Game.LocalPlayer
|
||||
&& a.traits.Contains<Building>()
|
||||
&& a.Info.Selectable).FirstOrDefault();
|
||||
|
||||
var building = underCursor != null ? underCursor.Info as BuildingInfo : null;
|
||||
|
||||
if (building != null && building.Repairable && underCursor.Health < building.Strength)
|
||||
yield return new Order("Repair", underCursor, null, int2.Zero, null);
|
||||
}
|
||||
}
|
||||
|
||||
public void Tick()
|
||||
{
|
||||
if (!Game.Settings.RepairRequiresConyard)
|
||||
return;
|
||||
|
||||
var hasFact = Game.world.Actors
|
||||
.Any(a => a.Owner == Game.LocalPlayer && a.traits.Contains<ConstructionYard>());
|
||||
|
||||
if (!hasFact)
|
||||
Game.controller.CancelInputMode();
|
||||
}
|
||||
|
||||
public void Render() {}
|
||||
|
||||
public Cursor GetCursor(int2 xy, MouseInput mi)
|
||||
{
|
||||
mi.Button = MouseButton.Left;
|
||||
return OrderInner(xy, mi).Any()
|
||||
? Cursor.Repair : Cursor.RepairBlocked;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,30 +11,37 @@ namespace OpenRa.Game.Orders
|
||||
{
|
||||
public IEnumerable<Order> Order(int2 xy, MouseInput mi)
|
||||
{
|
||||
if (!mi.IsFake && mi.Button == MouseButton.Right)
|
||||
{
|
||||
if (mi.Button == MouseButton.Right)
|
||||
Game.controller.CancelInputMode();
|
||||
yield break;
|
||||
}
|
||||
|
||||
var loc = mi.Location + Game.viewport.Location;
|
||||
var underCursor = Game.FindUnits(loc, loc)
|
||||
.Where(a => a.Owner == Game.LocalPlayer
|
||||
&& a.traits.Contains<Building>()
|
||||
&& a.Info.Selectable).FirstOrDefault();
|
||||
return OrderInner(xy, mi);
|
||||
}
|
||||
|
||||
var building = underCursor != null ? underCursor.Info as BuildingInfo : null;
|
||||
|
||||
if (building == null || building.Unsellable)
|
||||
IEnumerable<Order> OrderInner(int2 xy, MouseInput mi)
|
||||
{
|
||||
if (mi.Button == MouseButton.Left)
|
||||
{
|
||||
yield return new Order("NoSell", Game.LocalPlayer.PlayerActor, null, int2.Zero, null);
|
||||
yield break;
|
||||
}
|
||||
var loc = mi.Location + Game.viewport.Location;
|
||||
var underCursor = Game.FindUnits(loc, loc)
|
||||
.Where(a => a.Owner == Game.LocalPlayer
|
||||
&& a.traits.Contains<Building>()
|
||||
&& a.Info.Selectable).FirstOrDefault();
|
||||
|
||||
yield return new Order("Sell", underCursor, null, int2.Zero, null);
|
||||
var building = underCursor != null ? underCursor.Info as BuildingInfo : null;
|
||||
|
||||
if (building != null && !building.Unsellable)
|
||||
yield return new Order("Sell", underCursor, null, int2.Zero, null);
|
||||
}
|
||||
}
|
||||
|
||||
public void Tick() {}
|
||||
public void Render() {}
|
||||
|
||||
public Cursor GetCursor(int2 xy, MouseInput mi)
|
||||
{
|
||||
mi.Button = MouseButton.Left;
|
||||
return OrderInner(xy, mi).Any()
|
||||
? Cursor.Sell : Cursor.SellBlocked;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
40
OpenRa.Game/Orders/TeleportOrderGenerator.cs
Normal file
40
OpenRa.Game/Orders/TeleportOrderGenerator.cs
Normal file
@@ -0,0 +1,40 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Drawing;
|
||||
|
||||
namespace OpenRa.Game.Orders
|
||||
{
|
||||
class TeleportOrderGenerator : IOrderGenerator
|
||||
{
|
||||
public readonly Actor self;
|
||||
|
||||
public TeleportOrderGenerator(Actor self)
|
||||
{
|
||||
this.self = self;
|
||||
}
|
||||
|
||||
public IEnumerable<Order> Order(int2 xy, MouseInput mi)
|
||||
{
|
||||
if (mi.Button == MouseButton.Left)
|
||||
{
|
||||
Game.controller.CancelInputMode();
|
||||
yield break;
|
||||
}
|
||||
|
||||
yield return new Order("Chronoshift", self, null, xy, null);
|
||||
}
|
||||
|
||||
public void Tick() {}
|
||||
public void Render()
|
||||
{
|
||||
Game.worldRenderer.DrawSelectionBox(self, Color.White, true);
|
||||
}
|
||||
|
||||
public Cursor GetCursor(int2 xy, MouseInput mi)
|
||||
{
|
||||
return Cursor.Chronoshift;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
using OpenRa.Game.Traits;
|
||||
using OpenRa.Game.GameRules;
|
||||
|
||||
namespace OpenRa.Game.Orders
|
||||
{
|
||||
@@ -33,5 +35,59 @@ namespace OpenRa.Game.Orders
|
||||
foreach( var a in selection )
|
||||
Game.worldRenderer.DrawSelectionBox( a, Color.White, true );
|
||||
}
|
||||
|
||||
public Cursor GetCursor(int2 xy, MouseInput mi)
|
||||
{
|
||||
return ChooseCursor(mi);
|
||||
}
|
||||
|
||||
Cursor ChooseCursor( MouseInput mi )
|
||||
{
|
||||
var p = Game.controller.MousePosition;
|
||||
var c = Order(p.ToInt2(), mi)
|
||||
.Where(o => o.Validate())
|
||||
.Select(o => CursorForOrderString(o.OrderString, o.Subject, o.TargetLocation))
|
||||
.FirstOrDefault(a => a != null);
|
||||
|
||||
return c ??
|
||||
(Game.SelectActorsInBox(Game.CellSize * p,
|
||||
Game.CellSize * p).Any()
|
||||
? Cursor.Select : Cursor.Default);
|
||||
}
|
||||
|
||||
Cursor CursorForOrderString(string s, Actor a, int2 location)
|
||||
{
|
||||
var movement = a.traits.WithInterface<IMovement>().FirstOrDefault();
|
||||
switch (s)
|
||||
{
|
||||
case "Attack": return Cursor.Attack;
|
||||
case "Heal": return Cursor.Heal;
|
||||
case "C4": return Cursor.C4;
|
||||
case "Move":
|
||||
if (movement.CanEnterCell(location))
|
||||
return Cursor.Move;
|
||||
else
|
||||
return Cursor.MoveBlocked;
|
||||
case "DeployMcv":
|
||||
var factBuildingInfo = (BuildingInfo)Rules.UnitInfo["fact"];
|
||||
if (Game.CanPlaceBuilding(factBuildingInfo, a.Location - new int2(1, 1), a, false))
|
||||
return Cursor.Deploy;
|
||||
else
|
||||
return Cursor.DeployBlocked;
|
||||
case "Deploy": return Cursor.Deploy;
|
||||
case "Chronoshift":
|
||||
if (movement.CanEnterCell(location))
|
||||
return Cursor.Chronoshift;
|
||||
else
|
||||
return Cursor.MoveBlocked;
|
||||
case "Enter": return Cursor.Enter;
|
||||
case "Deliver": return Cursor.Enter;
|
||||
case "Infiltrate": return Cursor.Enter;
|
||||
case "Capture": return Cursor.Capture;
|
||||
case "Harvest": return Cursor.Attack; // TODO: special harvest cursor?
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ namespace OpenRa.Game.Traits.Activities
|
||||
{
|
||||
Game.world.AddFrameEndTask( _ =>
|
||||
{
|
||||
self.Health = 0;
|
||||
Game.world.Remove( self );
|
||||
if (self.Owner == Game.LocalPlayer)
|
||||
{
|
||||
|
||||
@@ -35,7 +35,7 @@ namespace OpenRa.Game.Traits.Activities
|
||||
self.traits.Get<Helicopter>().reservation = res.Reserve(self);
|
||||
|
||||
var offset = (dest.Info as BuildingInfo).SpawnOffset;
|
||||
var offsetVec = new float2(offset[0], offset[1]);
|
||||
var offsetVec = offset != null ? new float2(offset[0], offset[1]) : float2.Zero;
|
||||
|
||||
return Util.SequenceActivities(
|
||||
new HeliFly(dest.CenterLocation + offsetVec),
|
||||
|
||||
@@ -9,17 +9,23 @@ namespace OpenRa.Game.Traits.Activities
|
||||
{
|
||||
public IActivity NextActivity { get; set; }
|
||||
bool isCanceled;
|
||||
int remainingTicks = ticksPerPoint;
|
||||
|
||||
const int ticksPerPoint = 15;
|
||||
const int hpPerPoint = 8;
|
||||
int remainingTicks;
|
||||
|
||||
public IActivity Tick(Actor self)
|
||||
{
|
||||
if (isCanceled) return NextActivity;
|
||||
if (--remainingTicks == 0)
|
||||
if (remainingTicks == 0)
|
||||
{
|
||||
self.InflictDamage(self, -hpPerPoint, Rules.WarheadInfo["Super"]);
|
||||
var costPerHp = (Rules.General.URepairPercent * self.Info.Cost) / self.Info.Strength;
|
||||
var hpToRepair = Math.Min(Rules.General.URepairStep, self.Info.Strength - self.Health);
|
||||
var cost = (int)Math.Ceiling(costPerHp * hpToRepair);
|
||||
if (!self.Owner.TakeCash(cost))
|
||||
{
|
||||
remainingTicks = 1;
|
||||
return this;
|
||||
}
|
||||
|
||||
self.InflictDamage(self, -hpToRepair, Rules.WarheadInfo["Super"]);
|
||||
if (self.Health == self.Info.Strength)
|
||||
return NextActivity;
|
||||
|
||||
@@ -27,10 +33,12 @@ namespace OpenRa.Game.Traits.Activities
|
||||
.FirstOrDefault(a => a.traits.Contains<RenderBuilding>());
|
||||
|
||||
if (hostBuilding != null)
|
||||
hostBuilding.traits.Get<RenderBuilding>().PlayCustomAnim(hostBuilding, "active" );
|
||||
hostBuilding.traits.Get<RenderBuilding>().PlayCustomAnim(hostBuilding, "active");
|
||||
|
||||
remainingTicks = ticksPerPoint;
|
||||
remainingTicks = (int)(Rules.General.RepairRate * 60 * 25);
|
||||
}
|
||||
else
|
||||
--remainingTicks;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -6,24 +6,24 @@ using OpenRa.Game.GameRules;
|
||||
|
||||
namespace OpenRa.Game.Traits.Activities
|
||||
{
|
||||
class Teleport : IActivity
|
||||
{
|
||||
public IActivity NextActivity { get; set; }
|
||||
class Teleport : IActivity
|
||||
{
|
||||
public IActivity NextActivity { get; set; }
|
||||
|
||||
int2 destination;
|
||||
int2 destination;
|
||||
|
||||
public Teleport(int2 destination)
|
||||
{
|
||||
this.destination = destination;
|
||||
}
|
||||
public Teleport(int2 destination)
|
||||
{
|
||||
this.destination = destination;
|
||||
}
|
||||
|
||||
public IActivity Tick(Actor self)
|
||||
{
|
||||
var mobile = self.traits.Get<Mobile>();
|
||||
public IActivity Tick(Actor self)
|
||||
{
|
||||
var mobile = self.traits.Get<Mobile>();
|
||||
mobile.TeleportTo(self, destination);
|
||||
return NextActivity;
|
||||
}
|
||||
return NextActivity;
|
||||
}
|
||||
|
||||
public void Cancel(Actor self) { }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
43
OpenRa.Game/Traits/Activities/UndeployMcv.cs
Normal file
43
OpenRa.Game/Traits/Activities/UndeployMcv.cs
Normal file
@@ -0,0 +1,43 @@
|
||||
using System;
|
||||
|
||||
namespace OpenRa.Game.Traits.Activities
|
||||
{
|
||||
class UndeployMcv : IActivity
|
||||
{
|
||||
public IActivity NextActivity { get; set; }
|
||||
bool started;
|
||||
|
||||
void DoUndeploy(World w,Actor self)
|
||||
{
|
||||
self.Health = 0;
|
||||
foreach (var ns in self.traits.WithInterface<INotifySold>())
|
||||
ns.Sold(self);
|
||||
w.Remove(self);
|
||||
|
||||
var mcv = new Actor(Rules.UnitInfo["MCV"], self.Location + new int2(1, 1), self.Owner);
|
||||
mcv.traits.Get<Unit>().Facing = 96;
|
||||
w.Add(mcv);
|
||||
}
|
||||
|
||||
public IActivity Tick(Actor self)
|
||||
{
|
||||
if (!started)
|
||||
{
|
||||
var rb = self.traits.Get<RenderBuilding>();
|
||||
rb.PlayCustomAnimBackwards(self, "make",
|
||||
() => Game.world.AddFrameEndTask(w => DoUndeploy(w,self)));
|
||||
|
||||
Sound.Play("cashturn.aud");
|
||||
started = true;
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public void Cancel(Actor self)
|
||||
{
|
||||
// Cancel can't happen between this being moved to the head of the list, and it being Ticked.
|
||||
throw new InvalidOperationException("UndeployMcvAction: Cancel() should never occur.");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,17 @@
|
||||
using OpenRa.Game.GameRules;
|
||||
using OpenRa.Game.Traits.Activities;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using OpenRa.Game.Effects;
|
||||
|
||||
namespace OpenRa.Game.Traits
|
||||
{
|
||||
class Building : INotifyDamage, IOrder
|
||||
class Building : INotifyDamage, IOrder, ITick
|
||||
{
|
||||
public readonly BuildingInfo unitInfo;
|
||||
bool isRepairing = false;
|
||||
|
||||
public Building(Actor self)
|
||||
{
|
||||
@@ -32,6 +38,41 @@ namespace OpenRa.Game.Traits
|
||||
self.CancelActivity();
|
||||
self.QueueActivity(new Sell());
|
||||
}
|
||||
|
||||
if (order.OrderString == "Repair")
|
||||
{
|
||||
isRepairing = !isRepairing;
|
||||
}
|
||||
}
|
||||
|
||||
int remainingTicks;
|
||||
|
||||
public void Tick(Actor self)
|
||||
{
|
||||
if (!isRepairing) return;
|
||||
|
||||
if (remainingTicks == 0)
|
||||
{
|
||||
var costPerHp = (Rules.General.URepairPercent * self.Info.Cost) / self.Info.Strength;
|
||||
var hpToRepair = Math.Min(Rules.General.URepairStep, self.Info.Strength - self.Health);
|
||||
var cost = (int)Math.Ceiling(costPerHp * hpToRepair);
|
||||
if (!self.Owner.TakeCash(cost))
|
||||
{
|
||||
remainingTicks = 1;
|
||||
return;
|
||||
}
|
||||
|
||||
Game.world.AddFrameEndTask(w => w.Add(new RepairIndicator(self)));
|
||||
self.InflictDamage(self, -hpToRepair, Rules.WarheadInfo["Super"]);
|
||||
if (self.Health == self.Info.Strength)
|
||||
{
|
||||
isRepairing = false;
|
||||
return;
|
||||
}
|
||||
remainingTicks = (int)(Rules.General.RepairRate * 60 * 25);
|
||||
}
|
||||
else
|
||||
--remainingTicks;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,58 +1,54 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using OpenRa.Game.Orders;
|
||||
|
||||
namespace OpenRa.Game.Traits
|
||||
{
|
||||
class ChronoshiftDeploy : IOrder, ISpeedModifier, ITick, IPips
|
||||
{
|
||||
public ChronoshiftDeploy(Actor self) { }
|
||||
bool chronoshiftActive = false; // Is the chronoshift engine active?
|
||||
int remainingChargeTime = 0; // How long until we can chronoshift again?
|
||||
int chargeTime = (int)(Rules.Aftermath.ChronoTankDuration * 60 * 25); // How long between shifts?
|
||||
class ChronoshiftDeploy : IOrder, ISpeedModifier, ITick, IPips
|
||||
{
|
||||
public ChronoshiftDeploy(Actor self) { }
|
||||
int remainingChargeTime = 0; // How long until we can chronoshift again?
|
||||
int chargeTime = (int)(Rules.Aftermath.ChronoTankDuration * 60 * 25); // How long between shifts?
|
||||
|
||||
public void Tick(Actor self)
|
||||
{
|
||||
if (remainingChargeTime > 0)
|
||||
remainingChargeTime--;
|
||||
}
|
||||
public void Tick(Actor self)
|
||||
{
|
||||
if (remainingChargeTime > 0)
|
||||
remainingChargeTime--;
|
||||
}
|
||||
|
||||
public Order IssueOrder(Actor self, int2 xy, MouseInput mi, Actor underCursor)
|
||||
{
|
||||
if (mi.Button == MouseButton.Left) return null;
|
||||
public Order IssueOrder(Actor self, int2 xy, MouseInput mi, Actor underCursor)
|
||||
{
|
||||
if( mi.Button == MouseButton.Right && xy == self.Location && remainingChargeTime <= 0 )
|
||||
return new Order( "Deploy", self, null, int2.Zero, null );
|
||||
|
||||
if (chronoshiftActive)
|
||||
return new Order("Chronoshift", self, null, xy, null);
|
||||
return null;
|
||||
}
|
||||
|
||||
else if (xy == self.Location && remainingChargeTime <= 0)
|
||||
return new Order("Deploy", self, null, int2.Zero, null);
|
||||
|
||||
return null;
|
||||
}
|
||||
public void ResolveOrder(Actor self, Order order)
|
||||
{
|
||||
if (order.OrderString == "Deploy")
|
||||
{
|
||||
Game.controller.orderGenerator = new TeleportOrderGenerator(self);
|
||||
return;
|
||||
}
|
||||
|
||||
public void ResolveOrder(Actor self, Order order)
|
||||
{
|
||||
var movement = self.traits.WithInterface<IMovement>().FirstOrDefault();
|
||||
if (order.OrderString == "Deploy" && remainingChargeTime <= 0)
|
||||
{
|
||||
chronoshiftActive = true;
|
||||
self.CancelActivity();
|
||||
}
|
||||
|
||||
if (order.OrderString == "Chronoshift" && movement.CanEnterCell(order.TargetLocation))
|
||||
{
|
||||
{
|
||||
Game.controller.CancelInputMode();
|
||||
self.CancelActivity();
|
||||
self.QueueActivity(new Activities.Teleport(order.TargetLocation));
|
||||
Sound.Play("chrotnk1.aud");
|
||||
chronoshiftActive = false;
|
||||
remainingChargeTime = chargeTime;
|
||||
}
|
||||
}
|
||||
|
||||
public float GetSpeedModifier()
|
||||
{
|
||||
return chronoshiftActive ? 0f : 1f;
|
||||
}
|
||||
|
||||
self.QueueActivity(new Activities.Teleport(order.TargetLocation));
|
||||
Sound.Play("chrotnk1.aud");
|
||||
remainingChargeTime = chargeTime;
|
||||
}
|
||||
}
|
||||
|
||||
public float GetSpeedModifier()
|
||||
{
|
||||
// ARGH! You must not do this, it will desync!
|
||||
return (Game.controller.orderGenerator is TeleportOrderGenerator) ? 0f : 1f;
|
||||
}
|
||||
|
||||
// Display 5 pips indicating the current charge status
|
||||
public IEnumerable<PipType> GetPips()
|
||||
{
|
||||
@@ -81,5 +77,5 @@ namespace OpenRa.Game.Traits
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,16 +12,16 @@ namespace OpenRa.Game.Traits
|
||||
|
||||
public void Attacking(Actor self)
|
||||
{
|
||||
if (remainingUncloakTime <= 0)
|
||||
if (remainingUncloakTime <= 0)
|
||||
OnCloak();
|
||||
|
||||
remainingUncloakTime = (int)(Rules.General.SubmergeDelay * 60 * 25);
|
||||
remainingUncloakTime = (int)(Rules.General.SubmergeDelay * 60 * 25);
|
||||
}
|
||||
|
||||
public IEnumerable<Renderable>
|
||||
ModifyRender(Actor self, IEnumerable<Renderable> rs)
|
||||
{
|
||||
if (remainingUncloakTime > 0)
|
||||
if (remainingUncloakTime > 0)
|
||||
return rs;
|
||||
|
||||
if (self.Owner == Game.LocalPlayer)
|
||||
@@ -32,8 +32,8 @@ namespace OpenRa.Game.Traits
|
||||
|
||||
public void Tick(Actor self)
|
||||
{
|
||||
if (remainingUncloakTime > 0)
|
||||
if (--remainingUncloakTime <= 0)
|
||||
if (remainingUncloakTime > 0)
|
||||
if (--remainingUncloakTime <= 0)
|
||||
OnUncloak();
|
||||
}
|
||||
|
||||
|
||||
69
OpenRa.Game/Traits/ConstructionYard.cs
Normal file
69
OpenRa.Game/Traits/ConstructionYard.cs
Normal file
@@ -0,0 +1,69 @@
|
||||
using OpenRa.Game.GameRules;
|
||||
using OpenRa.Game.Traits.Activities;
|
||||
|
||||
namespace OpenRa.Game.Traits
|
||||
{
|
||||
class ConstructionYard : IOrder, IMovement
|
||||
{
|
||||
readonly Actor self;
|
||||
|
||||
public ConstructionYard(Actor self)
|
||||
{
|
||||
this.self = self;
|
||||
}
|
||||
|
||||
public Order IssueOrder(Actor self, int2 xy, MouseInput mi, Actor underCursor)
|
||||
{
|
||||
if (!Rules.General.MCVUndeploy) return null;
|
||||
|
||||
if (mi.Button == MouseButton.Left) return null;
|
||||
|
||||
if (underCursor != null)
|
||||
{
|
||||
// force-move
|
||||
if (!mi.Modifiers.HasModifier(Modifiers.Alt)) return null;
|
||||
if (!Game.IsActorCrushableByActor(underCursor, self)) return null;
|
||||
}
|
||||
|
||||
return new Order("Move", self, null, xy, null);
|
||||
}
|
||||
|
||||
public void ResolveOrder(Actor self, Order order)
|
||||
{
|
||||
if (order.OrderString == "Move")
|
||||
{
|
||||
self.CancelActivity();
|
||||
self.QueueActivity(new UndeployMcv());
|
||||
}
|
||||
}
|
||||
|
||||
// HACK: This should make reference to an MCV actor, and use of its Mobile trait
|
||||
public UnitMovementType GetMovementType()
|
||||
{
|
||||
return UnitMovementType.Wheel;
|
||||
}
|
||||
|
||||
public bool CanEnterCell(int2 a)
|
||||
{
|
||||
if (!Game.BuildingInfluence.CanMoveHere(a)) return false;
|
||||
|
||||
var crushable = true;
|
||||
foreach (Actor actor in Game.UnitInfluence.GetUnitsAt(a))
|
||||
{
|
||||
if (actor == self) continue;
|
||||
|
||||
if (!Game.IsActorCrushableByActor(actor, self))
|
||||
{
|
||||
crushable = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!crushable) return false;
|
||||
|
||||
return Rules.Map.IsInMap(a.X, a.Y) &&
|
||||
TerrainCosts.Cost(GetMovementType(),
|
||||
Rules.TileSet.GetWalkability(Rules.Map.MapTiles[a.X, a.Y])) < double.PositiveInfinity;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -34,7 +34,7 @@ namespace OpenRa.Game.Traits
|
||||
if (underCursor != null
|
||||
&& underCursor.Owner == self.Owner
|
||||
&& underCursor.traits.Contains<AcceptsOre>() && !IsEmpty)
|
||||
return new Order("Enter", self, underCursor, int2.Zero, null);
|
||||
return new Order("Deliver", self, underCursor, int2.Zero, null);
|
||||
|
||||
if (underCursor == null && Rules.Map.ContainsResource(xy))
|
||||
return new Order("Harvest", self, null, xy, null);
|
||||
@@ -50,7 +50,7 @@ namespace OpenRa.Game.Traits
|
||||
self.QueueActivity(new Move(order.TargetLocation, 0));
|
||||
self.QueueActivity(new Harvest());
|
||||
}
|
||||
else if (order.OrderString == "Enter")
|
||||
else if (order.OrderString == "Deliver")
|
||||
{
|
||||
self.CancelActivity();
|
||||
self.QueueActivity(new DeliverOre(order.TargetActor));
|
||||
|
||||
@@ -56,7 +56,7 @@ namespace OpenRa.Game.Traits
|
||||
reservation = res.Reserve(self);
|
||||
|
||||
var offset = (order.TargetActor.Info as BuildingInfo).SpawnOffset;
|
||||
var offsetVec = new float2(offset[0], offset[1]);
|
||||
var offsetVec = offset != null ? new float2(offset[0], offset[1]) : float2.Zero;
|
||||
|
||||
self.CancelActivity();
|
||||
self.QueueActivity(new HeliFly(order.TargetActor.CenterLocation + offsetVec));
|
||||
|
||||
@@ -14,6 +14,10 @@ namespace OpenRa.Game.Traits
|
||||
var limitedAmmo = self.traits.GetOrDefault<LimitedAmmo>();
|
||||
if (limitedAmmo != null && !limitedAmmo.HasAmmo())
|
||||
return null;
|
||||
|
||||
// Ensure that the cell is empty except for the minelayer
|
||||
if (Game.UnitInfluence.GetUnitsAt( xy ).Any(a => a != self))
|
||||
return null;
|
||||
|
||||
if (mi.Button == MouseButton.Right && underCursor == self)
|
||||
return new Order("Deploy", self, null, int2.Zero, null);
|
||||
|
||||
@@ -4,11 +4,11 @@ using OpenRa.Game.Graphics;
|
||||
|
||||
namespace OpenRa.Game.Traits
|
||||
{
|
||||
class Submarine : IRenderModifier, INotifyAttack, ITick, INotifyDamage
|
||||
{
|
||||
int remainingSurfaceTime = 2; /* setup for initial dive */
|
||||
class Submarine : IRenderModifier, INotifyAttack, ITick, INotifyDamage
|
||||
{
|
||||
int remainingSurfaceTime = 2; /* setup for initial dive */
|
||||
|
||||
public Submarine(Actor self) { }
|
||||
public Submarine(Actor self) { }
|
||||
|
||||
void DoSurface()
|
||||
{
|
||||
@@ -18,36 +18,36 @@ namespace OpenRa.Game.Traits
|
||||
remainingSurfaceTime = (int)(Rules.General.SubmergeDelay * 60 * 25);
|
||||
}
|
||||
|
||||
public void Attacking(Actor self) { DoSurface(); }
|
||||
public void Attacking(Actor self) { DoSurface(); }
|
||||
public void Damaged(Actor self, AttackInfo e) { DoSurface(); }
|
||||
|
||||
public IEnumerable<Renderable>
|
||||
ModifyRender(Actor self, IEnumerable<Renderable> rs)
|
||||
{
|
||||
if (remainingSurfaceTime > 0)
|
||||
return rs;
|
||||
public IEnumerable<Renderable>
|
||||
ModifyRender(Actor self, IEnumerable<Renderable> rs)
|
||||
{
|
||||
if (remainingSurfaceTime > 0)
|
||||
return rs;
|
||||
|
||||
if (self.Owner == Game.LocalPlayer)
|
||||
return rs.Select(a => a.WithPalette(PaletteType.Shadow));
|
||||
else
|
||||
return new Renderable[] { };
|
||||
}
|
||||
}
|
||||
|
||||
public void Tick(Actor self)
|
||||
{
|
||||
if (remainingSurfaceTime > 0)
|
||||
if (--remainingSurfaceTime <= 0)
|
||||
OnDive();
|
||||
}
|
||||
public void Tick(Actor self)
|
||||
{
|
||||
if (remainingSurfaceTime > 0)
|
||||
if (--remainingSurfaceTime <= 0)
|
||||
OnDive();
|
||||
}
|
||||
|
||||
void OnSurface()
|
||||
{
|
||||
Sound.Play("subshow1.aud");
|
||||
}
|
||||
void OnSurface()
|
||||
{
|
||||
Sound.Play("subshow1.aud");
|
||||
}
|
||||
|
||||
void OnDive()
|
||||
{
|
||||
Sound.Play("subshow1.aud"); /* is this the right sound?? */
|
||||
}
|
||||
void OnDive()
|
||||
{
|
||||
Sound.Play("subshow1.aud"); /* is this the right sound?? */
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,16 +70,12 @@ namespace SequenceEditor
|
||||
{
|
||||
Application.EnableVisualStyles();
|
||||
Application.SetCompatibleTextRenderingDefault(false);
|
||||
|
||||
FileSystem.MountDefaultPackages();
|
||||
try
|
||||
{
|
||||
FileSystem.MountDefault( true );
|
||||
}
|
||||
catch( FileNotFoundException fnf )
|
||||
{
|
||||
if( fnf.FileName != "expand2.mix" )
|
||||
throw new InvalidOperationException( "Unable to load MIX files" );
|
||||
FileSystem.MountAftermathPackages();
|
||||
}
|
||||
catch( FileNotFoundException ){}
|
||||
|
||||
FileSystem.MountTemporary(new Package("temperat.mix"));
|
||||
|
||||
|
||||
@@ -372,7 +372,7 @@
|
||||
<sequence name="move-minimap" start="29" length="6" />
|
||||
<sequence name="repair" start="35" length="24" />
|
||||
<sequence name="deploy" start="59" length="9" x="12" y="12" />
|
||||
<sequence name="sell" start="68" length="12" x="12" y="12"/>
|
||||
<sequence name="sell" start="68" length="12" x="12" y="12" />
|
||||
<sequence name="default-minimap" start="80" length="1" />
|
||||
<sequence name="ability" start="82" length="8" />
|
||||
<sequence name="nuke" start="90" length="7" x="12" y="12" />
|
||||
@@ -388,7 +388,7 @@
|
||||
<sequence name="guard-minimap" start="146" length="1" />
|
||||
<sequence name="guard" start="147" length="1" x="12" y="12" />
|
||||
<sequence name="sell-vehicle" start="148" length="12" />
|
||||
<sequence name="heal" start="160" length="4" x="12" y="12"/>
|
||||
<sequence name="heal" start="160" length="4" x="12" y="12" />
|
||||
<sequence name="capture" start="164" length="3" />
|
||||
<sequence name="capture-minimap" start="167" length="3" />
|
||||
<sequence name="repair2" start="170" length="24" />
|
||||
@@ -991,4 +991,24 @@
|
||||
<unit name="minv">
|
||||
<sequence name="idle" start="0" length="1" />
|
||||
</unit>
|
||||
<unit name="select">
|
||||
<sequence name="repair" start="2" length="1" />
|
||||
</unit>
|
||||
<unit name="power">
|
||||
<sequence name="power-level-indicator" start="0" length="1" />
|
||||
</unit>
|
||||
<unit name="powerbar">
|
||||
<sequence name="powerbar-top" start="0" length="1" />
|
||||
<sequence name="powerbar-bottom" start="1" length="1" />
|
||||
</unit>
|
||||
<unit name="repair">
|
||||
<sequence name="normal" start="0" length="1" />
|
||||
<sequence name="pressed" start="1" length="1" />
|
||||
<sequence name="disabled" start="2" length="1" />
|
||||
</unit>
|
||||
<unit name="sell">
|
||||
<sequence name="normal" start="0" length="1" />
|
||||
<sequence name="pressed" start="1" length="1" />
|
||||
<sequence name="disabled" start="2" length="1" />
|
||||
</unit>
|
||||
</sequences>
|
||||
18
units.ini
18
units.ini
@@ -92,13 +92,13 @@ LongDesc=Deploys into another Construction Yard.\n Unarmed
|
||||
|
||||
[MNLY.AP]
|
||||
Description=Minelayer (Anti-Personnel)
|
||||
Traits=Unit, Mobile, RenderUnit, Minelayer, MineImmune, Repairable
|
||||
Traits=Unit, Mobile, RenderUnit, Minelayer, MineImmune, Repairable, LimitedAmmo
|
||||
Voice=VehicleVoice
|
||||
LongDesc=Lays mines to destroy unwary enemy units.\n Unarmed
|
||||
Primary=MINP ;; temporary hack
|
||||
[MNLY.AT]
|
||||
Description=Minelayer (Anti-Tank)
|
||||
Traits=Unit, Mobile, RenderUnit, Minelayer, MineImmune, Repairable
|
||||
Traits=Unit, Mobile, RenderUnit, Minelayer, MineImmune, Repairable, LimitedAmmo
|
||||
Voice=VehicleVoice
|
||||
LongDesc=Lays mines to destroy unwary enemy units.\n Unarmed
|
||||
Primary=MINV ;; temporary hack
|
||||
@@ -373,7 +373,7 @@ SelectionPriority=3
|
||||
LongDesc=Produces and repairs submarines and \ntransports
|
||||
[FACT]
|
||||
Description=Construction Yard
|
||||
Traits=Building, RenderBuilding
|
||||
Traits=Building, RenderBuilding, ConstructionYard
|
||||
Dimensions=3,3
|
||||
Footprint=xxx xxx xxx
|
||||
Produces=Building,Defense
|
||||
@@ -544,54 +544,64 @@ MEDI
|
||||
Description=Attack Dog
|
||||
BuiltAt=KENN
|
||||
Voice=DogVoice
|
||||
Traits=Unit, Mobile, RenderInfantry, Infantry, AutoTarget
|
||||
Traits=Unit, Mobile, RenderInfantry ;; AttackBase, SquishByTank, AutoTarget, dog??
|
||||
LongDesc=Anti-infantry unit. Not fooled by the \nSpy's disguise.\n Strong vs Infantry\n Weak vs Vehicles
|
||||
SelectionSize=12,17,-1,-4
|
||||
[E1]
|
||||
Description=Rifle Infantry
|
||||
Traits=Unit, Mobile, RenderInfantry, AttackBase, TakeCover, SquishByTank, AutoTarget
|
||||
LongDesc=General-purpose infantry. Strong vs Infantry\n Weak vs Vehicles
|
||||
SelectionSize=12,17,0,-9
|
||||
[E2]
|
||||
Description=Grenadier
|
||||
Traits=Unit, Mobile, RenderInfantry, AttackBase, TakeCover, SquishByTank, AutoTarget
|
||||
FireDelay=15
|
||||
PrimaryOffset=0,0,0,-13
|
||||
LongDesc=Infantry armed with grenades. \n Strong vs Buildings, Infantry\n Weak vs Vehicles
|
||||
SelectionSize=12,17,0,-9
|
||||
[E3]
|
||||
Description=Rocket Soldier
|
||||
Traits=Unit, Mobile, RenderInfantry, AttackBase, TakeCover, SquishByTank, AutoTarget
|
||||
PrimaryOffset=0,0,0,-13
|
||||
LongDesc=Anti-tank/Anti-aircraft infantry.\n Strong vs Tanks, Aircraft\n Weak vs Infantry
|
||||
SelectionSize=12,17,0,-9
|
||||
[E4]
|
||||
Description=Flamethrower
|
||||
Traits=Unit, Mobile, RenderInfantry, AttackBase, TakeCover, SquishByTank, AutoTarget
|
||||
FireDelay=8
|
||||
PrimaryOffset=0,0,0,-7
|
||||
LongDesc=Advanced Anti-infantry unit.\n Strong vs Infantry, Buildings\n Weak vs Vehicles
|
||||
SelectionSize=12,17,0,-9
|
||||
[E6]
|
||||
Description=Engineer
|
||||
Traits=Unit, Mobile, EngineerCapture, RenderInfantry, TakeCover, SquishByTank
|
||||
Voice=EngineerVoice
|
||||
LongDesc=Infiltrates and captures enemy structures.\n Strong vs Nothing\n Weak vs Everything
|
||||
SelectionSize=12,17,0,-9
|
||||
[SPY]
|
||||
Description=Spy
|
||||
Voice=SpyVoice
|
||||
Traits=Unit, Mobile, RenderInfantry, TakeCover, SquishByTank
|
||||
LongDesc=Infiltrates enemy structures to gather \nintelligence. Exact effect depends on the \nbuilding infiltrated.\n Strong vs Nothing\n Weak vs Everything\n Special Ability: Disguised
|
||||
SelectionSize=12,17,0,-9
|
||||
[THF]
|
||||
Description=Thief
|
||||
Voice=ThiefVoice
|
||||
Traits=Unit, Mobile, RenderInfantry, TakeCover, SquishByTank
|
||||
LongDesc=Infiltrates enemy refineries & \nsilos, and steals money stored there.\n Unarmed
|
||||
SelectionSize=12,17,0,-9
|
||||
[E7]
|
||||
Description=Tanya
|
||||
Voice=TanyaVoice
|
||||
Traits=Unit, Mobile, RenderInfantry, C4Demolition, AttackBase, TakeCover, SquishByTank, AutoTarget
|
||||
LongDesc=Elite commando infantry, armed with \ndual pistols and C4.\n Strong vs Infantry, Buildings\n Weak vs Vehicles\n Special Ability: Destroy Building with C4
|
||||
SelectionSize=12,17,0,-9
|
||||
[MEDI]
|
||||
Description=Medic
|
||||
Voice=MedicVoice
|
||||
Traits=Unit, Mobile, RenderInfantry, AutoHeal, AttackBase, TakeCover, SquishByTank
|
||||
LongDesc=Heals nearby infantry.\n Strong vs Nothing\n Weak vs Everything
|
||||
SelectionSize=12,17,0,-9
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user