diff --git a/Content.Client/Traits/ParacusiaSystem.cs b/Content.Client/Traits/ParacusiaSystem.cs new file mode 100644 index 0000000000..c55c12315a --- /dev/null +++ b/Content.Client/Traits/ParacusiaSystem.cs @@ -0,0 +1,74 @@ +using Content.Shared.Traits.Assorted; +using Content.Client.Camera; +using Robust.Shared.Random; +using Robust.Client.GameObjects; +using Robust.Client.Player; +using Robust.Shared.Timing; + +namespace Content.Client.Traits; + +public sealed class ParacusiaSystem : SharedParacusiaSystem +{ + [Dependency] private readonly IGameTiming _timing = default!; + [Dependency] private readonly IPlayerManager _player = default!; + [Dependency] private readonly IRobustRandom _random = default!; + [Dependency] private readonly SharedAudioSystem _audio = default!; + [Dependency] private readonly CameraRecoilSystem _camera = default!; + + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent(OnComponentStartup); + SubscribeLocalEvent(OnPlayerDetach); + } + + public override void Update(float frameTime) + { + base.Update(frameTime); + + if (!_timing.IsFirstTimePredicted) + return; + + if (_player.LocalPlayer?.ControlledEntity is not EntityUid localPlayer) + return; + + PlayParacusiaSounds(localPlayer); + } + + private void OnComponentStartup(EntityUid uid, ParacusiaComponent component, ComponentStartup args) + { + component.NextIncidentTime = _timing.CurTime + TimeSpan.FromSeconds(_random.NextFloat(component.MinTimeBetweenIncidents, component.MaxTimeBetweenIncidents)); + } + + private void OnPlayerDetach(EntityUid uid, ParacusiaComponent component, PlayerDetachedEvent args) + { + component.Stream?.Stop(); + } + + private void PlayParacusiaSounds(EntityUid uid) + { + if (!TryComp(uid, out var paracusia)) + return; + + if (_timing.CurTime <= paracusia.NextIncidentTime) + return; + + // Set the new time. + var timeInterval = _random.NextFloat(paracusia.MinTimeBetweenIncidents, paracusia.MaxTimeBetweenIncidents); + paracusia.NextIncidentTime += TimeSpan.FromSeconds(timeInterval); + + // Offset position where the sound is played + var randomOffset = + new Vector2 + ( + _random.NextFloat(-paracusia.MaxSoundDistance, paracusia.MaxSoundDistance), + _random.NextFloat(-paracusia.MaxSoundDistance, paracusia.MaxSoundDistance) + ); + + var newCoords = Transform(uid).Coordinates.Offset(randomOffset); + + // Play the sound + paracusia.Stream = _audio.PlayStatic(paracusia.Sounds, uid, newCoords); + } + +} diff --git a/Content.Server/Chemistry/ReagentEffects/Drunk.cs b/Content.Server/Chemistry/ReagentEffects/Drunk.cs index d844d43d2e..c5d24e362d 100644 --- a/Content.Server/Chemistry/ReagentEffects/Drunk.cs +++ b/Content.Server/Chemistry/ReagentEffects/Drunk.cs @@ -9,7 +9,7 @@ public sealed class Drunk : ReagentEffect /// BoozePower is how long each metabolism cycle will make the drunk effect last for. /// [DataField("boozePower")] - public float BoozePower = 2f; + public float BoozePower = 3f; /// /// Whether speech should be slurred. diff --git a/Content.Server/Traits/Assorted/ParacusiaSystem.cs b/Content.Server/Traits/Assorted/ParacusiaSystem.cs new file mode 100644 index 0000000000..2008d170ab --- /dev/null +++ b/Content.Server/Traits/Assorted/ParacusiaSystem.cs @@ -0,0 +1,8 @@ +using Content.Shared.Traits.Assorted; + +namespace Content.Server.Traits.Assorted; + +public sealed class ParacusiaSystem : SharedParacusiaSystem +{ + +} diff --git a/Content.Shared/Drunk/DrunkSystem.cs b/Content.Shared/Drunk/DrunkSystem.cs index ee9cc34fd0..f09f260817 100644 --- a/Content.Shared/Drunk/DrunkSystem.cs +++ b/Content.Shared/Drunk/DrunkSystem.cs @@ -1,5 +1,6 @@ using Content.Shared.Speech.EntitySystems; using Content.Shared.StatusEffect; +using Content.Shared.Traits.Assorted; namespace Content.Shared.Drunk; @@ -19,6 +20,9 @@ public abstract class SharedDrunkSystem : EntitySystem if (applySlur) _slurredSystem.DoSlur(uid, TimeSpan.FromSeconds(boozePower), status); + if (TryComp(uid, out var trait)) + boozePower *= trait.BoozeStrengthMultiplier; + if (!_statusEffectsSystem.HasStatusEffect(uid, DrunkKey, status)) { _statusEffectsSystem.TryAddStatusEffect(uid, DrunkKey, TimeSpan.FromSeconds(boozePower), true, status); diff --git a/Content.Shared/Traits/Assorted/LightweightDrunkComponent.cs b/Content.Shared/Traits/Assorted/LightweightDrunkComponent.cs new file mode 100644 index 0000000000..fda1e3de4f --- /dev/null +++ b/Content.Shared/Traits/Assorted/LightweightDrunkComponent.cs @@ -0,0 +1,15 @@ +using Robust.Shared.GameStates; +using Content.Shared.Drunk; + +namespace Content.Shared.Traits.Assorted; + +/// +/// Used for the lightweight trait. DrunkSystem will check for this component and modify the boozePower accordingly if it finds it. +/// +[RegisterComponent, NetworkedComponent] +[Access(typeof(SharedDrunkSystem))] +public sealed class LightweightDrunkComponent : Component +{ + [DataField("boozeStrengthMultiplier"), ViewVariables(VVAccess.ReadWrite)] + public float BoozeStrengthMultiplier = 4f; +} diff --git a/Content.Shared/Traits/Assorted/ParacusiaComponent.cs b/Content.Shared/Traits/Assorted/ParacusiaComponent.cs new file mode 100644 index 0000000000..ef86169a69 --- /dev/null +++ b/Content.Shared/Traits/Assorted/ParacusiaComponent.cs @@ -0,0 +1,61 @@ +using Robust.Shared.Audio; +using Robust.Shared.GameStates; +using System; +using Robust.Shared.Serialization; +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom; + +namespace Content.Shared.Traits.Assorted; + +/// +/// This component is used for paracusia, which causes auditory hallucinations. +/// +[RegisterComponent, NetworkedComponent] +[Access(typeof(SharedParacusiaSystem))] +public sealed class ParacusiaComponent : Component +{ + /// + /// The maximum time between incidents in seconds + /// + [DataField("maxTimeBetweenIncidents", required: true), ViewVariables(VVAccess.ReadWrite)] + public float MaxTimeBetweenIncidents = 30f; + + /// + /// The minimum time between incidents in seconds + /// + [DataField("minTimeBetweenIncidents", required: true), ViewVariables(VVAccess.ReadWrite)] + public float MinTimeBetweenIncidents = 60f; + + /// + /// How far away at most can the sound be? + /// + [DataField("maxSoundDistance", required: true), ViewVariables(VVAccess.ReadWrite)] + public float MaxSoundDistance; + + /// + /// The sounds to choose from + /// + [DataField("sounds", required: true)] + public SoundSpecifier Sounds = default!; + + [DataField("timeBetweenIncidents", customTypeSerializer: typeof(TimeOffsetSerializer)), ViewVariables(VVAccess.ReadWrite)] + public TimeSpan NextIncidentTime; + + public IPlayingAudioStream? Stream; +} + +[Serializable, NetSerializable] +public sealed class ParacusiaComponentState : ComponentState +{ + public readonly float MaxTimeBetweenIncidents; + public readonly float MinTimeBetweenIncidents; + public readonly float MaxSoundDistance; + public readonly SoundSpecifier Sounds = default!; + + public ParacusiaComponentState(float maxTimeBetweenIncidents, float minTimeBetweenIncidents, float maxSoundDistance, SoundSpecifier sounds) + { + MaxTimeBetweenIncidents = maxTimeBetweenIncidents; + MinTimeBetweenIncidents = minTimeBetweenIncidents; + MaxSoundDistance = maxSoundDistance; + Sounds = sounds; + } +} diff --git a/Content.Shared/Traits/Assorted/SharedParacusiaSystem.cs b/Content.Shared/Traits/Assorted/SharedParacusiaSystem.cs new file mode 100644 index 0000000000..6eb0f6b223 --- /dev/null +++ b/Content.Shared/Traits/Assorted/SharedParacusiaSystem.cs @@ -0,0 +1,30 @@ +using Content.Shared.GameTicking; +using Robust.Shared.GameStates; + +namespace Content.Shared.Traits.Assorted; + +public abstract class SharedParacusiaSystem : EntitySystem +{ + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent(GetCompState); + SubscribeLocalEvent(HandleCompState); + } + + private void GetCompState(EntityUid uid, ParacusiaComponent component, ref ComponentGetState args) + { + args.State = new ParacusiaComponentState(component.MaxTimeBetweenIncidents, component.MinTimeBetweenIncidents, component.MaxSoundDistance, component.Sounds); + } + + private void HandleCompState(EntityUid uid, ParacusiaComponent component, ref ComponentHandleState args) + { + if (args.Current is not ParacusiaComponentState state) + return; + + component.MaxTimeBetweenIncidents = state.MaxTimeBetweenIncidents; + component.MinTimeBetweenIncidents = state.MinTimeBetweenIncidents; + component.MaxSoundDistance = state.MaxSoundDistance; + component.Sounds = state.Sounds; + } +} diff --git a/Resources/Locale/en-US/traits/traits.ftl b/Resources/Locale/en-US/traits/traits.ftl index 0700969aaf..b9ac930021 100644 --- a/Resources/Locale/en-US/traits/traits.ftl +++ b/Resources/Locale/en-US/traits/traits.ftl @@ -10,3 +10,12 @@ trait-sneezing-name = Runny nose trait-sneezing-desc = You sneeze and cough uncontrollably permanent-blindness-trait-examined = [color=lightblue]{CAPITALIZE(POSS-ADJ($target))} eyes are glassy and unfocused. It doesn't seem like {SUBJECT($target)} can see you.[/color] + +trait-lightweight-name = Lightweight Drunk +trait-lightweight-desc = Alcohol has a stronger effect on you + +trait-muted-name = Muted +trait-muted-desc = You can't speak + +trait-paracusia-name = Paracusia +trait-paracusia-desc = You hear sounds that aren't really there diff --git a/Resources/Prototypes/SoundCollections/traits.yml b/Resources/Prototypes/SoundCollections/traits.yml new file mode 100644 index 0000000000..5b140541a4 --- /dev/null +++ b/Resources/Prototypes/SoundCollections/traits.yml @@ -0,0 +1,76 @@ +- type: soundCollection + id: Paracusia + files: + #- /Audio/Effects/adminhelp.ogg + #- /Audio/Machines/Nuke/nuke_alarm.ogg + #- /Audio/Misc/emergency_meeting.ogg + - /Audio/Effects/countdown.ogg + - /Audio/Effects/explosion1.ogg + - /Audio/Effects/explosion2.ogg + - /Audio/Effects/explosion3.ogg + - /Audio/Effects/explosion4.ogg + - /Audio/Effects/explosion5.ogg + - /Audio/Effects/explosion6.ogg + - /Audio/Effects/glass_break1.ogg + - /Audio/Effects/glass_break2.ogg + - /Audio/Effects/glass_break3.ogg + - /Audio/Effects/bodyfall1.ogg + - /Audio/Effects/bodyfall2.ogg + - /Audio/Effects/bodyfall3.ogg + - /Audio/Effects/bodyfall4.ogg + - /Audio/Effects/demon_dies.ogg + - /Audio/Effects/demon_attack1.ogg + - /Audio/Effects/bang.ogg + - /Audio/Effects/clang.ogg + - /Audio/Effects/metalbreak.ogg + - /Audio/Effects/minibombcountdown.ogg + - /Audio/Effects/sadtrombone.ogg + - /Audio/Effects/sparks1.ogg + - /Audio/Effects/sparks2.ogg + - /Audio/Effects/sparks3.ogg + - /Audio/Effects/sparks4.ogg + - /Audio/Effects/radpulse1.ogg + - /Audio/Effects/radpulse5.ogg + - /Audio/Effects/radpulse9.ogg + - /Audio/Effects/Chemistry/bubbles.ogg + - /Audio/Machines/airlock_close.ogg + - /Audio/Machines/airlock_deny.ogg + - /Audio/Machines/airlock_open.ogg + - /Audio/Machines/airlock_ext_open.ogg + - /Audio/Machines/anomaly_generate.ogg + - /Audio/Machines/phasein.ogg + - /Audio/Machines/vending_restock_start.ogg + - /Audio/Machines/vending_restock_done.ogg + - /Audio/Magic/disintegrate.ogg + - /Audio/Magic/staff_animation.ogg + - /Audio/Weapons/ebladeon.ogg + - /Audio/Weapons/smash.ogg + - /Audio/Weapons/bladeslice.ogg + - /Audio/Weapons/punch1.ogg + - /Audio/Weapons/punch2.ogg + - /Audio/Weapons/punch3.ogg + - /Audio/Weapons/punch4.ogg + - /Audio/Weapons/genhit1.ogg + - /Audio/Weapons/Guns/Hits/bullet_hit.ogg + - /Audio/Weapons/Guns/Hits/snap.ogg + - /Audio/Weapons/Guns/Gunshots/atreides.ogg + - /Audio/Weapons/Guns/Gunshots/c-20r.ogg + - /Audio/Weapons/Guns/Gunshots/pistol.ogg + - /Audio/Items/bikehorn.ogg + - /Audio/Items/Toys/weh.ogg + - /Audio/Items/Toys/toysqueak1.ogg + - /Audio/Items/Toys/toysqueak2.ogg + - /Audio/Items/Toys/toysqueak3.ogg + - /Audio/Voice/Talk/lizard.ogg + - /Audio/Voice/Talk/pai.ogg + - /Audio/Voice/Talk/speak_1.ogg + - /Audio/Voice/Talk/speak_2_ask.ogg + - /Audio/Voice/Talk/speak_3_exclaim.ogg + - /Audio/Voice/Human/malescream_1.ogg + - /Audio/Voice/Human/malescream_6.ogg + - /Audio/Voice/Human/femalescream_2.ogg + - /Audio/Voice/Human/femalescream_4.ogg + - /Audio/Voice/Zombie/zombie-1.ogg + - /Audio/Voice/Zombie/zombie-2.ogg + - /Audio/Voice/Zombie/zombie-3.ogg + - /Audio/Voice/Vox/shriek1.ogg diff --git a/Resources/Prototypes/Traits/disabilities.yml b/Resources/Prototypes/Traits/disabilities.yml index c5ed02c946..c6deeff61f 100644 --- a/Resources/Prototypes/Traits/disabilities.yml +++ b/Resources/Prototypes/Traits/disabilities.yml @@ -22,3 +22,22 @@ name: trait-pacifist-name components: - type: Pacifist + +- type: trait + id: Paracusia + name: trait-paracusia-name + description: trait-paracusia-desc + components: + - type: Paracusia + minTimeBetweenIncidents: 0.1 + maxTimeBetweenIncidents: 300 + maxSoundDistance: 7 + sounds: + collection: Paracusia + +- type: trait + id: Muted + name: trait-muted-name + description: trait-muted-desc + components: + - type: Muted diff --git a/Resources/Prototypes/Traits/inconveniences.yml b/Resources/Prototypes/Traits/inconveniences.yml index de25174c43..54fbd252cc 100644 --- a/Resources/Prototypes/Traits/inconveniences.yml +++ b/Resources/Prototypes/Traits/inconveniences.yml @@ -12,3 +12,11 @@ params: variation: 0.2 timeBetweenIncidents: 0.3, 300 + +- type: trait + id: LightweightDrunk + name: trait-lightweight-name + description: trait-lightweight-desc + components: + - type: LightweightDrunk + boozeStrengthMultiplier: 2