Skip to content

Commit

Permalink
Add SpawnActorWarhead
Browse files Browse the repository at this point in the history
  • Loading branch information
PunkPun committed Mar 10, 2024
1 parent 519db10 commit f7ecaaf
Showing 1 changed file with 217 additions and 0 deletions.
217 changes: 217 additions & 0 deletions OpenRA.Mods.Common/Warheads/SpawnActorWarhead.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,217 @@
#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 System.Linq;
using OpenRA.GameRules;
using OpenRA.Mods.Common.Activities;
using OpenRA.Mods.Common.Effects;
using OpenRA.Mods.Common.Traits;
using OpenRA.Primitives;
using OpenRA.Traits;

namespace OpenRA.Mods.Common.Warheads
{
[Desc("Spawns actors upon detonation.")]
public class SpawnActorWarhead : Warhead, IRulesetLoaded<WeaponInfo>
{
[FieldLoader.Require]
[Desc("Actors to spawn.")]
public readonly string[] Actors;

[Desc("The cell range to try placing the actors within.")]
public readonly int Range = 10;

[Desc("Map player to give the actors to. Defaults to the attacker's owner.")]
public readonly string Owner;

[Desc("Should this actor link to the actor which created it?")]
public readonly bool LinkToParent = false;

[Desc("Should actors always be spawned on the ground?")]
public readonly bool ForceGround = false;

[Desc("Should actors spawn facing the same direction as the projectile?")]
public readonly bool SetFacing = false;

[Desc("Don't spawn actors on this terrain.")]
public readonly HashSet<string> InvalidTerrain = new();

[Desc("Defines the image of an optional animation played at the spawning location.")]
public readonly string Image;

[SequenceReference(nameof(Image), allowNullImage: true)]
[Desc("Defines the sequence of an optional animation played at the spawning location.")]
public readonly string Sequence = "idle";

[PaletteReference]
[Desc("Defines the palette of an optional animation played at the spawning location.")]
public readonly string Palette = "effect";

public readonly bool UsePlayerPalette = false;

[Desc("List of sounds that can be played at the spawning location.")]
public readonly string[] Sounds = Array.Empty<string>();

void IRulesetLoaded<WeaponInfo>.RulesetLoaded(Ruleset rules, WeaponInfo info)
{
foreach (var a in Actors)
{
var actorInfo = rules.Actors[a.ToLowerInvariant()];
var buildingInfo = actorInfo.TraitInfoOrDefault<BuildingInfo>();

if (buildingInfo != null)
throw new YamlException($"SpawnActorWarhead cannot be used to spawn building actor '{a}'!");
}
}

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 pos = target.CenterPosition;
var targetCell = map.CellContaining(pos);
if (!map.Contains(targetCell))
return;

var directHit = false;
foreach (var victim in world.FindActorsOnCircle(pos, WDist.Zero))
{
if (!IsValidAgainst(victim, firedBy))
continue;

if (!victim.Info.HasTraitInfo<IHealthInfo>())
continue;

if (victim.TraitsImplementing<HitShape>()
.Any(i => !i.IsTraitDisabled && i.DistanceFromEdge(victim, pos).Length <= 0))
{
directHit = true;
break;
}
}

if (!directHit && !IsValidTarget(map.GetTerrainInfo(targetCell).TargetTypes))
return;

foreach (var a in Actors)
{
var td = new TypeDictionary();
var actorName = a.ToLowerInvariant();
var ai = map.Rules.Actors[actorName];

var owner = Owner == null ? firedBy.Owner : world.Players.First(p => p.InternalName == Owner);
td.Add(new OwnerInit(owner));

if (LinkToParent)
td.Add(new ParentActorInit(firedBy));

if (SetFacing)
td.Add(new FacingInit(args.ImpactOrientation.Yaw));

var cachedTarget = target;
world.AddFrameEndTask(w =>
{
var immobileInfo = ai.TraitInfoOrDefault<ImmobileInfo>();
if (immobileInfo != null)
{
foreach (var cell in map.FindTilesInCircle(targetCell, Range))
{
if (!InvalidTerrain.Contains(map.GetTerrainInfo(cell).Type)
&& (!immobileInfo.OccupiesSpace || !world.ActorMap.GetActorsAt(cell).Any()))
{
td.Add(new LocationInit(cell));
world.CreateActor(actorName, td);
PlaySoundAndEffect(w, owner, map.CenterOfCell(cell));
return;
}
}
return;
}
var unit = world.CreateActor(false, actorName, td);
var positionable = unit.Trait<IPositionable>();
foreach (var cell in map.FindTilesInCircle(targetCell, Range))
{
if (InvalidTerrain.Contains(map.GetTerrainInfo(cell).Type))
continue;
if (positionable is Aircraft aircraft)
{
var position = map.CenterOfCell(cell);
var dat = map.DistanceAboveTerrain(cachedTarget.CenterPosition);
var isAtGroundLevel = ForceGround || dat.Length <= 0;
if (isAtGroundLevel)
{
if (aircraft.Info.CanEnterCell(world, unit, cell))
positionable.SetPosition(unit, position);
else
continue;
}
else
{
position = new WPos(position.X, position.Y, Math.Max(position.Z, dat.Length));
positionable.SetPosition(unit, position);
unit.QueueActivity(new FlyIdle(unit));
}
PlaySoundAndEffect(w, owner, position);
w.Add(unit);
return;
}
else
{
var subCell = positionable.GetAvailableSubCell(cell);
if (subCell != SubCell.Invalid)
{
positionable.SetPosition(unit, cell, subCell);
var position = positionable.CenterPosition;
if (positionable is Mobile mobile)
{
var dat = map.DistanceAboveTerrain(cachedTarget.CenterPosition);
if (!ForceGround && dat.Length > 0)
{
positionable.SetCenterPosition(unit,
new WPos(position.X, position.Y, Math.Max(position.Z, dat.Length)));
unit.QueueActivity(mobile.ReturnToCell(unit));
}
}
PlaySoundAndEffect(w, owner, position);
w.Add(unit);
return;
}
}
}
unit.Dispose();
});
}
}

void PlaySoundAndEffect(World w, Player owner, WPos pos)
{
if (Image != null)
w.Add(new SpriteEffect(pos, w, Image, Sequence, UsePlayerPalette ? Palette + owner.InternalName : Palette));

var sound = Sounds.RandomOrDefault(Game.CosmeticRandom);
if (sound != null)
Game.Sound.Play(SoundType.World, sound, pos);
}
}
}

0 comments on commit f7ecaaf

Please sign in to comment.