-
Notifications
You must be signed in to change notification settings - Fork 10
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add SpawnHuskEffectOnDeath and ProjectileHusk
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
Showing
12 changed files
with
935 additions
and
818 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.