diff --git a/Content.Server/_Amour/Hallucinations/HallucinationsEffect.cs b/Content.Server/_Amour/Hallucinations/HallucinationsEffect.cs
new file mode 100644
index 0000000000..b3b95421c7
--- /dev/null
+++ b/Content.Server/_Amour/Hallucinations/HallucinationsEffect.cs
@@ -0,0 +1,66 @@
+using Content.Server._Amour.Hallucinations;
+using Content.Shared.Chemistry.Reagent;
+using Content.Shared.StatusEffect;
+using Robust.Shared.Prototypes;
+
+namespace Content.Server.Chemistry.ReagentEffects
+{
+ ///
+ /// That looks like GenericStatusEffect but with hallucinations pack selection
+ ///
+ public sealed partial class HallucinationsReagentEffect : ReagentEffect
+ {
+ [DataField("key")]
+ public string Key = "Hallucinations";
+
+ [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;
+
+ protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
+ {
+ return Loc.GetString("reagent-effect-guidebook-status-effect",
+ ("chance", Probability),
+ ("type", Type),
+ ("time", Time),
+ ("key", $"reagent-effect-status-effect-{Key}"));
+ }
+
+ public override void Effect(ReagentEffectArgs args)
+ {
+ var statusSys = args.EntityManager.EntitySysManager.GetEntitySystem();
+ var hallucinationsSys = args.EntityManager.EntitySysManager.GetEntitySystem();
+
+ var time = Time;
+ time *= args.Scale;
+
+ if (Type == HallucinationsMetabolismType.Add)
+ {
+ if (!hallucinationsSys.StartHallucinations(args.SolutionEntity, Key, TimeSpan.FromSeconds(Time), Refresh, Proto))
+ return;
+ }
+ else if (Type == HallucinationsMetabolismType.Remove)
+ {
+ statusSys.TryRemoveTime(args.SolutionEntity, Key, TimeSpan.FromSeconds(time));
+ }
+ else if (Type == HallucinationsMetabolismType.Set)
+ {
+ statusSys.TrySetTime(args.SolutionEntity, Key, TimeSpan.FromSeconds(time));
+ }
+ }
+ }
+ public enum HallucinationsMetabolismType
+ {
+ Add,
+ Remove,
+ Set
+ }
+}
diff --git a/Content.Server/_Amour/Hallucinations/HallucinationsSystem.cs b/Content.Server/_Amour/Hallucinations/HallucinationsSystem.cs
new file mode 100644
index 0000000000..666f3c42f2
--- /dev/null
+++ b/Content.Server/_Amour/Hallucinations/HallucinationsSystem.cs
@@ -0,0 +1,149 @@
+using Content.Shared._Amour.Hallucinations;
+using Content.Shared.Administration.Logs;
+using Content.Shared.Database;
+using Content.Shared.Humanoid;
+using Content.Shared.StatusEffect;
+using Robust.Server.GameObjects;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Random;
+using Robust.Shared.Timing;
+
+namespace Content.Server._Amour.Hallucinations;
+
+public sealed partial class HallucinationsSystem : EntitySystem
+{
+ [Dependency] private readonly EntityLookupSystem _lookup = default!;
+ [Dependency] private readonly IEntityManager _entityManager = default!;
+ [Dependency] private readonly IGameTiming _timing = default!;
+ [Dependency] private readonly IPrototypeManager _proto = default!;
+ [Dependency] private readonly IRobustRandom _random = default!;
+ [Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
+ [Dependency] private readonly SharedEyeSystem _eye = default!;
+ [Dependency] private readonly StatusEffectsSystem _status = default!;
+ [Dependency] private readonly VisibilitySystem _visibilitySystem = default!;
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeLocalEvent(OnHallucinationsInit);
+ SubscribeLocalEvent(OnHallucinationsShutdown);
+ }
+
+ private void OnHallucinationsInit(EntityUid uid, HallucinationsComponent component, MapInitEvent args)
+ {
+ component.Layer = _random.Next(100, 150);
+ 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.");
+ }
+
+ ///
+ /// Updates hallucinations component settings to match prototype
+ ///
+ /// Active HallucinationsComponent
+ 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.");
+ }
+
+ ///
+ /// 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;
+ }
+ 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;
+
+ // To be sure that entity will see right entities
+ UpdatePreset(stat);
+
+ // More people - worse hallucinations
+ 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(_random.Pick(stat.Spawns), newCoords);
+ EnsureComp(hallucination, out var visibility);
+ _visibilitySystem.SetLayer(hallucination, visibility, (int) stat.Layer, false);
+ _visibilitySystem.RefreshVisibility(hallucination, visibilityComponent: visibility);
+ }
+
+ // If there is no one... You are hallucinations source too
+ var uidnewCoords = Transform(uid).MapPosition.Offset(_random.NextVector2(stat.Range));
+ if (stat.SpawnedCount >= stat.MaxSpawns)
+ continue;
+ stat.SpawnedCount = stat.SpawnedCount += 1;
+
+ var uidhallucination = Spawn(_random.Pick(stat.Spawns), uidnewCoords);
+ EnsureComp(uidhallucination, out var uidvisibility);
+ _visibilitySystem.SetLayer(uidhallucination, uidvisibility, (int) stat.Layer, false);
+ _visibilitySystem.RefreshVisibility(uidhallucination, visibilityComponent: uidvisibility);
+
+ }
+ }
+
+}
diff --git a/Content.Shared/_Amour/Hallucinations/HallucinationsComponent.cs b/Content.Shared/_Amour/Hallucinations/HallucinationsComponent.cs
new file mode 100644
index 0000000000..c933ceab13
--- /dev/null
+++ b/Content.Shared/_Amour/Hallucinations/HallucinationsComponent.cs
@@ -0,0 +1,74 @@
+using Robust.Shared.Prototypes;
+using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
+
+namespace Content.Shared._Amour.Hallucinations;
+
+[RegisterComponent]
+public sealed partial class HallucinationsComponent : Component
+{
+ [DataField(customTypeSerializer: typeof(TimeOffsetSerializer)), ViewVariables(VVAccess.ReadWrite)]
+ public TimeSpan NextSecond = TimeSpan.Zero;
+
+ ///
+ /// How far from humanoid can appear hallucination
+ ///
+ [DataField, ViewVariables(VVAccess.ReadWrite)]
+ public float Range = 7f;
+
+ ///
+ /// How often (in seconds) hallucinations spawned
+ ///
+ [DataField, ViewVariables(VVAccess.ReadWrite)]
+ public float SpawnRate = 15f;
+
+ ///
+ /// Minimum spawn chance per humanoid
+ ///
+ [DataField, ViewVariables(VVAccess.ReadWrite)]
+ public float MinChance = 0.1f;
+
+ ///
+ /// Max spawn chance per humanoid
+ ///
+ [DataField, ViewVariables(VVAccess.ReadWrite)]
+ public float MaxChance = 0.8f;
+
+ ///
+ /// How much chance increased per spawn
+ ///
+ [DataField, ViewVariables(VVAccess.ReadWrite)]
+ public float IncreaseChance = 0.1f;
+
+ ///
+ /// Max spawned hallucinations count for one spawn
+ ///
+ [DataField, ViewVariables(VVAccess.ReadWrite)]
+ public int MaxSpawns = 3;
+
+ ///
+ /// How much entities already spawned
+ ///
+ public int SpawnedCount = 0;
+
+ ///
+ /// Current spawn chance
+ ///
+ public float CurChance = 0.1f;
+
+ ///
+ /// List of prototypes that are spawned as a hallucination.
+ ///
+ [DataField, ViewVariables(VVAccess.ReadWrite)]
+ public List Spawns = new();
+
+ ///
+ /// Hallucinations pack proto
+ ///
+ [DataField, ViewVariables(VVAccess.ReadWrite)]
+ public HallucinationsPrototype? Proto;
+
+ ///
+ /// Currently selected for hallucinations layer
+ ///
+ public int Layer = 50;
+}
diff --git a/Content.Shared/_Amour/Hallucinations/HallucinationsPrototype.cs b/Content.Shared/_Amour/Hallucinations/HallucinationsPrototype.cs
new file mode 100644
index 0000000000..45f506f0fa
--- /dev/null
+++ b/Content.Shared/_Amour/Hallucinations/HallucinationsPrototype.cs
@@ -0,0 +1,56 @@
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared._Amour.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();
+
+ ///
+ /// How far from humanoid can appear hallucination
+ ///
+ [DataField("spawnRange")]
+ public float Range = 7f;
+
+ ///
+ /// How often (in seconds) hallucinations spawned
+ ///
+ [DataField("spawnRate")]
+ public float SpawnRate = 15f;
+
+ ///
+ /// Minimum spawn chance per humanoid
+ ///
+ [DataField("minChance")]
+ public float MinChance = 0.8f;
+
+ ///
+ /// Max spawn chance per humanoid
+ ///
+ [DataField("maxChance")]
+ public float MaxChance = 0.8f;
+
+ ///
+ /// How much chance increased per spawn
+ ///
+ [DataField("increasedPerSpawn")]
+ public float IncreaseChance = 0.1f;
+
+ ///
+ /// Max spawned hallucinations count for one spawn
+ ///
+ [DataField("maxSpawns")]
+ public int MaxSpawns = 3;
+}
diff --git a/Resources/Audio/Ambience/Objects/attributions.yml b/Resources/Audio/Ambience/Objects/attributions.yml
index e5cd81a372..8e6e787977 100644
--- a/Resources/Audio/Ambience/Objects/attributions.yml
+++ b/Resources/Audio/Ambience/Objects/attributions.yml
@@ -62,3 +62,8 @@
license: "CC-BY-4.0"
copyright: "Taken and edited from source"
source: "https://freesound.org/people/juskiddink/sounds/215658/"
+
+- files: ["base-hallucination-mob.ogg"]
+ license: "CC0-1.0"
+ copyright: "Taken and edited from source"
+ source: "https://freesound.org/people/Nihil_Existentia/sounds/703361/"
diff --git a/Resources/Audio/Ambience/Objects/base-hallucination-mob.ogg b/Resources/Audio/Ambience/Objects/base-hallucination-mob.ogg
new file mode 100644
index 0000000000..67f66e3bdc
Binary files /dev/null and b/Resources/Audio/Ambience/Objects/base-hallucination-mob.ogg differ
diff --git a/Resources/Locale/ru-RU/guidebook/chemistry/statuseffects.ftl b/Resources/Locale/ru-RU/guidebook/chemistry/statuseffects.ftl
index 6626968466..cc880dc5eb 100644
--- a/Resources/Locale/ru-RU/guidebook/chemistry/statuseffects.ftl
+++ b/Resources/Locale/ru-RU/guidebook/chemistry/statuseffects.ftl
@@ -2,7 +2,7 @@ reagent-effect-status-effect-Stun = оглушительный
reagent-effect-status-effect-KnockedDown = нокаутирующий
reagent-effect-status-effect-Jitter = дрожание
reagent-effect-status-effect-TemporaryBlindness = ослепление
-reagent-effect-status-effect-SeeingRainbows = галлюцинации
+reagent-effect-status-effect-SeeingRainbows = видеть радугу
reagent-effect-status-effect-Muted = неспособность говорить
reagent-effect-status-effect-Stutter = заикание
reagent-effect-status-effect-ForcedSleep = бессознательность
diff --git a/Resources/Prototypes/Reagents/toxins.yml b/Resources/Prototypes/Reagents/toxins.yml
index 954d4a6bef..1ecd45867d 100644
--- a/Resources/Prototypes/Reagents/toxins.yml
+++ b/Resources/Prototypes/Reagents/toxins.yml
@@ -346,6 +346,12 @@
type: Add
time: 10
refresh: false
+ - !type:HallucinationsReagentEffect
+ key: Hallucinations
+ proto: MindBreaker
+ type: Add
+ time: 20
+ refresh: true
# TODO: PROPER hallucinations
- type: reagent
diff --git a/Resources/Prototypes/_Amour/Entities/Mobs/NPC/hallucinations.yml b/Resources/Prototypes/_Amour/Entities/Mobs/NPC/hallucinations.yml
new file mode 100644
index 0000000000..1b84c78cdd
--- /dev/null
+++ b/Resources/Prototypes/_Amour/Entities/Mobs/NPC/hallucinations.yml
@@ -0,0 +1,120 @@
+# Base
+- type: entity
+ parent: BaseMob
+ id: BaseEntityHallucination
+ name: "???"
+ description: "???"
+ suffix: DO NOT MAP
+ abstract: true
+ components:
+ #- type: Hallucination
+ - type: Fixtures
+ fixtures:
+ fix1:
+ shape:
+ !type:PhysShapeCircle
+ radius: 0.35
+ density: 15
+ mask:
+ - MobMask
+ layer:
+ - None
+ - type: TimedDespawn
+ lifetime: 15.0
+
+
+- type: entity
+ parent: BaseEntityHallucination
+ id: HallucinationMobMedical
+ name: medical
+ description: That one looks friendly!
+ noSpawn: true
+ components:
+ - type: Sprite
+ sprite: Mobs/Aliens/Hallucinations/base_hallucinations.rsi
+ state: medical
+ - type: MovementSpeedModifier
+ baseWalkSpeed: 2.0
+ baseSprintSpeed: 3.7
+ - type: HTN
+ rootTask:
+ task: IdleCompound
+ - type: AmbientSound
+ volume: -2
+ range: 15
+ sound:
+ path: /Audio/Ambience/Objects/base-hallucination-mob.ogg
+ - type: TimedDespawn
+ lifetime: 45.0
+
+- type: entity
+ parent: BaseEntityHallucination
+ id: HallucinationMobNukeop
+ name: nukie
+ description: Boom.
+ noSpawn: true
+ components:
+ - type: Sprite
+ sprite: Mobs/Aliens/Hallucinations/base_hallucinations.rsi
+ state: nukeop
+ - type: MovementSpeedModifier
+ baseWalkSpeed: 3.0
+ baseSprintSpeed: 4.7
+ - type: HTN
+ rootTask:
+ task: IdleCompound
+ - type: AmbientSound
+ volume: -2
+ range: 15
+ sound:
+ path: /Audio/Ambience/Objects/base-hallucination-mob.ogg
+ - type: TimedDespawn
+ lifetime: 25.0
+
+- type: entity
+ parent: BaseEntityHallucination
+ id: HallucinationMobMusician
+ name: musician
+ description: He is definetly playing something...
+ noSpawn: true
+ components:
+ - type: Sprite
+ sprite: Mobs/Aliens/Hallucinations/base_hallucinations.rsi
+ state: musician
+ - type: MovementSpeedModifier
+ baseWalkSpeed: 1.0
+ baseSprintSpeed: 1.7
+ - type: HTN
+ rootTask:
+ task: IdleCompound
+ - type: AmbientSound
+ volume: -2
+ range: 15
+ sound:
+ path: /Audio/Ambience/Objects/base-hallucination-mob.ogg
+ - type: TimedDespawn
+ lifetime: 35.0
+
+- type: entity
+ parent: BaseEntityHallucination
+ id: HallucinationMobYeti
+ name: yeti
+ description: I want to believe
+ noSpawn: true
+ components:
+ - type: Sprite
+ sprite: Mobs/Aliens/Hallucinations/base_hallucinations.rsi
+ state: yeti
+ - type: MovementSpeedModifier
+ baseWalkSpeed: 2.0
+ baseSprintSpeed: 3.0
+ - type: HTN
+ rootTask:
+ task: IdleCompound
+ - type: AmbientSound
+ volume: -2
+ range: 15
+ sound:
+ path: /Audio/Ambience/Objects/base-hallucination-mob.ogg
+ - type: TimedDespawn
+ lifetime: 30.0
diff --git a/Resources/Prototypes/_Amour/HallucinationPacks/mindbreaker.yml b/Resources/Prototypes/_Amour/HallucinationPacks/mindbreaker.yml
new file mode 100644
index 0000000000..bee6691bde
--- /dev/null
+++ b/Resources/Prototypes/_Amour/HallucinationPacks/mindbreaker.yml
@@ -0,0 +1,9 @@
+- type: hallucinationsPack
+ id: MindBreaker
+ minChance: 0.4
+ maxChance: 0.9
+ entities:
+ - HallucinationMobMedical
+ - HallucinationMobNukeop
+ - HallucinationMobMusician
+ - HallucinationMobYeti
diff --git a/Resources/Prototypes/status_effects.yml b/Resources/Prototypes/status_effects.yml
index a9ea56db57..69491ac588 100644
--- a/Resources/Prototypes/status_effects.yml
+++ b/Resources/Prototypes/status_effects.yml
@@ -71,3 +71,6 @@
#WD EDIT
- type: statusEffect
id: Incorporeal
+
+- type: statusEffect
+ id: Hallucinations
diff --git a/Resources/Textures/Mobs/Aliens/Hallucinations/base_hallucinations.rsi/medical.png b/Resources/Textures/Mobs/Aliens/Hallucinations/base_hallucinations.rsi/medical.png
new file mode 100644
index 0000000000..2ecb1047fb
Binary files /dev/null and b/Resources/Textures/Mobs/Aliens/Hallucinations/base_hallucinations.rsi/medical.png differ
diff --git a/Resources/Textures/Mobs/Aliens/Hallucinations/base_hallucinations.rsi/meta.json b/Resources/Textures/Mobs/Aliens/Hallucinations/base_hallucinations.rsi/meta.json
new file mode 100644
index 0000000000..22b35a73a5
--- /dev/null
+++ b/Resources/Textures/Mobs/Aliens/Hallucinations/base_hallucinations.rsi/meta.json
@@ -0,0 +1,27 @@
+{
+ "version": 1,
+ "size": {
+ "x": 32,
+ "y": 32
+ },
+ "license": "CC-BY-SA-3.0",
+ "copyright": "Created by discord:bolbyy for SpaceStation14",
+ "states": [
+ {
+ "name": "medical",
+ "directions": 4
+ },
+ {
+ "name": "musician",
+ "directions": 4
+ },
+ {
+ "name": "nukeop",
+ "directions": 4
+ },
+ {
+ "name": "yeti",
+ "directions": 4
+ }
+ ]
+}
diff --git a/Resources/Textures/Mobs/Aliens/Hallucinations/base_hallucinations.rsi/musician.png b/Resources/Textures/Mobs/Aliens/Hallucinations/base_hallucinations.rsi/musician.png
new file mode 100644
index 0000000000..52111cc417
Binary files /dev/null and b/Resources/Textures/Mobs/Aliens/Hallucinations/base_hallucinations.rsi/musician.png differ
diff --git a/Resources/Textures/Mobs/Aliens/Hallucinations/base_hallucinations.rsi/nukeop.png b/Resources/Textures/Mobs/Aliens/Hallucinations/base_hallucinations.rsi/nukeop.png
new file mode 100644
index 0000000000..c95a8d36e4
Binary files /dev/null and b/Resources/Textures/Mobs/Aliens/Hallucinations/base_hallucinations.rsi/nukeop.png differ
diff --git a/Resources/Textures/Mobs/Aliens/Hallucinations/base_hallucinations.rsi/yeti.png b/Resources/Textures/Mobs/Aliens/Hallucinations/base_hallucinations.rsi/yeti.png
new file mode 100644
index 0000000000..06e6263dd3
Binary files /dev/null and b/Resources/Textures/Mobs/Aliens/Hallucinations/base_hallucinations.rsi/yeti.png differ