Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
150 changes: 150 additions & 0 deletions OpenRA.Mods.AS/Traits/World/WeaponTriggerCells.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
#region Copyright & License Information
/*
* Copyright 2015- OpenRA.Mods.AS Developers (see AUTHORS)
* This file is a part of a third-party plugin for 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 OpenRA.Graphics;
using OpenRA.Mods.Common.Effects;
using OpenRA.Primitives;
using OpenRA.Traits;

namespace OpenRA.Mods.AS.Traits
{
[Desc("A layer that support weapon like infernal cannon like in cnc General")]
[TraitLocation(SystemActors.World | SystemActors.EditorWorld)]
public sealed class WeaponTriggerCellsInfo : TraitInfo
{
[Desc("Name of the layer type")]
public readonly string Name = "";

[Desc("Speed of restore per tick to 0")]
public readonly int RestorePerTick = 4;

[Desc("Show debug overlay of this WeaponTriggerCells")]
public readonly bool ShowDebugOverlay = false;

public override object Create(ActorInitializer init) { return new WeaponTriggerCells(init.Self, this); }
}

sealed class TriggerCell
{
public int Level;
}

public sealed class WeaponTriggerCells : INotifyActorDisposing, ITick, ITickRender
{
readonly World world;
public readonly WeaponTriggerCellsInfo Info;

readonly Dictionary<CPos, TriggerCell> tiles = new Dictionary<CPos, TriggerCell>();

public WeaponTriggerCells(Actor self, WeaponTriggerCellsInfo info)
{
world = self.World;
Info = info;
}

void ITick.Tick(Actor self)
{
var remove = new List<CPos>();

// Apply half life to each cell.
foreach (var kv in tiles)
{
// has to be decreased by at least 1 so that it disappears eventually.
var level = kv.Value.Level;
if (level > 0)
{
if ((level -= Info.RestorePerTick) <= 0)
remove.Add(kv.Key);
else
kv.Value.Level = level;
}
else
{
if ((level += Info.RestorePerTick) >= 0)
remove.Add(kv.Key);
else
kv.Value.Level = level;
}
}

foreach (var r in remove)
tiles.Remove(r);
}

public int GetLevel(CPos cell)
{
if (!tiles.ContainsKey(cell))
return 0;

return tiles[cell].Level;
}

public void SetLevel(CPos cell, int level)
{
if (!tiles.ContainsKey(cell))
return;

tiles[cell].Level = level;
}

public void IncreaseLevel(CPos cell, int add_level, int max_level)
{
if (add_level == 0)
return;

var currentLevel = 0;

// Initialize, on fresh impact.
if (tiles.ContainsKey(cell))
currentLevel = tiles[cell].Level;

// the given weapon can't saturate the cell anymore.
if ((add_level > 0 && currentLevel >= max_level) ||
(add_level < 0 && currentLevel <= max_level))
return;

var new_level = currentLevel + add_level;

if ((add_level > 0 && new_level > max_level) ||
(add_level < 0 && new_level < max_level))
return;

currentLevel = new_level;

if (!tiles.ContainsKey(cell))
tiles[cell] = new TriggerCell() { Level = currentLevel };
else
tiles[cell].Level = currentLevel;
}

bool disposed = false;
void INotifyActorDisposing.Disposing(Actor self)
{
if (disposed)
return;

disposed = true;
}

// Debug only, require enabling the `ITickRender` interface in this class
public void TickRender(WorldRenderer wr, Actor self)
{
if (Info.ShowDebugOverlay)
{
foreach (var kv in tiles)
{
var i = new FloatingText(world.Map.CenterOfCell(kv.Key), Color.Gold, kv.Value.Level.ToString(), 1);
world.Add(i);
}
}
}
}
}
136 changes: 136 additions & 0 deletions OpenRA.Mods.AS/Warheads/TriggerLayerWeaponWarhead.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
using System;
using System.Linq;
using OpenRA.GameRules;
using OpenRA.Mods.AS.Traits;
using OpenRA.Mods.Common.Traits;
using OpenRA.Mods.Common.Warheads;
using OpenRA.Traits;

namespace OpenRA.Mods.TA.Warheads
{
[Desc("Works like infernal cannon like in cnc General, used by TA")]
public sealed class TriggerLayerWeaponWarhead : Warhead, IRulesetLoaded<WeaponInfo>
{
[Desc("Range between falloff steps in cells.")]
public readonly WDist Spread = new WDist(1024);

[Desc("Level percentage at each range step.")]
public readonly int[] Falloff = { 100, 75, 50 };

[Desc("The name of the layer we want to increase the level of.")]
public readonly string LayerName = "";

[Desc("Ranges at which each Falloff step is defined (in cells). Overrides Spread.")]
public WDist[] Range = null;

[Desc("Level this weapon puts on the ground. Accumulates over previously trigger area.")]
public int Level = 200;

[Desc("It saturates at this level, by this weapon.")]
public int MaxLevel = 600;

[Desc("Allow triggering effects when the impacted cell has the value in [triggerAtLevelMax, triggerAtLevelMin]")]
public bool AllowtriggerLevel = true;

[Desc("Allows a triggering effect: cells (affected by Falloff and Range) set to a specific level defined by triggerSetLevel")]
public bool AllowSetLevelWhenTrigger = true;

[Desc("Allows a triggering effect: impacted cell explode a weapon")]
public bool AllowtriggerWeaponWhenTrigger = true;

[Desc("Impacted cell has the value in [triggerAtLevelMax, triggerAtLevelMin] to trigger effect. Requires \"AllowtriggerLevel = true\".")]
public int TriggerAtLevelMax = int.MaxValue;

[Desc("Impacted cell has the value in [triggerAtLevelMax, triggerAtLevelMin] to trigger effect. Requires \"AllowtriggerLevel = true\".")]
public int TriggerAtLevelMin = int.MinValue;

[Desc("Cells (affected by Falloff and Range) set to this level when trigger. Requires \"AllowtriggerLevel = true\" and \"AllowSetLevelWhenTrigger = true\"")]
public int TriggerSetLevel = 0;

[WeaponReference]
[Desc("Impacted cell explode a weapon when trigger. Has to be defined in weapons.yaml as well.")]
public readonly string TriggerWeapon = null;

WeaponInfo weapon;

public void RulesetLoaded(Ruleset rules, WeaponInfo info)
{
if (Range == null)
Range = Exts.MakeArray(Falloff.Length, i => i * Spread);
else
{
if (Range.Length != 1 && Range.Length != Falloff.Length)
throw new YamlException("Number of range values must be 1 or equal to the number of Falloff values.");

for (var i = 0; i < Range.Length - 1; i++)
if (Range[i] > Range[i + 1])
throw new YamlException("Range values must be specified in an increasing order.");
}

if (AllowtriggerLevel && AllowtriggerWeaponWhenTrigger && !rules.Weapons.TryGetValue(TriggerWeapon.ToLowerInvariant(), out weapon))
throw new YamlException($"Weapons Ruleset does not contain an entry '{TriggerWeapon.ToLowerInvariant()}'");
}

public override void DoImpact(in Target target, WarheadArgs args)
{
var firedBy = args.SourceActor;
var world = firedBy.World;

if (world.LocalPlayer != null)
{
var devMode = world.LocalPlayer.PlayerActor.TraitOrDefault<DebugVisualizations>();
if (devMode != null && devMode.CombatGeometry)
{
var rng = Exts.MakeArray(Range.Length, i => WDist.FromCells(Range[i].Length));
world.WorldActor.Trait<WarheadDebugOverlay>().AddImpact(target.CenterPosition, rng, Primitives.Color.Gold);
}
}

var targetTile = world.Map.CellContaining(target.CenterPosition);
var raLayer = world.WorldActor.TraitsImplementing<WeaponTriggerCells>()
.First(l => l.Info.Name == LayerName);

var triggeredSetLevel = false;
if (AllowtriggerLevel &&
raLayer.GetLevel(targetTile) >= TriggerAtLevelMin &&
raLayer.GetLevel(targetTile) <= TriggerAtLevelMax)
{
if (AllowtriggerWeaponWhenTrigger)
weapon.Impact(Target.FromPos(target.CenterPosition), firedBy);

var affectedCells = world.Map.FindTilesInCircle(targetTile, (int)Math.Ceiling((decimal)Range[Range.Length - 1].Length / 1024));
if (AllowSetLevelWhenTrigger)
{
triggeredSetLevel = true;
foreach (var cell in affectedCells)
raLayer.SetLevel(cell, TriggerSetLevel);
}
}

if (!triggeredSetLevel && Level != 0)
{
var affectedCells = world.Map.FindTilesInCircle(targetTile, (int)Math.Ceiling((decimal)Range[Range.Length - 1].Length / 1024));
foreach (var cell in affectedCells)
{
var mul = GetIntensityFalloff((target.CenterPosition - world.Map.CenterOfCell(cell)).Length);
raLayer.IncreaseLevel(cell, Level * mul / 100, MaxLevel);
}
}
}

int GetIntensityFalloff(int distance)
{
var inner = Range[0].Length;
for (var i = 1; i < Range.Length; i++)
{
var outer = Range[i].Length;
if (outer > distance)
return int2.Lerp(Falloff[i - 1], Falloff[i], distance - inner, outer - inner);

inner = outer;
}

return 0;
}
}
}