From 7f975e713cf38da8f59927288a5d867ef74d8ee7 Mon Sep 17 00:00:00 2001 From: dnqbob Date: Tue, 14 Nov 2023 00:26:05 +0800 Subject: [PATCH] Scrin Essence prefers wounded units. --- .../Warheads/ScrinEssenceHitWarhead.cs | 145 ++++++++++++++++++ mods/sp/weapons/scrweapons.yaml | 27 ++-- 2 files changed, 154 insertions(+), 18 deletions(-) create mode 100644 OpenRA.Mods.Sp/Warheads/ScrinEssenceHitWarhead.cs diff --git a/OpenRA.Mods.Sp/Warheads/ScrinEssenceHitWarhead.cs b/OpenRA.Mods.Sp/Warheads/ScrinEssenceHitWarhead.cs new file mode 100644 index 000000000..a31357656 --- /dev/null +++ b/OpenRA.Mods.Sp/Warheads/ScrinEssenceHitWarhead.cs @@ -0,0 +1,145 @@ +#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; +using System.Linq; +using OpenRA.GameRules; +using OpenRA.Mods.Common; +using OpenRA.Mods.Common.Traits; +using OpenRA.Mods.Common.Warheads; +using OpenRA.Primitives; +using OpenRA.Traits; + +namespace OpenRA.Mods.SP.Warheads +{ + public class ScrinEssenceHitWarhead : Warhead, IRulesetLoaded + { + [WeaponReference] + [FieldLoader.Require] + [Desc("Has to be defined in weapons.yaml as well.")] + public readonly string Weapon = null; + + public readonly string WeaponName = "primary"; + + [Desc("Target types that will not be considered first.")] + public readonly BitSet SecondaryTargets = default; + + [Desc("Amount of shrapnels thrown.")] + public readonly int Amount = 1; + + [Desc("What diplomatic stances can be targeted by the shrapnel.")] + public readonly PlayerRelationship AimTargetStances = PlayerRelationship.Ally | PlayerRelationship.Neutral | PlayerRelationship.Enemy; + + [Desc("Should the weapons be fired around the intended target or at the explosion's epicenter.")] + public readonly bool AroundTarget = false; + + 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 '{Weapon.ToLowerInvariant()}'"); + } + + public override void DoImpact(in Target target, WarheadArgs args) + { + var firedBy = args.SourceActor; + if (!target.IsValidFor(firedBy)) + return; + + var world = firedBy.World; + var map = world.Map; + + var epicenter = AroundTarget && args.WeaponTarget.Type != TargetType.Invalid + ? args.WeaponTarget.CenterPosition + : target.CenterPosition; + + var availableTargetActors = world.FindActorsOnCircle(epicenter, weapon.Range) + .Where(x => + { + if (!weapon.IsValidAgainst(Target.FromActor(x), firedBy.World, firedBy) || !AimTargetStances.HasRelationship(firedBy.Owner.RelationshipWith(x.Owner))) + return false; + + var activeShapes = x.TraitsImplementing().Where(Exts.IsTraitEnabled); + if (!activeShapes.Any()) + return false; + + var distance = activeShapes.Min(t => t.DistanceFromEdge(x, epicenter)); + + if (distance < weapon.Range) + return true; + + return false; + }).ToArray(); + + var preferedTargetActors = availableTargetActors.Where(x => !SecondaryTargets.Overlaps(x.GetEnabledTargetTypes())).Shuffle(world.SharedRandom).ToList(); + + var amount = 0; + for (; amount < Amount && amount < preferedTargetActors.Count; amount++) + GenerateWeapon(firedBy, preferedTargetActors[amount], epicenter, target); + + if (Amount <= amount) + return; + else + amount = Amount - amount; + + var otherTargetActors = availableTargetActors.Where(x => SecondaryTargets.Overlaps(x.GetEnabledTargetTypes())).Shuffle(world.SharedRandom).ToList(); + for (var i = 0; i < amount && i < otherTargetActors.Count; i++) + GenerateWeapon(firedBy, otherTargetActors[i], epicenter, target); + } + + void GenerateWeapon(Actor firedBy, Actor victim, WPos epicenter, in Target target) + { + var shrapnelTarget = Target.FromActor(victim); + + if (shrapnelTarget.Type == TargetType.Invalid) + return; + + var shrapnelFacing = (shrapnelTarget.CenterPosition - epicenter).Yaw; + + // Lambdas can't use 'in' variables, so capture a copy for later + var centerPosition = target.CenterPosition; + + var projectileArgs = new ProjectileArgs + { + Weapon = weapon, + Facing = shrapnelFacing, + CurrentMuzzleFacing = () => shrapnelFacing, + + DamageModifiers = !firedBy.IsDead ? firedBy.TraitsImplementing() + .Select(a => a.GetFirepowerModifier(WeaponName)).ToArray() : Array.Empty(), + + InaccuracyModifiers = Array.Empty(), + + RangeModifiers = Array.Empty(), + + Source = target.CenterPosition, + CurrentSource = () => centerPosition, + SourceActor = firedBy, + GuidedTarget = shrapnelTarget, + PassiveTarget = shrapnelTarget.CenterPosition + }; + + if (projectileArgs.Weapon.Projectile != null) + { + var projectile = projectileArgs.Weapon.Projectile.Create(projectileArgs); + if (projectile != null) + firedBy.World.AddFrameEndTask(w => w.Add(projectile)); + + if (projectileArgs.Weapon.Report != null && projectileArgs.Weapon.Report.Length > 0) + { + var pos = target.CenterPosition; + if (projectileArgs.Weapon.AudibleThroughFog || (!firedBy.World.ShroudObscures(pos) && !firedBy.World.FogObscures(pos))) + Game.Sound.Play(SoundType.World, projectileArgs.Weapon.Report, firedBy.World, pos, null, projectileArgs.Weapon.SoundVolume); + } + } + } + } +} diff --git a/mods/sp/weapons/scrweapons.yaml b/mods/sp/weapons/scrweapons.yaml index 7fbc0076b..b663958a9 100644 --- a/mods/sp/weapons/scrweapons.yaml +++ b/mods/sp/weapons/scrweapons.yaml @@ -114,14 +114,11 @@ FloatTesla: Warhead@1Dam: SpreadDamage Damage: 0 InvalidTargets: Summoned - Warhead@op: FireShrapnel + Warhead@op: ScrinEssenceHit Weapon: EssenceMissile - ImpactActors: false Amount: 2 - AimChance: 100 - AllowDirectHit: true AimTargetStances: Ally - ThrowWithoutTarget: false + SecondaryTargets: FullHealth ValidTargets: Ground, Water, Air InvalidTargets: Summoned AffectsParent: true @@ -1073,15 +1070,12 @@ CouncilorPlasma: ImpactSounds: expnew14.aud AirThreshold: 8c0 ValidTargets: Ground, Water - Warhead@op: FireShrapnel + Warhead@op: ScrinEssenceHit Weapon: EssenceMissile - ImpactActors: true + ValidTargets: Ground, Water, Air Amount: 1 - AimChance: 100 - AllowDirectHit: true AimTargetStances: Ally - ThrowWithoutTarget: false - ValidTargets: Ground, Water + SecondaryTargets: FullHealth AffectsParent: true AirThreshold: 8c0 @@ -1232,15 +1226,12 @@ EssenceSmall: Range: 10c0 MinRange: 1c0 Projectile: InstantExplode - Warhead@op: FireShrapnel + Warhead@op: ScrinEssenceHit + ValidTargets: Ground, Water, Air Weapon: EssenceMissile - ImpactActors: false Amount: 1 - AimChance: 100 - ThrowWithoutTarget: false - AllowDirectHit: true AimTargetStances: Ally - ValidTargets: Ground, Water, Air + SecondaryTargets: FullHealth AirThreshold: 8c0 AffectsParent: true @@ -1254,7 +1245,7 @@ SpawnEssence: Sequences: idle Duration: 50 Palette: apolra2ialpha - Warhead@op: FireShrapnel + Warhead@op: ScrinEssenceHit Delay: 50 AimTargetStances: Ally AffectsParent: true