using Content.Shared._Horizon.Pain.Components; using Content.Server.Chat.Systems; using Content.Shared._Horizon.Medical.Damage; using Content.Shared._Horizon.Pain.Prototypes; using Content.Shared.Damage; using Content.Shared.FixedPoint; using Content.Shared.Mobs; using Content.Shared.Mobs.Components; using Content.Shared.Movement.Systems; using Content.Shared.Standing; using Content.Shared.Stunnable; using Content.Shared.Traits.Assorted; using Robust.Shared.Physics.Systems; using Robust.Shared.Prototypes; using Robust.Shared.Random; using Robust.Shared.Timing; namespace Content.Server._Horizon.Pain; public sealed class PainSystem : EntitySystem { [Dependency] private readonly MovementSpeedModifierSystem _movement = null!; [Dependency] private readonly StandingStateSystem _standSystem = null!; [Dependency] private readonly IPrototypeManager _protoMan = null!; [Dependency] private readonly IGameTiming _gameTiming = null!; [Dependency] private readonly IRobustRandom _random = null!; [Dependency] private readonly ChatSystem _chat = null!; /// public override void Initialize() { base.Initialize(); SubscribeLocalEvent(OnDamageCause); SubscribeLocalEvent(SlowdownBody); } private void OnDamageCause(Entity entity, ref DamageBeforeApplyEvent ev) { if (ev.Cancelled || ev.Damage.Empty || HasComp(entity.Owner)) return; if (CheckPainRequirements(entity)) return; if (ev.Damage.GetTotal() > 0) AdjustPainDamage(entity, entity.Comp, ev.Damage); else RemovePainDamage(entity, entity.Comp, ev.Damage.DamageDict); } private bool CheckPainRequirements(EntityUid uid) { if (!TryComp(uid, out var damageable) || !TryComp(uid, out var thresholds)) return true; var total = damageable.Damage.GetTotal(); foreach (var (damage, state) in thresholds.Thresholds) { if (total > damage && state is MobState.Critical or MobState.Invalid) return true; } return false; } public void AdjustPainDamage(EntityUid body, PainComponent pain, DamageSpecifier specifier) { var converter = pain.DamagePrototypeConverter; if (!_protoMan.TryIndex(converter, out var proto)) return; foreach (var (type, damage) in specifier.DamageDict) { if (!proto.PainPerDamage.TryGetValue(type, out var painPerDamage)) continue; pain.CurrentPain += painPerDamage.Float() * damage.Float(); } TryCauseScreamOfPain(body, pain.ScreamOfPainPrototype, specifier.GetTotal(), ref pain.NextPossibleScream); UpdatePainStage(body, ref pain, pain.CurrentPain, pain.PainThresholds); if (pain.CurrentPain > pain.PainThresholds[PainStages.DeadPain]) pain.CurrentPain = pain.PainThresholds[PainStages.DeadPain]; } public void RemovePainDamage(EntityUid body, PainComponent pain, Dictionary damageDict) { var converter = pain.DamagePrototypeConverter; if (!_protoMan.TryIndex(converter, out var proto)) return; foreach (var (type, damage) in damageDict) { if (!proto.PainPerDamage.TryGetValue(type, out var painPerDamage)) continue; pain.CurrentPain += painPerDamage.Float() * damage.Float(); } UpdatePainStage(body, ref pain, pain.CurrentPain, pain.PainThresholds); if (pain.CurrentPain < pain.PainThresholds[PainStages.Nothing]) pain.CurrentPain = pain.PainThresholds[PainStages.Nothing]; } private void UpdatePainStage(EntityUid body, ref PainComponent comp, float pain, SortedDictionary painThresholds) { var actualStage = PainStages.Nothing; foreach (var (stage, painLevel) in painThresholds) { if (pain >= painLevel) actualStage = stage; } comp.CurrentStage = actualStage; TryDownBody(body, comp.CurrentStage); _movement.RefreshMovementSpeedModifiers(body); DirtyField(body, comp, nameof(PainComponent.CurrentStage)); } private void SlowdownBody(EntityUid body, PainComponent pain, ref RefreshMovementSpeedModifiersEvent ev) { if (pain.CurrentStage <= PainStages.AveragePain) return; if (!TryComp(body, out var damageable)) return; var total = damageable.Damage.GetTotal(); if (total == 0) return; var heat = damageable.Damage.DamageDict["Heat"]; var percentage = Math.Clamp(1f - (heat / total).Float(), 0.4f, 1f); if (_standSystem.IsDown(body)) ev.ModifySpeed(percentage * 0.4f); ev.ModifySpeed(percentage); } private void TryDownBody(EntityUid body, PainStages currentStage) { if (currentStage != PainStages.UnbeatablePain) return; if (_standSystem.IsDown(body)) return; _standSystem.Down(body); _movement.RefreshMovementSpeedModifiers(body); } private void TryCauseScreamOfPain(EntityUid body, string screamList, FixedPoint2 pain, ref TimeSpan nextPossibleScream) { if (_gameTiming.CurTime < nextPossibleScream) return; var screamProto = _protoMan.Index(screamList); string? scream = null; foreach (var (damage, list) in screamProto.ScreamList) { if (pain >= damage) scream = list[_random.Next(0, list.Count)]; } if (scream is null) return; _chat.TrySendInGameICMessage(body, scream, InGameICChatType.Speak, false, true); nextPossibleScream = _gameTiming.CurTime + TimeSpan.FromSeconds(1); } }