fish-station/Content.Server/_Sunrise/BloodCult/Items/Systems/CultMirrorShieldSystem.cs
2026-01-02 14:17:03 +03:00

397 lines
14 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Content.Server._Sunrise.BloodCult.Items.Components;
using Content.Server.Body.Components;
using Content.Server.GameTicking;
using Content.Server.Hands.Systems;
using Content.Server.NPC.Components;
using Content.Server.Station.Systems;
using Content.Server.Stunnable;
using Content.Shared._Sunrise.BloodCult.Components;
using Content.Shared._Sunrise.BloodCult.Items;
using Content.Shared._Sunrise.Events;
using Content.Shared.Blocking;
using Content.Shared.Body.Events;
using Content.Shared.Damage;
using Content.Shared.Damage.Components;
using Content.Shared.FixedPoint;
using Content.Shared.Hands.EntitySystems;
using Content.Shared.Interaction.Components;
using Content.Shared.Inventory;
using Content.Shared.Mobs;
using Content.Shared.Mobs.Components;
using Content.Shared.Mobs.Systems;
using Content.Shared.NPC.Systems;
using Content.Shared.Popups;
using Content.Shared.SSDIndicator;
using Content.Shared.Strip.Components;
using Robust.Server.GameObjects;
using Robust.Shared.Audio.Systems;
using Robust.Shared.Player;
using Robust.Shared.Random;
using Robust.Shared.Spawners;
namespace Content.Server._Sunrise.BloodCult.Items.Systems;
/// <summary>
/// Система для обработки логики зеркального щита. Референс: https://youtu.be/SiFY7ek_91Y?t=330&si=GB2jxaBrhe2vG5vc
/// </summary>
public sealed partial class CultMirrorShieldSystem : EntitySystem
{
[Dependency] private readonly ILogManager _log = default!;
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly StunSystem _stun = default!;
[Dependency] private readonly SharedPopupSystem _popup = default!;
[Dependency] private readonly HandsSystem _hands = default!;
[Dependency] private readonly TransformSystem _transform = default!;
[Dependency] private readonly StationSystem _stations = default!;
[Dependency] private readonly GameTicker _ticker = default!;
[Dependency] private readonly StationSpawningSystem _spawning = default!;
[Dependency] private readonly ISharedPlayerManager _playerMan = default!;
[Dependency] private readonly InventorySystem _inventory = default!;
[Dependency] private readonly NpcFactionSystem _faction = default!;
[Dependency] private readonly MobThresholdSystem _mobThreshold = default!;
private ISawmill _sawmill = default!;
/// <inheritdoc />
public override void Initialize()
{
base.Initialize();
// Initialize subsystems
InitializeCommands();
_sawmill = _log.GetSawmill("mirrorshield");
SubscribeLocalEvent<CultMirrorShieldComponent, BlockingEvent>(OnMeleeBlocking);
SubscribeLocalEvent<CultMirrorShieldComponent, ReflectedEvent>(OnHitScanReflected);
SubscribeLocalEvent<CultMirrorShieldComponent, ComponentShutdown>(OnShieldShutdown);
SubscribeLocalEvent<CultMirrorIllusionComponent, ComponentInit>(OnIllusionInit);
SubscribeLocalEvent<CultMirrorIllusionComponent, MobStateChangedEvent>(OnIllusionMobStateChanged);
SubscribeLocalEvent<CultMirrorIllusionComponent, BeingGibbedEvent>(OnIllusionGib);
SubscribeLocalEvent<CultMirrorIllusionComponent, ComponentShutdown>(OnIllusionShutdown);
}
private void OnMeleeBlocking(EntityUid uid, CultMirrorShieldComponent component, BlockingEvent args)
{
if (TryBreakShield(uid, args.Damage))
return;
TrySpawnIllusion(uid);
}
#region ShieldBreak
/// <summary>
/// Щит отразил что-то
/// </summary>
private void OnHitScanReflected(EntityUid uid, CultMirrorShieldComponent component, ReflectedEvent args)
{
if (args.Damage == null)
return;
if (TryBreakShield(uid, args.Damage))
return;
TrySpawnIllusion(uid);
}
/// <summary>
/// Логика ломания щита
/// </summary>
public bool TryBreakShield(Entity<CultMirrorShieldComponent?> entity, DamageSpecifier damage)
{
if (!Resolve(entity.Owner, ref entity.Comp))
return false;
var xform = Transform(entity);
var owner = xform.ParentUid;
if (!HasComp<BloodCultistComponent>(owner))
return false;
var sum = CalculateDamage(damage);
_sawmill.Debug($"sum damage: {sum}");
var chance = CalculateChance(sum);
if (_random.NextFloat() > chance)
return false;
BreakShield(entity);
return true;
}
/// <summary>
/// Само ломание щита
/// </summary>
public void BreakShield(Entity<CultMirrorShieldComponent?> entity)
{
if (!Resolve(entity.Owner, ref entity.Comp))
return;
// Стан хозяина
var xform = Transform(entity);
var parent = xform.ParentUid;
_stun.TryKnockdown(parent, entity.Comp.KnockdownDuration, true);
// Звук ломания
_audio.PlayPvs(entity.Comp.BreakSound, parent);
// Попап
_popup.PopupEntity(Loc.GetString("cultshield-broken", ("name", MetaData(entity.Owner).EntityName)),
parent,
PopupType.LargeCaution);
// Удаление щита
QueueDel(entity.Owner);
}
#endregion ShieldBreak
#region Illusion
/// <summary>
/// Иллюзии должны разбиваться после смерти
/// </summary>
private void OnIllusionMobStateChanged(EntityUid uid, CultMirrorIllusionComponent component, MobStateChangedEvent args)
{
if (args.NewMobState == MobState.Alive)
return;
QueueDel(uid);
}
/// <summary>
/// Иллюзии должны быть дохлыми
/// </summary>
private void OnIllusionInit(EntityUid uid, CultMirrorIllusionComponent component, ComponentInit args)
{
EnsureComp<MobStateComponent>(uid);
EnsureComp<MobThresholdsComponent>(uid);
EnsureComp<DamageableComponent>(uid);
_mobThreshold.SetMobStateThreshold(uid, 15, MobState.Critical);
_mobThreshold.SetMobStateThreshold(uid, 20, MobState.Dead);
}
/// <summary>
/// Иллюзию разбили. Вычеркиваем
/// </summary>
private void OnIllusionShutdown(EntityUid uid, CultMirrorIllusionComponent component, ComponentShutdown args)
{
if (!TryComp<CultMirrorShieldComponent>(component.ParentShield, out var mirror))
return;
mirror.Illusions.Remove(uid);
}
/// <summary>
/// А что если иллюзию гибнули быстрее, чем она успела умереть? Крайний случай, эта функция не должна быть вызвана никогда
/// </summary>
private void OnIllusionGib(EntityUid uid, CultMirrorIllusionComponent component, BeingGibbedEvent args)
{
if (!TryComp<CultMirrorShieldComponent>(component.ParentShield, out var mirror))
return;
mirror.Illusions.Remove(uid);
}
/// <summary>
/// Щит разбился. Иллюзии должны разбиться вместе с ним
/// </summary>
private void OnShieldShutdown(EntityUid uid, CultMirrorShieldComponent component, ComponentShutdown args)
{
foreach (var illusion in component.Illusions)
{
QueueDel(illusion);
}
}
/// <summary>
/// Логика спавна иллюзий
/// </summary>
public void TrySpawnIllusion(Entity<CultMirrorShieldComponent?> entity)
{
if (!Resolve(entity.Owner, ref entity.Comp))
return;
if (_random.NextFloat() > entity.Comp.IllusionChance)
return;
var xform = Transform(entity);
var owner = xform.ParentUid;
EntityUid? illusion = null;
if (HasComp<BloodCultistComponent>(owner))
{
// Количество иллюзий ограничено только если хозяин щита культист
if (entity.Comp.Illusions.Count > entity.Comp.MaxIllusionCounter)
return;
// Если хозяин культист, то...
if (_random.NextFloat() < 0.8f)
CreateIllusion(owner, out illusion, agressive: true);
else
CreateIllusion(owner, out illusion, agressive: false);
}
else
{
// Если хозяин не культист, то...
CreateIllusion(owner, out illusion, false, owner);
}
if (illusion == null)
return;
var illusionComp = EnsureComp<CultMirrorIllusionComponent>(illusion.Value);
illusionComp.ParentShield = entity.Owner;
entity.Comp.Illusions.Add(illusion.Value);
}
public bool CreateIllusion(EntityUid uid, [NotNullWhen(true)] out EntityUid? illusionUid, bool agressive = true, EntityUid? targetAggro = null)
{
illusionUid = null;
if (!CreateBaseIllusion(uid, out var mobUid, agressive, targetAggro))
return false;
if (!CloneGear(uid, mobUid.Value))
return false;
illusionUid = mobUid;
return true;
}
#endregion Illusion
# region Helper functions
public bool CloneGear(EntityUid uid, EntityUid mobUid)
{
if (!_inventory.TryGetContainerSlotEnumerator(uid, out var originSlotEnumerator))
return false;
while (originSlotEnumerator.MoveNext(out var containerSlot))
{
if (containerSlot.ContainedEntity is null)
continue;
if (!CloneItem(containerSlot.ContainedEntity.Value, out var item))
continue;
EnsureComp<UnremoveableComponent>(item.Value);
_inventory.TryEquip(mobUid, item.Value, containerSlot.ID, true, true);
}
List<EntityUid> heldItems = new();
foreach (var held in _hands.EnumerateHeld(uid))
{
if (!CloneItem(held, out var item))
continue;
EnsureComp<UnremoveableComponent>(item.Value);
heldItems.Add(item.Value);
}
if (heldItems.Count != 0)
{
foreach (var hand in _hands.EnumerateHands(mobUid).Reverse())
{
if (heldItems.Count == 0 || !HasComp<CultMirrorShieldComponent>(heldItems[0]))
{
_hands.TrySetActiveHand(mobUid, hand);
}
if (heldItems.Count != 0)
{
_hands.TryPickup(mobUid, heldItems[0], hand);
heldItems.RemoveAt(0);
}
}
}
// А что если у цели было больше двух рук? У клона же в свою очередь будет скорее всего 2
foreach (var leftoveritem in heldItems)
{
QueueDel(leftoveritem);
}
return true;
}
public bool CreateBaseIllusion(EntityUid uid, [NotNullWhen(true)] out EntityUid? mobUid, bool wasSourceCultist = true, EntityUid? userUid = null, bool agressive = true)
{
mobUid = null;
if (!_transform.TryGetMapOrGridCoordinates(uid, out var coords))
{
return false;
}
var stationUid = _stations.GetOwningStation(uid);
if (!_playerMan.TryGetSessionByEntity(uid, out var session))
return false;
var profile = _ticker.GetPlayerProfile(session);
mobUid = _spawning.SpawnPlayerMob(coords.Value, null, profile, stationUid);
if (HasComp<SSDIndicatorComponent>(mobUid))
RemComp<SSDIndicatorComponent>(mobUid.Value);
if (HasComp<StrippableComponent>(mobUid))
RemComp<StrippableComponent>(mobUid.Value);
// Через 15 секунд иллюзия удаляется - Не канон, удали эту хуету
var timedDelete = EnsureComp<TimedDespawnComponent>(mobUid.Value);
timedDelete.Lifetime = wasSourceCultist ? 10f : 15f;
_faction.ClearFactions(mobUid.Value);
if (wasSourceCultist)
{
// Она должна атаковать всех не культистов
if (agressive)
{
_faction.AddFaction(mobUid.Value, "BloodCult");
_console.ExecuteCommand($"addnpc {mobUid.Value} HostileIllusionCompound");
}
else
{
_faction.AddFaction(mobUid.Value, "Passive");
EnsureComp<NPCRetaliationComponent>(mobUid.Value);
_console.ExecuteCommand($"addnpc {mobUid.Value} IdleCompound");
}
}
else
{
if (userUid != null)
{
_faction.AggroEntity(mobUid.Value, userUid.Value);
_console.ExecuteCommand($"addnpc {mobUid.Value} HostileIllusionCompound");
}
}
return true;
}
public bool CloneItem(EntityUid origin, [NotNullWhen(true)] out EntityUid? item)
{
item = null;
var meta = MetaData(origin);
var proto = meta.EntityPrototype;
if (proto is null)
return false;
item = Spawn(proto.ID);
return true;
}
public FixedPoint2 CalculateDamage(DamageSpecifier damageSpecifier)
{
FixedPoint2 sum = 0;
foreach (var damageDictKey in damageSpecifier.DamageDict.Keys)
{
damageSpecifier.DamageDict.TryGetValue(damageDictKey, out var damage);
sum += damage;
}
return sum;
}
public FixedPoint2 CalculateChance(FixedPoint2 sum)
{
return Math.Clamp((sum.Float() / 100f - 0.1f) * 3f, 0f, 0.75f);
}
# endregion Helper functions
}