Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
a5fce29
add(src): Порт того, что я уже нахуевертил на губ
le-kote Dec 24, 2025
e9e2a3b
remove(src): Удаление упоминаний старой системы
le-kote Dec 25, 2025
6a68d38
add(src): Поддержка реагентов
le-kote Dec 25, 2025
0d2dbcb
wip(src): Система сокрытия мобов
le-kote Dec 25, 2025
7a8911e
wip(proto, texture): прототипирование
le-kote Dec 25, 2025
54af4d2
add(src): Использование типов метаболизма для галлюцинаций
le-kote Dec 29, 2025
2850c38
add(src): Система сокрытия мобов стала лучше
le-kote Dec 29, 2025
fb7db83
add(src): Попап при начале галлюцинаций
le-kote Dec 29, 2025
2ea8517
add(src): Дебаг команда галюнов
le-kote Dec 29, 2025
619bff4
add(lang): Локаль для команды
le-kote Dec 29, 2025
f99f4c1
add(proto): Компонент для галлюцинаций мобам
le-kote Dec 29, 2025
7dd4495
tweak(proto, texture): Изменение штуки для лиминалиума
le-kote Dec 29, 2025
bc1ca52
add(texture, proto): Hue шейдер
le-kote Dec 30, 2025
57a30bd
add(src): Рабочий HueShiftComponent
le-kote Dec 30, 2025
3fac1cd
style(src): Структуризация
le-kote Dec 30, 2025
5794a50
add(src): Сокрытие попапов
le-kote Dec 30, 2025
37d0273
style(src): Фикс названия системы
le-kote Dec 30, 2025
be989ca
add(src): Музыка от галллюцинаций
le-kote Dec 30, 2025
df1b18b
add(src): FadeIn для музычки
le-kote Dec 31, 2025
591369d
add(src): СИСТЕМА СКРИМЕРОВ
le-kote Jan 1, 2026
2f305ef
add(lang): Локаль для команды скримера
le-kote Jan 1, 2026
a9bdf92
add(proto, lang): Первые прототипы скримеров
le-kote Jan 1, 2026
1c0e981
add(src): Масштабирование галюнов
le-kote Jan 1, 2026
71f0d9f
fix(src): Фиксик
le-kote Jan 1, 2026
9553116
add(src, texture): Спид
le-kote Jan 1, 2026
31c0833
add(src): Поддержка только скримеров из своей категории
le-kote Jan 1, 2026
7846375
add(src): Общая система с функцией
le-kote Jan 1, 2026
e4f731e
refactor(src): Возможность одному паку иметь несколько эффектов
le-kote Jan 5, 2026
54b1696
add(proto): Маленькие добавления
le-kote Jan 5, 2026
db279a3
fix(src): Фикс отсутствующего компонента
le-kote Sep 2, 2026
33099ee
fix(src): Фикс OverrideEmoteSoundEvent
le-kote Sep 2, 2026
7b1ae97
fix(src): Фикс доступа TryEmoteChatInput
le-kote Sep 2, 2026
edbf7e7
fix(src): Общие фиксы системы
le-kote Sep 2, 2026
0740eeb
fix(src): Ещё больше общих фиксов
le-kote Sep 2, 2026
340a818
refactor(src): Рефактор применения галлюцинаций в чуть более читабель…
le-kote Sep 2, 2026
0cbaa3b
refactor(src): Удалён хардкод отправки сообщения
le-kote Sep 2, 2026
8629e2a
remove(src): Удаление ненужного кода
le-kote Sep 2, 2026
82029b9
add(src): Ентити эффекты галлюцинаций
le-kote Sep 2, 2026
97f642d
fix(src): Фикс сломанного цикла
le-kote Sep 2, 2026
37df159
fix(proto): Фиксы прототипов
le-kote Sep 2, 2026
993936c
fix(src): Фикс забытой правки
le-kote Sep 2, 2026
35ba850
add(src): Команда, добавляющая сущность А как галлюцинацию сущности Б
le-kote Sep 2, 2026
df79a97
style(src, lang): Улучшение локали команд
le-kote Sep 2, 2026
4f8673f
style(src): Большая работа над неймспейсами и варнингами
le-kote Sep 3, 2026
3aa815a
fix(src): Игнорирование прототипов галлюцинаций на клиенте
le-kote Sep 3, 2026
1227b14
style(src): Пояснение
le-kote Sep 3, 2026
d0fee4e
add(src, proto): Эффект укачивания
le-kote Sep 3, 2026
221a814
add(proto): Компонент для скримеров в базе
le-kote Sep 3, 2026
b7fbdbf
refactor(src, proto): Передача раскачивания камеры эффекту от алкоголя
le-kote Sep 3, 2026
ec9f1f1
add(proto, texture): Спрайты и прототипы иконок для определения шизов
le-kote Sep 3, 2026
c629c5c
add(src): Использование иконок
le-kote Sep 3, 2026
86717c3
refactor(src): Рефактор спавна галлюцинаций
le-kote Sep 4, 2026
e53b8bb
style(src): Убрал наследование во всех файлах partial класса кроме ос…
le-kote Sep 4, 2026
ae6ffca
add(src): Компонент, устанавливающий FOV шизойда галлюцинации
le-kote Sep 6, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
270 changes: 270 additions & 0 deletions Content.Client/ADT/Hallucinations/SchizophreniaSystem.cs
Original file line number Diff line number Diff line change
@@ -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<NetEntity, TimeSpan> _layers = new();

