Skip to content

Commit

Permalink
Add SpawnHuskEffectOnDeath and ProjectileHusk
Browse files Browse the repository at this point in the history
1. this is to solve the bug that when actor aircraft husk spawn at ground level can never die

2. aircraft husk now has inertia of the dead aircraft, more reasonable

3. aircraft use projectile will save us some PERF on both CPU and RAM.
  • Loading branch information
dnqbob committed Oct 13, 2023
1 parent 2d88794 commit 2bff231
Show file tree
Hide file tree
Showing 12 changed files with 935 additions and 818 deletions.
243 changes: 243 additions & 0 deletions OpenRA.Mods.Sp/Projectiles/ProjetcileHusk.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,243 @@
#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.Graphics;
using OpenRA.Mods.Common.Effects;
using OpenRA.Mods.SP.Traits;
using OpenRA.Primitives;
using OpenRA.Traits;
using Util = OpenRA.Mods.Common.Util;

namespace OpenRA.Mods.SP.Projectiles
{
[Desc("Projectile with customisable acceleration vector, recieve dead actor speed by using range modifier, used as aircraft husk.")]
public class ProjetcileHuskInfo : IProjectileInfo
{
public readonly string Image = null;

[SequenceReference(nameof(Image), allowNullImage: true)]
[Desc("Loop a randomly chosen sequence of Image from this list while falling.")]
public readonly string[] Sequences = { "idle" };

[PaletteReference]
[Desc("The palette used to draw this projectile.")]
public readonly string Palette = "effect";

[Desc("Palette is a player palette BaseName")]
public readonly bool IsPlayerPalette = false;

[Desc("Does this projectile have a shadow?")]
public readonly bool Shadow = false;

[Desc("Color to draw shadow if Shadow is true.")]
public readonly Color ShadowColor = Color.FromArgb(140, 0, 0, 0);

[Desc("Projectile movement vector per tick (forward, right, up), use negative values for opposite directions.")]
public readonly WVec Velocity = WVec.Zero;

[Desc("Value added to Velocity every tick when spin is activated.")]
public readonly WVec AccelerationWhenSpin = new(0, 0, -10);

[Desc("Value added to Velocity every tickwhen spin is NOT activated.")]
public readonly WVec Acceleration = new(0, 0, -10);

[Desc("The X of the speed becomes dead actor speed by using range modifier, coop with " + nameof(SpawnHuskEffectOnDeath) + ".")]
public readonly bool UseRangeModifierAsVelocityX = true;

[Desc("Chance of Spin. Activate Spin.")]
public readonly int SpinChance = 100;

[Desc("Limit the maximum spin (in angle units per tick) that can be achieved.",
"0 Disables spinning.")]
public readonly int MaximumSpinSpeed = 0;

[Desc("Spin acceleration.")]
public readonly int SpinAcc = 0;

[Desc("begin spin speed.")]
public readonly int Spin = 0;

[Desc("Revert the Y of the speed, spin and horizongtal acceleration at 50% randomness.")]
public readonly bool HorizontalRevert = false;

[Desc("Trail animation.")]
public readonly string TrailImage = null;

[SequenceReference(nameof(TrailImage), allowNullImage: true)]
[Desc("Loop a randomly chosen sequence of TrailImage from this list while this projectile is moving.")]
public readonly string[] TrailSequences = { "idle" };

[Desc("Interval in ticks between each spawned Trail animation.")]
public readonly int TrailInterval = 2;

[Desc("Delay in ticks until trail animation is spawned.")]
public readonly int TrailDelay = 0;

[PaletteReference(nameof(TrailUsePlayerPalette))]
[Desc("Palette used to render the trail sequence.")]
public readonly string TrailPalette = "effect";

[Desc("Use the Player Palette to render the trail sequence.")]
public readonly bool TrailUsePlayerPalette = false;

public IProjectile Create(ProjectileArgs args) { return new ProjetcileHusk(this, args); }
}

public class ProjetcileHusk : IProjectile, ISync
{
readonly ProjetcileHuskInfo info;
readonly Animation anim;
readonly ProjectileArgs args;
readonly string trailPalette;

readonly float3 shadowColor;
readonly float shadowAlpha;
readonly int spinAcc;
readonly int maxSpin;

WVec velocity;
WVec acceleration;
WAngle facing;
int spin;

[Sync]
WPos pos, lastPos;
int smokeTicks;

public ProjetcileHusk(ProjetcileHuskInfo info, ProjectileArgs args)
{
this.info = info;
this.args = args;
pos = args.Source;
facing = args.Facing;
var world = args.SourceActor.World;

var vx = info.UseRangeModifierAsVelocityX && args.RangeModifiers.Length > 0 ? args.RangeModifiers[0] : info.Velocity.X;

if (info.HorizontalRevert && world.SharedRandom.Next(2) == 0)
{
velocity = new WVec(-info.Velocity.Y, -vx, info.Velocity.Z);
if (info.MaximumSpinSpeed > 0 && world.SharedRandom.Next(1, 101) <= info.SpinChance)
{
acceleration = new WVec(-info.AccelerationWhenSpin.Y, info.AccelerationWhenSpin.X, info.AccelerationWhenSpin.Z);
spin = -info.Spin;
spinAcc = -info.SpinAcc;
maxSpin = -info.MaximumSpinSpeed;
}
else
acceleration = new WVec(-info.Acceleration.Y, info.Acceleration.X, info.Acceleration.Z);
}
else
{
velocity = new WVec(info.Velocity.Y, -vx, info.Velocity.Z);
if (info.MaximumSpinSpeed > 0 && world.SharedRandom.Next(1, 101) <= info.SpinChance)
{
acceleration = new WVec(info.AccelerationWhenSpin.Y, -info.AccelerationWhenSpin.X, info.AccelerationWhenSpin.Z);
spin = info.Spin;
spinAcc = info.SpinAcc;
maxSpin = info.MaximumSpinSpeed;
}
else
acceleration = new WVec(info.Acceleration.Y, -info.Acceleration.X, info.Acceleration.Z);
}

velocity = velocity.Rotate(WRot.FromYaw(facing));
acceleration = acceleration.Rotate(WRot.FromYaw(facing));

if (!string.IsNullOrEmpty(info.Image))
{
anim = new Animation(args.SourceActor.World, info.Image, GetEffectiveFacing);
anim.PlayRepeating(info.Sequences.Random(args.SourceActor.World.SharedRandom));
}

shadowColor = new float3(info.ShadowColor.R, info.ShadowColor.G, info.ShadowColor.B) / 255f;
shadowAlpha = info.ShadowColor.A / 255f;

trailPalette = info.TrailPalette;
if (info.TrailUsePlayerPalette)
trailPalette += args.SourceActor.Owner.InternalName;
smokeTicks = info.TrailDelay;
}

public void Tick(World world)
{
lastPos = pos;
pos += velocity;
var spinAngle = new WAngle(spin);
facing += spinAngle;
acceleration = acceleration.Rotate(WRot.FromYaw(spinAngle));
velocity += acceleration;

spin = Math.Abs(spin) < Math.Abs(maxSpin) ? spin + spinAcc : maxSpin;

if (pos.Z <= args.PassiveTarget.Z)
{
pos += new WVec(0, 0, args.PassiveTarget.Z - pos.Z);
world.AddFrameEndTask(w => w.Remove(this));

var warheadArgs = new WarheadArgs(args)
{
ImpactOrientation = new WRot(WAngle.Zero, Util.GetVerticalAngle(lastPos, pos), args.Facing),
ImpactPosition = pos,
};

args.Weapon.Impact(Target.FromPos(pos), warheadArgs);
}

if (!string.IsNullOrEmpty(info.TrailImage) && --smokeTicks < 0)
{
world.AddFrameEndTask(w => w.Add(new SpriteEffect(pos, GetEffectiveFacing(), w,
info.TrailImage, info.TrailSequences.Random(world.SharedRandom), trailPalette)));

smokeTicks = info.TrailInterval;
}

anim?.Tick();
}

WAngle GetEffectiveFacing()
{
return facing;
}

public IEnumerable<IRenderable> Render(WorldRenderer wr)
{
if (anim == null)
yield break;

var world = args.SourceActor.World;
if (!world.FogObscures(pos))
{
var paletteName = info.Palette;
if (paletteName != null && info.IsPlayerPalette)
paletteName += args.SourceActor.Owner.InternalName;

var palette = wr.Palette(paletteName);

if (info.Shadow)
{
var dat = world.Map.DistanceAboveTerrain(pos);
var shadowPos = pos - new WVec(0, 0, dat.Length);
foreach (var r in anim.Render(shadowPos, palette))
yield return ((IModifyableRenderable)r)
.WithTint(shadowColor, ((IModifyableRenderable)r).TintModifiers | TintModifiers.ReplaceColor)
.WithAlpha(shadowAlpha);
}

foreach (var r in anim.Render(pos, palette))
yield return r;
}
}
}
}
139 changes: 139 additions & 0 deletions OpenRA.Mods.Sp/Traits/SpawnHuskEffectOnDeath.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
#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.Linq;
using OpenRA.GameRules;
using OpenRA.Mods.Common.Activities;
using OpenRA.Mods.Common.Traits;
using OpenRA.Primitives;
using OpenRA.Traits;

