Skip to content

Commit 666e26e

Browse files
authored
Merge branch 'funky-station:master' into master
2 parents ae11680 + c6ab433 commit 666e26e

177 files changed

Lines changed: 10149 additions & 3214 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
// SPDX-FileCopyrightText: 2025 Terkala <appleorange64@gmail.com>
2+
//
3+
// SPDX-License-Identifier: AGPL-3.0-or-later OR MIT
4+
5+
using System.Collections.Generic;
6+
using Content.Shared.BloodCult;
7+
using Content.Shared.BloodCult.Components;
8+
using Content.Shared.Weapons.Melee.Events;
9+
using Robust.Shared.GameObjects;
10+
11+
namespace Content.Client._Funkystation.BloodCult.Systems;
12+
13+
/// <summary>
14+
/// Mirrors the server-side ally protection logic so clientside prediction doesn't show fake hits.
15+
/// </summary>
16+
public sealed class BloodCultMeleePredictionSystem : EntitySystem
17+
{
18+
public override void Initialize()
19+
{
20+
base.Initialize();
21+
SubscribeLocalEvent<BloodCultMeleeWeaponComponent, MeleeHitEvent>(OnMeleeHit);
22+
}
23+
24+
private void OnMeleeHit(EntityUid uid, BloodCultMeleeWeaponComponent component, MeleeHitEvent args)
25+
{
26+
if (!args.IsHit || args.HitEntities.Count == 0)
27+
return;
28+
29+
if (args.HitEntities is not List<EntityUid> hitList)
30+
return;
31+
32+
var removedAny = false;
33+
34+
for (var i = hitList.Count - 1; i >= 0; i--)
35+
{
36+
var target = hitList[i];
37+
38+
if (!HasComp<BloodCultistComponent>(target) && !HasComp<BloodCultConstructComponent>(target))
39+
continue;
40+
41+
hitList.RemoveAt(i);
42+
removedAny = true;
43+
}
44+
45+
if (!removedAny)
46+
return;
47+
48+
if (hitList.Count == 0)
49+
args.Handled = true;
50+
}
51+
}
52+
Lines changed: 275 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,275 @@
1+
// SPDX-FileCopyrightText: 2025 Terkala <appleorange64@gmail.com>
2+
//
3+
// SPDX-License-Identifier: AGPL-3.0-or-later OR MIT
4+
5+
using System;
6+
using System.Collections.Generic;
7+
using System.Diagnostics.CodeAnalysis;
8+
using Content.Client._Funkystation.Effects.ManualPlayback.Components;
9+
using Content.Client._Funkystation.Effects.ManualPlayback.Systems;
10+
using Content.Shared.BloodCult;
11+
using Content.Shared.Weapons.Ranged.Systems;
12+
using Robust.Client.GameObjects;
13+
using Robust.Client.Graphics;
14+
using Robust.Shared.GameObjects;
15+
using Robust.Shared.Graphics;
16+
using Robust.Shared.Graphics.RSI;
17+
using Robust.Shared.Map;
18+
using Robust.Shared.Maths;
19+
using Robust.Shared.Timing;
20+
using TimedDespawnComponent = Robust.Shared.Spawners.TimedDespawnComponent;
21+
22+
namespace Content.Client._Funkystation.BloodCult.Systems;
23+
24+
public sealed class BloodCultRuneEffectSystem : EntitySystem
25+
{
26+
private readonly Dictionary<uint, EntityUid> _activeEffects = new();
27+
28+
[Dependency] private readonly IGameTiming _timing = default!;
29+
[Dependency] private readonly SpriteSystem _sprite = default!;
30+
31+
private ManualPlaybackEffectSystem? _manualPlaybackSystem;
32+
33+
private const string TearVeilEffectPrototype = "TearVeilRune_drawing";
34+
35+
public override void Initialize()
36+
{
37+
base.Initialize();
38+
SubscribeNetworkEvent<RuneDrawingEffectEvent>(OnRuneEffectEvent);
39+
EntityManager.EntitySysManager.TryGetEntitySystem(out _manualPlaybackSystem);
40+
EntityManager.EntitySysManager.SystemLoaded += OnSystemLoaded;
41+
EntityManager.EntitySysManager.SystemUnloaded += OnSystemUnloaded;
42+
}
43+
44+
public override void Shutdown()
45+
{
46+
base.Shutdown();
47+
EntityManager.EntitySysManager.SystemLoaded -= OnSystemLoaded;
48+
EntityManager.EntitySysManager.SystemUnloaded -= OnSystemUnloaded;
49+
}
50+
51+
private void OnSystemLoaded(object? sender, SystemChangedArgs args)
52+
{
53+
if (args.System is ManualPlaybackEffectSystem manual)
54+
_manualPlaybackSystem = manual;
55+
}
56+
57+
private void OnSystemUnloaded(object? sender, SystemChangedArgs args)
58+
{
59+
if (args.System is ManualPlaybackEffectSystem)
60+
_manualPlaybackSystem = null;
61+
}
62+
63+
private void OnRuneEffectEvent(RuneDrawingEffectEvent ev)
64+
{
65+
var coordinates = GetCoordinates(ev.Coordinates);
66+
67+
switch (ev.Action)
68+
{
69+
case RuneEffectAction.Start:
70+
HandleStart(ev, coordinates);
71+
break;
72+
case RuneEffectAction.Stop:
73+
HandleStop(ev);
74+
break;
75+
}
76+
}
77+
78+
private void HandleStart(RuneDrawingEffectEvent ev, EntityCoordinates coordinates)
79+
{
80+
if (string.IsNullOrEmpty(ev.Prototype))
81+
return;
82+
83+
if (_activeEffects.TryGetValue(ev.EffectId, out var existing) && !Deleted(existing))
84+
QueueDel(existing);
85+
86+
var effect = Spawn(ev.Prototype, coordinates);
87+
88+
if (!TryComp<SpriteComponent>(effect, out var sprite))
89+
{
90+
_activeEffects.Remove(ev.EffectId);
91+
return;
92+
}
93+
94+
var spriteEntity = new Entity<SpriteComponent?>(effect, sprite);
95+
var manualEffect = EnsureComp<ManualPlaybackStateComponent>(effect);
96+
manualEffect.Duration = ev.Duration;
97+
manualEffect.StartTime = _timing.RealTime;
98+
ResetState(manualEffect);
99+
100+
manualEffect.LayerKey = EffectLayers.Unshaded;
101+
manualEffect.RowsPerColumn = 4;
102+
manualEffect.ColumnsOverride = 0;
103+
manualEffect.Direction = RsiDirection.South;
104+
var isTearVeil = string.Equals(ev.Prototype, TearVeilEffectPrototype, StringComparison.Ordinal);
105+
106+
manualEffect.ManualPlaybackEnabled = !isTearVeil;
107+
108+
var useManual = manualEffect.ManualPlaybackEnabled
109+
&& TryGetManualPlaybackSystem(out var manualSystem)
110+
&& manualSystem.TryEnableManualPlayback(spriteEntity, manualEffect);
111+
112+
if (!useManual)
113+
{
114+
var handledFallback = false;
115+
116+
if (isTearVeil)
117+
handledFallback = TryEnableTearVeilFallback(spriteEntity, manualEffect);
118+
119+
if (!handledFallback)
120+
{
121+
manualEffect.ActiveManualPlayback = false;
122+
manualEffect.Frames = Array.Empty<Texture>();
123+
manualEffect.FrameDelays = Array.Empty<float>();
124+
manualEffect.TotalDelay = 0f;
125+
126+
if (_sprite.TryGetLayer(spriteEntity, manualEffect.LayerKey, out var layer, false))
127+
{
128+
manualEffect.BaseColor = layer.Color;
129+
manualEffect.BaseShader = layer.ShaderPrototype;
130+
manualEffect.BaseUnshaded = layer.ShaderPrototype == SpriteSystem.UnshadedId;
131+
if (!_sprite.LayerMapTryGet(spriteEntity, manualEffect.LayerKey, out var layerIndex, false))
132+
layerIndex = -1;
133+
manualEffect.LayerIndex = layerIndex;
134+
135+
_sprite.LayerSetAutoAnimated(spriteEntity, manualEffect.LayerKey, true);
136+
_sprite.LayerSetAnimationTime(spriteEntity, manualEffect.LayerKey, 0f);
137+
if (manualEffect.LayerIndex >= 0)
138+
{
139+
if (manualEffect.BaseUnshaded)
140+
sprite.LayerSetShader(manualEffect.LayerIndex, (ShaderInstance?) null, SpriteSystem.UnshadedId.Id);
141+
else if (manualEffect.BaseShader is { } shader)
142+
sprite.LayerSetShader(manualEffect.LayerIndex, shader.Id);
143+
else
144+
sprite.LayerSetShader(manualEffect.LayerIndex, (ShaderInstance?) null, null);
145+
}
146+
_sprite.LayerSetColor(spriteEntity, manualEffect.LayerKey, manualEffect.BaseColor);
147+
}
148+
}
149+
}
150+
151+
if (TryComp(effect, out TimedDespawnComponent? despawn))
152+
{
153+
var manualDuration = manualEffect.ActiveManualPlayback ? Math.Max(manualEffect.TotalDelay, (float) ev.Duration.TotalSeconds) : (float) ev.Duration.TotalSeconds;
154+
var desiredLifetime = manualDuration + 0.5f;
155+
if (despawn.Lifetime < desiredLifetime)
156+
despawn.Lifetime = desiredLifetime;
157+
}
158+
159+
_activeEffects[ev.EffectId] = effect;
160+
}
161+
162+
private void HandleStop(RuneDrawingEffectEvent ev)
163+
{
164+
if (!_activeEffects.TryGetValue(ev.EffectId, out var effectUid))
165+
return;
166+
167+
_activeEffects.Remove(ev.EffectId);
168+
169+
if (Deleted(effectUid))
170+
return;
171+
172+
if (!TryComp(effectUid, out ManualPlaybackStateComponent? manualEffect) ||
173+
!TryComp<SpriteComponent>(effectUid, out var sprite))
174+
{
175+
QueueDel(effectUid);
176+
return;
177+
}
178+
179+
var spriteEntity = new Entity<SpriteComponent?>(effectUid, sprite);
180+
181+
if (manualEffect.ActiveManualPlayback && TryGetManualPlaybackSystem(out var manualSystem))
182+
{
183+
manualSystem.ApplyFinalFrame(spriteEntity, manualEffect);
184+
}
185+
186+
QueueDel(effectUid);
187+
}
188+
189+
private bool TryGetManualPlaybackSystem([NotNullWhen(true)] out ManualPlaybackEffectSystem? system)
190+
{
191+
if (_manualPlaybackSystem != null)
192+
{
193+
system = _manualPlaybackSystem;
194+
return true;
195+
}
196+
197+
if (EntityManager.EntitySysManager.TryGetEntitySystem<ManualPlaybackEffectSystem>(out var resolved))
198+
{
199+
_manualPlaybackSystem = resolved;
200+
system = resolved;
201+
return true;
202+
}
203+
204+
system = null;
205+
return false;
206+
}
207+
208+
private static void ResetState(ManualPlaybackStateComponent state)
209+
{
210+
state.ActiveManualPlayback = false;
211+
state.Frames = Array.Empty<Texture>();
212+
state.FrameDelays = Array.Empty<float>();
213+
state.TotalDelay = 0f;
214+
state.BaseShader = null;
215+
state.BaseUnshaded = false;
216+
state.BaseColor = Color.White;
217+
state.LayerIndex = -1;
218+
}
219+
220+
private bool TryEnableTearVeilFallback(Entity<SpriteComponent?> spriteEntity, ManualPlaybackStateComponent manualEffect)
221+
{
222+
if (!_sprite.TryGetLayer(spriteEntity, manualEffect.LayerKey, out var layer, false))
223+
return false;
224+
225+
manualEffect.BaseShader = layer.ShaderPrototype;
226+
manualEffect.BaseUnshaded = layer.ShaderPrototype == SpriteSystem.UnshadedId;
227+
if (!_sprite.LayerMapTryGet(spriteEntity, manualEffect.LayerKey, out var layerIndex, false))
228+
layerIndex = -1;
229+
manualEffect.LayerIndex = layerIndex;
230+
231+
var state = layer.ActualState;
232+
if (state == null)
233+
return false;
234+
235+
var frames = state.GetFrames(manualEffect.Direction);
236+
if (frames.Length == 0)
237+
return false;
238+
239+
var delays = state.GetDelays();
240+
var frameDelays = new float[frames.Length];
241+
242+
for (var i = 0; i < frames.Length; i++)
243+
frameDelays[i] = delays.Length > i ? delays[i] : 0.1f;
244+
245+
manualEffect.Frames = frames;
246+
manualEffect.FrameDelays = frameDelays;
247+
248+
var totalDelay = 0f;
249+
foreach (var delay in frameDelays)
250+
totalDelay += delay;
251+
252+
if (totalDelay <= 0f)
253+
totalDelay = frameDelays.Length * 0.1f;
254+
255+
manualEffect.TotalDelay = totalDelay;
256+
manualEffect.BaseColor = layer.Color;
257+
manualEffect.ActiveManualPlayback = true;
258+
259+
_sprite.LayerSetAutoAnimated(spriteEntity, manualEffect.LayerKey, false);
260+
_sprite.LayerSetTexture(spriteEntity, manualEffect.LayerKey, frames[0]);
261+
_sprite.LayerSetColor(spriteEntity, manualEffect.LayerKey, manualEffect.BaseColor);
262+
263+
if (manualEffect.LayerIndex >= 0)
264+
{
265+
if (manualEffect.BaseUnshaded)
266+
spriteEntity.Comp?.LayerSetShader(manualEffect.LayerIndex, (ShaderInstance?) null, SpriteSystem.UnshadedId.Id);
267+
else if (manualEffect.BaseShader is { } shader)
268+
spriteEntity.Comp?.LayerSetShader(manualEffect.LayerIndex, shader.Id);
269+
else
270+
spriteEntity.Comp?.LayerSetShader(manualEffect.LayerIndex, (ShaderInstance?) null, null);
271+
}
272+
273+
return true;
274+
}
275+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
// SPDX-FileCopyrightText: 2025 Terkala <appleorange64@gmail.com>
2+
//
3+
// SPDX-License-Identifier: AGPL-3.0-or-later OR MIT
4+
5+
using System;
6+
using Content.Shared.Weapons.Ranged.Systems;
7+
using Robust.Client.Graphics;
8+
using Robust.Shared.GameObjects;
9+
using Robust.Shared.Graphics.RSI;
10+
using Robust.Shared.Maths;
11+
using Robust.Shared.Prototypes;
12+
13+
namespace Content.Client._Funkystation.Effects.ManualPlayback.Components;
14+
15+
/// <summary>
16+
/// Client-side state for manually controlling playback of a sprite layer.
17+
/// </summary>
18+
[RegisterComponent]
19+
public sealed partial class ManualPlaybackStateComponent : Component
20+
{
21+
public TimeSpan Duration;
22+
public TimeSpan StartTime;
23+
24+
public EffectLayers LayerKey = EffectLayers.Unshaded;
25+
26+
public int RowsPerColumn = 4;
27+
28+
public int ColumnsOverride = 0;
29+
30+
public RsiDirection Direction = RsiDirection.South;
31+
32+
public bool ManualPlaybackEnabled = true;
33+
34+
public bool ActiveManualPlayback;
35+
36+
public Texture[] Frames = Array.Empty<Texture>();
37+
public float[] FrameDelays = Array.Empty<float>();
38+
public float TotalDelay;
39+
public Color BaseColor = Color.White;
40+
41+
public ProtoId<ShaderPrototype>? BaseShader;
42+
43+
public bool BaseUnshaded;
44+
public int LayerIndex = -1;
45+
}
46+

0 commit comments

Comments
 (0)