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