Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions Content.Client/Magic/Systems/NecromanticSummonerSystem.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
using Content.Shared.Magic.Systems;

namespace Content.Client.Magic.Systems;

public sealed class NecromanticSummonerSystem : SharedNecromanticSummonerSystem;
3 changes: 3 additions & 0 deletions Content.Client/Popups/PopupSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,9 @@ public override void PopupPredictedCursor(string? message, EntityUid recipient,

public override void PopupCoordinates(string? message, EntityCoordinates coordinates, Filter filter, bool replayRecord, PopupType type = PopupType.Small)
{
if (!filter.Recipients.Contains(_playerManager.LocalSession))
return;

PopupCoordinates(message, coordinates, type);
}

Expand Down
18 changes: 15 additions & 3 deletions Content.Server/Ghost/Roles/GhostRoleSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -572,16 +572,28 @@ public bool Takeover(ICommonSession player, uint identifier)
if (!_ghostRoles.TryGetValue(identifier, out var role))
return false;

if (!Takeover(role, player))
return false;

CloseEui(player);
return true;
}

/// <summary>
/// Attempts having the player take over the ghost role on the given entity.
/// </summary>
/// <returns>True if takeover was successful, otherwise false.</returns>
public bool Takeover(Entity<GhostRoleComponent> ent, ICommonSession player)
{
var ev = new TakeGhostRoleEvent(player);
RaiseLocalEvent(role, ref ev);
RaiseLocalEvent(ent, ref ev);

if (!ev.TookRole)
return false;

if (player.AttachedEntity != null)
_adminLogger.Add(LogType.GhostRoleTaken, LogImpact.Low, $"{player:player} took the {role.Comp.RoleName:roleName} ghost role {ToPrettyString(player.AttachedEntity.Value):entity}");
_adminLogger.Add(LogType.GhostRoleTaken, LogImpact.Low, $"{player:player} took the {ent.Comp.RoleName:roleName} ghost role {ToPrettyString(player.AttachedEntity.Value):entity}");

CloseEui(player);
return true;
}

Expand Down
31 changes: 31 additions & 0 deletions Content.Server/Magic/Systems/NecromanticSummonerSystem.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
using Content.Server.Ghost.Roles;
using Content.Server.Ghost.Roles.Components;
using Content.Shared.Magic.Systems;
using Content.Shared.Mind;
using Robust.Server.Player;
using Robust.Shared.Map;
using Robust.Shared.Prototypes;

namespace Content.Server.Magic.Systems;

public sealed class NecromanticSummonerSystem : SharedNecromanticSummonerSystem
{
[Dependency] private readonly SharedMindSystem _mind = default!;
[Dependency] private readonly GhostRoleSystem _ghostRole = default!;
[Dependency] private readonly IPlayerManager _player = default!;

// GhostRoleComponent is on the server, so we can't do this in shared.
public override void SpawnSummonAndTransferPlayer(EntProtoId summonPrototype, EntityCoordinates coords, EntityUid target)
{
var spawn = SpawnAtPosition(summonPrototype, coords);
if (_mind.TryGetMind(target, out var targetMindUid, out var targetMindComp))
{
// If the spawned mob has a ghost role, then use that so that the relevant roles get added,
// otherwise just transfer the mind.
if (!TryComp<GhostRoleComponent>(spawn, out var ghostRole)
|| !_player.TryGetSessionById(targetMindComp.UserId, out var session)
|| !_ghostRole.Takeover((spawn, ghostRole), session))
_mind.TransferTo(targetMindUid, spawn, mind: targetMindComp, ghostCheckOverride: true);
}
}
}
95 changes: 95 additions & 0 deletions Content.Shared/Magic/Components/NecromanticSummonerComponent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
using Content.Shared.Whitelist;
using Robust.Shared.Audio;
using Robust.Shared.GameStates;
using Robust.Shared.Prototypes;

namespace Content.Shared.Magic.Components;

