Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement D2k Death Hand & TS Cluster Missile warheads #16363

Merged
merged 5 commits into from May 9, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
29 changes: 22 additions & 7 deletions OpenRA.Mods.Common/Projectiles/NukeLaunch.cs
Expand Up @@ -33,6 +33,8 @@ public class NukeLaunch : IProjectile, ISpatiallyPartitionable
readonly WPos ascendTarget;
readonly WPos descendSource;
readonly WPos descendTarget;
readonly WDist detonationAltitude;
readonly bool removeOnDetonation;
readonly int impactDelay;
readonly int turn;
readonly string trailImage;
Expand All @@ -45,9 +47,11 @@ public class NukeLaunch : IProjectile, ISpatiallyPartitionable
int ticks, trailTicks;
int launchDelay;
bool isLaunched;
bool detonated;

public NukeLaunch(Player firedBy, string name, WeaponInfo weapon, string weaponPalette, string upSequence, string downSequence,
WPos launchPos, WPos targetPos, WDist velocity, int launchDelay, int impactDelay, bool skipAscent, string flashType,
WPos launchPos, WPos targetPos, WDist detonationAltitude, bool removeOnDetonation, WDist velocity, int launchDelay, int impactDelay,
bool skipAscent, string flashType,
string trailImage, string[] trailSequences, string trailPalette, bool trailUsePlayerPalette, int trailDelay, int trailInterval)
{
this.firedBy = firedBy;
Expand All @@ -74,6 +78,8 @@ public class NukeLaunch : IProjectile, ISpatiallyPartitionable
ascendTarget = launchPos + offset;
descendSource = targetPos + offset;
descendTarget = targetPos;
this.detonationAltitude = detonationAltitude;
this.removeOnDetonation = removeOnDetonation;

anim = new Animation(firedBy.World, name);

Expand All @@ -100,14 +106,15 @@ public void Tick(World world)
if (ticks == turn)
anim.PlayRepeating(downSequence);

if (ticks < turn)
var isDescending = ticks >= turn;
if (!isDescending)
pos = WPos.LerpQuadratic(ascendSource, ascendTarget, WAngle.Zero, ticks, turn);
else
pos = WPos.LerpQuadratic(descendSource, descendTarget, WAngle.Zero, ticks - turn, impactDelay - turn);

if (!string.IsNullOrEmpty(trailImage) && --trailTicks < 0)
{
var trailPos = ticks < turn ? WPos.LerpQuadratic(ascendSource, ascendTarget, WAngle.Zero, ticks - trailDelay, turn)
var trailPos = !isDescending ? WPos.LerpQuadratic(ascendSource, ascendTarget, WAngle.Zero, ticks - trailDelay, turn)
: WPos.LerpQuadratic(descendSource, descendTarget, WAngle.Zero, ticks - turn - trailDelay, impactDelay - turn);

world.AddFrameEndTask(w => w.Add(new SpriteEffect(trailPos, w, trailImage, trailSequences.Random(world.SharedRandom),
Expand All @@ -116,23 +123,31 @@ public void Tick(World world)
trailTicks = trailInterval;
}

if (ticks == impactDelay)
Explode(world);
var dat = world.Map.DistanceAboveTerrain(pos);
if (ticks == impactDelay || (isDescending && dat <= detonationAltitude))
Explode(world, ticks == impactDelay || removeOnDetonation);

world.ScreenMap.Update(this, pos, anim.Image);

ticks++;
}

void Explode(World world)
void Explode(World world, bool removeProjectile)
{
world.AddFrameEndTask(w => { w.Remove(this); w.ScreenMap.Remove(this); });
if (removeProjectile)
world.AddFrameEndTask(w => { w.Remove(this); w.ScreenMap.Remove(this); });

if (detonated)
return;

weapon.Impact(Target.FromPos(pos), firedBy.PlayerActor, Enumerable.Empty<int>());
world.WorldActor.Trait<ScreenShaker>().AddEffect(20, pos, 5);

foreach (var flash in world.WorldActor.TraitsImplementing<FlashPaletteEffect>())
if (flash.Info.Type == flashType)
flash.Enable(-1);

detonated = true;
}

public IEnumerable<IRenderable> Render(WorldRenderer wr)
Expand Down
9 changes: 8 additions & 1 deletion OpenRA.Mods.Common/Traits/SupportPowers/NukePower.cs
Expand Up @@ -35,6 +35,13 @@ class NukePowerInfo : SupportPowerInfo, IRulesetLoaded, Requires<BodyOrientation
[Desc("Offset from the actor the missile spawns on.")]
public readonly WVec SpawnOffset = WVec.Zero;

[Desc("Altitude offset from the target position at which the warhead should detonate.")]
public readonly WDist DetonationAltitude = WDist.Zero;

[Desc("Should nuke missile projectile be removed on detonation above ground.",
"'False' will make the missile continue until it hits the ground and disappears (without triggering another explosion).")]
public readonly bool RemoveMissileOnDetonation = true;

[Desc("Palette to use for the missile weapon image.")]
[PaletteReference("IsPlayerPalette")] public readonly string MissilePalette = "effect";

Expand Down Expand Up @@ -133,7 +140,7 @@ public void Activate(Actor self, WPos targetPosition)
var palette = info.IsPlayerPalette ? info.MissilePalette + self.Owner.InternalName : info.MissilePalette;
var missile = new NukeLaunch(self.Owner, info.MissileWeapon, info.WeaponInfo, palette, info.MissileUp, info.MissileDown,
self.CenterPosition + body.LocalToWorld(info.SpawnOffset),
targetPosition,
targetPosition, info.DetonationAltitude, info.RemoveMissileOnDetonation,
info.FlightVelocity, info.MissileDelay, info.FlightDelay, info.SkipAscent,
info.FlashType,
info.TrailImage, info.TrailSequences, info.TrailPalette, info.TrailUsePlayerPalette, info.TrailDelay, info.TrailInterval);
Expand Down
115 changes: 115 additions & 0 deletions OpenRA.Mods.Common/Warheads/FireClusterWarhead.cs
@@ -0,0 +1,115 @@
#region Copyright & License Information
/*
* Copyright 2007-2019 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, either version 3 of
* the License, or (at your option) any later version. For more
* information, see COPYING.
*/
#endregion

using System.Collections.Generic;
using System.Linq;
using OpenRA.GameRules;
using OpenRA.Mods.Common.Traits;
using OpenRA.Traits;

namespace OpenRA.Mods.Common.Warheads
{
public class FireClusterWarhead : Warhead, IRulesetLoaded<WeaponInfo>
{
[WeaponReference, FieldLoader.Require]
[Desc("Has to be defined in weapons.yaml as well.")]
public readonly string Weapon = null;

[Desc("Number of weapons fired at random 'x' cells. Negative values will result in a number equal to 'x' footprint cells fired.")]
public readonly int RandomClusterCount = -1;

[FieldLoader.Require]
[Desc("Size of the cluster footprint")]
public readonly CVec Dimensions = CVec.Zero;

[FieldLoader.Require]
[Desc("Cluster footprint. Cells marked as X will be attacked.",
"Cells marked as x will be attacked randomly until RandomClusterCount is reached.")]
public readonly string Footprint = string.Empty;

WeaponInfo weapon;

public void RulesetLoaded(Ruleset rules, WeaponInfo info)
{
if (!rules.Weapons.TryGetValue(Weapon.ToLowerInvariant(), out weapon))
throw new YamlException("Weapons Ruleset does not contain an entry '{0}'".F(Weapon.ToLowerInvariant()));
}

public override void DoImpact(Target target, Actor firedBy, IEnumerable<int> damageModifiers)
{
if (!target.IsValidFor(firedBy))
return;

var map = firedBy.World.Map;
var targetCell = map.CellContaining(target.CenterPosition);

var targetCells = CellsMatching(targetCell, false);
foreach (var c in targetCells)
FireProjectileAtCell(map, firedBy, target, c, damageModifiers);

if (RandomClusterCount != 0)
{
var randomTargetCells = CellsMatching(targetCell, true);
var clusterCount = RandomClusterCount < 0 ? randomTargetCells.Count() : RandomClusterCount;
if (randomTargetCells.Any())
for (var i = 0; i < clusterCount; i++)
FireProjectileAtCell(map, firedBy, target, randomTargetCells.Random(firedBy.World.SharedRandom), damageModifiers);
}
}

void FireProjectileAtCell(Map map, Actor firedBy, Target target, CPos targetCell, IEnumerable<int> damageModifiers)
{
var tc = Target.FromCell(firedBy.World, targetCell);

if (!weapon.IsValidAgainst(tc, firedBy.World, firedBy))
return;

var args = new ProjectileArgs
{
Weapon = weapon,
Facing = (map.CenterOfCell(targetCell) - target.CenterPosition).Yaw.Facing,

DamageModifiers = damageModifiers.ToArray(),
InaccuracyModifiers = new int[0],
RangeModifiers = new int[0],

Source = target.CenterPosition,
CurrentSource = () => target.CenterPosition,
SourceActor = firedBy,
PassiveTarget = map.CenterOfCell(targetCell),
GuidedTarget = tc
};

if (args.Weapon.Projectile != null)
{
var projectile = args.Weapon.Projectile.Create(args);
if (projectile != null)
firedBy.World.AddFrameEndTask(w => w.Add(projectile));

if (args.Weapon.Report != null && args.Weapon.Report.Any())
Game.Sound.Play(SoundType.World, args.Weapon.Report, firedBy.World, target.CenterPosition);
}
}

IEnumerable<CPos> CellsMatching(CPos location, bool random)
{
var cellType = !random ? 'X' : 'x';
var index = 0;
var footprint = Footprint.Where(c => !char.IsWhiteSpace(c)).ToArray();
var x = location.X - (Dimensions.X - 1) / 2;
var y = location.Y - (Dimensions.Y - 1) / 2;
for (var j = 0; j < Dimensions.Y; j++)
for (var i = 0; i < Dimensions.X; i++)
if (footprint[index++] == cellType)
yield return new CPos(x + i, y + j);
}
}
}
4 changes: 3 additions & 1 deletion mods/d2k/rules/structures.yaml
Expand Up @@ -1151,9 +1151,11 @@ palace:
BeginChargeSpeechNotification: DeathHandMissilePrepping
EndChargeSpeechNotification: DeathHandMissileReady
LaunchSpeechNotification: MissileLaunchDetected
MissileWeapon: atomic
MissileWeapon: deathhand
MissileDelay: 18
SpawnOffset: 32,816,0
DetonationAltitude: 3c0
RemoveMissileOnDetonation: False
DisplayBeacon: True
DisplayRadarPing: True
CameraRange: 10c0
Expand Down
2 changes: 1 addition & 1 deletion mods/d2k/sequences/misc.yaml
Expand Up @@ -259,7 +259,7 @@ missile2:
Facings: -32
ZOffset: 1023

atomic:
deathhand:
up: DATA.R8
Start: 2147
ZOffset: 1023
Expand Down
52 changes: 32 additions & 20 deletions mods/d2k/weapons/other.yaml
Expand Up @@ -106,36 +106,48 @@ Demolish:
ImpactSounds: EXPLLG2.WAV
ImpactActors: false

Atomic:
DeathHand:
Warhead@Cluster: FireCluster
Weapon: DeathHandCluster
RandomClusterCount: 14
Dimensions: 3,3
Footprint: xxx xXx xxx
Warhead@2Eff: CreateEffect
Explosions: nuke
ImpactSounds: EXPLLG2.WAV
ImpactActors: false

DeathHandCluster:
Inherits: Debris2
Range: 7c0
Projectile: Bullet
Image: 120mm
TrailImage: small_trail2
Speed: 96
LaunchAngle: 0, 32
Inaccuracy: 1c512
BounceCount: 0
Warhead@1Dam: SpreadDamage
Spread: 1c0
Falloff: 200, 100, 50, 25, 12, 0
Damage: 27000 ##225 in vanilla but of course is a cluster bomb instead, so damage spread out
Damage: 4500
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tweaked both the DeathHand and ClusterMissile, including their damage (was way too low before, especially on the DH compared to Atreides airstrike).

Do you mean relative to the original game, or relative to common sense?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Both. TS Cluster Missile was normally a guaranteed kill for at least up to Advanced Power Plants in the original, provided they were on the targeted cell.

Death Hand in the original had some inherent Missile inaccuracy, but when it hit something directly, it hit pretty hard.
Additionally, Ornithopters in our mod are more powerful than in the original, and actually feel a tiny bit more powerful than the DH even with this.
But to be honest, I'd prefer to leave any further D2k balance polishing to someone like @MustaphaTR, this was just to make the difference between the super weapons small enough that it doesn't make the DH a joke in comparison.

Versus:
none: 90
wall: 50
building: 75
building: 100
wood: 60
light: 60
heavy: 60
invulnerable: 0
cy: 25
harvester: 60
DamageTypes: Prone50Percent, TriggerProne, SoundDeath
Warhead@2Eff: CreateEffect
Explosions: nuke
ImpactSounds: EXPLLG2.WAV
ImpactActors: false
Warhead@3Concrete: DamagesConcrete
Damage: 24300

CrateNuke:
Inherits: Atomic
Warhead@1Dam: SpreadDamage
Spread: 320
Falloff: 100, 60, 30, 15, 0
Damage: 5000
AffectsParent: true
DamageTypes: Prone50Percent, TriggerProne, ExplosionDeath
Warhead@2Smu: LeaveSmudge
SmudgeType: SandCrater, RockCrater
InvalidTargets: Vehicle, Structure
Warhead@3Eff: CreateEffect
Explosions: large_explosion
ImpactSounds: EXPLSML4.WAV
Warhead@2Concrete: DamagesConcrete
Damage: 4500

CrateExplosion:
Warhead@1Dam: SpreadDamage
Expand Down
6 changes: 4 additions & 2 deletions mods/ts/rules/nod-support.yaml
Expand Up @@ -358,13 +358,15 @@ NAMISL:
Icon: clustermissile
ChargeInterval: 13500
Description: Cluster Missile
LongDesc: Launches a conventional warhead\nat a target location.
LongDesc: Launches an explosive cluster warhead\nat a target location.
EndChargeSpeechNotification: ClusterMissileReady
SelectTargetSpeechNotification: SelectTarget
IncomingSpeechNotification: MissileLaunchDetected
LaunchSound: icbm1.aud
MissileWeapon: ClusterMissile
SpawnOffset: 0,427,0
MissileDelay: 10
DetonationAltitude: 5c0
SpawnOffset: 72,72,0
DisplayTimerStances: None
DisplayBeacon: False
DisplayRadarPing: True
Expand Down