#region Copyright & License Information /* * Copyright (c) The OpenRA Developers and Contributors * 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.GameRules; using OpenRA.Primitives; using OpenRA.Traits; namespace OpenRA.Mods.Common.Traits { [Desc("Bridge actor that can't be passed underneath.")] sealed class GroundLevelBridgeInfo : TraitInfo, IRulesetLoaded, Requires, Requires { public readonly string TerrainType = "Bridge"; public readonly string Type = "GroundLevelBridge"; public readonly CVec[] NeighbourOffsets = Array.Empty(); [WeaponReference] [Desc("The name of the weapon to use when demolishing the bridge")] public readonly string DemolishWeapon = "Demolish"; public WeaponInfo DemolishWeaponInfo { get; private set; } [Desc("Types of damage that this bridge causes to units over/in path of it while being destroyed/repaired. Leave empty for no damage types.")] public readonly BitSet DamageTypes = default; public void RulesetLoaded(Ruleset rules, ActorInfo ai) { var weaponToLower = (DemolishWeapon ?? string.Empty).ToLowerInvariant(); if (!rules.Weapons.TryGetValue(weaponToLower, out var weapon)) throw new YamlException($"Weapons Ruleset does not contain an entry '{weaponToLower}'"); DemolishWeaponInfo = weapon; } public override object Create(ActorInitializer init) { return new GroundLevelBridge(init.Self, this); } } sealed class GroundLevelBridge : IBridgeSegment, INotifyAddedToWorld, INotifyRemovedFromWorld { public readonly GroundLevelBridgeInfo Info; readonly Actor self; readonly BridgeLayer bridgeLayer; readonly IEnumerable cells; readonly IHealth health; public GroundLevelBridge(Actor self, GroundLevelBridgeInfo info) { Info = info; this.self = self; health = self.Trait(); bridgeLayer = self.World.WorldActor.Trait(); var buildingInfo = self.Info.TraitInfo(); cells = buildingInfo.PathableTiles(self.Location); } void UpdateTerrain(Actor self, byte terrainIndex) { foreach (var cell in cells) self.World.Map.CustomTerrain[cell] = terrainIndex; } void INotifyAddedToWorld.AddedToWorld(Actor self) { bridgeLayer.Add(self); var terrainIndex = self.World.Map.Rules.TerrainInfo.GetTerrainIndex(Info.TerrainType); UpdateTerrain(self, terrainIndex); KillInvalidActorsInFootprint(self); } void INotifyRemovedFromWorld.RemovedFromWorld(Actor self) { bridgeLayer.Remove(self); UpdateTerrain(self, byte.MaxValue); KillInvalidActorsInFootprint(self); } void KillInvalidActorsInFootprint(Actor self) { foreach (var c in cells) foreach (var a in self.World.ActorMap.GetActorsAt(c)) if (a.Info.HasTraitInfo() && !a.Trait().CanExistInCell(c)) a.Kill(self, Info.DamageTypes); } void IBridgeSegment.Repair(Actor repairer) { health.InflictDamage(self, repairer, new Damage(-health.MaxHP), true); } void IBridgeSegment.Demolish(Actor saboteur, BitSet damageTypes) { self.World.AddFrameEndTask(w => { if (self.IsDead) return; // Use .FromPos since this actor is dead. Cannot use Target.FromActor Info.DemolishWeaponInfo.Impact(Target.FromPos(self.CenterPosition), saboteur); self.Kill(saboteur, damageTypes); }); } string IBridgeSegment.Type => Info.Type; DamageState IBridgeSegment.DamageState => self.GetDamageState(); bool IBridgeSegment.Valid => self.IsInWorld; CVec[] IBridgeSegment.NeighbourOffsets => Info.NeighbourOffsets; CPos IBridgeSegment.Location => self.Location; } }