/// <summary>
/// Allows an item to turn a dead player into a summoned mob.
/// </summary>
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
public sealed partial class NecromanticSummonerComponent : Component
{
/// <summary>
/// The entity prototype to spawn and transfer the dead target player to.
/// Why do moths have a human skeleton inside them you ask? It's a magic skeleton of course!
/// </summary>
[DataField, AutoNetworkedField]
public EntProtoId Prototype = "MobSkeletonSummon";

/// <summary>
/// The whitelist for which mobs are allowed to be turned into thralls.
/// </summary>
[DataField, AutoNetworkedField]
public EntityWhitelist? Whitelist;

/// <summary>
/// The blacklist for which mobs are allowed to be turned into thralls.
/// </summary>
[DataField, AutoNetworkedField]
public EntityWhitelist? Blacklist;

/// <summary>
/// The DoAfter time when the item is being used.
/// </summary>
[DataField, AutoNetworkedField]
public TimeSpan DoAfterTime = TimeSpan.FromSeconds(3);

/// <summary>
/// The sound to play when the summon is successful.
/// </summary>
[DataField, AutoNetworkedField]
public SoundSpecifier? SummonSound;

/// <summary>
/// The popup to show to the user when starting the DoAfter.
/// </summary>
[DataField, AutoNetworkedField]
public LocId DoAfterPopup = "necromantic-summoner-doafter";

/// <summary>
/// The popup to show to the user when the summon fails due to the item having no charges left.
/// </summary>
[DataField, AutoNetworkedField]
public LocId NoChargesPopup = "necromantic-summoner-empty";

/// <summary>
/// The popup to show to the user when the summon fails due to the target not being dead.
/// </summary>
[DataField, AutoNetworkedField]
public LocId NotDeadPopup = "necromantic-summoner-not-dead";

/// <summary>
/// The popup to show to the user when the summon fails due to the target not having a player attached.
/// </summary>
[DataField, AutoNetworkedField]
public LocId NoSoulPopup = "necromantic-summoner-no-soul";

/// <summary>
/// The popup to show to the user when the summon is successful.
/// </summary>
[DataField, AutoNetworkedField]
public LocId SummonUserPopup = "necromantic-summoner-summon-user";

/// <summary>
/// The popup to show to the target when the summon is successful.
/// </summary>
[DataField, AutoNetworkedField]
public LocId SummonTargetPopup = "necromantic-summoner-summon-target";

/// <summary>
/// The popup to show to others when the summon is successful.
/// </summary>
[DataField, AutoNetworkedField]
public LocId SummonOthersPopup = "necromantic-summoner-summon-others";

/// <summary>
/// Gib the old body on successful summon?
/// </summary>
/// <remarks>
/// Once we have surgery consider actually removing their skeleton from their body instead of spawning one.
/// </remarks>
[DataField, AutoNetworkedField]
public bool GibBody = true;
}
9 changes: 8 additions & 1 deletion Content.Shared/Magic/Events/ChargeSpellEvent.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Content.Shared.Actions;
using Content.Shared.Whitelist;

namespace Content.Shared.Magic.Events;

Expand All @@ -7,9 +8,15 @@ namespace Content.Shared.Magic.Events;
/// </summary>
public sealed partial class ChargeSpellEvent : InstantActionEvent
{
/// <summary>
/// How many charges to refill.
/// </summary>
[DataField(required: true)]
public int Charge;

/// <summary>
/// Whitelist for entities that can be recharged.
/// </summary>
[DataField]
public string WandTag = "WizardWand";
public EntityWhitelist Whitelist = new();
}
6 changes: 4 additions & 2 deletions Content.Shared/Magic/SharedMagicSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
using Content.Shared.Tag;
using Content.Shared.Weapons.Ranged.Components;
using Content.Shared.Weapons.Ranged.Systems;
using Content.Shared.Whitelist;
using Robust.Shared.Audio.Systems;
using Robust.Shared.Map;
using Robust.Shared.Map.Components;
Expand Down Expand Up @@ -67,8 +68,9 @@ public abstract class SharedMagicSystem : EntitySystem
[Dependency] private readonly SharedStunSystem _stun = default!;
[Dependency] private readonly TurfSystem _turf = default!;
[Dependency] private readonly SharedChargesSystem _charges = default!;
[Dependency] private readonly ExamineSystemShared _examine= default!;
[Dependency] private readonly ExamineSystemShared _examine = default!;
[Dependency] private readonly TargetSystem _target = default!;
[Dependency] private readonly EntityWhitelistSystem _whitelist = default!;

private static readonly ProtoId<TagPrototype> InvalidForGlobalSpawnSpellTag = "InvalidForGlobalSpawnSpell";

Expand Down Expand Up @@ -449,7 +451,7 @@ private void OnChargeSpell(ChargeSpellEvent ev)
EntityUid? wand = null;
foreach (var item in _hands.EnumerateHeld((ev.Performer, handsComp)))
{
if (!_tag.HasTag(item, ev.WandTag))
if (!_whitelist.IsWhitelistPass(ev.Whitelist, item))
continue;

wand = item;
Expand Down
Loading
Loading