diff --git a/Content.Client/ADT/Hallucinations/SchizophreniaSystem.cs b/Content.Client/ADT/Hallucinations/SchizophreniaSystem.cs new file mode 100644 index 00000000000..bd9537f4b53 --- /dev/null +++ b/Content.Client/ADT/Hallucinations/SchizophreniaSystem.cs @@ -0,0 +1,270 @@ +using System.Linq; +using System.Numerics; +using Content.Client.Audio; +using Content.Shared.ADT.Hallucinations.Components; +using Content.Shared.ADT.Hallucinations.Events; +using Content.Shared.Antag; +using Content.Shared.Humanoid; +using Content.Shared.Mobs.Components; +using Content.Shared.StatusIcon.Components; +using Robust.Client.Audio; +using Robust.Client.GameObjects; +using Robust.Client.Player; +using Robust.Shared.Audio; +using Robust.Shared.Map; +using Robust.Shared.Player; +using Robust.Shared.Prototypes; +using Robust.Shared.Random; +using Robust.Shared.Timing; + +namespace Content.Client.ADT.Hallucinations; + +public sealed partial class SchizophreniaSystem : EntitySystem +{ + [Dependency] private IPrototypeManager _proto = default!; + [Dependency] private IPlayerManager _player = default!; + [Dependency] private IRobustRandom _random = default!; + [Dependency] private IGameTiming _timing = default!; + [Dependency] private AudioSystem _audio = default!; + [Dependency] private SpriteSystem _sprite = default!; + [Dependency] private TransformSystem _transform = default!; + [Dependency] private ContentAudioSystem _contentAudio = default!; + + private Dictionary _layers = new(); + + public override void Initialize() + { + base.Initialize(); + + SubscribeNetworkEvent(OnAppearanceMessage); + + SubscribeLocalEvent(OnGetHallucinatingIcons); + SubscribeLocalEvent(OnGetHallucinationIcons); + + SubscribeLocalEvent(OnMusicInit); + SubscribeLocalEvent(OnMusicShutdown); + SubscribeLocalEvent(OnMusicAttach); + SubscribeLocalEvent(OnMusicDetach); + SubscribeLocalEvent(OnMusicHandleState); + } + + private void OnAppearanceMessage(SetHallucinationAppearanceMessage args) + { + if (_player.LocalSession == null) + return; + + // Get target entity + var ents = EntityManager.AllEntities().Where(x => x.Owner != _player.LocalEntity && !HasComp(x)).ToList(); + var selected = _random.Pick(ents); + + var proto = _random.Pick(args.Appearance.Prototypes); + var item = Spawn(proto); + + if (!TryComp(selected, out var sprite) || !TryComp(item, out var itemSprite)) + return; + + var state = _random.Pick(args.Appearance.States); + + // Ensure that given prototype sprite contains our state + var rsi = itemSprite.BaseRSI; + if (rsi == null || !rsi.TryGetState(state, out _)) + return; + + // Build layer + var layer = new PrototypeLayerData(); + layer.RsiPath = rsi.Path.ToString(); + layer.State = state; + + // Set layer and play sound + _sprite.LayerMapReserve(selected.Owner, "hallucination"); + _sprite.LayerSetData(selected.Owner, "hallucination", layer); + if (_layers.TryAdd(GetNetEntity(selected), _timing.CurTime + TimeSpan.FromSeconds(5))) + _audio.PlayEntity(args.Appearance.Sound, _player.LocalSession, selected); + + QueueDel(item); + } + + private void OnGetHallucinatingIcons(Entity ent, ref GetStatusIconsEvent args) + { + if (!(TryComp(_player.LocalEntity, out var hallucination) && hallucination.Idx == ent.Comp.Idx) && + !HasComp(_player.LocalEntity)) + return; + + args.StatusIcons.Add(_proto.Index(ent.Comp.FactionIcon)); + } + + private void OnGetHallucinationIcons(Entity ent, ref GetStatusIconsEvent args) + { + if (!(TryComp(_player.LocalEntity, out var hallucination) && hallucination.Idx == ent.Comp.Idx) && + !HasComp(_player.LocalEntity)) + return; + + args.StatusIcons.Add(_proto.Index(ent.Comp.FactionIcon)); + } + + private void OnMusicInit(Entity ent, ref MapInitEvent args) + { + if (_player.LocalEntity != ent.Owner) + return; + + foreach (var item in ent.Comp.Music) + { + if (ent.Comp.ActiveMusic.ContainsKey(item.Key) || ent.Comp.NextMusic.ContainsKey(item.Key)) + continue; + + ent.Comp.NextMusic[item.Key] = _timing.CurTime + TimeSpan.FromSeconds(10f); + } + } + + private void OnMusicShutdown(Entity ent, ref ComponentShutdown args) + { + if (_player.LocalEntity != ent.Owner) + return; + + foreach (var item in ent.Comp.ActiveMusic.ToList()) + { + _contentAudio.FadeOut(item.Value, duration: 5f); + ent.Comp.ActiveMusic.Remove(item.Key); + ent.Comp.NextMusic.Remove(item.Key); + } + } + + private void OnMusicAttach(Entity ent, ref LocalPlayerAttachedEvent args) + { + foreach (var item in ent.Comp.Music) + { + if (ent.Comp.ActiveMusic.ContainsKey(item.Key) || ent.Comp.NextMusic.ContainsKey(item.Key)) + continue; + + ent.Comp.NextMusic[item.Key] = _timing.CurTime + TimeSpan.FromSeconds(item.Value.Delay.HasValue ? item.Value.Delay.Value.Next(_random) : 10f); + } + } + + private void OnMusicDetach(Entity ent, ref LocalPlayerDetachedEvent args) + { + foreach (var item in ent.Comp.ActiveMusic.ToList()) + { + _contentAudio.FadeOut(item.Value, duration: 1f); + ent.Comp.ActiveMusic.Remove(item.Key); + ent.Comp.NextMusic.Remove(item.Key); + } + } + + private void OnMusicHandleState(Entity ent, ref AfterAutoHandleStateEvent args) + { + if (_player.LocalEntity != ent.Owner) + return; + + foreach (var item in ent.Comp.Music) + { + if (ent.Comp.ActiveMusic.ContainsKey(item.Key) || ent.Comp.NextMusic.ContainsKey(item.Key)) + continue; + + ent.Comp.NextMusic[item.Key] = _timing.CurTime + TimeSpan.FromSeconds(10f); + } + + foreach (var item in ent.Comp.ActiveMusic.ToList()) + { + if (!ent.Comp.Music.ContainsKey(item.Key)) + { + _contentAudio.FadeOut(item.Value, duration: 8f); + ent.Comp.ActiveMusic.Remove(item.Key); + ent.Comp.NextMusic.Remove(item.Key); + } + } + } + + public bool CanSee(EntityUid target) + { + if (!HasComp(_player.LocalEntity)) + return true; + + if (target == _player.LocalEntity) + return true; + + if (HasComp(target)) + return true; + + if (Transform(target).ParentUid == _player.LocalEntity) + return true; + + return false; + } + + public override void Update(float frameTime) + { + base.Update(frameTime); + + if (!TryComp(_player.LocalEntity, out var comp)) + return; + + foreach (var item in comp.NextMusic.ToDictionary()) + { + if (item.Value > _timing.CurTime) + continue; + + if (!comp.Music.TryGetValue(item.Key, out var music)) + continue; + + if (music.Delay.HasValue) + comp.NextMusic[item.Key] = _timing.CurTime + TimeSpan.FromSeconds(music.Delay.Value.Next(_random)); + else + comp.NextMusic.Remove(item.Key); + + var hasMusic = comp.ActiveMusic.TryGetValue(item.Key, out var exsisting) && exsisting.IsValid(); + + var mus = _audio.PlayGlobal(music.Sound, _player.LocalEntity.Value, AudioParams.Default.WithLoop(music.Delay == null)); + if (!mus.HasValue) + continue; + + comp.ActiveMusic[item.Key] = mus.Value.Entity; + + if (!hasMusic) + _contentAudio.FadeIn(mus.Value.Entity, duration: 5f); + } + } + + public override void FrameUpdate(float frameTime) + { + base.FrameUpdate(frameTime); + + FrameUpdateExtraLayers(); + FrameUpdateMobs(); + } + + private void FrameUpdateExtraLayers() + { + foreach (var item in _layers.ToDictionary()) + { + if (item.Value > _timing.CurTime) + continue; + + if (!TryGetEntity(item.Key, out var ent)) + continue; + + _sprite.RemoveLayer(ent.Value, "hallucination", false); + _layers.Remove(item.Key); + } + } + + private void FrameUpdateMobs() + { + // Так как невозможно просто изолировать сущности по компоненту, это является лучшим вариантом для их сокрытия. + // Сущность существует, но она убирается из поля зрения до отрисовки, при этом сохраняя всю функциональность. После + // удаления компонента у клиента, он просто перестанет перемещать сущности в нуллспейс, не ломая ничего при этом. + + // В паре систем пришлось внести правки для работы с этим подходом, но это всё ещё не так плохо, как могло бы быть. + + if (!HasComp(_player.LocalEntity)) + return; + + var ents = EntityManager.AllEntities().Where(x => !HasComp(x)).ToList(); + foreach (var item in ents) + { + if (item.Owner == _player.LocalEntity || Transform(item.Owner).ParentUid == _player.LocalEntity) + continue; + + _transform.SetCoordinates(item.Owner, new EntityCoordinates(EntityUid.Invalid, Vector2.Zero)); + } + } +} diff --git a/Content.Client/ADT/Overlays/Shaders/HueShiftOverlay.cs b/Content.Client/ADT/Overlays/Shaders/HueShiftOverlay.cs new file mode 100644 index 00000000000..6f029bce13c --- /dev/null +++ b/Content.Client/ADT/Overlays/Shaders/HueShiftOverlay.cs @@ -0,0 +1,45 @@ +using Robust.Client.Graphics; +using Robust.Client.Player; +using Robust.Shared.Enums; +using Robust.Shared.Prototypes; +using Content.Shared.ADT.Hallucinations.Components; + +namespace Content.Client.ADT.Overlays +{ + public sealed partial class HueShiftOverlay : Overlay + { + [Dependency] private IPrototypeManager _prototypeManager = default!; + [Dependency] private IPlayerManager _playerManager = default!; + [Dependency] IEntityManager _entityManager = default!; + + + public override bool RequestScreenTexture => true; + public override OverlaySpace Space => OverlaySpace.WorldSpace; + private readonly ShaderInstance _shader; + + public HueShiftOverlay() + { + IoCManager.InjectDependencies(this); + _shader = _prototypeManager.Index((ProtoId)"HueShift").InstanceUnique(); + } + + protected override void Draw(in OverlayDrawArgs args) + { + if (ScreenTexture == null) + return; + + if (!_entityManager.TryGetComponent(_playerManager.LocalEntity, out var hue)) + return; + + _shader.SetParameter("SCREEN_TEXTURE", ScreenTexture); + _shader.SetParameter("shift", hue.Shift); + + var worldHandle = args.WorldHandle; + var viewport = args.WorldBounds; + + worldHandle.UseShader(_shader); + worldHandle.DrawRect(viewport, Color.White); + worldHandle.UseShader(null); + } + } +} diff --git a/Content.Client/ADT/Overlays/Shaders/ScreenWaveOverlay.cs b/Content.Client/ADT/Overlays/Shaders/ScreenWaveOverlay.cs new file mode 100644 index 00000000000..ded0a3923ee --- /dev/null +++ b/Content.Client/ADT/Overlays/Shaders/ScreenWaveOverlay.cs @@ -0,0 +1,123 @@ +using Content.Shared.Drunk; +using Robust.Client.Graphics; +using Robust.Client.Player; +using Robust.Shared.Enums; +using Robust.Shared.Prototypes; +using Robust.Shared.Timing; + +namespace Content.Client.Drunk; + +public sealed partial class ScreenWaveOverlay : Overlay +{ + private static readonly ProtoId RotateShader = "ScreenRotation"; + + [Dependency] private IEntityManager _entityManager = default!; + [Dependency] private IPrototypeManager _prototypeManager = default!; + [Dependency] private IPlayerManager _playerManager = default!; + [Dependency] private IGameTiming _timing = default!; + private readonly Shared.StatusEffectNew.StatusEffectsSystem _statusEffectsSystem; + + public override OverlaySpace Space => OverlaySpace.WorldSpace; + public override bool RequestScreenTexture => true; + + public float CurrentBoozePower = 0.0f; + + private const float VisualThreshold = 10.0f; + private const float PowerDivisor = 250.0f; + /// + /// This is a magic number based on my person preference of how quickly the bloodloss effect should kick in. + /// It is entirely arbitrary, and you should change it if it sucks. + /// Honestly should be refactored to be based on amount of blood lost but that's out of scope for what I'm doing atm. + /// Also caps all booze visual effects to a max intensity of 100 seconds or 100 booze power. + /// + private const float MaxBoozePower = 100f; + + private const float BoozePowerScale = 8f; + + private const float MaxRotationAngle = 0.035f; + + private const float RotationFrequency = 0.85f; + + private float _visualScale = 0f; + + private readonly ShaderInstance _rotateShader; + + private float _timeTicker = 0.0f; + + public ScreenWaveOverlay() + { + IoCManager.InjectDependencies(this); + _statusEffectsSystem = _entityManager.System(); + _rotateShader = _prototypeManager.Index(RotateShader).InstanceUnique(); + } + + protected override void FrameUpdate(FrameEventArgs args) + { + + var playerEntity = _playerManager.LocalEntity; + + if (playerEntity == null) + return; + + if (!_statusEffectsSystem.TryGetMaxTime(playerEntity.Value, out var status)) + return; + + var time = status.Item2; + + var power = time == null ? MaxBoozePower : (float)Math.Min((time - _timing.CurTime).Value.TotalSeconds, MaxBoozePower); + + CurrentBoozePower += BoozePowerScale * (power - CurrentBoozePower) * args.DeltaSeconds / (power + 1); + + _timeTicker += args.DeltaSeconds; + } + + protected override bool BeforeDraw(in OverlayDrawArgs args) + { + if (!_entityManager.TryGetComponent(_playerManager.LocalEntity, out EyeComponent? eyeComp)) + return false; + + if (args.Viewport.Eye != eyeComp.Eye) + return false; + + _visualScale = BoozePowerToVisual(CurrentBoozePower); + return _visualScale > 0; + } + + protected override void Draw(in OverlayDrawArgs args) + { + if (ScreenTexture == null) + return; + + var handle = args.WorldHandle; + + // ADT-Tweak-start + var angle = MathF.Sin(_timeTicker * RotationFrequency) * MaxRotationAngle * _visualScale; + + _rotateShader.SetParameter("SCREEN_TEXTURE", ScreenTexture); + _rotateShader.SetParameter("angle", angle); + + handle.DrawRect(args.WorldBounds.Enlarged(0.75f), Color.Black); + handle.UseShader(_rotateShader); + handle.DrawRect(args.WorldBounds, Color.White); + handle.UseShader(null); + // ADT-Tweak-end + } + + /// + /// Converts the # of seconds the drunk effect lasts for (booze power) to a percentage + /// used by the actual shader. + /// + /// + private float BoozePowerToVisual(float boozePower) + { + // Clamp booze power when it's low, to prevent really jittery effects + if (boozePower < 50f) + { + return 0; + } + else + { + return Math.Clamp((boozePower - VisualThreshold) / PowerDivisor, 0.0f, 1.0f); + } + } +} diff --git a/Content.Client/ADT/Overlays/Systems/HueShiftSystem.cs b/Content.Client/ADT/Overlays/Systems/HueShiftSystem.cs new file mode 100644 index 00000000000..287f7fe6419 --- /dev/null +++ b/Content.Client/ADT/Overlays/Systems/HueShiftSystem.cs @@ -0,0 +1,51 @@ +using Robust.Client.Graphics; +using Robust.Client.Player; +using Robust.Shared.Player; +using Content.Shared.ADT.Hallucinations.Components; + +namespace Content.Client.ADT.Overlays; + +public sealed partial class HueShiftSystem : EntitySystem +{ + [Dependency] private IPlayerManager _player = default!; + [Dependency] private IOverlayManager _overlayMan = default!; + + private HueShiftOverlay _overlay = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnMonochromacyStartup); + SubscribeLocalEvent(OnMonochromacyShutdown); + + SubscribeLocalEvent(OnPlayerAttached); + SubscribeLocalEvent(OnPlayerDetached); + + _overlay = new(); + } + + private void OnMonochromacyStartup(EntityUid uid, HueShiftComponent component, ComponentStartup args) + { + if (_player.LocalEntity == uid) + _overlayMan.AddOverlay(_overlay); + } + + private void OnMonochromacyShutdown(EntityUid uid, HueShiftComponent component, ComponentShutdown args) + { + if (_player.LocalEntity == uid) + { + _overlayMan.RemoveOverlay(_overlay); + } + } + + private void OnPlayerAttached(EntityUid uid, HueShiftComponent component, PlayerAttachedEvent args) + { + _overlayMan.AddOverlay(_overlay); + } + + private void OnPlayerDetached(EntityUid uid, HueShiftComponent component, PlayerDetachedEvent args) + { + _overlayMan.RemoveOverlay(_overlay); + } +} diff --git a/Content.Client/ADT/Screamer/ScreamerOverlay.cs b/Content.Client/ADT/Screamer/ScreamerOverlay.cs new file mode 100644 index 00000000000..e95317a4be6 --- /dev/null +++ b/Content.Client/ADT/Screamer/ScreamerOverlay.cs @@ -0,0 +1,161 @@ +using System.Linq; +using System.Numerics; +using Robust.Client.GameObjects; +using Robust.Client.Graphics; +using Robust.Client.Player; +using Robust.Shared.Enums; +using Robust.Shared.Timing; + +namespace Content.Client.ADT.Screamer; + +public sealed partial class ScreamerOverlay : Overlay +{ + [Dependency] private IPlayerManager _player = default!; + [Dependency] private IEntityManager _entity = default!; + [Dependency] private IGameTiming _timing = default!; + private readonly SharedTransformSystem _xformSystem; + private readonly SpriteSystem _sprite; + + public override bool RequestScreenTexture => false; + public override OverlaySpace Space => OverlaySpace.WorldSpace; + private Dictionary _activeScreamers = new(); + private readonly EntityQuery _spriteQuery; + private readonly EntityQuery _xformQuery; + + public ScreamerOverlay() + { + IoCManager.InjectDependencies(this); + + _xformSystem = _entity.System(); + _sprite = _entity.System(); + + _spriteQuery = _entity.GetEntityQuery(); + _xformQuery = _entity.GetEntityQuery(); + } + + public void AddScreamer(EntityUid entity, Vector2 offset, float duration, float alpha, bool fadeIn, bool fadeOut) + { + var data = new ScreamerData() + { + EndTime = duration > 0 ? _timing.CurTime + TimeSpan.FromSeconds(duration) : null, + Duration = duration, + FadeIn = fadeIn, + FadeOut = fadeOut, + Offset = offset, + Alpha = alpha + }; + + _activeScreamers.Add(entity, data); + } + + public void Clear() + { + for (var i = _activeScreamers.Count - 1; i >= 0; i--) + { + var ent = _activeScreamers.ElementAt(i).Key; + + _activeScreamers.Remove(ent); + _entity.QueueDeleteEntity(ent); + } + } + + protected override void Draw(in OverlayDrawArgs args) + { + if (args.Viewport.Eye is not { } eye) + return; + + if (_player.LocalEntity is not { Valid: true } player || !_xformQuery.TryComp(player, out var xform)) + return; + + if (_activeScreamers.Count <= 0) + return; + + var handle = args.WorldHandle; + var eyeRot = eye.Rotation; + + for (var i = _activeScreamers.Count - 1; i >= 0; i--) + { + var item = _activeScreamers.ElementAt(i); + if (item.Value.EndTime != null && item.Value.EndTime <= _timing.CurTime) + { + var ent = item.Key; + _activeScreamers.Remove(ent); + _entity.QueueDeleteEntity(ent); + continue; + } + + if (!_spriteQuery.TryComp(item.Key, out var sprite)) + continue; + + var alpha = GetAlpha(item.Value) * item.Value.Alpha; + + RenderEntity((item.Key, sprite), (player, xform), handle, eyeRot, alpha, item.Value.Offset, args.Viewport); + } + } + + private float GetAlpha(ScreamerData data) + { + if (data.EndTime == null) + return 1f; + + var timeLeft = (data.EndTime.Value - _timing.CurTime).TotalSeconds; + var elapsed = data.Duration - timeLeft; + var segment = data.Duration / 3f; + + float factor = 1f; + if (data.FadeIn && elapsed < segment) + { + factor = (float)(elapsed / segment); + } + else if (data.FadeOut) + { + if (data.FadeIn && elapsed > segment * 2) + factor = 1f - (float)((elapsed - segment * 2) / segment); + else if (!data.FadeIn) + factor = 1f - (float)(elapsed / data.Duration); + } + // If not fading in/out, factor remains 1f + + return factor; + } + + private void RenderEntity( + Entity ent, + Entity player, + DrawingHandleWorld handle, + Angle eyeRot, + float alpha, + Vector2 offset, + IClydeViewport viewport) + { + var position = _xformSystem.GetWorldPosition(player.Comp); + + handle.SetTransform(position + offset, eyeRot); + + var originalColor = ent.Comp.Color; + var originalScale = ent.Comp.Scale; + + var textureSize = ent.Comp.Icon?.TextureFor(Direction.South).Size ?? Vector2.One; + var scaleX = viewport.Size.X / textureSize.X; + var scaleY = viewport.Size.Y / textureSize.Y; + + //_sprite.SetRotation(ent.Owner, eyeRot); + _sprite.SetColor(ent.Owner, originalColor.WithAlpha(alpha)); + _sprite.SetScale(ent.Owner, new Vector2(Math.Min(scaleX, scaleY))); + _sprite.RenderSprite(ent, handle, eyeRot, eyeRot, position + offset); + + _sprite.SetColor(ent.Owner, originalColor); + _sprite.SetScale(ent.Owner, originalScale); + handle.SetTransform(Vector2.Zero, Angle.Zero); + } + + private struct ScreamerData + { + public TimeSpan? EndTime; + public float Duration; + public bool FadeIn; + public bool FadeOut; + public Vector2 Offset; + public float Alpha; + } +} diff --git a/Content.Client/ADT/Screamer/ScreamerSystem.cs b/Content.Client/ADT/Screamer/ScreamerSystem.cs new file mode 100644 index 00000000000..9deedfca5a5 --- /dev/null +++ b/Content.Client/ADT/Screamer/ScreamerSystem.cs @@ -0,0 +1,74 @@ +using Content.Shared.ADT.Screamer; +using Robust.Client.Audio; +using Robust.Client.Graphics; +using Robust.Client.Player; +using Robust.Shared.Audio; +using Robust.Shared.Player; + +namespace Content.Client.ADT.Screamer; + +public sealed partial class ScreamerSystem : EntitySystem +{ + [Dependency] private IPlayerManager _player = default!; + [Dependency] private IOverlayManager _overlayMan = default!; + [Dependency] private AudioSystem _audio = default!; + + private ScreamerOverlay _overlay = default!; + + public override void Initialize() + { + base.Initialize(); + + _overlay = new(); + + SubscribeNetworkEvent(OnScreamerMessage); + + SubscribeLocalEvent(OnScreamersInit); + SubscribeLocalEvent(OnScreamersShutdown); + SubscribeLocalEvent(OnScreamersAttach); + SubscribeLocalEvent(OnScreamersDetach); + + } + + private void OnScreamerMessage(DoScreamerMessage args) + { + if (!HasComp(_player.LocalEntity)) + return; + + if (args.Sound != null) + _audio.PlayGlobal(new SoundPathSpecifier(args.Sound), _player.LocalEntity.Value); + + var ent = Spawn(args.ProtoId); + _overlay.AddScreamer(ent, args.Offset, args.Duration, args.Alpha, args.FadeIn, args.FadeOut); + } + + private void OnScreamersInit(Entity ent, ref ComponentInit args) + { + if (_player.LocalEntity != ent.Owner) + return; + + _overlay.Clear(); + _overlayMan.AddOverlay(_overlay); + } + + private void OnScreamersShutdown(Entity ent, ref ComponentShutdown args) + { + if (_player.LocalEntity != ent.Owner) + return; + + _overlay.Clear(); + _overlayMan.RemoveOverlay(_overlay); + } + + private void OnScreamersAttach(Entity ent, ref LocalPlayerAttachedEvent args) + { + _overlay.Clear(); + _overlayMan.AddOverlay(_overlay); + } + + private void OnScreamersDetach(Entity ent, ref LocalPlayerDetachedEvent args) + { + _overlay.Clear(); + _overlayMan.RemoveOverlay(_overlay); + } +} diff --git a/Content.Client/Drunk/DrunkOverlay.cs b/Content.Client/Drunk/DrunkOverlay.cs index 44d82f3794b..2ac25dbba95 100644 --- a/Content.Client/Drunk/DrunkOverlay.cs +++ b/Content.Client/Drunk/DrunkOverlay.cs @@ -45,12 +45,23 @@ public sealed class DrunkOverlay : Overlay private float _timeScale = 1f; private float _distortionScale = 1f; + // ADT-Tweak-start + private static readonly ProtoId RotateShader = "ScreenRotation"; + private readonly ShaderInstance _rotateShader; + + private float _timeTicker = 0.0f; + private const float MaxRotationAngle = 0.035f; + private const float RotationFrequency = 0.85f; + // ADT-Tweak-end + public DrunkOverlay() { IoCManager.InjectDependencies(this); _statusEffectsSystem = _entityManager.System(); _drunkShader = _prototypeManager.Index(DrunkShader).InstanceUnique(); _configManager.OnValueChanged(CCVars.ReducedMotion, OnReducedMotionChanged, invokeImmediately: true); + + _rotateShader = _prototypeManager.Index(RotateShader).InstanceUnique(); // ADT-Tweak } private void OnReducedMotionChanged(bool reducedMotion) @@ -75,6 +86,8 @@ protected override void FrameUpdate(FrameEventArgs args) var power = time == null ? MaxBoozePower : (float)Math.Min((time - _timing.CurTime).Value.TotalSeconds, MaxBoozePower); CurrentBoozePower += BoozePowerScale * (power - CurrentBoozePower) * args.DeltaSeconds / (power + 1); + + _timeTicker += args.DeltaSeconds; // ADT-Tweak } protected override bool BeforeDraw(in OverlayDrawArgs args) @@ -96,6 +109,17 @@ protected override void Draw(in OverlayDrawArgs args) var handle = args.WorldHandle; + // ADT-Tweak-start + var angle = MathF.Sin(_timeTicker * RotationFrequency) * MaxRotationAngle * _visualScale; + + _rotateShader.SetParameter("SCREEN_TEXTURE", ScreenTexture); + _rotateShader.SetParameter("angle", angle); + + handle.DrawRect(args.WorldBounds.Enlarged(0.75f), Color.Black); + handle.UseShader(_rotateShader); + handle.DrawRect(args.WorldBounds, Color.White); + // ADT-Tweak-end + _drunkShader.SetParameter("SCREEN_TEXTURE", ScreenTexture); _drunkShader.SetParameter("boozePower", _visualScale); _drunkShader.SetParameter("timeScale", _timeScale); diff --git a/Content.Client/Drunk/DrunkSystem.cs b/Content.Client/Drunk/DrunkSystem.cs index 2e1f8157aa4..a4cfda06181 100644 --- a/Content.Client/Drunk/DrunkSystem.cs +++ b/Content.Client/Drunk/DrunkSystem.cs @@ -14,6 +14,7 @@ public sealed class DrunkSystem : SharedDrunkSystem [Dependency] private readonly IRobustRandom _random = default!; private DrunkOverlay _overlay = default!; + private ScreenWaveOverlay _waveOverlay = default!; // ADT-Tweak public override void Initialize() { @@ -26,6 +27,7 @@ public override void Initialize() SubscribeLocalEvent>(OnPlayerDetached); _overlay = new(); + _waveOverlay = new(); // ADT-Tweak } private void OnStatusApplied(Entity entity, ref StatusEffectAppliedEvent args) @@ -34,6 +36,7 @@ private void OnStatusApplied(Entity entity, ref Stat { _overlay.Phase = _random.NextFloat(MathF.Tau); // random starting phase for movement effect _overlayMan.AddOverlay(_overlay); + _overlayMan.AddOverlay(_waveOverlay); // ADT-Tweak } } @@ -47,17 +50,23 @@ private void OnStatusRemoved(Entity entity, ref Stat _overlay.CurrentBoozePower = 0; _overlayMan.RemoveOverlay(_overlay); + + _waveOverlay.CurrentBoozePower = 0; // ADT-Tweak + _overlayMan.RemoveOverlay(_waveOverlay); // ADT-Tweak } private void OnPlayerAttached(Entity entity, ref StatusEffectRelayedEvent args) { _overlayMan.AddOverlay(_overlay); - + _overlayMan.AddOverlay(_waveOverlay); // ADT-Tweak } private void OnPlayerDetached(Entity entity, ref StatusEffectRelayedEvent args) { _overlay.CurrentBoozePower = 0; _overlayMan.RemoveOverlay(_overlay); + + _waveOverlay.CurrentBoozePower = 0; // ADT-Tweak + _overlayMan.RemoveOverlay(_waveOverlay); // ADT-Tweak } } diff --git a/Content.Client/Entry/EntryPoint.cs b/Content.Client/Entry/EntryPoint.cs index af398feb53f..96ad9d3bb80 100644 --- a/Content.Client/Entry/EntryPoint.cs +++ b/Content.Client/Entry/EntryPoint.cs @@ -148,6 +148,7 @@ public override void Init() _prototypeManager.RegisterIgnore("sponsorLoadout"); // ADT-Sponsor-Loadout _prototypeManager.RegisterIgnore("sponsorPersonalLoadout"); // ADT-Sponsor-Loadout _prototypeManager.RegisterIgnore("sponsorLoadoutTierSetting"); // ADT-Sponsor-Loadout + _prototypeManager.RegisterIgnore("hallucinationsPack"); // ADT-Tweak _componentFactory.GenerateNetIds(); _adminManager.Initialize(); diff --git a/Content.Client/Popups/PopupSystem.cs b/Content.Client/Popups/PopupSystem.cs index 725568344d5..f8a3ca7f231 100644 --- a/Content.Client/Popups/PopupSystem.cs +++ b/Content.Client/Popups/PopupSystem.cs @@ -1,4 +1,5 @@ using System.Linq; +using Content.Client.ADT.Hallucinations; using Content.Shared.Containers; using Content.Shared.Examine; using Content.Shared.GameTicking; @@ -30,6 +31,7 @@ public sealed class PopupSystem : SharedPopupSystem [Dependency] private readonly IReplayRecordingManager _replayRecording = default!; [Dependency] private readonly ExamineSystemShared _examine = default!; [Dependency] private readonly SharedTransformSystem _transform = default!; + [Dependency] private readonly SchizophreniaSystem _schiz = default!; // ADT-Tweak - hallucinations public IReadOnlyCollection WorldLabels => _aliveWorldLabels.Values; public IReadOnlyCollection CursorLabels => _aliveCursorLabels.Values; @@ -237,7 +239,7 @@ public override void PopupClient(string? message, EntityCoordinates coordinates, public override void PopupEntity(string? message, EntityUid uid, PopupType type = PopupType.Small) { - if (TryComp(uid, out TransformComponent? transform)) + if (TryComp(uid, out TransformComponent? transform) && _schiz.CanSee(uid)) // ADT-Tweak - hallucinations PopupMessage(message, type, transform.Coordinates, uid, true); } diff --git a/Content.Client/Weapons/Ranged/Systems/GunSystem.cs b/Content.Client/Weapons/Ranged/Systems/GunSystem.cs index c34d70caee7..9212d1f2270 100644 --- a/Content.Client/Weapons/Ranged/Systems/GunSystem.cs +++ b/Content.Client/Weapons/Ranged/Systems/GunSystem.cs @@ -3,6 +3,7 @@ using Content.Client.Gameplay; using Content.Client.Items; using Content.Client.Weapons.Ranged.Components; +using Content.Shared.ADT.Hallucinations.Components; using Content.Shared.Camera; using Content.Shared.CCVar; using Content.Shared.CombatMode; @@ -355,6 +356,11 @@ protected override void CreateEffect(EntityUid gunUid, MuzzleFlashEvent message, return; } + // ADT-Tweak-start + if (HasComp(_player.LocalEntity) && tracked != _player.LocalEntity && !HasComp(tracked)) + tracked = null; + // ADT-Tweak-end + var ent = Spawn(message.Prototype, coordinates); TransformSystem.SetWorldRotationNoLerp(ent, message.Angle); diff --git a/Content.Server/ADT/Chat/Events/CanReceiveChatMessageEvent.cs b/Content.Server/ADT/Chat/Events/CanReceiveChatMessageEvent.cs new file mode 100644 index 00000000000..15bca4a8a90 --- /dev/null +++ b/Content.Server/ADT/Chat/Events/CanReceiveChatMessageEvent.cs @@ -0,0 +1,4 @@ +namespace Content.Server.ADT.Chat; + +[ByRefEvent] +public record struct CanReceiveChatMessageEvent(EntityUid Source, bool Whisper, bool Cancelled = false); diff --git a/Content.Server/ADT/Hallucinations/Commands/AddAsHallucinationCommand.cs b/Content.Server/ADT/Hallucinations/Commands/AddAsHallucinationCommand.cs new file mode 100644 index 00000000000..edb1352bb99 --- /dev/null +++ b/Content.Server/ADT/Hallucinations/Commands/AddAsHallucinationCommand.cs @@ -0,0 +1,62 @@ +using System.Linq; +using Content.Server.Administration; +using Content.Server.ADT.Hallucinations.Components; +using Content.Server.ADT.Hallucinations.Systems; +using Content.Shared.Administration; +using Robust.Shared.Console; +using Robust.Shared.Prototypes; + +namespace Content.Server.ADT.Hallucinations.Commands; + +[AdminCommand(AdminFlags.Admin)] +public sealed partial class AddAsHallucinationCommand : IConsoleCommand +{ + [Dependency] private IEntityManager _entManager = default!; + [Dependency] private IPrototypeManager _proto = default!; + + public string Command => "add-as-hallucination"; + + public string Description => Loc.GetString("add-as-hallucination-command-description"); + + public string Help => Loc.GetString("add-as-hallucination-command-help-text", ("command", Command)); + + public void Execute(IConsoleShell shell, string argStr, string[] args) + { + if (args.Length < 4) + { + shell.WriteLine(Loc.GetString("shell-wrong-arguments-number")); + return; + } + + if (!EntityUid.TryParse(args[0], out var target)) + { + shell.WriteLine(Loc.GetString("shell-entity-uid-must-be-number")); + return; + } + if (!EntityUid.TryParse(args[1], out var toAdd)) + { + shell.WriteLine(Loc.GetString("shell-entity-uid-must-be-number")); + return; + } + + var hall = _entManager.System(); + hall.AddAsHallucination(target, toAdd); + shell.WriteLine(Loc.GetString("add-as-hallucination-command-success", ("target", target), ("added", toAdd))); + } + + public CompletionResult GetCompletion(IConsoleShell shell, string[] args) + { + if (args.Length == 1) + { + var opts = _entManager.AllEntities().Select(ent => new CompletionOption(ent.Owner.ToString(), _entManager.ToPrettyString(ent))).ToList(); + return CompletionResult.FromHintOptions(opts, ""); + } + + if (args.Length == 2) + { + return CompletionResult.FromHint(""); + } + + return CompletionResult.Empty; + } +} diff --git a/Content.Server/ADT/Hallucinations/Commands/HallucinateCommand.cs b/Content.Server/ADT/Hallucinations/Commands/HallucinateCommand.cs new file mode 100644 index 00000000000..8d4dbc51b99 --- /dev/null +++ b/Content.Server/ADT/Hallucinations/Commands/HallucinateCommand.cs @@ -0,0 +1,89 @@ +using System.Linq; +using Content.Server.Administration; +using Content.Server.ADT.Hallucinations.Components; +using Content.Server.ADT.Hallucinations.Systems; +using Content.Shared.Administration; +using Content.Shared.EntityEffects.Effects.StatusEffects; +using Robust.Shared.Console; +using Robust.Shared.Prototypes; + +namespace Content.Server.ADT.Hallucinations.Commands; + +[AdminCommand(AdminFlags.Admin)] +public sealed partial class HallucinateCommand : IConsoleCommand +{ + [Dependency] private IEntityManager _entManager = default!; + [Dependency] private IPrototypeManager _proto = default!; + + public string Command => "hallucinate"; + + public string Description => Loc.GetString("hallucinate-command-description"); + + public string Help => Loc.GetString("hallucinate-command-help-text", ("command", Command)); + + public void Execute(IConsoleShell shell, string argStr, string[] args) + { + if (args.Length < 4) + { + shell.WriteLine(Loc.GetString("shell-wrong-arguments-number")); + return; + } + + if (!EntityUid.TryParse(args[0], out var uid)) + { + shell.WriteLine(Loc.GetString("shell-entity-uid-must-be-number")); + return; + } + + if (!Enum.TryParse(args[1], out var type)) + { + shell.WriteLine(Loc.GetString("shell-invalid-metabolism-type")); + return; + } + + if (!int.TryParse(args[2], out var duration)) + { + shell.WriteError(Loc.GetString("cmd-parse-failure-integer", ("arg", args[2]))); + return; + } + + var hall = _entManager.System(); + + for (var i = 3; i < args.Length; i++) + { + if (!_proto.HasIndex(args[i])) + shell.WriteLine($"Invalid pack: {args[i]}"); + else + { + hall.AddOrAdjustHallucinations(uid, args[i], duration, type); + shell.WriteLine(Loc.GetString("hallucinate-command-success", ("target", uid), ("added", args[i]))); + } + } + } + + public CompletionResult GetCompletion(IConsoleShell shell, string[] args) + { + if (args.Length == 1) + { + var opts = _entManager.AllEntities().Select(ent => new CompletionOption(ent.Owner.ToString(), _entManager.ToPrettyString(ent))).ToList(); + return CompletionResult.FromHintOptions(opts, ""); + } + + if (args.Length == 2) + { + var opts = new List() + { + "Add", + "Set", + "Remove" + }; + return CompletionResult.FromHintOptions(opts, ""); + } + + if (args.Length == 3) + return CompletionResult.FromHint(""); + + var packs = _proto.EnumeratePrototypes().Select(pack => new CompletionOption(pack.ID, pack.ID)).ToList(); + return CompletionResult.FromHintOptions(packs, ""); + } +} diff --git a/Content.Server/ADT/Hallucinations/Components/CanHallucinateComponent.cs b/Content.Server/ADT/Hallucinations/Components/CanHallucinateComponent.cs new file mode 100644 index 00000000000..275acdcef3d --- /dev/null +++ b/Content.Server/ADT/Hallucinations/Components/CanHallucinateComponent.cs @@ -0,0 +1,6 @@ +namespace Content.Server.ADT.Hallucinations.Components; +/// +/// Component added to entities that can experience hallucinations +/// +[RegisterComponent] +public sealed partial class CanHallucinateComponent : Component; diff --git a/Content.Server/ADT/Hallucinations/Components/HallucinatingComponent.cs b/Content.Server/ADT/Hallucinations/Components/HallucinatingComponent.cs new file mode 100644 index 00000000000..d94179e1579 --- /dev/null +++ b/Content.Server/ADT/Hallucinations/Components/HallucinatingComponent.cs @@ -0,0 +1,42 @@ +using Content.Server.ADT.Hallucinations.Types; + +namespace Content.Server.ADT.Hallucinations.Components; + +/// +/// Component added to currently hallucinating entities +/// +[RegisterComponent] +public sealed partial class HallucinatingComponent : Component +{ + /// + /// Current hallucinations with their ids + /// + [ViewVariables(VVAccess.ReadWrite)] + public Dictionary> Hallucinations = new(); + + /// + /// Lifetimes for temporal hallucinations + /// + [ViewVariables(VVAccess.ReadWrite)] + public Dictionary Removes = new(); + + /// + /// Hallucinations music + /// + [ViewVariables(VVAccess.ReadWrite)] + public List Music = new(); + + public TimeSpan NextUpdate = TimeSpan.Zero; + + public sealed class HallucinationCompound + { + public BaseHallucinationsType Type; + public TimeSpan PerformTime; + + public HallucinationCompound(BaseHallucinationsType type, TimeSpan performTime) + { + Type = type; + PerformTime = performTime; + } + } +} diff --git a/Content.Server/ADT/Hallucinations/Events/AddHallucinationsEvent.cs b/Content.Server/ADT/Hallucinations/Events/AddHallucinationsEvent.cs new file mode 100644 index 00000000000..9a7cbb99401 --- /dev/null +++ b/Content.Server/ADT/Hallucinations/Events/AddHallucinationsEvent.cs @@ -0,0 +1,28 @@ +using Robust.Shared.Prototypes; + +namespace Content.Server.ADT.Hallucinations.Events; + +/// +/// Applies hallucinations to entity +/// +[DataDefinition] +public sealed partial class AddHallucinationsEvent : EntityEventArgs +{ + /// + /// Hallucinations pack that will be applied + /// + [DataField(required: true)] + public ProtoId Id; + + /// + /// Hallucinations duration. If negative, entity will hallucinate forever + /// + [DataField] + public float Duration = -1f; + + /// + /// Whether overwrite exsisting hallucinations duration or not + /// + [DataField] + public bool OverwriteTimer = false; +} diff --git a/Content.Server/ADT/Hallucinations/Events/RemoveHallucinationsEvent.cs b/Content.Server/ADT/Hallucinations/Events/RemoveHallucinationsEvent.cs new file mode 100644 index 00000000000..a57ae76cd8c --- /dev/null +++ b/Content.Server/ADT/Hallucinations/Events/RemoveHallucinationsEvent.cs @@ -0,0 +1,14 @@ +namespace Content.Server.ADT.Hallucinations.Events; + +/// +/// Removes hallucinations with specified key from entity +/// +[DataDefinition] +public sealed partial class RemoveHallucinationsEvent : EntityEventArgs +{ + /// + /// Time to remove from pack duration + /// + [DataField] + public float Time; +} diff --git a/Content.Server/ADT/Hallucinations/HallucinationsPackPrototype.cs b/Content.Server/ADT/Hallucinations/HallucinationsPackPrototype.cs new file mode 100644 index 00000000000..1f1cb7e42fc --- /dev/null +++ b/Content.Server/ADT/Hallucinations/HallucinationsPackPrototype.cs @@ -0,0 +1,35 @@ +using Content.Server.ADT.Hallucinations.Types; +using Content.Shared.Destructible.Thresholds; +using Content.Shared.Popups; +using Robust.Shared.Audio; +using Robust.Shared.Prototypes; + +namespace Content.Server.ADT.Hallucinations; + +[Prototype] +public sealed partial class HallucinationsPackPrototype : IPrototype +{ + [IdDataField] + public string ID { get; private set; } = default!; + + [DataField] + public List? Data; + + [DataField] + public ComponentRegistry Components = new(); + + [DataField] + public string? StartingMessage; + + [DataField] + public PopupType MessageType = PopupType.MediumCaution; + + [DataField] + public SoundSpecifier? Music; + + [DataField] + public float MusicDurationThreshold = 1f; + + [DataField] + public MinMax? MusicPlayInterval; +} diff --git a/Content.Server/ADT/Hallucinations/Systems/HallucinateEntityEffectSystem.cs b/Content.Server/ADT/Hallucinations/Systems/HallucinateEntityEffectSystem.cs new file mode 100644 index 00000000000..bd2c59b62ac --- /dev/null +++ b/Content.Server/ADT/Hallucinations/Systems/HallucinateEntityEffectSystem.cs @@ -0,0 +1,18 @@ +using Content.Server.ADT.Hallucinations.Components; +using Content.Shared.ADT.Hallucinations.EntityEffects; +using Content.Shared.EntityEffects; + +namespace Content.Server.ADT.Hallucinations.Systems; + +public sealed partial class HallucinateEntityEffectSystem : EntityEffectSystem +{ + [Dependency] private SchizophreniaSystem _schiz = default!; + + protected override void Effect(Entity entity, ref EntityEffectEvent args) + { + foreach (var item in args.Effect.HallucinationPacks) + { + _schiz.AddOrAdjustHallucinations(entity.Owner, item, args.Effect.Time * args.Scale, args.Effect.Type); + } + } +} diff --git a/Content.Server/ADT/Hallucinations/Systems/HallucinationsSystem.cs b/Content.Server/ADT/Hallucinations/Systems/HallucinationsSystem.cs deleted file mode 100644 index ca6ca8ad5f5..00000000000 --- a/Content.Server/ADT/Hallucinations/Systems/HallucinationsSystem.cs +++ /dev/null @@ -1,276 +0,0 @@ -using Content.Shared.Humanoid; -using Content.Shared.StatusEffect; -using Robust.Shared.Timing; -using Content.Shared.Database; -using Content.Shared.ADT.Hallucinations; -using Robust.Server.GameObjects; -using Robust.Shared.Prototypes; -using Robust.Shared.Random; -using Content.Server.Chat.Systems; -using Content.Shared.Administration.Logs; -using Content.Shared.Chat; - -namespace Content.Server.ADT.Hallucinations; - -public sealed partial class HallucinationsSystem : EntitySystem -{ - [Dependency] private readonly EntityLookupSystem _lookup = default!; - [Dependency] private readonly IEntityManager _entityManager = default!; - [Dependency] private readonly VisibilitySystem _visibilitySystem = default!; - [Dependency] private readonly ISharedAdminLogManager _adminLogger = default!; - [Dependency] private readonly SharedEyeSystem _eye = default!; - [Dependency] private readonly IGameTiming _timing = default!; - [Dependency] private readonly IRobustRandom _random = default!; - [Dependency] private readonly StatusEffectsSystem _status = default!; - [Dependency] private readonly IPrototypeManager _proto = default!; - - public static string HallucinatingKey = "Hallucinations"; - - public override void Initialize() - { - base.Initialize(); - - SubscribeLocalEvent(OnHallucinationsInit); - SubscribeLocalEvent(OnHallucinationsShutdown); - SubscribeLocalEvent(OnHallucinationsDiseaseInit); - SubscribeLocalEvent(OnHallucinationsDiseaseShutdown); - SubscribeLocalEvent(OnEntitySpoke); - } - - private void OnHallucinationsInit(EntityUid uid, HallucinationsComponent component, MapInitEvent args) - { - component.Layer = (ushort)(_random.Next(4, 32) << 1); - if (!_entityManager.TryGetComponent(uid, out var eye)) - return; - UpdatePreset(component); - _eye.SetVisibilityMask(uid, eye.VisibilityMask | component.Layer, eye); - - _adminLogger.Add(LogType.Action, LogImpact.Medium, - $"{ToPrettyString(uid):player} began to hallucinate."); - } - - private void OnHallucinationsDiseaseInit(EntityUid uid, HallucinationsDiseaseComponent component, MapInitEvent args) - { - component.Layer = (ushort)(_random.Next(4, 32) << 1); - if (!_entityManager.TryGetComponent(uid, out var eye)) - return; - - _eye.SetVisibilityMask(uid, eye.VisibilityMask | component.Layer, eye); - _adminLogger.Add(LogType.Action, LogImpact.Medium, - - $"{ToPrettyString(uid):player} began to hallucinate."); - } - - public void UpdatePreset(HallucinationsComponent component) - { - if (component.Proto == null) - return; - - var preset = component.Proto; - - component.Spawns = preset.Entities; - component.Range = preset.Range; - component.SpawnRate = preset.SpawnRate; - component.MinChance = preset.MinChance; - component.MaxChance = preset.MaxChance; - component.MaxSpawns = preset.MaxSpawns; - component.IncreaseChance = preset.IncreaseChance; - } - - private void OnHallucinationsShutdown(EntityUid uid, HallucinationsComponent component, ComponentShutdown args) - { - if (!_entityManager.TryGetComponent(uid, out var eye)) - return; - - _eye.SetVisibilityMask(uid, eye.VisibilityMask & ~component.Layer, eye); - _adminLogger.Add(LogType.Action, LogImpact.Medium, - $"{ToPrettyString(uid):player} stopped hallucinating."); - } - - private void OnHallucinationsDiseaseShutdown(EntityUid uid, HallucinationsDiseaseComponent component, ComponentShutdown args) - { - if (!_entityManager.TryGetComponent(uid, out var eye)) - return; - - _eye.SetVisibilityMask(uid, eye.VisibilityMask & ~(ushort)component.Layer, eye); - _adminLogger.Add(LogType.Action, LogImpact.Medium, - $"{ToPrettyString(uid):player} stopped hallucinating."); - } - - /// - /// Attempts to start hallucinations for target - /// - /// The target. - /// Status effect key. - /// Duration of hallucinations effect. - /// Refresh active effects. - /// Hallucinations pack prototype. - public bool StartHallucinations(EntityUid target, string key, TimeSpan time, bool refresh, string proto) - { - if (proto == null) - return false; - - if (!_proto.TryIndex(proto, out var prototype)) - return false; - - if (!_status.TryAddStatusEffect(target, key, time, refresh)) - return false; - - var hallucinations = _entityManager.GetComponent(target); - hallucinations.Proto = prototype; - UpdatePreset(hallucinations); - hallucinations.CurChance = prototype.MinChance; - - return true; - } - - /// - /// Attempts to start epidemic hallucinations. Spreads by speech - /// - /// The target. - /// Hallucinations pack prototype. - public bool StartEpidemicHallucinations(EntityUid target, string proto) - { - if (proto == null) - return false; - - if (!_proto.TryIndex(proto, out var prototype)) - return false; - - var hallucinations = EnsureComp(target); - hallucinations.EndTime = _timing.CurTime + TimeSpan.FromSeconds(15); - - hallucinations.Proto = prototype; - hallucinations.Spawns = prototype.Entities; - hallucinations.Range = prototype.Range; - hallucinations.SpawnRate = prototype.SpawnRate; - hallucinations.MinChance = prototype.MinChance; - hallucinations.MaxChance = prototype.MaxChance; - hallucinations.MaxSpawns = prototype.MaxSpawns; - hallucinations.IncreaseChance = prototype.IncreaseChance; - hallucinations.CurChance = prototype.MinChance; - - return true; - } - - private void OnEntitySpoke(EntityUid uid, HallucinationsDiseaseComponent component, EntitySpokeEvent args) - { - if (component.Proto == null) - return; - - foreach (var ent in _lookup.GetEntitiesInRange(uid, 7f)) - { - if (!HasComp(ent)) - continue; - - StartEpidemicHallucinations(ent, component.Proto.ID); - } - } - - public override void Update(float frameTime) - { - base.Update(frameTime); - - var query = EntityQueryEnumerator(); - while (query.MoveNext(out var uid, out var stat, out var xform)) - { - if (_timing.CurTime < stat.NextSecond) - continue; - - var rate = stat.SpawnRate; - stat.NextSecond = _timing.CurTime + TimeSpan.FromSeconds(rate); - - if (stat.CurChance < stat.MaxChance && stat.CurChance + stat.IncreaseChance <= 1) - stat.CurChance = stat.CurChance + stat.IncreaseChance; - - if (!_random.Prob(stat.CurChance)) - continue; - - stat.SpawnedCount = 0; - - var range = stat.Range * 4; - UpdatePreset(stat); - - foreach (var (ent, comp) in _lookup.GetEntitiesInRange(xform.MapPosition, range)) - { - var newCoords = Transform(ent).MapPosition.Offset(_random.NextVector2(stat.Range)); - - if (stat.SpawnedCount >= stat.MaxSpawns) - continue; - - stat.SpawnedCount = stat.SpawnedCount += 1; - - var hallucination = Spawn(stat.Spawns[_random.Next(0, stat.Spawns.Count - 1)], newCoords); - EnsureComp(hallucination, out var visibility); - _visibilitySystem.SetLayer((hallucination, visibility), stat.Layer, false); - _visibilitySystem.RefreshVisibility(hallucination, visibilityComponent: visibility); - } - - var uidnewCoords = Transform(uid).MapPosition.Offset(_random.NextVector2(stat.Range)); - - if (stat.SpawnedCount >= stat.MaxSpawns) - continue; - - stat.SpawnedCount = stat.SpawnedCount += 1; - - var uidhallucination = Spawn(stat.Spawns[_random.Next(0, stat.Spawns.Count - 1)], uidnewCoords); - EnsureComp(uidhallucination, out var uidvisibility); - _visibilitySystem.SetLayer((uidhallucination, uidvisibility), stat.Layer, false); - _visibilitySystem.RefreshVisibility(uidhallucination, visibilityComponent: uidvisibility); - - } - - var diseaseQuery = EntityQueryEnumerator(); - while (diseaseQuery.MoveNext(out var uid, out var stat, out var xform)) - { - if (_timing.CurTime >= stat.EndTime) - { - RemCompDeferred(uid); - continue; - } - - if (_timing.CurTime < stat.NextSecond) - continue; - - var rate = stat.SpawnRate; - stat.NextSecond = _timing.CurTime + TimeSpan.FromSeconds(rate); - - if (stat.CurChance < stat.MaxChance && stat.CurChance + stat.IncreaseChance <= 1) - stat.CurChance = stat.CurChance + stat.IncreaseChance; - - if (!_random.Prob(stat.CurChance)) - continue; - - stat.SpawnedCount = 0; - - var range = stat.Range * 4; - - foreach (var (ent, comp) in _lookup.GetEntitiesInRange(xform.MapPosition, range)) - { - var newCoords = Transform(ent).MapPosition.Offset(_random.NextVector2(stat.Range)); - - if (stat.SpawnedCount >= stat.MaxSpawns) - continue; - - stat.SpawnedCount = stat.SpawnedCount += 1; - - var hallucination = Spawn(stat.Spawns[_random.Next(0, stat.Spawns.Count - 1)], newCoords); - EnsureComp(hallucination, out var visibility); - _visibilitySystem.SetLayer((hallucination, visibility), stat.Layer, false); - _visibilitySystem.RefreshVisibility(hallucination, visibilityComponent: visibility); - } - - var uidnewCoords = Transform(uid).MapPosition.Offset(_random.NextVector2(stat.Range)); - - if (stat.SpawnedCount >= stat.MaxSpawns) - continue; - - stat.SpawnedCount = stat.SpawnedCount += 1; - - var uidhallucination = Spawn(stat.Spawns[_random.Next(0, stat.Spawns.Count - 1)], uidnewCoords); - EnsureComp(uidhallucination, out var uidvisibility); - _visibilitySystem.SetLayer((uidhallucination, uidvisibility), stat.Layer, false); - _visibilitySystem.RefreshVisibility(uidhallucination, visibilityComponent: uidvisibility); - } - } -} diff --git a/Content.Server/ADT/Hallucinations/Systems/ReduceHallucinationsEntityEffectSystem.cs b/Content.Server/ADT/Hallucinations/Systems/ReduceHallucinationsEntityEffectSystem.cs new file mode 100644 index 00000000000..ffb132d84c8 --- /dev/null +++ b/Content.Server/ADT/Hallucinations/Systems/ReduceHallucinationsEntityEffectSystem.cs @@ -0,0 +1,15 @@ +using Content.Server.ADT.Hallucinations.Components; +using Content.Shared.ADT.Hallucinations.EntityEffects; +using Content.Shared.EntityEffects; + +namespace Content.Server.ADT.Hallucinations.Systems; + +public sealed partial class ReduceHallucinationsEntityEffectSystem : EntityEffectSystem +{ + [Dependency] private SchizophreniaSystem _schiz = default!; + + protected override void Effect(Entity entity, ref EntityEffectEvent args) + { + _schiz.AdjustAllHallucinations(entity.Owner, -args.Effect.Time * args.Scale); + } +} diff --git a/Content.Server/ADT/Hallucinations/Systems/SchizophreniaSystem.HallucinationTypes.cs b/Content.Server/ADT/Hallucinations/Systems/SchizophreniaSystem.HallucinationTypes.cs new file mode 100644 index 00000000000..f19ccbe38b5 --- /dev/null +++ b/Content.Server/ADT/Hallucinations/Systems/SchizophreniaSystem.HallucinationTypes.cs @@ -0,0 +1,99 @@ +using System.Linq; +using System.Numerics; +using Content.Server.ADT.Hallucinations.Types; +using Content.Shared.ADT.Hallucinations.Events; +using Content.Shared.Maps; +using Content.Shared.Whitelist; +using Robust.Server.GameObjects; +using Robust.Shared.Map; +using Robust.Shared.Map.Components; +using Robust.Shared.Random; + +namespace Content.Server.ADT.Hallucinations.Systems; + +public sealed partial class SchizophreniaSystem +{ + [Dependency] private TransformSystem _xform = default!; + [Dependency] private MapSystem _map = default!; + [Dependency] private EntityLookupSystem _lookup = default!; + [Dependency] private EntityWhitelistSystem _whitelist = default!; + + private void Perform(EntityUid uid, BaseHallucinationsType type) + { + switch (type) + { + case MobHallucinations mob: + PerformMob(uid, mob); + break; + case AppearanceHallucinations appearance: + PerformAppearance(uid, appearance); + break; + default: + break; + } + } + + private void PerformMob(EntityUid uid, MobHallucinations mob) + { + var xform = Transform(uid); + + if (!TryComp(xform.GridUid, out var mapGrid)) + return; + + var worldPos = _xform.GetMapCoordinates(uid).Position; + + if (!TryGetValidTiles((xform.GridUid.Value, mapGrid), worldPos, mob, out var tiles)) + return; + + var count = Math.Min(mob.SpawnCount.Next(_random), tiles.Count); + + for (var i = 0; i < count; i++) + { + var tile = _random.Pick(tiles); + + var ent = Spawn(_random.Pick(mob.Entities), new EntityCoordinates(tile.GridUid, tile.GridIndices + mapGrid.TileSizeHalfVector)); + AddAsHallucination(uid, ent); + + tiles.Remove(tile); + } + } + + private bool TryGetValidTiles(Entity grid, Vector2 source, MobHallucinations mob, out List tiles) + { + var exceptTiles = _map.GetTilesIntersecting(grid.Owner, grid.Comp, new Circle(source, mob.Range.Min)); + tiles = _map.GetTilesIntersecting(grid.Owner, grid.Comp, new Circle(source, mob.Range.Max)).Except(exceptTiles).ToList(); + + if (tiles.Count <= 0) + return false; + + for (var i = tiles.Count() - 1; i >= 0; i--) + { + var item = tiles[i]; + var ents = _lookup.GetEntitiesInTile(item); + + if (ents.Count <= 0 && mob.Whitelist != null) + { + tiles.RemoveAt(i); + break; + } + + foreach (var ent in ents) + { + if (!_whitelist.IsWhitelistPassOrNull(mob.Whitelist, ent) || + _whitelist.IsWhitelistPass(mob.Blacklist, ent)) + { + tiles.RemoveAt(i); + break; + } + } + } + + return tiles.Count > 0; + } + + private void PerformAppearance(EntityUid uid, AppearanceHallucinations appearance) + { + var selected = _random.Pick(appearance.Appearances); + RaiseNetworkEvent(new SetHallucinationAppearanceMessage(selected), uid); + } +} diff --git a/Content.Server/ADT/Hallucinations/Systems/SchizophreniaSystem.Hallucinations.cs b/Content.Server/ADT/Hallucinations/Systems/SchizophreniaSystem.Hallucinations.cs new file mode 100644 index 00000000000..cf194bd68f7 --- /dev/null +++ b/Content.Server/ADT/Hallucinations/Systems/SchizophreniaSystem.Hallucinations.cs @@ -0,0 +1,153 @@ +using Content.Server.ADT.Pointing; +using Content.Server.Chat.Systems; +using Content.Shared.ADT.Actions; +using Content.Shared.Interaction.Events; +using Content.Shared.Movement.Systems; +using Content.Shared.StepTrigger.Components; +using Robust.Shared.Physics.Events; +using Robust.Shared.Player; +using Content.Shared.ADT.Chat; +using Content.Shared.ADT.Hallucinations.Components; + +namespace Content.Server.ADT.Hallucinations.Systems; + +public sealed partial class SchizophreniaSystem +{ + private void InitializeHallucinations() + { + SubscribeLocalEvent(OnPlayerAttached); + SubscribeLocalEvent(OnPlayerDetached); + + SubscribeLocalEvent(OnActionAdded); + + SubscribeLocalEvent(OnHallucinationInit); + SubscribeLocalEvent(OnHallucinationShutdown); + + SubscribeLocalEvent(OnBeforeChatMessage); + SubscribeLocalEvent(OverrideEmoteSound); + SubscribeLocalEvent(OnSetupPointer); + + SubscribeLocalEvent(OnMobCollision); + SubscribeLocalEvent(OnMobCollisionTarget); + SubscribeLocalEvent(OnPreventCollision); + SubscribeLocalEvent(OnInteractionAttempt); + } + + #region Pvs overrides + private void OnPlayerAttached(Entity ent, ref PlayerAttachedEvent args) + { + if (!TryComp(ent.Comp.Ent, out var schizophrenia)) + return; + + foreach (var item in schizophrenia.Hallucinations) + _pvsOverride.AddForceSend(item, args.Player); + } + + private void OnPlayerDetached(Entity ent, ref PlayerDetachedEvent args) + { + if (!TryComp(ent.Comp.Ent, out var schizophrenia)) + return; + + foreach (var item in schizophrenia.Hallucinations) + _pvsOverride.RemoveForceSend(item, args.Player); + } + private void OnActionAdded(Entity ent, ref ActionAddedDirectEvent args) + { + AddAsHallucination(ent.Comp.Ent, args.Action); + + if (_player.TryGetSessionByEntity(ent.Owner, out var ourSession)) + _pvsOverride.AddForceSend(args.Action, ourSession); + } + private void OnHallucinationInit(Entity ent, ref MapInitEvent args) + { + foreach (var action in _actions.GetActions(ent.Owner)) + { + AddAsHallucination(ent.Comp.Ent, action); + + if (_player.TryGetSessionByEntity(ent.Owner, out var ourSession)) + _pvsOverride.AddForceSend(action, ourSession); + } + } + + private void OnHallucinationShutdown(Entity ent, ref ComponentShutdown args) + { + if (!TryComp(ent.Comp.Ent, out var schizophrenia)) + return; + + schizophrenia.Hallucinations.Remove(ent.Owner); + + if (_player.TryGetSessionByEntity(ent.Comp.Ent, out var session)) + _pvsOverride.RemoveForceSend(ent.Owner, session); + + // For sounds that are deleted really fast but need to be heard by hallucinations + foreach (var item in schizophrenia.Hallucinations) + { + if (_player.TryGetSessionByEntity(item, out var hallucinationSession)) + _pvsOverride.RemoveForceSend(ent.Owner, hallucinationSession); + } + + if (schizophrenia.Hallucinations.Count <= 0) + RemComp(ent.Comp.Ent, schizophrenia); + } + #endregion + + private void OnBeforeChatMessage(Entity ent, ref ExpandICChatRecipientsEvent args) + { + List toRemove = new(); + + foreach (var recipient in args.Recipients) + { + if (_schizQuery.TryGetComponent(recipient.Key.AttachedEntity, out var schiz) && schiz.Idx == ent.Comp.Idx) + continue; + + if (_hallucinationQuery.TryGetComponent(recipient.Key.AttachedEntity, out var hallucination) && hallucination.Idx == ent.Comp.Idx) + continue; + + toRemove.Add(recipient.Key); + } + + foreach (var item in toRemove) + args.Recipients.Remove(item); + } + + private void OverrideEmoteSound(Entity ent, ref OverrideEmoteSoundEvent args) + { + var filter = Filter.Entities(ent.Owner, ent.Comp.Ent); + var sound = _audio.PlayEntity(args.Sound, filter, ent.Owner, false); + + if (!sound.HasValue) + return; + + foreach (var recipient in filter.Recipients) + { + _pvsOverride.AddForceSend(sound.Value.Entity, recipient); + + AddAsHallucination(ent.Comp.Ent, sound.Value.Entity, false); // to avoid error spam + } + } + + private void OnSetupPointer(Entity ent, ref SetupPointingArrowEvent args) + { + if (_player.TryGetSessionByEntity(ent.Owner, out var hallucinationSession)) + _pvsOverride.AddForceSend(args.Arrow, hallucinationSession); + + AddAsHallucination(ent.Comp.Ent, args.Arrow, false); + } + + #region Everything interaction-related + private void OnMobCollision(Entity ent, ref AttemptMobCollideEvent args) + => args.Cancelled = true; + + private void OnMobCollisionTarget(Entity ent, ref AttemptMobTargetCollideEvent args) + => args.Cancelled = true; + + private void OnPreventCollision(Entity ent, ref PreventCollideEvent args) + { + if (HasComp(args.OtherEntity)) + args.Cancelled = true; + } + + private void OnInteractionAttempt(Entity ent, ref InteractionAttemptEvent args) + => args.Cancelled = true; + #endregion +} diff --git a/Content.Server/ADT/Hallucinations/Systems/SchizophreniaSystem.Shizophrenic.cs b/Content.Server/ADT/Hallucinations/Systems/SchizophreniaSystem.Shizophrenic.cs new file mode 100644 index 00000000000..e106c22fe4a --- /dev/null +++ b/Content.Server/ADT/Hallucinations/Systems/SchizophreniaSystem.Shizophrenic.cs @@ -0,0 +1,229 @@ +using System.Linq; +using Content.Server.ADT.Chat; +using Content.Server.ADT.Hallucinations.Components; +using Content.Server.ADT.Hallucinations.Events; +using Content.Shared.ADT.Hallucinations.Components; +using Content.Shared.Damage.Systems; +using Content.Shared.EntityEffects.Effects.StatusEffects; +using Content.Shared.Eye; +using Content.Shared.Mobs.Components; +using Robust.Shared.Player; +using Robust.Shared.Prototypes; + +namespace Content.Server.ADT.Hallucinations.Systems; + +public sealed partial class SchizophreniaSystem +{ + private void InitializeShizophrenic() + { + SubscribeLocalEvent(OnPlayerAttached); + SubscribeLocalEvent(OnPlayerDetached); + + SubscribeLocalEvent(OnAddMobs); + SubscribeLocalEvent(OnRemove); + + SubscribeLocalEvent(OnRemoveMobsStartup); + SubscribeLocalEvent(OnCanHearVoice); + SubscribeLocalEvent(OnCanReceiveMessage); + SubscribeLocalEvent(OnDamage); + } + + private void OnPlayerAttached(Entity ent, ref PlayerAttachedEvent args) + { + foreach (var item in ent.Comp.Hallucinations) + _pvsOverride.AddForceSend(item, args.Player); + } + + private void OnPlayerDetached(Entity ent, ref PlayerDetachedEvent args) + { + foreach (var item in ent.Comp.Hallucinations) + _pvsOverride.RemoveForceSend(item, args.Player); + } + + private void OnAddMobs(Entity ent, ref AddHallucinationsEvent args) + { + AddOrAdjustHallucinations(ent.Owner, args.Id, args.Duration, args.OverwriteTimer ? StatusEffectMetabolismType.Set : StatusEffectMetabolismType.Add); + } + + private void OnRemove(Entity ent, ref RemoveHallucinationsEvent args) + { + AdjustAllHallucinations(ent.Owner, args.Time); + } + + private void OnRemoveMobsStartup(Entity ent, ref ComponentStartup args) + { + if (ent.Comp.StartingMessage != "") + _popup.PopupEntity(Loc.GetString(ent.Comp.StartingMessage), ent.Owner, ent.Owner, Shared.Popups.PopupType.MediumCaution); + } + + private void OnCanHearVoice(Entity ent, ref CanHearVoiceEvent args) + { + if (args.Source == ent.Owner) + return; + + if (HasComp(args.Source) && !HasComp(args.Source)) + args.Cancelled = true; + } + + private void OnCanReceiveMessage(Entity ent, ref CanReceiveChatMessageEvent args) + { + if (args.Source == ent.Owner) + return; + + if (HasComp(args.Source) && !HasComp(args.Source)) + args.Cancelled = true; + } + + private void OnDamage(Entity ent, ref DamageDealtEvent args) + { + if (!args.Origin.HasValue) + return; + + if (!args.Damage.AnyPositive()) + return; + + if (string.IsNullOrEmpty(ent.Comp.Reveal)) + return; + + var reveal = Spawn(ent.Comp.Reveal, Transform(args.Origin.Value).Coordinates); + AddAsHallucination(ent.Owner, reveal); + } + + private void AddHallucinations(EntityUid uid, ProtoId pack, float duration, StatusEffectMetabolismType metabolism) + { + if (metabolism == StatusEffectMetabolismType.Remove) + return; + + var comp = EnsureComp(uid); + + // Get and add entry + var packProto = _proto.Index(pack); + var data = packProto.Data; + + HashSet? entries = new(); + if (data != null) + { + entries = new(); + foreach (var type in data) + { + entries.Add(new HallucinatingComponent.HallucinationCompound(type, _timing.CurTime)); + } + } + + comp.Hallucinations.Add(pack, entries); + + EntityManager.AddComponents(uid, packProto.Components); + + if (!string.IsNullOrEmpty(packProto.StartingMessage)) + _popup.PopupEntity(Loc.GetString(packProto.StartingMessage), uid, uid, packProto.MessageType); + + // If not infinite, add timer + if (duration > 0) + comp.Removes.Add(pack, _timing.CurTime + TimeSpan.FromSeconds(duration)); + } + + private void AdjustHallucinations(EntityUid uid, ProtoId pack, float duration, StatusEffectMetabolismType metabolism) + { + var comp = EnsureComp(uid); + + switch (metabolism) + { + case StatusEffectMetabolismType.Update: + if (comp.Removes.TryGetValue(pack, out _)) + comp.Removes[pack] = _timing.CurTime + TimeSpan.FromSeconds(duration); + else + comp.Removes.Add(pack, _timing.CurTime + TimeSpan.FromSeconds(duration)); + + break; + case StatusEffectMetabolismType.Add: + if (comp.Removes.TryGetValue(pack, out _)) + comp.Removes[pack] += TimeSpan.FromSeconds(duration); + else + comp.Removes.Add(pack, _timing.CurTime + TimeSpan.FromSeconds(duration)); + break; + case StatusEffectMetabolismType.Set: + if (comp.Removes.TryGetValue(pack, out _)) + comp.Removes[pack] = _timing.CurTime + TimeSpan.FromSeconds(duration); + else + comp.Removes.Add(pack, _timing.CurTime + TimeSpan.FromSeconds(duration)); + + break; + default: + break; + } + } + + #region Public API + /// + /// Makes entity a hallucination for another one + /// + /// Hallucinating entity + /// Hallucination + /// Whether dirty comps or not. Used for sounds and pointers that does not have to be networked + public void AddAsHallucination(EntityUid uid, EntityUid toAdd, bool dirty = true) + { + var comp = EnsureComp(uid); + + // Set invisible (kinda) layer + _visibility.SetLayer(toAdd, (ushort) VisibilityFlags.Hallucination, true); + + // Add pvs override if can + if (_player.TryGetSessionByEntity(uid, out var session)) + _pvsOverride.AddForceSend(toAdd, session); + + comp.Hallucinations.Add(toAdd); + + // Just needed, else game crashes + var hallucination = new HallucinationComponent() + { + Ent = uid + }; + AddComp(toAdd, hallucination); + + // We dont need to change index if entity is already hallucinating + if (comp.Idx <= 0) + { + comp.Idx = _nextIdx; + _nextIdx++; + } + + hallucination.Idx = comp.Idx; + + // Dirty if needed + if (dirty) + { + Dirty(uid, comp); + Dirty(toAdd, hallucination); + } + } + + /// + /// Applies a certain hallucination pack to the entity + /// + /// Target entity + /// Hallucinations pack + /// Duration of the effect or removed time + /// Add/Set/Remove + public void AddOrAdjustHallucinations(EntityUid uid, ProtoId pack, float duration, StatusEffectMetabolismType type) + { + var comp = EnsureComp(uid); + + if (comp.Hallucinations.Keys.Contains(pack)) + AdjustHallucinations(uid, pack, duration, type); + else + AddHallucinations(uid, pack, duration, type); + } + + public void AdjustAllHallucinations(EntityUid uid, float duration) + { + var comp = EnsureComp(uid); + + for (var i = 0; i < comp.Removes.Count; i++) + { + var item = comp.Removes.ElementAt(i); + + comp.Removes[item.Key] = item.Value + TimeSpan.FromSeconds(duration); + } + } + #endregion +} diff --git a/Content.Server/ADT/Hallucinations/Systems/SchizophreniaSystem.cs b/Content.Server/ADT/Hallucinations/Systems/SchizophreniaSystem.cs new file mode 100644 index 00000000000..3c752294604 --- /dev/null +++ b/Content.Server/ADT/Hallucinations/Systems/SchizophreniaSystem.cs @@ -0,0 +1,148 @@ +using System.Linq; +using Content.Server.Actions; +using Content.Server.ADT.Hallucinations.Components; +using Content.Server.Popups; +using Content.Shared.ADT.Hallucinations.Components; +using Robust.Server.Audio; +using Robust.Server.GameObjects; +using Robust.Server.GameStates; +using Robust.Server.Player; +using Robust.Shared.Prototypes; +using Robust.Shared.Random; +using Robust.Shared.Timing; + +namespace Content.Server.ADT.Hallucinations.Systems; + +public sealed partial class SchizophreniaSystem : EntitySystem +{ + [Dependency] private IPlayerManager _player = default!; + [Dependency] private AudioSystem _audio = default!; + [Dependency] private VisibilitySystem _visibility = default!; + [Dependency] private PvsOverrideSystem _pvsOverride = default!; + [Dependency] private ActionsSystem _actions = default!; + [Dependency] private IGameTiming _timing = default!; + [Dependency] private IRobustRandom _random = default!; + [Dependency] private IPrototypeManager _proto = default!; + [Dependency] private PopupSystem _popup = default!; + + [Dependency] private EntityQuery _schizQuery; + [Dependency] private EntityQuery _hallucinationQuery; + + private int _nextIdx = 1; + + public override void Initialize() + { + base.Initialize(); + UpdatesBefore.Add(typeof(ActionsSystem)); + + InitializeShizophrenic(); + InitializeHallucinations(); + } + + public override void Update(float frameTime) + { + base.Update(frameTime); + + var query = EntityQueryEnumerator(); + while (query.MoveNext(out var uid, out var comp)) + { + if (comp.NextUpdate > _timing.CurTime) + continue; + + comp.NextUpdate = _timing.CurTime + TimeSpan.FromSeconds(0.5f); + + UpdateMusic(uid, comp); + + if (!UpdateRemoving(uid, comp)) + continue; + + UpdateEffects(uid, comp); + } + } + + private bool UpdateRemoving(EntityUid uid, HallucinatingComponent comp) + { + // Handle remove timers + foreach (var item in comp.Removes.ToDictionary()) + { + if (item.Value <= _timing.CurTime) + { + comp.Hallucinations.Remove(item.Key); + comp.Removes.Remove(item.Key); + EntityManager.RemoveComponents(uid, _proto.Index(item.Key).Components); + + if (!TryComp(uid, out var musicComp) || + !musicComp.Music.ContainsKey(item.Key)) + continue; + + musicComp.Music.Remove(item.Key); + + if (musicComp.Music.Count > 0) + Dirty(uid, musicComp); + else + RemComp(uid, musicComp); + } + } + + // If there is no hallucinations, remove component + if (comp.Hallucinations.Count <= 0) + { + RemCompDeferred(uid, comp); + return false; + } + + return true; + } + + private void UpdateEffects(EntityUid uid, HallucinatingComponent comp) + { + // Hallucinate + foreach (var (_, hallucinations) in comp.Hallucinations) + { + if (hallucinations.Count <= 0) + continue; + + foreach (var compound in hallucinations) + { + if (compound.PerformTime > _timing.CurTime) + continue; + + Perform(uid, compound.Type); + compound.PerformTime = _timing.CurTime + TimeSpan.FromSeconds(compound.Type.Delay.Next(_random)); + } + } + } + + private void UpdateMusic(EntityUid uid, HallucinatingComponent comp) + { + // Hallucinate + foreach (var (id, _) in comp.Hallucinations) + { + var proto = _proto.Index(id); + if (proto.Music == null) + continue; + + if (comp.Removes.TryGetValue(id, out var removeTime) && + (removeTime - _timing.CurTime).TotalSeconds < proto.MusicDurationThreshold) + { + if (!TryComp(uid, out var musicComp) || + !musicComp.Music.ContainsKey(id)) + continue; + + musicComp.Music.Remove(id); + + if (musicComp.Music.Count > 0) + Dirty(uid, musicComp); + else + RemComp(uid, musicComp); + } + else if (!TryComp(uid, out var musicComp) || + !musicComp.Music.ContainsKey(id)) + { + musicComp = EnsureComp(uid); + musicComp.Music.Add(id, new(proto.Music, proto.MusicPlayInterval)); + Dirty(uid, musicComp); + } + } + } +} diff --git a/Content.Server/ADT/Hallucinations/Types/AppearanceHallucinations.cs b/Content.Server/ADT/Hallucinations/Types/AppearanceHallucinations.cs new file mode 100644 index 00000000000..624b3058a6c --- /dev/null +++ b/Content.Server/ADT/Hallucinations/Types/AppearanceHallucinations.cs @@ -0,0 +1,9 @@ +using Content.Shared.ADT.Hallucinations.Events; + +namespace Content.Server.ADT.Hallucinations.Types; + +public sealed partial class AppearanceHallucinations : BaseHallucinationsType +{ + [DataField] + public List Appearances = new(); +} diff --git a/Content.Server/ADT/Hallucinations/Types/BaseHallucinationsType.cs b/Content.Server/ADT/Hallucinations/Types/BaseHallucinationsType.cs new file mode 100644 index 00000000000..06d53020f77 --- /dev/null +++ b/Content.Server/ADT/Hallucinations/Types/BaseHallucinationsType.cs @@ -0,0 +1,10 @@ +using Content.Shared.Destructible.Thresholds; + +namespace Content.Server.ADT.Hallucinations.Types; + +[ImplicitDataDefinitionForInheritors] +public abstract partial class BaseHallucinationsType +{ + [DataField] + public MinMax Delay = new(); +} diff --git a/Content.Server/ADT/Hallucinations/Types/EntityHallucinations.cs b/Content.Server/ADT/Hallucinations/Types/EntityHallucinations.cs new file mode 100644 index 00000000000..2d8f0c83cce --- /dev/null +++ b/Content.Server/ADT/Hallucinations/Types/EntityHallucinations.cs @@ -0,0 +1,26 @@ +using Content.Shared.Destructible.Thresholds; +using Content.Shared.Whitelist; +using Robust.Shared.Prototypes; + +namespace Content.Server.ADT.Hallucinations.Types; + +public sealed partial class MobHallucinations : BaseHallucinationsType +{ + [DataField] + public List Entities = new(); + + [DataField] + public MinMax Range = new(); + + [DataField] + public MinMax SpawnCount = new(); + + [DataField] + public EntityWhitelist? Whitelist; + + [DataField] + public EntityWhitelist? Blacklist = new() + { + Tags = new(){ "Wall" } + }; +} diff --git a/Content.Server/ADT/HungerEffect/EntityEffects/HungerEffectSystem.cs b/Content.Server/ADT/HungerEffect/EntityEffects/HungerEffectSystem.cs index 3cf0fd4b53e..04c52c50e73 100644 --- a/Content.Server/ADT/HungerEffect/EntityEffects/HungerEffectSystem.cs +++ b/Content.Server/ADT/HungerEffect/EntityEffects/HungerEffectSystem.cs @@ -3,7 +3,6 @@ using Content.Shared.StatusEffect; using Robust.Shared.Timing; using Content.Shared.Database; -using Content.Shared.ADT.Hallucinations; using Robust.Server.GameObjects; using Robust.Shared.Prototypes; using Robust.Shared.Random; diff --git a/Content.Server/ADT/Pointing/SetupPointingArrowEvent.cs b/Content.Server/ADT/Pointing/SetupPointingArrowEvent.cs new file mode 100644 index 00000000000..f5fb40b85b2 --- /dev/null +++ b/Content.Server/ADT/Pointing/SetupPointingArrowEvent.cs @@ -0,0 +1,8 @@ +namespace Content.Server.ADT.Pointing; + +/// +/// Raised at user when they are pointing at something +/// +/// +[ByRefEvent] +public record struct SetupPointingArrowEvent(EntityUid Arrow); diff --git a/Content.Server/ADT/Screamer/ScreamerCommand.cs b/Content.Server/ADT/Screamer/ScreamerCommand.cs new file mode 100644 index 00000000000..af0265e43b4 --- /dev/null +++ b/Content.Server/ADT/Screamer/ScreamerCommand.cs @@ -0,0 +1,172 @@ +using System.Linq; +using System.Numerics; +using Content.Server.ADT.Screamer; +using Content.Server.ADT.Hallucinations; +using Content.Shared.Administration; +using Content.Shared.ADT.Screamer; +using Content.Shared.EntityEffects.Effects; +using Content.Shared.Mind; +using Content.Shared.Mind.Components; +using Robust.Server.Player; +using Robust.Shared.Audio; +using Robust.Shared.Console; +using Robust.Shared.ContentPack; +using Robust.Shared.Prototypes; + +namespace Content.Server.Administration.Commands; + +[AdminCommand(AdminFlags.Admin)] +public sealed partial class ScreamerCommand : IConsoleCommand +{ + [Dependency] private IEntityManager _entManager = default!; + [Dependency] private IPrototypeManager _proto = default!; + [Dependency] private IResourceManager _res = default!; + + public string Command => "screamer"; + + public string Description => Loc.GetString("screamer-command-description"); + + public string Help => Loc.GetString("screamer-command-help-text", ("command", Command)); + + public void Execute(IConsoleShell shell, string argStr, string[] args) + { + if (args.Length < 3) + { + shell.WriteLine(Loc.GetString("shell-wrong-arguments-number")); + return; + } + + if (!EntityUid.TryParse(args[0], out var uid)) + { + shell.WriteLine(Loc.GetString("shell-entity-uid-must-be-number")); + return; + } + + if (!_proto.HasIndex(args[1])) + { + shell.WriteLine(Loc.GetString("shell-invalid-entity-id")); + return; + } + + if (!int.TryParse(args[2], out var duration)) + { + shell.WriteError(Loc.GetString("cmd-parse-failure-integer", ("arg", args[2]))); + return; + } + + string? sound = null; + var alpha = 0.5f; + var fadeIn = false; + var fadeOut = false; + var offset = Vector2.Zero; + + if (args.Length > 3) + { + if (args[3] == "null") + sound = null; + else + sound = args[3]; + } + + + if (args.Length > 4) + { + if (!float.TryParse(args[4], out alpha)) + { + shell.WriteError(Loc.GetString("cmd-parse-failure-float", ("arg", args[4]))); + return; + } + else if (alpha > 1 || alpha < 0) + { + alpha = 0.5f; + } + } + + if (args.Length > 5) + { + if (!bool.TryParse(args[5], out fadeIn)) + { + shell.WriteError(Loc.GetString("cmd-parse-failure-bool", ("arg", args[5]))); + return; + } + } + + if (args.Length > 6) + { + if (!bool.TryParse(args[6], out fadeOut)) + { + shell.WriteError(Loc.GetString("cmd-parse-failure-bool", ("arg", args[6]))); + return; + } + } + + if (args.Length > 7) + { + if (!float.TryParse(args[7], out var x)) + { + shell.WriteError(Loc.GetString("cmd-parse-failure-float", ("arg", args[7]))); + return; + } + + offset.X = x; + } + + if (args.Length > 8) + { + if (!float.TryParse(args[8], out var y)) + { + shell.WriteError(Loc.GetString("cmd-parse-failure-float", ("arg", args[8]))); + return; + } + + offset.Y = y; + } + + _entManager.System().DoScreamer(uid, args[1], sound, offset, alpha, duration, fadeIn, fadeOut); + shell.WriteLine(Loc.GetString("screamer-command-success")); + } + + public CompletionResult GetCompletion(IConsoleShell shell, string[] args) + { + if (args.Length == 1) + { + var opts = _entManager.AllEntities().Select(ent => new CompletionOption(ent.Owner.ToString(), _entManager.ToPrettyString(ent))).ToList(); + return CompletionResult.FromHintOptions(opts, ""); + } + + if (args.Length == 2) + { + var opts = _proto.EnumeratePrototypes().Where(x => x.Categories.Count > 0 && x.Categories.First().ID == "Screamers").Select(proto => proto.ID).ToList(); + return CompletionResult.FromHintOptions(opts, ""); + } + + if (args.Length == 3) + return CompletionResult.FromHint(""); + + if (args.Length == 4) + { + var hint = Loc.GetString("play-global-sound-command-arg-path"); + + var options = CompletionHelper.AudioFilePath(args[4], _proto, _res); + + return CompletionResult.FromHintOptions(options, hint); + } + + if (args.Length == 5) + return CompletionResult.FromHintOptions(new List() { "0.5" }, ""); + + if (args.Length == 6) + return CompletionResult.FromHintOptions(new List() { "true", "false" }, ""); + + if (args.Length == 7) + return CompletionResult.FromHintOptions(new List() { "true", "false" }, ""); + + if (args.Length == 8) + return CompletionResult.FromHint(""); + + if (args.Length == 9) + return CompletionResult.FromHint(""); + + return CompletionResult.Empty; + } +} diff --git a/Content.Server/ADT/Screamer/ScreamerSystem.cs b/Content.Server/ADT/Screamer/ScreamerSystem.cs new file mode 100644 index 00000000000..07abcbad0e3 --- /dev/null +++ b/Content.Server/ADT/Screamer/ScreamerSystem.cs @@ -0,0 +1,19 @@ +using System.Numerics; +using Content.Shared.ADT.Screamer; +using Robust.Server.Player; + +namespace Content.Server.ADT.Screamer; + +public sealed class ScreamerSystem : EntitySystem +{ + [Dependency] private readonly IPlayerManager _player = default!; + + public void DoScreamer(EntityUid uid, string protoId, string? sound, Vector2 offset, float alpha, float duration, bool fadeIn, bool fadeOut) + { + if (!_player.TryGetSessionByEntity(uid, out var session)) + return; + + var msg = new DoScreamerMessage(protoId, sound, offset, alpha, duration, fadeIn, fadeOut); + RaiseNetworkEvent(msg, session.Channel); + } +} diff --git a/Content.Server/ADT/Supermatter/Systems/SupermatterEffectsSystem.cs b/Content.Server/ADT/Supermatter/Systems/SupermatterEffectsSystem.cs index b84e3546801..40cbe035517 100644 --- a/Content.Server/ADT/Supermatter/Systems/SupermatterEffectsSystem.cs +++ b/Content.Server/ADT/Supermatter/Systems/SupermatterEffectsSystem.cs @@ -67,7 +67,7 @@ public void HandleVision(EntityUid uid, SupermatterComponent sm) var hallucinationKey = "ADTHallucination"; var hallucinationProto = "SupermatterPack"; - _hallucinations.StartHallucinations(mob, hallucinationKey, TimeSpan.FromSeconds(100), true, hallucinationProto); + //_hallucinations.StartHallucinations(mob, hallucinationKey, TimeSpan.FromSeconds(100), true, hallucinationProto); } } diff --git a/Content.Server/ADT/Supermatter/Systems/SupermatterSystem.cs b/Content.Server/ADT/Supermatter/Systems/SupermatterSystem.cs index a0e3a025e85..d99eab4c2a3 100644 --- a/Content.Server/ADT/Supermatter/Systems/SupermatterSystem.cs +++ b/Content.Server/ADT/Supermatter/Systems/SupermatterSystem.cs @@ -1,6 +1,5 @@ using Content.Server.Administration.Logs; using Content.Server.Radiation.Systems; -using Content.Server.ADT.Hallucinations; using Content.Server.AlertLevel; using Content.Server.Atmos.EntitySystems; using Content.Server.Atmos.Piping.Components; @@ -80,7 +79,6 @@ public sealed partial class SupermatterSystem : EntitySystem [Dependency] private readonly StationSystem _station = default!; [Dependency] private readonly SharedTransformSystem _transform = default!; [Dependency] private readonly RoundEndSystem _roundEnd = default!; - [Dependency] private readonly HallucinationsSystem _hallucinations = default!; [Dependency] private readonly TagSystem _tag = default!; public override void Initialize() diff --git a/Content.Server/Chat/Systems/ChatSystem.cs b/Content.Server/Chat/Systems/ChatSystem.cs index 80a16adddd0..b43504eb7d1 100644 --- a/Content.Server/Chat/Systems/ChatSystem.cs +++ b/Content.Server/Chat/Systems/ChatSystem.cs @@ -990,13 +990,18 @@ public Dictionary GetRecipients(EntityUid s var observer = ghostHearing.HasComponent(playerEntity); - // ADT Resomi start + // ADT-Tweak-start var range = voiceGetRange; if (TryComp(playerEntity, out var modifier) && modifier.Modifiers.ContainsKey(ChatModifierType.Say)) { range = modifier.Modifiers[ChatModifierType.Say]; } - // ADT Resomi end + + var ev = new CanReceiveChatMessageEvent(source, false); + RaiseLocalEvent(playerEntity, ref ev); + if (ev.Cancelled) + continue; + // ADT-Tweak-end // even if they are a ghost hearer, in some situations we still need the range if (sourceCoords.TryDistance(EntityManager, transformEntity.Coordinates, out var distance) && distance < range) // ADT Resomi tweaked diff --git a/Content.Server/Corvax/TTS/TTSSystem.cs b/Content.Server/Corvax/TTS/TTSSystem.cs index 8bac6f7d779..0dbda4a90f9 100644 --- a/Content.Server/Corvax/TTS/TTSSystem.cs +++ b/Content.Server/Corvax/TTS/TTSSystem.cs @@ -13,6 +13,8 @@ using Content.Shared.ADT.Language; using Content.Server.Examine; using Content.Shared.Ghost; +using Content.Server.ADT.Chat; +using Content.Server.DeviceLinking.Systems; namespace Content.Server.Corvax.TTS; @@ -125,7 +127,27 @@ private async void HandleSay(EntityUid uid, string message, string speaker, Lang // ADT Languages start var languageSoundData = await GenerateTTS(_language.ObfuscateMessage(uid, message, gen.Replacement, gen.ObfuscateSyllables, gen.ReplaceEntireMessage), speaker); if (languageSoundData is null) return; - // ADT Languages end + + var pvs = Filter.Pvs(uid); + + foreach (var item in pvs.Recipients) + { + if (!item.AttachedEntity.HasValue) + { + pvs.RemovePlayer(item); + continue; + } + + var ev = new CanHearVoiceEvent(uid, false); + RaiseLocalEvent(item.AttachedEntity.Value, ref ev); + + if (ev.Cancelled) + { + pvs.RemovePlayer(item); + continue; + } + } + // ADT-Tweak-end // ADT-Tweak start var ttsEvent = new PlayTTSEvent(soundData, languageSoundData, language, GetNetEntity(uid)); @@ -178,6 +200,14 @@ private async void HandleWhisper(EntityUid uid, string message, string obfMessag if (distance > ChatSystem.VoiceRange * ChatSystem.VoiceRange) continue; + // ADT-Tweak-start + var ev = new CanHearVoiceEvent(uid, true); + RaiseLocalEvent(session.AttachedEntity.Value, ref ev); + + if (ev.Cancelled) + continue; + // ADT-Tweak-end + // ADT-Tweak start if (!HasComp(session.AttachedEntity.Value) && !_examineSystem.InRangeUnOccluded(session.AttachedEntity.Value, uid, ChatSystem.WhisperMuffledRange)) continue; diff --git a/Content.Server/Pointing/EntitySystems/PointingSystem.cs b/Content.Server/Pointing/EntitySystems/PointingSystem.cs index 4d89c8a126c..52a0a10fc02 100644 --- a/Content.Server/Pointing/EntitySystems/PointingSystem.cs +++ b/Content.Server/Pointing/EntitySystems/PointingSystem.cs @@ -1,6 +1,8 @@ using System.Linq; using Content.Server.Administration.Logs; +using Content.Server.ADT.Pointing; using Content.Server.Pointing.Components; +using Content.Shared.ADT.Hallucinations.Components; using Content.Shared.CCVar; using Content.Shared.Database; using Content.Shared.Examine; @@ -162,6 +164,11 @@ public bool TryPoint(ICommonSession? session, EntityCoordinates coordsPointed, E var arrow = Spawn("PointingArrow", coordsPointed); + // ADT-Tweak-start + var setupEv = new SetupPointingArrowEvent(arrow); + RaiseLocalEvent(player, ref setupEv); + // ADT-Tweak-end + if (TryComp(arrow, out var pointing)) { pointing.StartPosition = _transform.ToCoordinates((arrow, Transform(arrow)), _transform.ToMapCoordinates(Transform(player).Coordinates)).Position; @@ -208,6 +215,11 @@ bool ViewerPredicate(ICommonSession playerSession) var playerName = Identity.Entity(player, EntityManager); EntityUid? iconTarget = null; // ADT-Tweak + // ADT-Tweak-start + if (HasComp(pointed)) + pointed = EntityUid.Invalid; + // ADT-Tweak-end + if (Exists(pointed)) { iconTarget = pointed; // ADT-Tweak diff --git a/Content.Server/Revenant/EntitySystems/RevenantSystem.Abilities.cs b/Content.Server/Revenant/EntitySystems/RevenantSystem.Abilities.cs index af408656768..f9bb23ba993 100644 --- a/Content.Server/Revenant/EntitySystems/RevenantSystem.Abilities.cs +++ b/Content.Server/Revenant/EntitySystems/RevenantSystem.Abilities.cs @@ -29,7 +29,6 @@ using Content.Shared.Revenant.Components; using Robust.Shared.Physics.Components; using Robust.Shared.Utility; -using Content.Server.ADT.Hallucinations; using Content.Shared.StatusEffect; using Content.Shared.Eye.Blinding.Components; using Content.Shared.Eye.Blinding.Systems; @@ -59,7 +58,7 @@ public sealed partial class RevenantSystem [Dependency] private readonly MobThresholdSystem _mobThresholdSystem = default!; [Dependency] private readonly GhostSystem _ghost = default!; [Dependency] private readonly TileSystem _tile = default!; - [Dependency] private readonly HallucinationsSystem _hallucinations = default!; + //[Dependency] private readonly HallucinationsSystem _hallucinations = default!; [Dependency] private readonly StatusEffectsSystem _status = default!; [Dependency] private readonly SmokeSystem _smoke = default!; [Dependency] private readonly SharedAudioSystem _audio = default!; @@ -417,7 +416,7 @@ private void OnHysteriaAction(EntityUid uid, RevenantComponent component, Revena // ADT-Tweak end _status.TryAddStatusEffect(ent, BlindnessSystem.BlindingStatusEffect, TimeSpan.FromSeconds(3), true); - _hallucinations.StartHallucinations(ent, "ADTHallucinations", component.HysteriaDuration, true, component.HysteriaProto); + // _hallucinations.StartHallucinations(ent, "ADTHallucinations", component.HysteriaDuration, true, component.HysteriaProto); if (!_mind.TryGetMind(ent, out var mindId, out var mind) || !_player.TryGetSessionById(mind.UserId, out var session)) continue; _audio.PlayGlobal(component.HysteriaSound, Filter.SinglePlayer(session), false); diff --git a/Content.Shared/ADT/Actions/ActionAddedEvent.cs b/Content.Shared/ADT/Actions/ActionAddedEvent.cs new file mode 100644 index 00000000000..dac53974c2d --- /dev/null +++ b/Content.Shared/ADT/Actions/ActionAddedEvent.cs @@ -0,0 +1,4 @@ +namespace Content.Shared.ADT.Actions; + +[ByRefEvent] +public record struct ActionAddedDirectEvent(EntityUid Action); diff --git a/Content.Shared/ADT/Chat/OverrideEmoteSoundEvent.cs b/Content.Shared/ADT/Chat/OverrideEmoteSoundEvent.cs new file mode 100644 index 00000000000..fd5627c22f0 --- /dev/null +++ b/Content.Shared/ADT/Chat/OverrideEmoteSoundEvent.cs @@ -0,0 +1,12 @@ +using Robust.Shared.Audio; + +namespace Content.Shared.ADT.Chat; + +/// +/// Used to override how emote sound plays +/// +[ByRefEvent] +public record struct OverrideEmoteSoundEvent(SoundSpecifier? Sound, AudioParams Params) +{ + public bool Cancelled = false; +}; diff --git a/Content.Shared/ADT/Hallucinations/Components/BoundHallucinationComponent.cs b/Content.Shared/ADT/Hallucinations/Components/BoundHallucinationComponent.cs new file mode 100644 index 00000000000..0bcf5d2411a --- /dev/null +++ b/Content.Shared/ADT/Hallucinations/Components/BoundHallucinationComponent.cs @@ -0,0 +1,8 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.ADT.Hallucinations.Components; + +[RegisterComponent, NetworkedComponent] +public sealed partial class BoundHallucinationComponent : Component +{ +} diff --git a/Content.Shared/ADT/Hallucinations/Components/HallicinationsRemoveMobsComponent.cs b/Content.Shared/ADT/Hallucinations/Components/HallicinationsRemoveMobsComponent.cs new file mode 100644 index 00000000000..34ea72714dd --- /dev/null +++ b/Content.Shared/ADT/Hallucinations/Components/HallicinationsRemoveMobsComponent.cs @@ -0,0 +1,16 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.ADT.Hallucinations.Components; + +/// +/// Component added to hallucinating entity to prevent them from seeing mobs +/// +[RegisterComponent, NetworkedComponent] +public sealed partial class HallucinationsRemoveMobsComponent : Component +{ + [DataField] + public string Reveal = ""; + + [DataField] + public string StartingMessage = ""; +} diff --git a/Content.Shared/ADT/Hallucinations/Components/HallucinationComponent.cs b/Content.Shared/ADT/Hallucinations/Components/HallucinationComponent.cs new file mode 100644 index 00000000000..d05bbef5268 --- /dev/null +++ b/Content.Shared/ADT/Hallucinations/Components/HallucinationComponent.cs @@ -0,0 +1,32 @@ +using Content.Shared.StatusIcon; +using Robust.Shared.GameStates; +using Robust.Shared.Prototypes; + +namespace Content.Shared.ADT.Hallucinations.Components; + +/// +/// Component added to hallucinations to have access to main entity and visualization them on client +/// Hallucinations can't see or hear each other +/// +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +public sealed partial class HallucinationComponent : Component +{ + /// + /// Unique index for identifying hallucinations and their owner + /// + [AutoNetworkedField] + public int Idx = 0; + + /// + /// Hallucinating entity + /// + [ViewVariables(VVAccess.ReadWrite)] + public EntityUid Ent; + + [DataField] + public Color ChatColor = Color.FromHex("#b81500FF"); + + [AutoNetworkedField] + [ViewVariables(VVAccess.ReadWrite)] + public ProtoId FactionIcon = "Hallucination"; +} diff --git a/Content.Shared/ADT/Hallucinations/Components/HallucinationsComponent.cs b/Content.Shared/ADT/Hallucinations/Components/HallucinationsComponent.cs deleted file mode 100644 index 5b8d29917e2..00000000000 --- a/Content.Shared/ADT/Hallucinations/Components/HallucinationsComponent.cs +++ /dev/null @@ -1,49 +0,0 @@ -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom; -using Robust.Shared.Prototypes; -using Robust.Shared.Audio; - -namespace Content.Shared.ADT.Hallucinations; - -[RegisterComponent] -public sealed partial class HallucinationsComponent : Component -{ - [DataField(customTypeSerializer: typeof(TimeOffsetSerializer)), ViewVariables(VVAccess.ReadWrite)] - public TimeSpan NextSecond = TimeSpan.Zero; - - [DataField, ViewVariables(VVAccess.ReadWrite)] - public float Range = 7f; - - [DataField, ViewVariables(VVAccess.ReadWrite)] - public float SpawnRate = 15f; - - [DataField, ViewVariables(VVAccess.ReadWrite)] - public float MinChance = 0.1f; - - [DataField, ViewVariables(VVAccess.ReadWrite)] - public float MaxChance = 0.8f; - - [DataField, ViewVariables(VVAccess.ReadWrite)] - public float IncreaseChance = 0.1f; - - [DataField, ViewVariables(VVAccess.ReadWrite)] - public int MaxSpawns = 3; - - [DataField, ViewVariables(VVAccess.ReadWrite)] - public int SpawnedCount = 0; - - [DataField, ViewVariables(VVAccess.ReadWrite)] - public float CurChance = 0.1f; - - public List Spawns = new(); - - [DataField] - public SoundSpecifier Sound = new SoundPathSpecifier("/Audio/ADT/ling-drugs.ogg"); - - [DataField] - public ushort Layer = 50; - - public HallucinationsPrototype? Proto; - - [ValidatePrototypeId] - public string? HallucinationsPreset; -} diff --git a/Content.Shared/ADT/Hallucinations/Components/HallucinationsDiseaseComponent.cs b/Content.Shared/ADT/Hallucinations/Components/HallucinationsDiseaseComponent.cs deleted file mode 100644 index c33754a2a12..00000000000 --- a/Content.Shared/ADT/Hallucinations/Components/HallucinationsDiseaseComponent.cs +++ /dev/null @@ -1,59 +0,0 @@ -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom; -using Robust.Shared.Prototypes; -using Robust.Shared.Audio; - -namespace Content.Shared.ADT.Hallucinations; - -[RegisterComponent] -public sealed partial class HallucinationsDiseaseComponent : Component -{ - - [DataField(customTypeSerializer: typeof(TimeOffsetSerializer)), ViewVariables(VVAccess.ReadWrite)] - public TimeSpan NextSecond = TimeSpan.Zero; - - [DataField, ViewVariables(VVAccess.ReadWrite)] - public float Range = 7f; - - [DataField, ViewVariables(VVAccess.ReadWrite)] - public float SpawnRate = 15f; - - [DataField, ViewVariables(VVAccess.ReadWrite)] - public float MinChance = 0.1f; - - [DataField, ViewVariables(VVAccess.ReadWrite)] - public float MaxChance = 0.8f; - - [DataField, ViewVariables(VVAccess.ReadWrite)] - public float IncreaseChance = 0.1f; - - [DataField, ViewVariables(VVAccess.ReadWrite)] - public int MaxSpawns = 3; - - [DataField, ViewVariables(VVAccess.ReadWrite)] - public int SpawnedCount = 0; - - [DataField, ViewVariables(VVAccess.ReadWrite)] - public float CurChance = 0.1f; - - [DataField, ViewVariables(VVAccess.ReadWrite)] - public TimeSpan? EndTime; - - public List Spawns = new(); - - [DataField] - public SoundSpecifier Sound = new SoundPathSpecifier("/Audio/ADT/ling-drugs.ogg"); - - [DataField] - public ushort Layer = 50; - - public HallucinationsPrototype? Proto; - - [ValidatePrototypeId] - public string? HallucinationsPreset; - - [DataField] - public bool Epidemic = false; - - [DataField, ViewVariables(VVAccess.ReadWrite)] - public TimeSpan SpreadTime = TimeSpan.Zero; -} diff --git a/Content.Shared/ADT/Hallucinations/Components/HallucinationsMusicComponent.cs b/Content.Shared/ADT/Hallucinations/Components/HallucinationsMusicComponent.cs new file mode 100644 index 00000000000..65f3f481842 --- /dev/null +++ b/Content.Shared/ADT/Hallucinations/Components/HallucinationsMusicComponent.cs @@ -0,0 +1,43 @@ +using Content.Shared.Destructible.Thresholds; +using Robust.Shared.Audio; +using Robust.Shared.GameStates; +using Robust.Shared.Serialization; + +namespace Content.Shared.ADT.Hallucinations.Components; + +/// +/// Component added to hallucinating entity to store music +/// +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState(true)] +public sealed partial class HallucinationsMusicComponent : Component +{ + [ViewVariables, AutoNetworkedField] + public Dictionary Music = new(); + + /// + /// Current stored audio streams + /// CLIENT-ONLY + /// + [ViewVariables] + public Dictionary ActiveMusic = new(); + + /// + /// Timers for next music + /// CLIENT-ONLY + /// + [ViewVariables] + public Dictionary NextMusic = new(); +} + +[Serializable, NetSerializable] +public sealed class HalluciantionMusic +{ + public SoundSpecifier Sound = default!; + public MinMax? Delay; + + public HalluciantionMusic(SoundSpecifier sound, MinMax? delay) + { + Sound = sound; + Delay = delay; + } +} diff --git a/Content.Shared/ADT/Hallucinations/Components/HueShiftComponent.cs b/Content.Shared/ADT/Hallucinations/Components/HueShiftComponent.cs new file mode 100644 index 00000000000..243029c4a99 --- /dev/null +++ b/Content.Shared/ADT/Hallucinations/Components/HueShiftComponent.cs @@ -0,0 +1,10 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.ADT.Hallucinations.Components; + +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +public sealed partial class HueShiftComponent : Component +{ + [DataField, AutoNetworkedField] + public float Shift = 0f; +} diff --git a/Content.Shared/ADT/Hallucinations/Components/SchizophreniaComponent.cs b/Content.Shared/ADT/Hallucinations/Components/SchizophreniaComponent.cs new file mode 100644 index 00000000000..b15d0117e5c --- /dev/null +++ b/Content.Shared/ADT/Hallucinations/Components/SchizophreniaComponent.cs @@ -0,0 +1,31 @@ +using Content.Shared.StatusIcon; +using Robust.Shared.GameStates; +using Robust.Shared.Prototypes; + +namespace Content.Shared.ADT.Hallucinations.Components; + +/// +/// Component added to entities experiencing hallucinations +/// +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +public sealed partial class SchizophreniaComponent : Component +{ + /// + /// List of hallucination entities + /// Server-only + /// + [ViewVariables(VVAccess.ReadWrite)] + public List Hallucinations = new(); + + /// + /// Unique index for component owner and their hallucinations + /// Used for sentinent hallucinations to identify owner + /// + [AutoNetworkedField] + [ViewVariables(VVAccess.ReadWrite)] + public int Idx = 0; + + [AutoNetworkedField] + [ViewVariables(VVAccess.ReadWrite)] + public ProtoId FactionIcon = "Schizophrenic"; +} diff --git a/Content.Shared/ADT/Hallucinations/EntityEffects/Hallucinate.cs b/Content.Shared/ADT/Hallucinations/EntityEffects/Hallucinate.cs new file mode 100644 index 00000000000..67dd1ef9fe9 --- /dev/null +++ b/Content.Shared/ADT/Hallucinations/EntityEffects/Hallucinate.cs @@ -0,0 +1,27 @@ +using Content.Shared.EntityEffects; +using Content.Shared.EntityEffects.Effects.StatusEffects; +using Robust.Shared.Prototypes; + +namespace Content.Shared.ADT.Hallucinations.EntityEffects; + +public sealed partial class Hallucinate : EntityEffectBase +{ + [DataField("hallucinations", required: true)] + public List HallucinationPacks = default!; + + [DataField] + public float Time = 2.0f; + + [DataField] + public bool Refresh = true; + + [DataField] + public StatusEffectMetabolismType Type = StatusEffectMetabolismType.Update; + + public override string? EntityEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) + { + return Loc.GetString("reagent-effect-guidebook-hallucinations", + ("chance", Probability), + ("time", Time)); + } +} diff --git a/Content.Shared/ADT/Hallucinations/EntityEffects/ReduceHallucinations.cs b/Content.Shared/ADT/Hallucinations/EntityEffects/ReduceHallucinations.cs new file mode 100644 index 00000000000..a86cac37a18 --- /dev/null +++ b/Content.Shared/ADT/Hallucinations/EntityEffects/ReduceHallucinations.cs @@ -0,0 +1,17 @@ +using Content.Shared.EntityEffects; +using Robust.Shared.Prototypes; + +namespace Content.Shared.ADT.Hallucinations.EntityEffects; + +public sealed partial class ReduceHallucinations : EntityEffectBase +{ + [DataField] + public float Time = 2.0f; + + public override string? EntityEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) + { + return Loc.GetString("reagent-effect-guidebook-reduce-hallucinations", + ("chance", Probability), + ("time", Time)); + } +} diff --git a/Content.Shared/ADT/Hallucinations/Events/SetHallucinationAppearanceMessage.cs b/Content.Shared/ADT/Hallucinations/Events/SetHallucinationAppearanceMessage.cs new file mode 100644 index 00000000000..30ee33adec9 --- /dev/null +++ b/Content.Shared/ADT/Hallucinations/Events/SetHallucinationAppearanceMessage.cs @@ -0,0 +1,41 @@ +using Robust.Shared.Audio; +using Robust.Shared.Serialization; + +namespace Content.Shared.ADT.Hallucinations.Events; + +[Serializable, NetSerializable] +public sealed partial class SetHallucinationAppearanceMessage : EntityEventArgs +{ + public HallucinationAppearanceData Appearance = new(); + + public SetHallucinationAppearanceMessage(HallucinationAppearanceData appearance) + { + Appearance = appearance; + } +} + +/// +/// Data container for fake appearance applied to entities while player is hallucinating +/// +[DataDefinition] +[Serializable, NetSerializable] +public sealed partial class HallucinationAppearanceData +{ + /// + /// Prototypes which sprites could be used + /// + [DataField] + public List Prototypes; + + /// + /// Rsi states + /// + [DataField] + public List States = new(); + + /// + /// Sound played clientside + /// + [DataField] + public SoundSpecifier? Sound; +} diff --git a/Content.Shared/ADT/Hallucinations/HallucinationsEffect.cs b/Content.Shared/ADT/Hallucinations/HallucinationsEffect.cs deleted file mode 100644 index 85dda35de02..00000000000 --- a/Content.Shared/ADT/Hallucinations/HallucinationsEffect.cs +++ /dev/null @@ -1,40 +0,0 @@ -using Robust.Shared.Prototypes; - -namespace Content.Shared.EntityEffects.Effects -{ - /// - /// Default metabolism for stimulants and tranqs. Attempts to find a MovementSpeedModifier on the target, - /// adding one if not there and to change the movespeed - /// - public sealed partial class HallucinationsReagentEffect : EntityEffectBase - { - [DataField("key")] - public string Key = "ADTHallucinations"; - - [DataField(required: true)] - public string Proto = string.Empty; - - [DataField] - public float Time = 2.0f; - - [DataField] - public bool Refresh = true; - - [DataField] - public HallucinationsMetabolismType Type = HallucinationsMetabolismType.Add; - - public override string? EntityEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) - { - return Loc.GetString("reagent-effect-guidebook-hallucinations", - ("chance", Probability), - ("time", Time)); - } - } - - public enum HallucinationsMetabolismType - { - Add, - Remove, - Set - } -} diff --git a/Content.Shared/ADT/Hallucinations/HallucinationsPrototype.cs b/Content.Shared/ADT/Hallucinations/HallucinationsPrototype.cs deleted file mode 100644 index da4fe6282f7..00000000000 --- a/Content.Shared/ADT/Hallucinations/HallucinationsPrototype.cs +++ /dev/null @@ -1,43 +0,0 @@ -using Robust.Shared.Prototypes; -using Robust.Shared.Serialization; -using Robust.Shared.Utility; - -namespace Content.Shared.ADT.Hallucinations; - -/// -/// Packs of entities that can become a hallucination -/// - -[Prototype("hallucinationsPack")] -public sealed partial class HallucinationsPrototype : IPrototype -{ - [IdDataField] - public string ID { get; private set; } = default!; - - /// - /// List of prototypes that are spawned as a hallucination. - /// - [DataField("entities")] - public List Entities = new(); - - [DataField("spawnRange")] - public float Range = 7f; - - [DataField("spawnRate")] - public float SpawnRate = 15f; - - [DataField("minChance")] - public float MinChance = 0.8f; - - [DataField("maxChance")] - public float MaxChance = 0.8f; - - [DataField("increasedPerSpawn")] - public float IncreaseChance = 0.1f; - - [DataField("maxSpawns")] - public int MaxSpawns = 3; - - [DataField("epidemic")] - public bool Epicemic = false; -} diff --git a/Content.Shared/ADT/Hallucinations/Systems/BoundHallucinationSystem.cs b/Content.Shared/ADT/Hallucinations/Systems/BoundHallucinationSystem.cs new file mode 100644 index 00000000000..5bbf8368db4 --- /dev/null +++ b/Content.Shared/ADT/Hallucinations/Systems/BoundHallucinationSystem.cs @@ -0,0 +1,53 @@ +using Content.Shared.ADT.Hallucinations.Components; + +namespace Content.Shared.ADT.Hallucinations.Systems; + +public sealed partial class BoundHallucinationSystem : EntitySystem +{ + [Dependency] private SharedEyeSystem _eye = default!; + [Dependency] private SharedTransformSystem _xform = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnShutdown); + } + + private void OnShutdown(Entity ent, ref ComponentShutdown args) + { + _eye.SetTarget(ent.Owner, null); + _eye.SetPvsScale(ent.Owner, 1f); + } + + public override void Update(float frameTime) + { + base.Update(frameTime); + + var query = EntityQueryEnumerator(); + while (query.MoveNext(out var uid, out _, out var comp, out var xform, out var eye)) + { + if (comp.Ent is not { Valid: true }) + continue; + + EnsureBoundTarget((uid, eye), comp.Ent); + + if (Transform(comp.Ent).MapID != xform.MapID) + { + _eye.SetTarget(uid, null); + continue; + } + + _eye.SetOffset(uid, _xform.GetWorldPosition(uid) - _xform.GetWorldPosition(comp.Ent)); + } + } + + private void EnsureBoundTarget(Entity ent, EntityUid target) + { + if (ent.Comp.Target == target) + return; + + _eye.SetTarget(ent.Owner, target); + _eye.SetPvsScale(ent.Owner, 1.7f); + } +} diff --git a/Content.Shared/ADT/Screamer/DoScreamerMessage.cs b/Content.Shared/ADT/Screamer/DoScreamerMessage.cs new file mode 100644 index 00000000000..2caf5486fdd --- /dev/null +++ b/Content.Shared/ADT/Screamer/DoScreamerMessage.cs @@ -0,0 +1,27 @@ +using System.Numerics; +using Robust.Shared.Serialization; + +namespace Content.Shared.ADT.Screamer; + +[Serializable, NetSerializable] +public sealed class DoScreamerMessage : EntityEventArgs +{ + public string ProtoId = default!; + public string? Sound; + public Vector2 Offset; + public float Alpha; + public float Duration; + public bool FadeIn; + public bool FadeOut; + + public DoScreamerMessage(string protoId, string? sound, Vector2 offset, float alpha, float duration, bool fadeIn, bool fadeOut) + { + ProtoId = protoId; + Sound = sound; + Offset = offset; + Alpha = alpha; + Duration = duration; + FadeIn = fadeIn; + FadeOut = fadeOut; + } +} diff --git a/Content.Shared/ADT/Screamer/ScreamersComponent.cs b/Content.Shared/ADT/Screamer/ScreamersComponent.cs new file mode 100644 index 00000000000..d1ec975e9e7 --- /dev/null +++ b/Content.Shared/ADT/Screamer/ScreamersComponent.cs @@ -0,0 +1,9 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.ADT.Screamer; + +[RegisterComponent, NetworkedComponent] +public sealed partial class ScreamersComponent : Component +{ + +} diff --git a/Content.Shared/Actions/SharedActionsSystem.cs b/Content.Shared/Actions/SharedActionsSystem.cs index 642d942fb6d..163fb759363 100644 --- a/Content.Shared/Actions/SharedActionsSystem.cs +++ b/Content.Shared/Actions/SharedActionsSystem.cs @@ -4,6 +4,7 @@ using Content.Shared.Actions.Components; using Content.Shared.Actions.Events; using Content.Shared.Administration.Logs; +using Content.Shared.ADT.Actions; using Content.Shared.Database; using Content.Shared.DoAfter; using Content.Shared.Hands; @@ -694,6 +695,11 @@ public bool AddActionDirect(Entity performer, performer.Comp.Actions.Add(ent); Dirty(performer, performer.Comp); ActionAdded((performer, performer.Comp), (ent, ent.Comp)); + + // ADT-Tweak-start + var ev = new ActionAddedDirectEvent(ent); + RaiseLocalEvent(performer.Owner, ref ev); + // ADT-Tweak-end return true; } diff --git a/Content.Shared/Buckle/SharedBuckleSystem.Buckle.cs b/Content.Shared/Buckle/SharedBuckleSystem.Buckle.cs index 3538ce9ca56..ca651364daf 100644 --- a/Content.Shared/Buckle/SharedBuckleSystem.Buckle.cs +++ b/Content.Shared/Buckle/SharedBuckleSystem.Buckle.cs @@ -1,5 +1,6 @@ using System.Diagnostics.CodeAnalysis; using System.Numerics; +using Content.Shared.ADT.Hallucinations.Components; using Content.Shared.Alert; using Content.Shared.Buckle.Components; using Content.Shared.Cuffs.Components; @@ -378,7 +379,8 @@ private void Buckle(Entity buckle, Entity strap else if (user != null) _adminLogger.Add(LogType.Action, LogImpact.Low, $"{ToPrettyString(user):player} buckled {ToPrettyString(buckle)} to {ToPrettyString(strap)}"); - _audio.PlayPredicted(strap.Comp.BuckleSound, strap, user); + if (!(HasComp(_playerManager.LocalEntity) && user != _playerManager.LocalEntity)) // ADT-Tweak: Hallucinations + _audio.PlayPredicted(strap.Comp.BuckleSound, strap, user); SetBuckledTo(buckle, strap!); Appearance.SetData(strap, StrapVisuals.State, true); @@ -469,7 +471,8 @@ private void Unbuckle(Entity buckle, Entity str else if (user != null) _adminLogger.Add(LogType.Action, LogImpact.Low, $"{ToPrettyString(user):user} unbuckled {ToPrettyString(buckle):target} from {ToPrettyString(strap):strap}"); - _audio.PlayPredicted(strap.Comp.UnbuckleSound, strap, user); + if (!(HasComp(_playerManager.LocalEntity) && user != _playerManager.LocalEntity)) // ADT-Tweak: Hallucinations + _audio.PlayPredicted(strap.Comp.UnbuckleSound, strap, user); SetBuckledTo(buckle, null); diff --git a/Content.Shared/Chat/SharedChatSystem.Emote.cs b/Content.Shared/Chat/SharedChatSystem.Emote.cs index c7b47a43213..384c23ae043 100644 --- a/Content.Shared/Chat/SharedChatSystem.Emote.cs +++ b/Content.Shared/Chat/SharedChatSystem.Emote.cs @@ -1,4 +1,5 @@ using System.Collections.Frozen; +using Content.Shared.ADT.Chat; using Content.Shared.Chat.Prototypes; using Content.Shared.Speech; using Robust.Shared.Audio; @@ -156,6 +157,15 @@ public bool TryPlayEmoteSound(EntityUid uid, EmoteSoundsPrototype? proto, string // optional override params > general params for all sounds in set > individual sound params var param = audioParams ?? proto.GeneralParams ?? sound.Params; + + // ADT-Tweak-start + var overrideEv = new OverrideEmoteSoundEvent(sound, param); + RaiseLocalEvent(uid, ref overrideEv); + + if (overrideEv.Cancelled) + return true; + // ADT-Tweak-end + _audio.PlayPvs(sound, uid, param); return true; } diff --git a/Content.Shared/Eye/VisibilityFlags.cs b/Content.Shared/Eye/VisibilityFlags.cs index 39275053cac..0e53bc35101 100644 --- a/Content.Shared/Eye/VisibilityFlags.cs +++ b/Content.Shared/Eye/VisibilityFlags.cs @@ -10,11 +10,8 @@ public enum VisibilityFlags : int Normal = 1 << 0, Ghost = 1 << 1, Subfloor = 1 << 2, - PhantomVessel = 1 << 3, // ADT Phantom - Narcotic = 1 << 4, // ADT-Changeling-Tweak - Schizo = 1 << 5, // ADT-Changeling-Tweak - LingToxin = 1 << 6, // ADT-Changeling-Tweak Eldritch = 1 << 7, // ADT-Tweak Heretic Bubblegum = 1 << 8, // ADT-Tweak Bubblegum + Hallucination = 1 << 10, // ADT-Tweak } } diff --git a/Resources/Audio/ADT/Effects/vine_boom.ogg b/Resources/Audio/ADT/Effects/vine_boom.ogg new file mode 100644 index 00000000000..f65ee0a5988 Binary files /dev/null and b/Resources/Audio/ADT/Effects/vine_boom.ogg differ diff --git a/Resources/Audio/ADT/hallucinations/Music/flamingoo.ogg b/Resources/Audio/ADT/hallucinations/Music/flamingoo.ogg new file mode 100644 index 00000000000..4af7a09ecdc Binary files /dev/null and b/Resources/Audio/ADT/hallucinations/Music/flamingoo.ogg differ diff --git a/Resources/Locale/ru-RU/ADT/administration/commands.ftl b/Resources/Locale/ru-RU/ADT/administration/commands.ftl index eaf87b136df..50269acb537 100644 --- a/Resources/Locale/ru-RU/ADT/administration/commands.ftl +++ b/Resources/Locale/ru-RU/ADT/administration/commands.ftl @@ -92,3 +92,20 @@ cmd-adt-converttodungeonroom-no-grid = Не удалось загрузить г cmd-adt-converttodungeonroom-empty = На гриде нет ни одного тайла, конвертировать нечего. cmd-adt-converttodungeonroom-too-many-tiles = В комнате слишком много разных тайлов, не вмещает. cmd-adt-converttodungeonroom-done = Комната { $room } собрана, выберите куда её сохранить. + +shell-invalid-metabolism-type = Неверный способ применения + +# Команда: hallucinate +hallucinate-command-description = Применяет определённые галлюцинации к сущности +hallucinate-command-help-text = Использование: { $command } <способ применения> <время> <паки галлюцинаций> +hallucinate-command-success = Применён пак галлюцинаций { $added } к сущности { $target }. + +# Команда: add-as-hallucination +add-as-hallucination-command-description = Добавляет указанной сущности другую сущность как галлюцинацию. +add-as-hallucination-command-help-text = Использование: { $command } +add-as-hallucination-command-success = Сущность { $added } теперь является галлюцинацией сущности { $target }. + +# Команда: screamer +screamer-command-description = Пугает указанного игрока скримером +screamer-command-help-text = Использование: { $command } <прототип скримера> <время> <звук> <непрозрачность> <плавное появление> <плавное затухание> +screamer-command-success = Скример успешно отправлен. diff --git a/Resources/Locale/ru-RU/ADT/entity-categories.ftl b/Resources/Locale/ru-RU/ADT/entity-categories.ftl new file mode 100644 index 00000000000..47c3572aa12 --- /dev/null +++ b/Resources/Locale/ru-RU/ADT/entity-categories.ftl @@ -0,0 +1 @@ +entity-category-name-screamers = Скримеры diff --git a/Resources/Prototypes/ADT/Effects/liminal-reveal.yml b/Resources/Prototypes/ADT/Effects/liminal-reveal.yml new file mode 100644 index 00000000000..b91dbdcae61 --- /dev/null +++ b/Resources/Prototypes/ADT/Effects/liminal-reveal.yml @@ -0,0 +1,19 @@ +- type: entity + id: ADTLiminalRevealEffect + categories: [ HideSpawnMenu ] + components: + - type: TimedDespawn + lifetime: 0.3 + - type: Sprite + noRot: true + drawdepth: Effects + layers: + - shader: unshaded + map: ["enum.EffectLayers.Unshaded"] + sprite: ADT/Effects/liminalium-reveal.rsi + state: icon + - type: EffectVisuals + - type: Tag + tags: + - HideContextMenu + - type: AnimationPlayer diff --git a/Resources/Prototypes/ADT/Entities/Mobs/NPCs/Hallucinations/base.yml b/Resources/Prototypes/ADT/Entities/Mobs/NPCs/Hallucinations/base.yml index 82b5db5c707..3e0f7463dd6 100644 --- a/Resources/Prototypes/ADT/Entities/Mobs/NPCs/Hallucinations/base.yml +++ b/Resources/Prototypes/ADT/Entities/Mobs/NPCs/Hallucinations/base.yml @@ -2,9 +2,7 @@ - type: entity parent: BaseMob id: ADTBaseEntityHallucination - name: "???" - description: "???" - suffix: DO NOT MAP + abstract: true categories: [ HideSpawnMenu ] components: - type: Fixtures diff --git a/Resources/Prototypes/ADT/Entities/Mobs/NPCs/slimes.yml b/Resources/Prototypes/ADT/Entities/Mobs/NPCs/slimes.yml index 7e6da0c4388..397364b18ab 100644 --- a/Resources/Prototypes/ADT/Entities/Mobs/NPCs/slimes.yml +++ b/Resources/Prototypes/ADT/Entities/Mobs/NPCs/slimes.yml @@ -165,11 +165,10 @@ - Flashed - Drowsiness - Adrenaline - - ADTHallucinations - ADTStarvation - type: SleepEmitSound - type: BloodCough postingSayDamage: blood-cough - type: TimeDespawnDamage - type: Hands # They still don't have hands. - - type: ComplexInteraction \ No newline at end of file + - type: ComplexInteraction diff --git a/Resources/Prototypes/ADT/Entities/Mobs/Player/silicon_base.yml b/Resources/Prototypes/ADT/Entities/Mobs/Player/silicon_base.yml index 50a56576cca..56ce771dddb 100644 --- a/Resources/Prototypes/ADT/Entities/Mobs/Player/silicon_base.yml +++ b/Resources/Prototypes/ADT/Entities/Mobs/Player/silicon_base.yml @@ -385,4 +385,6 @@ # - PsionicallyInsulated - SeeingStatic - type: Blindable - - type: SpeechBarks \ No newline at end of file + - type: SpeechBarks + - type: CanHallucinate + - type: Screamers diff --git a/Resources/Prototypes/ADT/Entities/Screamers/base.yml b/Resources/Prototypes/ADT/Entities/Screamers/base.yml new file mode 100644 index 00000000000..f1d80371754 --- /dev/null +++ b/Resources/Prototypes/ADT/Entities/Screamers/base.yml @@ -0,0 +1,7 @@ +- type: entity + parent: BaseItem + id: ADTBaseScreamer + categories: [ Screamers ] + components: + - type: Sprite + noRot: true diff --git a/Resources/Prototypes/ADT/Entities/Screamers/memes.yml b/Resources/Prototypes/ADT/Entities/Screamers/memes.yml new file mode 100644 index 00000000000..f79ece1ab6c --- /dev/null +++ b/Resources/Prototypes/ADT/Entities/Screamers/memes.yml @@ -0,0 +1,9 @@ +- type: entity + parent: ADTBaseScreamer + id: ADTScreamerSpeed + name: ishowsped + description: "dovolen" + components: + - type: Sprite + texture: /Textures/ADT/Misc/Screamers/speed.png + noRot: true diff --git a/Resources/Prototypes/ADT/Hallucinations/liminal.yml b/Resources/Prototypes/ADT/Hallucinations/liminal.yml new file mode 100644 index 00000000000..10b6aea4300 --- /dev/null +++ b/Resources/Prototypes/ADT/Hallucinations/liminal.yml @@ -0,0 +1,14 @@ +- type: hallucinationsPack + id: Liminal + music: + path: /Audio/ADT/Phantom/Music/help.ogg + params: + volume: -12 + musicPlayInterval: + min: 200 + max: 250 + musicDurationThreshold: 100 + components: + - type: HallucinationsRemoveMobs + reveal: ADTLiminalRevealEffect + startingMessage: Кажется, вы выпали за карту. diff --git a/Resources/Prototypes/ADT/Hallucinations/supermatter.yml b/Resources/Prototypes/ADT/Hallucinations/supermatter.yml index a3cb3609a77..5ee8a7df77d 100644 --- a/Resources/Prototypes/ADT/Hallucinations/supermatter.yml +++ b/Resources/Prototypes/ADT/Hallucinations/supermatter.yml @@ -1,5 +1,16 @@ - type: hallucinationsPack id: SupermatterPack - entities: - - ADTSMHallucinationDarkmatter - - ADTSMHallucinationCrystallBeing + data: + - !type:MobHallucinations + entities: + - ADTSMHallucinationDarkmatter + - ADTSMHallucinationCrystallBeing + range: + min: 1 + max: 7 + spawnCount: + min: 2 + max: 4 + delay: + min: 5 + max: 20 diff --git a/Resources/Prototypes/ADT/Reagents/narcotics.yml b/Resources/Prototypes/ADT/Reagents/narcotics.yml index 07d62c485a6..754f0e5f113 100644 --- a/Resources/Prototypes/ADT/Reagents/narcotics.yml +++ b/Resources/Prototypes/ADT/Reagents/narcotics.yml @@ -167,3 +167,32 @@ min: 12 probability: 0.15 pacifyDuration: 3 + +- type: reagent + id: ADTLiminalium + name: reagent-name-liminalium + group: Narcotics + desc: reagent-desc-liminalium + flavor: bitter + flavorMinimum: 0.05 + color: "#99986b" + physicalDesc: reagent-physical-desc-crystalline + plantMetabolism: + - !type:PlantAdjustNutrition + amount: -5 + - !type:PlantAdjustHealth + amount: -1 + metabolisms: + Narcotic: + effects: + - !type:ModifyStatusEffect + effectProto: StatusEffectSeeingRainbow + time: 16 + type: Add + refresh: false + - !type:Jitter + - !type:HallucinationsReagentEffect + hallucinations: [ Liminal ] + type: Add + time: 300 + refresh: false diff --git a/Resources/Prototypes/ADT/Shaders/shaders.yml b/Resources/Prototypes/ADT/Shaders/shaders.yml index f803081c80c..b77c309aee3 100644 --- a/Resources/Prototypes/ADT/Shaders/shaders.yml +++ b/Resources/Prototypes/ADT/Shaders/shaders.yml @@ -145,3 +145,14 @@ id: XenoBluespace kind: source path: "/Textures/ADT/Xenobiology/Shaders/xeno_bluespace.swsl" + +# ADT +- type: shader + id: HueShift + kind: source + path: "/Textures/ADT/Shaders/hue_shift.swsl" + +- type: shader + id: ScreenRotation + kind: source + path: "/Textures/ADT/Shaders/screen_rotation.swsl" diff --git a/Resources/Prototypes/ADT/StatusIcon/factions.yml b/Resources/Prototypes/ADT/StatusIcon/factions.yml index 5efd14083cc..85b20821ac4 100644 --- a/Resources/Prototypes/ADT/StatusIcon/factions.yml +++ b/Resources/Prototypes/ADT/StatusIcon/factions.yml @@ -6,4 +6,28 @@ state: carp showTo: components: - - ShowAntagIcons \ No newline at end of file + - ShowAntagIcons + +- type: factionIcon + id: Schizophrenic + priority: 10 + icon: + sprite: /Textures/ADT/Interface/Misc/schizo_icons.rsi + state: hallucinating + showTo: + components: + - Schizophrenia + - Hallucination + - ShowAntagIcons + +- type: factionIcon + id: Hallucination + priority: 10 + icon: + sprite: /Textures/ADT/Interface/Misc/schizo_icons.rsi + state: hallucination + showTo: + components: + - Schizophrenia + - Hallucination + - ShowAntagIcons diff --git a/Resources/Prototypes/ADT/categories.yml b/Resources/Prototypes/ADT/categories.yml new file mode 100644 index 00000000000..117f266d0c4 --- /dev/null +++ b/Resources/Prototypes/ADT/categories.yml @@ -0,0 +1,4 @@ +- type: entityCategory + id: Screamers + name: entity-category-name-screamers + hideSpawnMenu: true diff --git a/Resources/Prototypes/ADT/status_effects.yml b/Resources/Prototypes/ADT/status_effects.yml index aae8f30a48f..bd12ea8053a 100644 --- a/Resources/Prototypes/ADT/status_effects.yml +++ b/Resources/Prototypes/ADT/status_effects.yml @@ -27,4 +27,4 @@ id: StatusEffectMeleeVulnerability name: melee vulnerability components: - - type: MeleeVulnerabilityStatusEffect \ No newline at end of file + - type: MeleeVulnerabilityStatusEffect diff --git a/Resources/Prototypes/Body/species_base.yml b/Resources/Prototypes/Body/species_base.yml index 51d5459fc33..9102e1a8f31 100644 --- a/Resources/Prototypes/Body/species_base.yml +++ b/Resources/Prototypes/Body/species_base.yml @@ -86,7 +86,6 @@ - PressureImmunity - Flashed - Adrenaline - - ADTHallucinations # ADT-Tweak - ADTStarvation # ADT-Tweak - type: Identity - type: IdExaminable diff --git a/Resources/Prototypes/Entities/Mobs/base.yml b/Resources/Prototypes/Entities/Mobs/base.yml index 15bd3b1a873..7aad1d5cf55 100644 --- a/Resources/Prototypes/Entities/Mobs/base.yml +++ b/Resources/Prototypes/Entities/Mobs/base.yml @@ -77,6 +77,8 @@ - type: MobMover - type: MovementSpeedModifier - type: LagCompensation + - type: CanHallucinate # ADT-Tweak + - type: Screamers # ADT-Tweak - type: entity save: false @@ -299,4 +301,4 @@ # ADT-Tweak start bloodRefreshAmount: 0.3 bleedReductionAmount: 0.3 - # ADT-Tweak end \ No newline at end of file + # ADT-Tweak end diff --git a/Resources/Prototypes/Reagents/Consumable/Drink/alcohol.yml b/Resources/Prototypes/Reagents/Consumable/Drink/alcohol.yml index 5458f968959..e9f3370ec92 100644 --- a/Resources/Prototypes/Reagents/Consumable/Drink/alcohol.yml +++ b/Resources/Prototypes/Reagents/Consumable/Drink/alcohol.yml @@ -2313,4 +2313,4 @@ min: 8 - !type:MetabolizerTypeCondition type: [Dwarf] - inverted: true \ No newline at end of file + inverted: true diff --git a/Resources/Prototypes/Reagents/narcotics.yml b/Resources/Prototypes/Reagents/narcotics.yml index 1ea05670964..871a8315761 100644 --- a/Resources/Prototypes/Reagents/narcotics.yml +++ b/Resources/Prototypes/Reagents/narcotics.yml @@ -324,6 +324,13 @@ effectProto: StatusEffectSeeingRainbow type: Add time: 5 + refresh: false + # ADT-Tweak-start + - !type:Hallucinate + hallucinations: [ SupermatterPack ] + type: Add + time: 4 + # ADT-Tweak-end - type: reagent id: Bananadine @@ -340,6 +347,12 @@ effectProto: StatusEffectSeeingRainbow type: Add time: 5 + # ADT-Tweak-start + - !type:Hallucinate + hallucinations: [ SupermatterPack ] + type: Add + time: 4 + # ADT-Tweak-end # Probably replace this one with sleeping chem when putting someone in a comatose state is easier - type: reagent @@ -545,3 +558,9 @@ effectProto: StatusEffectSeeingRainbow type: Add time: 5 + # ADT-Tweak-start + - !type:Hallucinate + hallucinations: [ SupermatterPack ] + type: Add + time: 4 + # ADT-Tweak-end diff --git a/Resources/Textures/ADT/Effects/liminalium-reveal.rsi/icon.png b/Resources/Textures/ADT/Effects/liminalium-reveal.rsi/icon.png new file mode 100644 index 00000000000..a0231917cde Binary files /dev/null and b/Resources/Textures/ADT/Effects/liminalium-reveal.rsi/icon.png differ diff --git a/Resources/Textures/ADT/Effects/liminalium-reveal.rsi/meta.json b/Resources/Textures/ADT/Effects/liminalium-reveal.rsi/meta.json new file mode 100644 index 00000000000..9c7a9acfe61 --- /dev/null +++ b/Resources/Textures/ADT/Effects/liminalium-reveal.rsi/meta.json @@ -0,0 +1,23 @@ +{ + "version": 1, + "size": { + "x": 32, + "y": 32 + }, + "license": "CC-BY-SA-3.0", + "copyright": "made by _kote", + "states": [ + { + "name": "icon", + "delays": [ + [ + 0.05, + 0.05, + 0.05, + 0.05, + 0.05 + ] + ] + } + ] +} diff --git a/Resources/Textures/ADT/Interface/Misc/schizo_icons.rsi/hallucinating.png b/Resources/Textures/ADT/Interface/Misc/schizo_icons.rsi/hallucinating.png new file mode 100644 index 00000000000..09a799f4276 Binary files /dev/null and b/Resources/Textures/ADT/Interface/Misc/schizo_icons.rsi/hallucinating.png differ diff --git a/Resources/Textures/ADT/Interface/Misc/schizo_icons.rsi/hallucination.png b/Resources/Textures/ADT/Interface/Misc/schizo_icons.rsi/hallucination.png new file mode 100644 index 00000000000..3062db15a89 Binary files /dev/null and b/Resources/Textures/ADT/Interface/Misc/schizo_icons.rsi/hallucination.png differ diff --git a/Resources/Textures/ADT/Interface/Misc/schizo_icons.rsi/meta.json b/Resources/Textures/ADT/Interface/Misc/schizo_icons.rsi/meta.json new file mode 100644 index 00000000000..9dc154a4f94 --- /dev/null +++ b/Resources/Textures/ADT/Interface/Misc/schizo_icons.rsi/meta.json @@ -0,0 +1,17 @@ +{ + "version": 1, + "size": { + "x": 8, + "y": 8 + }, + "license": "CC-BY-NC-SA-3.0", + "copyright": "Made by egor2444 for Adventure Time server", + "states": [ + { + "name": "hallucinating" + }, + { + "name": "hallucination" + } + ] +} diff --git a/Resources/Textures/ADT/Misc/Screamers/speed.png b/Resources/Textures/ADT/Misc/Screamers/speed.png new file mode 100644 index 00000000000..b903eded87c Binary files /dev/null and b/Resources/Textures/ADT/Misc/Screamers/speed.png differ diff --git a/Resources/Textures/ADT/Shaders/hue_shift.swsl b/Resources/Textures/ADT/Shaders/hue_shift.swsl new file mode 100644 index 00000000000..435bb32c57e --- /dev/null +++ b/Resources/Textures/ADT/Shaders/hue_shift.swsl @@ -0,0 +1,36 @@ +uniform sampler2D SCREEN_TEXTURE; +uniform highp float shift; + +void fragment() { + highp vec4 color = zTextureSpec(SCREEN_TEXTURE, UV); + + highp vec3 colorNoAlpha = vec3(color.x, color.y, color.z); + highp vec3 result = HueShift(colorNoAlpha); + + COLOR = vec4(result, 1); +} + +highp vec3 HueShift(highp vec3 colour) +{ + highp vec3 yiq = YIQ_CONVERT * colour; + + highp mat2 rotMatrix = mat2( + cos(shift), -sin(shift), + sin(shift), cos(shift) + ); + yiq.yz *= rotMatrix; + + return RGB_CONVERT * yiq; +} + +const highp mat3 YIQ_CONVERT = mat3( + 0.299, 0.596, 0.211, + 0.587, -0.274, -0.523, + 0.114, -0.322, 0.312 +); + +const highp mat3 RGB_CONVERT = mat3( + 1.0, 1.0, 1.0, + 0.956, -0.272, -1.106, + 0.621, -0.647, 1.703 +); diff --git a/Resources/Textures/ADT/Shaders/screen_rotation.swsl b/Resources/Textures/ADT/Shaders/screen_rotation.swsl new file mode 100644 index 00000000000..ea77c7162ac --- /dev/null +++ b/Resources/Textures/ADT/Shaders/screen_rotation.swsl @@ -0,0 +1,19 @@ +uniform sampler2D SCREEN_TEXTURE; +uniform highp float angle; + +void fragment() { + highp vec2 center = vec2(0.5); + highp vec2 offset = (UV - center) / SCREEN_PIXEL_SIZE; + highp mat2 rotation = mat2( + cos(angle), -sin(angle), + sin(angle), cos(angle) + ); + highp vec2 rotatedUv = center + (rotation * offset) * SCREEN_PIXEL_SIZE; + + if (rotatedUv.x < 0.0 || rotatedUv.x > 1.0 || rotatedUv.y < 0.0 || rotatedUv.y > 1.0) { + COLOR = vec4(0.0); + return; + } + + COLOR = zTextureSpec(SCREEN_TEXTURE, rotatedUv); +}