public override void Initialize()
{
base.Initialize();

SubscribeNetworkEvent<SetHallucinationAppearanceMessage>(OnAppearanceMessage);

SubscribeLocalEvent<SchizophreniaComponent, GetStatusIconsEvent>(OnGetHallucinatingIcons);
SubscribeLocalEvent<HallucinationComponent, GetStatusIconsEvent>(OnGetHallucinationIcons);

SubscribeLocalEvent<HallucinationsMusicComponent, MapInitEvent>(OnMusicInit);
SubscribeLocalEvent<HallucinationsMusicComponent, ComponentShutdown>(OnMusicShutdown);
SubscribeLocalEvent<HallucinationsMusicComponent, LocalPlayerAttachedEvent>(OnMusicAttach);
SubscribeLocalEvent<HallucinationsMusicComponent, LocalPlayerDetachedEvent>(OnMusicDetach);
SubscribeLocalEvent<HallucinationsMusicComponent, AfterAutoHandleStateEvent>(OnMusicHandleState);
}

private void OnAppearanceMessage(SetHallucinationAppearanceMessage args)
{
if (_player.LocalSession == null)
return;

// Get target entity
var ents = EntityManager.AllEntities<HumanoidProfileComponent>().Where(x => x.Owner != _player.LocalEntity && !HasComp<HallucinationComponent>(x)).ToList();
var selected = _random.Pick(ents);

var proto = _random.Pick(args.Appearance.Prototypes);
var item = Spawn(proto);

if (!TryComp<SpriteComponent>(selected, out var sprite) || !TryComp<SpriteComponent>(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<SchizophreniaComponent> ent, ref GetStatusIconsEvent args)
{
if (!(TryComp<HallucinationComponent>(_player.LocalEntity, out var hallucination) && hallucination.Idx == ent.Comp.Idx) &&
!HasComp<ShowAntagIconsComponent>(_player.LocalEntity))
return;

args.StatusIcons.Add(_proto.Index(ent.Comp.FactionIcon));
}

private void OnGetHallucinationIcons(Entity<HallucinationComponent> ent, ref GetStatusIconsEvent args)
{
if (!(TryComp<HallucinationComponent>(_player.LocalEntity, out var hallucination) && hallucination.Idx == ent.Comp.Idx) &&
!HasComp<ShowAntagIconsComponent>(_player.LocalEntity))
return;

args.StatusIcons.Add(_proto.Index(ent.Comp.FactionIcon));
}

private void OnMusicInit(Entity<HallucinationsMusicComponent> 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<HallucinationsMusicComponent> 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<HallucinationsMusicComponent> 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<HallucinationsMusicComponent> 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<HallucinationsMusicComponent> 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<HallucinationsRemoveMobsComponent>(_player.LocalEntity))
return true;

if (target == _player.LocalEntity)
return true;

if (HasComp<HallucinationComponent>(target))
return true;

if (Transform(target).ParentUid == _player.LocalEntity)
return true;

return false;
}

public override void Update(float frameTime)
{
base.Update(frameTime);

if (!TryComp<HallucinationsMusicComponent>(_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<HallucinationsRemoveMobsComponent>(_player.LocalEntity))
return;

var ents = EntityManager.AllEntities<MobStateComponent>().Where(x => !HasComp<HallucinationComponent>(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));
}
}
}
45 changes: 45 additions & 0 deletions Content.Client/ADT/Overlays/Shaders/HueShiftOverlay.cs
Original file line number Diff line number Diff line change
@@ -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<ShaderPrototype>)"HueShift").InstanceUnique();
}

protected override void Draw(in OverlayDrawArgs args)
{
if (ScreenTexture == null)
return;

if (!_entityManager.TryGetComponent<HueShiftComponent>(_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);
}
}
}
Loading
Loading