Files
OpenRA/OpenRA.Mods.Common/Traits/Buildings/Bridge.cs
clemty 2bbc1fcda4 LUA and trait documentation fixes
- Typo in documentation ("proximitry").
- Add spaces between sentences.
- Remove most occurrences of "  " (two spaces) unless clearly used as indendation
- Punctuation (although no fullstop after filenames like "notifications.yaml").
2015-08-24 19:41:15 +02:00

357 lines
11 KiB
C#

#region Copyright & License Information
/*
* Copyright 2007-2015 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;
using System.Collections.Generic;
using System.Linq;
using OpenRA.Effects;
using OpenRA.Graphics;
using OpenRA.Primitives;
using OpenRA.Traits;
namespace OpenRA.Mods.Common.Traits
{
class BridgeInfo : ITraitInfo, Requires<HealthInfo>, Requires<BuildingInfo>
{
public readonly bool Long = false;
[Desc("Delay (in ticks) between repairing adjacent spans in long bridges")]
public readonly int RepairPropagationDelay = 20;
public readonly ushort Template = 0;
public readonly ushort DamagedTemplate = 0;
public readonly ushort DestroyedTemplate = 0;
// For long bridges
public readonly ushort DestroyedPlusNorthTemplate = 0;
public readonly ushort DestroyedPlusSouthTemplate = 0;
public readonly ushort DestroyedPlusBothTemplate = 0;
public readonly string[] ShorePieces = { "br1", "br2" };
public readonly int[] NorthOffset = null;
public readonly int[] SouthOffset = null;
[Desc("The name of the weapon to use when demolishing the bridge")]
[WeaponReference] public readonly string DemolishWeapon = "Demolish";
public object Create(ActorInitializer init) { return new Bridge(init.Self, this); }
public IEnumerable<Pair<ushort, int>> Templates
{
get
{
if (Template != 0)
yield return Pair.New(Template, 100);
if (DamagedTemplate != 0)
yield return Pair.New(DamagedTemplate, 50);
if (DestroyedTemplate != 0)
yield return Pair.New(DestroyedTemplate, 0);
if (DestroyedPlusNorthTemplate != 0)
yield return Pair.New(DestroyedPlusNorthTemplate, 0);
if (DestroyedPlusSouthTemplate != 0)
yield return Pair.New(DestroyedPlusSouthTemplate, 0);
if (DestroyedPlusBothTemplate != 0)
yield return Pair.New(DestroyedPlusBothTemplate, 0);
}
}
}
class Bridge : IRender, INotifyDamageStateChanged
{
readonly Building building;
readonly Bridge[] neighbours = new Bridge[2];
readonly BridgeHut[] huts = new BridgeHut[2]; // Huts before this / first & after this / last
readonly Health health;
readonly Actor self;
readonly BridgeInfo info;
readonly string type;
readonly Lazy<bool> isDangling;
ushort template;
Dictionary<CPos, byte> footprint;
public BridgeHut Hut { get; private set; }
public bool IsDangling { get { return isDangling.Value; } }
public Bridge(Actor self, BridgeInfo info)
{
this.self = self;
health = self.Trait<Health>();
health.RemoveOnDeath = false;
this.info = info;
type = self.Info.Name;
isDangling = new Lazy<bool>(() => huts[0] == huts[1] && (neighbours[0] == null || neighbours[1] == null));
building = self.Trait<Building>();
}
public Bridge Neighbour(int direction) { return neighbours[direction]; }
public IEnumerable<Bridge> Enumerate(int direction, bool includeSelf = false)
{
for (var b = includeSelf ? this : neighbours[direction]; b != null; b = b.neighbours[direction])
yield return b;
}
public void Do(Action<Bridge, int> action)
{
action(this, -1);
for (var d = 0; d <= 1; d++)
if (neighbours[d] != null)
action(neighbours[d], d);
}
public void Create(ushort template, Dictionary<CPos, byte> footprint)
{
this.template = template;
this.footprint = footprint;
// Set the initial custom terrain types
foreach (var c in footprint.Keys)
self.World.Map.CustomTerrain[c] = GetTerrainType(c);
}
byte GetTerrainType(CPos cell)
{
var dx = cell - self.Location;
var index = dx.X + self.World.TileSet.Templates[template].Size.X * dx.Y;
return self.World.TileSet.GetTerrainIndex(new TerrainTile(template, (byte)index));
}
public void LinkNeighbouringBridges(World world, BridgeLayer bridges)
{
for (var d = 0; d <= 1; d++)
{
if (neighbours[d] != null)
continue; // Already linked by reverse lookup
var offset = d == 0 ? info.NorthOffset : info.SouthOffset;
if (offset == null)
continue; // End piece type
neighbours[d] = GetNeighbor(offset, bridges);
if (neighbours[d] != null)
neighbours[d].neighbours[1 - d] = this; // Save reverse lookup
}
}
internal void AddHut(BridgeHut hut)
{
if (huts[0] == huts[1])
huts[1] = hut;
if (Hut == null)
{
Hut = hut; // Assume only one until called again
if (huts[0] == null)
huts[0] = hut; // Set only first time
for (var d = 0; d <= 1; d++)
for (var b = neighbours[d]; b != null; b = b.Hut == null ? b.neighbours[d] : null)
b.huts[1 - d] = hut;
}
else
Hut = null;
}
public BridgeHut GetHut(int index) { return huts[index]; }
public Bridge GetNeighbor(int[] offset, BridgeLayer bridges)
{
if (offset == null)
return null;
return bridges.GetBridge(self.Location + new CVec(offset[0], offset[1]));
}
IRenderable[] TemplateRenderables(WorldRenderer wr, PaletteReference palette, ushort template)
{
var offset = FootprintUtils.CenterOffset(self.World, building.Info).Y + 1024;
return footprint.Select(c => (IRenderable)(new SpriteRenderable(
wr.Theater.TileSprite(new TerrainTile(template, c.Value)),
wr.World.Map.CenterOfCell(c.Key), WVec.Zero, -offset, palette, 1f, true))).ToArray();
}
bool initialized;
Dictionary<ushort, IRenderable[]> renderables;
public IEnumerable<IRenderable> Render(Actor self, WorldRenderer wr)
{
if (!initialized)
{
var palette = wr.Palette("terrain");
renderables = new Dictionary<ushort, IRenderable[]>();
foreach (var t in info.Templates)
renderables.Add(t.First, TemplateRenderables(wr, palette, t.First));
initialized = true;
}
return renderables[template];
}
void KillUnitsOnBridge()
{
foreach (var c in footprint.Keys)
foreach (var a in self.World.ActorMap.GetUnitsAt(c))
if (a.HasTrait<IPositionable>() && !a.Trait<IPositionable>().CanEnterCell(c))
a.Kill(self);
}
bool NeighbourIsDeadShore(Bridge neighbour)
{
return neighbour != null && info.ShorePieces.Contains(neighbour.type) && neighbour.health.IsDead;
}
bool LongBridgeSegmentIsDead()
{
// The long bridge artwork requires a hack to display correctly
// if the adjacent shore piece is dead
if (!info.Long)
return health.IsDead;
if (NeighbourIsDeadShore(neighbours[0]) || NeighbourIsDeadShore(neighbours[1]))
return true;
return health.IsDead;
}
ushort ChooseTemplate()
{
if (info.Long && LongBridgeSegmentIsDead())
{
// Long bridges have custom art for multiple segments being destroyed
var previousIsDead = neighbours[0] != null && neighbours[0].LongBridgeSegmentIsDead();
var nextIsDead = neighbours[1] != null && neighbours[1].LongBridgeSegmentIsDead();
if (previousIsDead && nextIsDead)
return info.DestroyedPlusBothTemplate;
if (previousIsDead)
return info.DestroyedPlusNorthTemplate;
if (nextIsDead)
return info.DestroyedPlusSouthTemplate;
return info.DestroyedTemplate;
}
var ds = health.DamageState;
return (ds == DamageState.Dead && info.DestroyedTemplate > 0) ? info.DestroyedTemplate :
(ds >= DamageState.Heavy && info.DamagedTemplate > 0) ? info.DamagedTemplate : info.Template;
}
bool killedUnits = false;
void UpdateState()
{
var oldTemplate = template;
template = ChooseTemplate();
if (template == oldTemplate)
return;
// Update map
foreach (var c in footprint.Keys)
self.World.Map.CustomTerrain[c] = GetTerrainType(c);
// If this bridge repair operation connects two pathfinding domains,
// update the domain index.
var domainIndex = self.World.WorldActor.TraitOrDefault<DomainIndex>();
if (domainIndex != null)
domainIndex.UpdateCells(self.World, footprint.Keys);
if (LongBridgeSegmentIsDead() && !killedUnits)
{
killedUnits = true;
KillUnitsOnBridge();
}
}
public void Repair(Actor repairer, int direction, Action onComplete)
{
// Repair self
var initialDamage = health.DamageState;
self.World.AddFrameEndTask(w =>
{
if (health.IsDead)
{
health.Resurrect(self, repairer);
killedUnits = false;
KillUnitsOnBridge();
}
else
health.InflictDamage(self, repairer, -health.MaxHP, null, true);
if (direction < 0 ? neighbours[0] == null && neighbours[1] == null : Hut != null || neighbours[direction] == null)
onComplete(); // Done if single or reached other hut
});
// Repair adjacent spans onto next hut or end
if (direction >= 0 && Hut == null && neighbours[direction] != null)
{
var delay = initialDamage == DamageState.Undamaged || NeighbourIsDeadShore(neighbours[direction]) ?
0 : info.RepairPropagationDelay;
self.World.AddFrameEndTask(w => w.Add(new DelayedAction(delay, () =>
neighbours[direction].Repair(repairer, direction, onComplete))));
}
}
public void DamageStateChanged(Actor self, AttackInfo e)
{
Do((b, d) => b.UpdateState());
// Need to update the neighbours neighbour to correctly
// display the broken shore hack
if (info.ShorePieces.Contains(type))
for (var d = 0; d <= 1; d++)
if (neighbours[d] != null && neighbours[d].neighbours[d] != null)
neighbours[d].neighbours[d].UpdateState();
}
void AggregateDamageState(Bridge b, int d, ref DamageState damage)
{
if (b.health.DamageState > damage)
damage = b.health.DamageState;
if (b.Hut == null && d >= 0 && b.neighbours[d] != null)
AggregateDamageState(b.neighbours[d], d, ref damage);
}
// Find the worst span damage before other hut
public DamageState AggregateDamageState()
{
var damage = health.DamageState;
Do((b, d) => AggregateDamageState(b, d, ref damage));
return damage;
}
public void Demolish(Actor saboteur, int direction)
{
var initialDamage = health.DamageState;
self.World.AddFrameEndTask(w =>
{
var weapon = saboteur.World.Map.Rules.Weapons[info.DemolishWeapon.ToLowerInvariant()];
// Use .FromPos since this actor is killed. Cannot use Target.FromActor
weapon.Impact(Target.FromPos(self.CenterPosition), saboteur, Enumerable.Empty<int>());
self.World.WorldActor.Trait<ScreenShaker>().AddEffect(15, self.CenterPosition, 6);
self.Kill(saboteur);
});
// Destroy adjacent spans between (including) huts
if (direction >= 0 && Hut == null && neighbours[direction] != null)
{
var delay = initialDamage == DamageState.Dead || NeighbourIsDeadShore(neighbours[direction]) ?
0 : info.RepairPropagationDelay;
self.World.AddFrameEndTask(w => w.Add(new DelayedAction(delay, () =>
neighbours[direction].Demolish(saboteur, direction))));
}
}
}
}