namespace OpenRA.Mods.SP.Traits
{
[Desc("Spawn projectile as husk upon death.")]
public class SpawnHuskEffectOnDeathInfo : ConditionalTraitInfo, IRulesetLoaded
{
[WeaponReference]
[FieldLoader.Require]
[Desc("Weapon to spawn on death as husk.")]
public readonly string Weapon = null;

[Desc("DeathType(s) that trigger the effect. Leave empty to always trigger an effect.")]
public readonly BitSet<DamageType> DeathTypes = default;

[Desc("Offset to fire husk weapon from on death.")]
public readonly WVec LocalOffset = WVec.Zero;

[Desc("Give random facing instead of actor facing to husk weapon.")]
public readonly bool RandomFacing = false;

[Desc("Offset to fire husk weapon to on death.")]
public readonly WVec TargetOffset = new(200, 0, 0);

[Desc("Always target ground level when fire at TargetOffset.")]
public readonly bool ForceToGround = true;

[Desc("Pass current actor speed as RangeModifier to husk weapon.",
"Only supports aircraft for now.")]
public readonly bool UnitSpeedAsRangeModifier = true;

public WeaponInfo WeaponInfo { get; private set; }

public override void RulesetLoaded(Ruleset rules, ActorInfo ai)
{
if (string.IsNullOrEmpty(Weapon))
return;

var weaponToLower = Weapon.ToLowerInvariant();
if (!rules.Weapons.TryGetValue(weaponToLower, out var weapon))
throw new YamlException($"Weapons Ruleset does not contain an entry '{weaponToLower}'");

WeaponInfo = weapon;

base.RulesetLoaded(rules, ai);
}

public override object Create(ActorInitializer init) { return new SpawnHuskEffectOnDeath(this); }
}

public class SpawnHuskEffectOnDeath : ConditionalTrait<SpawnHuskEffectOnDeathInfo>, INotifyKilled
{
public SpawnHuskEffectOnDeath(SpawnHuskEffectOnDeathInfo info)
: base(info) { }

void INotifyKilled.Killed(Actor self, AttackInfo e)
{
if (IsTraitDisabled || (!Info.DeathTypes.IsEmpty && !e.Damage.DamageTypes.Overlaps(Info.DeathTypes)))
return;

var weapon = Info.WeaponInfo;
var body = self.TraitOrDefault<BodyOrientation>();
var facing = Info.RandomFacing ? new WAngle(self.World.SharedRandom.Next(1024)) : self.TraitOrDefault<IFacing>()?.Facing;
if (!facing.HasValue) facing = WAngle.Zero;

var epicenter = self.CenterPosition + (body != null
? body.LocalToWorld(Info.LocalOffset.Rotate(body.QuantizeOrientation(self.Orientation)))
: Info.LocalOffset);
var world = self.World;

var map = world.Map;
var targetpos = epicenter + body.LocalToWorld(new WVec(Info.TargetOffset.Length, 0, 0).Rotate(body.QuantizeOrientation(self.Orientation)));
var target = Target.FromPos(new WPos(targetpos.X, targetpos.Y, Info.ForceToGround ? map.CenterOfCell(map.CellContaining(targetpos)).Z : targetpos.Z));

var rangeModifiers = Array.Empty<int>();
if (Info.UnitSpeedAsRangeModifier)
{
var aircraft = self.TraitOrDefault<Aircraft>();
if (aircraft != null && !self.IsIdle)
{
if (self.CurrentActivity is FlyIdle)
rangeModifiers = new int[1] { aircraft.Info.CanHover ? 0 : aircraft.IdleMovementSpeed };
else if (self.CurrentActivity.ActivitiesImplementing<Fly>().Any())
rangeModifiers = new int[1] { aircraft.MovementSpeed };
}
else
rangeModifiers = new int[1] { 0 };
}

var projectileArgs = new ProjectileArgs
{
Weapon = weapon,
Facing = facing.Value,
CurrentMuzzleFacing = () => facing.Value,

DamageModifiers = Array.Empty<int>(),

InaccuracyModifiers = Array.Empty<int>(),

RangeModifiers = rangeModifiers,
Source = epicenter,
CurrentSource = () => epicenter,
SourceActor = self,
GuidedTarget = target,
PassiveTarget = target.CenterPosition
};

if (projectileArgs.Weapon.Projectile != null)
{
var projectile = projectileArgs.Weapon.Projectile.Create(projectileArgs);
if (projectile != null)
world.AddFrameEndTask(w => w.Add(projectile));
}

if (weapon.Report != null && weapon.Report.Any())
{
if (weapon.AudibleThroughFog || (!self.World.ShroudObscures(epicenter) && !self.World.FogObscures(epicenter)))
Game.Sound.Play(SoundType.World, weapon.Report, world, epicenter, null, weapon.SoundVolume);
}
}
}
}
Binary file added mods/sp/bits/gdi/infantry/jumpjet1.shp
Binary file not shown.
1 change: 0 additions & 1 deletion mods/sp/mod.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,6 @@ Rules:
sp|rules/civilianrules.yaml
sp|rules/techrules.yaml
sp|rules/creeps.yaml
sp|rules/husks.yaml
sp|rules/bridges.yaml
sp|rules/decorations.yaml
sp|rules/misc.yaml
Expand Down
Loading

0 comments on commit 2bff231

Please sign in to comment.