diff --git a/Content.Server/Imperial/Medieval/SoulsMaster/SoulsMasterSummonerComponent.cs b/Content.Server/Imperial/Medieval/SoulsMaster/SoulsMasterSummonerComponent.cs
new file mode 100644
index 00000000000..074aeda359f
--- /dev/null
+++ b/Content.Server/Imperial/Medieval/SoulsMaster/SoulsMasterSummonerComponent.cs
@@ -0,0 +1,50 @@
+///
+/// Spellward International add;
+/// Periodically summons goons around its summoner while the summoner is alive
+///
+using Robust.Shared.Prototypes;
+using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
+
+namespace Content.Server.Imperial.Medieval.SoulsMaster;
+
+[RegisterComponent]
+public sealed partial class SoulsMasterSummonerComponent : Component
+{
+ [DataField]
+ public EntProtoId SummonPrototype = "MedievalMobSkeletWeakSpell";
+
+ // Number of entities spawned each time the summon cooldown completes
+ [DataField]
+ public int SummonCount = 1;
+
+ // Cool effect proc'd when summon is spawned
+ [DataField]
+ public EntProtoId SummonEffect = "IceBarrierSpellCastEffectBeginner";
+
+ // Maximum number of this summoner's living summons that may exist at once
+ [DataField]
+ public int MaxSummons = 3;
+
+ [DataField]
+ public float SummonRadius = 2f;
+
+ // Number of random positions attempted for each summon before that spawn is skipped
+ // Summons must be on a non-space floor tile that is not blocked by walls or mobs
+ [DataField]
+ public int SpawnAttempts = 5;
+
+ [DataField(customTypeSerializer: typeof(TimeOffsetSerializer))]
+ public TimeSpan NextSummon;
+
+ [DataField]
+ public TimeSpan InitialDelay = TimeSpan.FromSeconds(5);
+
+ [DataField]
+ public TimeSpan Cooldown = TimeSpan.FromSeconds(20);
+
+ // Summons currently owned by this component. Dead or deleted entities stop the summoning
+ public readonly HashSet ActiveSummons = new();
+
+ // Whether the summoner had an active combat target during the previous update
+ public bool WasAggroed;
+}
diff --git a/Content.Server/Imperial/Medieval/SoulsMaster/SoulsMasterSummonerSystem.cs b/Content.Server/Imperial/Medieval/SoulsMaster/SoulsMasterSummonerSystem.cs
new file mode 100644
index 00000000000..cb0ed64b2cc
--- /dev/null
+++ b/Content.Server/Imperial/Medieval/SoulsMaster/SoulsMasterSummonerSystem.cs
@@ -0,0 +1,230 @@
+///
+/// Spellward International add;
+/// Periodically spawns configured summon entities around living mobs that have a
+/// SoulsMasterSummonerComponent. The first summon is delayed by the
+/// component's initial delay, and following summons use its configured cooldown
+/// Also, Summoning stops when aggro is lost and resumes with the initial delay when
+/// a new target is acquired. Each summoner tracks its own living summons and respects its
+/// configured maximum in the YAML
+///
+using Content.Server.NPC.HTN;
+using Content.Shared.Coordinates.Helpers;
+using Content.Shared.Maps;
+using Content.Shared.Mobs;
+using Content.Shared.Mobs.Components;
+using Content.Shared.Physics;
+using Robust.Shared.Map;
+using Robust.Shared.Map.Components;
+using Robust.Shared.Random;
+using Robust.Shared.Timing;
+
+namespace Content.Server.Imperial.Medieval.SoulsMaster;
+
+public sealed class SoulsMasterSummonerSystem : EntitySystem
+{
+ // Run the main summoner query once every second (30 ticks)
+ private const float UpdateInterval = 1f;
+
+ // Stores time until the next summoner update
+ private float _updateAccumulator;
+
+ [Dependency] private readonly IGameTiming _timing = default!;
+ [Dependency] private readonly IRobustRandom _random = default!;
+ [Dependency] private readonly SharedMapSystem _map = default!;
+ [Dependency] private readonly TurfSystem _turf = default!;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ // Starts the summon timer when the summoner enters the map
+ SubscribeLocalEvent(OnMapInit);
+ }
+
+ private void OnMapInit(
+ EntityUid uid,
+ SoulsMasterSummonerComponent component,
+ MapInitEvent args)
+ {
+ // This timer is reset again when the summoner first becomes aggroed
+ component.NextSummon = _timing.CurTime + component.InitialDelay;
+ }
+
+ public override void Update(float frameTime)
+ {
+ base.Update(frameTime);
+
+ // Update() is called every tick, but the main logic only runs
+ // after UpdateInterval seconds have elapsed
+ _updateAccumulator += frameTime;
+
+ if (_updateAccumulator < UpdateInterval)
+ return;
+
+ // Preserve any remaining time without running several
+ // catch-up updates after a server stall
+ _updateAccumulator %= UpdateInterval;
+
+ // Query only the custom component
+ // Other required components are retrieved below using TryComp<>
+ var query = EntityQueryEnumerator();
+
+ while (query.MoveNext(out var uid, out var summoner))
+ {
+ // Remove dead or deleted summons before calculating available slots
+ PruneSummons(summoner);
+
+ // Dead summoners cannot summon
+ if (!TryComp(uid, out var mobState) ||
+ mobState.CurrentState != MobState.Alive)
+ {
+ summoner.WasAggroed = false;
+ continue;
+ }
+
+ // The summoner requires a transform for spawning and an HTN
+ // component for checking its current combat target
+ if (!TryComp(uid, out var transform) ||
+ !TryComp(uid, out var htn))
+ {
+ summoner.WasAggroed = false;
+ continue;
+ }
+
+ // The HTN Target blackboard value represents the mob's active target
+ var aggroed =
+ htn.Blackboard.TryGetValue(
+ "Target",
+ out var target,
+ EntityManager) &&
+ EntityManager.EntityExists(target);
+
+ // Stop summon processing while no active target exists
+ if (!aggroed)
+ {
+ summoner.WasAggroed = false;
+ continue;
+ }
+
+ // Start a fresh initial delay whenever the summoner acquires
+ // a target after previously being unaggroed
+ if (!summoner.WasAggroed)
+ {
+ summoner.WasAggroed = true;
+ summoner.NextSummon =
+ _timing.CurTime + summoner.InitialDelay;
+
+ continue;
+ }
+
+ // Wait until the initial delay or normal cooldown has elapsed
+ if (summoner.NextSummon > _timing.CurTime)
+ continue;
+
+ // Begin the next cooldown before attempting to summon
+ summoner.NextSummon =
+ _timing.CurTime + summoner.Cooldown;
+
+ // Never exceed the configured maximum number of active summons
+ var availableSlots = Math.Max(
+ 0,
+ summoner.MaxSummons - summoner.ActiveSummons.Count);
+
+ // SummonCount controls the number spawned per cycle, while
+ // availableSlots prevents the configured cap from being exceeded
+ var amountToSpawn = Math.Min(
+ Math.Max(0, summoner.SummonCount),
+ availableSlots);
+
+ for (var i = 0; i < amountToSpawn; i++)
+ {
+ // Search for a valid floor tile that is not blocked by
+ // walls, impassable objects, or another mob
+ if (!TryFindSpawnCoordinates(
+ transform.Coordinates,
+ summoner,
+ out var spawnCoordinates))
+ {
+ continue;
+ }
+
+ // Play the configured visual effect at the spawned position
+ Spawn(summoner.SummonEffect, spawnCoordinates);
+
+ // Spawn and track the summon so it counts toward maxSummons
+ var summon = Spawn(
+ summoner.SummonPrototype,
+ spawnCoordinates);
+
+ summoner.ActiveSummons.Add(summon);
+ }
+ }
+ }
+
+ private bool TryFindSpawnCoordinates(
+ EntityCoordinates origin,
+ SoulsMasterSummonerComponent summoner,
+ out EntityCoordinates spawnCoordinates)
+ {
+ spawnCoordinates = default;
+
+ // Summons must be placed on an actual map grid
+ if (origin.GetGridUid(EntityManager) is not { } gridUid ||
+ !TryComp(gridUid, out var grid))
+ {
+ return false;
+ }
+
+ // Try several random positions before giving up on this summon
+ for (var attempt = 0;
+ attempt < summoner.SpawnAttempts;
+ attempt++)
+ {
+ var offset =
+ _random.NextVector2() * summoner.SummonRadius;
+
+ // Snap the random position to the center of tile
+ var candidate =
+ origin.Offset(offset).SnapToGrid(grid);
+
+ // Reject missing tiles, space, walls, impassable objects,
+ // and tiles currently occupied by mobs
+ if (!_map.TryGetTileRef(
+ gridUid,
+ grid,
+ candidate,
+ out var tileRef) ||
+ tileRef.Tile.IsEmpty ||
+ _turf.IsSpace(tileRef) ||
+ _turf.IsTileBlocked(
+ tileRef,
+ CollisionGroup.Impassable |
+ CollisionGroup.MobMask))
+ {
+ continue;
+ }
+
+ spawnCoordinates = candidate;
+ return true;
+ }
+
+ // No valid position found within the spawnAttempt count
+ return false;
+ }
+
+ private void PruneSummons(
+ SoulsMasterSummonerComponent summoner)
+ {
+ // Deleted or dead summons no longer occupy a summon slot
+ summoner.ActiveSummons.RemoveWhere(summon =>
+ {
+ if (!EntityManager.EntityExists(summon))
+ return true;
+
+ return TryComp(
+ summon,
+ out var mobState) &&
+ mobState.CurrentState != MobState.Alive;
+ });
+ }
+}
diff --git a/Resources/Prototypes/Imperial/Medieval/Bosses/SoulsMasterSummoner/soulsmastersummoner.yml b/Resources/Prototypes/Imperial/Medieval/Bosses/SoulsMasterSummoner/soulsmastersummoner.yml
new file mode 100644
index 00000000000..ceae67a6e90
--- /dev/null
+++ b/Resources/Prototypes/Imperial/Medieval/Bosses/SoulsMasterSummoner/soulsmastersummoner.yml
@@ -0,0 +1,82 @@
+- type: entity
+ id: MedievalMobSoulsMaster
+ name: Souls Master Summoner
+ description: SWALLOW YER SOUL! SWALLOW YER SOUL...
+ parent: [ SimpleMobBaseNoFood, MobCombat ]
+ components:
+ - type: Sprite
+ drawdepth: Mobs
+ sprite: Imperial/Medieval/Mobs/SoulsMaster/souls_master.rsi
+ layers:
+ - state: idle
+ - type: Physics
+ - type: Fixtures
+ fixtures:
+ fix1:
+ shape: !type:PhysShapeCircle
+ radius: 0.45
+ density: 50
+ mask:
+ - MobMask
+ layer:
+ - MobLayer
+ - type: Appearance
+ - type: NpcFactionMember
+ factions:
+ - Syndicate
+ - type: InputMover
+ - type: MobMover
+ - type: HTN
+ rootTask:
+ task: SimpleRangedHostileCompound
+ - type: MovementSpeedModifier
+ baseWalkSpeed: 2.8
+ baseSprintSpeed: 3.4
+ - type: MobThresholds
+ thresholds:
+ 0: Alive
+ 300: Dead
+ - type: Damageable
+ damageModifierSet: MedievalRespawn
+ - type: Bloodstream
+ bloodMaxVolume: 0
+ - type: Puller
+ - type: IgnoreSpiderWeb
+ - type: Tag
+ tags:
+ - CannotSuicide
+ - DoorBumpOpener
+ - MedievalMob
+ - type: RechargeBasicEntityAmmo
+ rechargeCooldown: 2.5
+ - type: BasicEntityAmmoProvider
+ proto: MedievalProjectileCursedArrow
+ capacity: 1
+ count: 1
+ - type: Gun
+ projectileSpeed: 12
+ fireRate: 1
+ useKey: false
+ selectedMode: SemiAuto
+ availableModes:
+ - SemiAuto
+ soundGunshot: /Audio/Magic/ethereal_exit.ogg
+ - type: SoulsMasterSummoner
+ summonPrototype: MedievalMobSkeletWeakSpell
+ summonEffect: IceBarrierSpellCastEffectBeginner
+ summonCount: 1
+ maxSummons: 3
+ summonRadius: 2
+ spawnAttempts: 5
+ initialDelay: 5
+ cooldown: 20
+ - type: Destructible
+ thresholds:
+ - trigger: !type:DamageTrigger
+ damage: 300
+ behaviors:
+ - !type:PlaySoundBehavior
+ sound:
+ collection: gib
+ - !type:DoActsBehavior
+ acts: [ "Destruction" ]
diff --git a/Resources/Textures/Imperial/Medieval/Mobs/SoulsMaster/souls_master.rsi/idle.png b/Resources/Textures/Imperial/Medieval/Mobs/SoulsMaster/souls_master.rsi/idle.png
new file mode 100644
index 00000000000..91070bc8dc9
Binary files /dev/null and b/Resources/Textures/Imperial/Medieval/Mobs/SoulsMaster/souls_master.rsi/idle.png differ
diff --git a/Resources/Textures/Imperial/Medieval/Mobs/SoulsMaster/souls_master.rsi/meta.json b/Resources/Textures/Imperial/Medieval/Mobs/SoulsMaster/souls_master.rsi/meta.json
new file mode 100644
index 00000000000..4a52c2fe1ed
--- /dev/null
+++ b/Resources/Textures/Imperial/Medieval/Mobs/SoulsMaster/souls_master.rsi/meta.json
@@ -0,0 +1,18 @@
+{
+ "version": 1,
+ "license": "this content is under ICLA licence, read more on https://wiki.imperialspace.net/icla",
+ "copyright": "by dantanat#7095 (i think -pierow)",
+ "size": {
+ "x": 48,
+ "y": 48
+ },
+ "states": [
+ {
+ "name": "idle",
+ "directions": 1,
+ "delays": [
+ [0.14, 0.14, 0.14, 0.14, 0.14, 0.14, 0.14]
+ ]
+ }
+ ]
+}