diff --git a/Content.Client/_White/Misc/ChristmasLights/ChristmasLightsVisualiserSystem.cs b/Content.Client/_White/Misc/ChristmasLights/ChristmasLightsVisualiserSystem.cs index 445385663b..9243eb4508 100644 --- a/Content.Client/_White/Misc/ChristmasLights/ChristmasLightsVisualiserSystem.cs +++ b/Content.Client/_White/Misc/ChristmasLights/ChristmasLightsVisualiserSystem.cs @@ -1,4 +1,6 @@ using Content.Shared._White.Misc.ChristmasLights; +using Content.Shared.Examine; +using Content.Shared.Verbs; using JetBrains.Annotations; using Microsoft.CodeAnalysis.FlowAnalysis.DataFlow; using Robust.Client.GameObjects; @@ -19,10 +21,12 @@ namespace Content.Client._White.Misc.ChristmasLights; -public sealed class ChristmasLightsVisualiserSystem : VisualizerSystem + + +public sealed class ChristmasLightsSystem : SharedChristmasLightsSystem { [Dependency] private readonly IGameTiming _timing = default!; - [Dependency] private readonly IRobustRandom _rng = default!; + //[Dependency] private readonly IReflectionManager _reflection = default!; // we have reflection at home @@ -31,28 +35,71 @@ public override void Initialize() base.Initialize(); SubscribeLocalEvent(OnInit); - //SubscribeLocalEvent(OnAppearanceChange); + SubscribeLocalEvent(OnAutoState); + SubscribeLocalEvent>(OnChristmasLightsAltVerbs); + SubscribeLocalEvent>(OnChristmasLightsVerbs); InitModes(); } - //protected virtual void OnAppearanceChange(EntityUid uid, T component, ref AppearanceChangeEvent args) { } + // Yes, this gets called for every autoupdate. Yes, i should use appearance system and events instead. No, i will not. + private void OnAutoState(EntityUid uid, ChristmasLightsComponent comp, AfterAutoHandleStateEvent args) + { + var sprite = Comp(uid); // if you're reading this with stack trace pointing to this line, consider the following: https://www.youtube.com/watch?v=CPF2-PpfAfA + sprite.LayerSetState(ChristmasLightsLayers.Glow1, $"greyscale_first_glow{(comp.LowPower ? "_lp" : "")}"); + sprite.LayerSetState(ChristmasLightsLayers.Glow2, $"greyscale_second_glow{(comp.LowPower ? "_lp" : "")}"); + } private void OnInit(EntityUid uid, ChristmasLightsComponent comp, ComponentInit args) { - if (!TryComp(uid, out var sprite)) - return; // anti-dumbass protection + var sprite = Comp(uid); // see above SetLamp1Color(sprite, comp.Color1); SetLamp2Color(sprite, comp.Color2); + sprite.LayerSetState(ChristmasLightsLayers.Glow1, $"greyscale_first_glow{(comp.LowPower ? "_lp" : "")}"); + sprite.LayerSetState(ChristmasLightsLayers.Glow2, $"greyscale_second_glow{(comp.LowPower ? "_lp" : "")}"); sprite.LayerSetAutoAnimated(ChristmasLightsLayers.Glow1, false); // evil grinch smile sprite.LayerSetAutoAnimated(ChristmasLightsLayers.Glow2, false); + } - //ApplyMode("always_on", comp, sprite); + private void OnChristmasLightsAltVerbs(EntityUid uid, ChristmasLightsComponent comp, GetVerbsEvent args) + { + args.Verbs.Add( + new AlternativeVerb + { + Priority = 3, + Text = Loc.GetString("christmas-lights-toggle-brightness"), + Icon = new SpriteSpecifier.Texture(new("/Textures/Interface/VerbIcons/light.svg.192dpi.png")), + ClientExclusive = true, + Act = () => { + if (_timing.IsFirstTimePredicted) // i hate the antichrist i hate the antichrist i hate the antichrist + RaiseNetworkEvent(new ChangeChristmasLightsBrightnessAttemptEvent(GetNetEntity(args.Target))); + } + }); + } + private void OnChristmasLightsVerbs(EntityUid uid, ChristmasLightsComponent comp, GetVerbsEvent args) + { + args.Verbs.Add( + new Verb + { + Priority = 3, + Text = Loc.GetString("christmas-lights-next-mode"), + Icon = new SpriteSpecifier.Texture(new("/Textures/Interface/VerbIcons/group.svg.192dpi.png")), + ClientExclusive = true, + Act = () => RaiseNetworkEvent(new ChangeChristmasLightsModeAttemptEvent(GetNetEntity(args.Target))) + }); } + + + + // Funny shitcode game: everything that gets called in FrameUpdate() must be coded as if it's from a shader. + + + + // paranoidal profiling, not for live #if DEBUG class circlebuf @@ -85,46 +132,53 @@ public double Add(double x) #endif - /// - /// https://www.desmos.com/calculator/ckyz8ikijz - /// Don't ask me why. I won't be able to answer. - /// - /// - /// - private void SetLamp1Color(SpriteComponent sprite, Color c) + private void SetLamp1Color(SpriteComponent sprite, Color c) // only for init, LEDs changing colors while off is lame { sprite.LayerSetColor(ChristmasLightsLayers.Lights1, c.WithAlpha(255)); sprite.LayerSetColor(ChristmasLightsLayers.Glow1, c); } - private void SetLamp2Color(SpriteComponent sprite, Color c) + private void SetLamp2Color(SpriteComponent sprite, Color c) // only for init, LEDs changing colors while off is lame { sprite.LayerSetColor(ChristmasLightsLayers.Lights2, c.WithAlpha(255)); sprite.LayerSetColor(ChristmasLightsLayers.Glow2, c); } +#region helper functions + /// + /// https://www.desmos.com/calculator/ckyz8ikijz + /// Don't ask me why. I won't be able to answer. + /// + /// + /// x but with a crippling disability private static float shitsin(float x) => (float) ((8 * MathF.Sin(x) + MathF.Sin(3 * x)) / 14 + 0.5); - //private static float shitsin(double x) => shitsin((float) x); private static float step(float x, int stepsNum) => MathF.Round(x * stepsNum) / stepsNum; private static int round(float x) => (int) MathF.Round(x); private float curtime => (float) _timing.CurTime.TotalSeconds; Color getRainbowColor(float secondsPerCycle, float hueOffset, int steps) => Color.FromHsv(new Vector4(step((curtime + hueOffset) % secondsPerCycle / secondsPerCycle, steps), 1f, 1f, 1f)); private bool cycle(float seconds) => curtime % (seconds * 2) <= seconds; + /// + /// will not be kept in sync between clients, but who cares, it is different with every access and is intended to induce epileptic seizures + /// + float random { get { + _shift++; + return (1 + MathF.Sin((_shift+curtime) * 5023.85929f)) * 9491.59902f % 1; + } } + int _shift = 0; +#endregion public override void FrameUpdate(float frameTime) { base.FrameUpdate(frameTime); - //float time1 = shitsin(curtime); - //float time2 = shitsin(curtime + Math.PI); - var query = AllEntityQuery(); + #if DEBUG stopwatch.Restart(); #endif while (query.MoveNext(out var comp, out var sprite)) { - ApplyMode(comp.mode, /*time1, time2,*/ comp, sprite); + ApplyMode(comp.mode, comp, sprite); } #if DEBUG _lastStopwatch = stopwatch.Elapsed.TotalMilliseconds; @@ -135,7 +189,7 @@ public override void FrameUpdate(float frameTime) { #endif } - private void ApplyMode(string mode, /*float time1, float time2,*/ ChristmasLightsComponent comp, SpriteComponent sprite) + private void ApplyMode(string mode, ChristmasLightsComponent comp, SpriteComponent sprite) { if (sprite.TryGetLayer(sprite.LayerMapGet(ChristmasLightsLayers.Glow1), out var layer1) && sprite.TryGetLayer(sprite.LayerMapGet(ChristmasLightsLayers.Glow2), out var layer2)) @@ -143,18 +197,14 @@ private void ApplyMode(string mode, /*float time1, float time2,*/ ChristmasLight layer1.Color = comp.Color1; // in case some mode doesn't change the colors, reset them so they don't get stuck at 0 alpha or something layer2.Color = comp.Color2; - if (modes.TryGetValue(mode, out var deleg)) { - deleg(/*time1, time2,*/ layer1, layer2, comp, sprite); - //continue; + deleg(layer1, layer2, comp, sprite); } } } - - // shit like this should be put in a shader methinks... delegate void ModeFunc(SpriteComponent.Layer layer1, SpriteComponent.Layer layer2, ChristmasLightsComponent comp, SpriteComponent sprite); Dictionary modes = new(); @@ -163,6 +213,8 @@ private void InitModes() { modes["test1"] = modeTest1; modes["test2"] = modeTest2; + modes["emp"] = modeFubar; + modes["emp_rainbow"] = modeFubarRainbow; modes["always_on"] = modeAlwaysOn; modes["fade"] = modeFade; modes["sinwave_full"] = modeSinWaveFull; @@ -175,15 +227,32 @@ private void InitModes() modes["strobe_slow"] = modeStrobeSlow; modes["rainbow"] = modeRainbow; } - + void modeTest1(SpriteComponent.Layer layer1, SpriteComponent.Layer layer2, ChristmasLightsComponent comp, SpriteComponent sprite) { - + // for playing with hot reload } void modeTest2(SpriteComponent.Layer layer1, SpriteComponent.Layer layer2, ChristmasLightsComponent comp, SpriteComponent sprite) { + // for playing with hot reload + } + void modeFubar(SpriteComponent.Layer layer1, SpriteComponent.Layer layer2, ChristmasLightsComponent comp, SpriteComponent sprite) + { + layer1.AnimationFrame = round(random*3); + layer2.AnimationFrame = round(random*3); + layer1.State = $"greyscale_first_glow{(random > 0.5 ? "_lp" : "")}"; + layer2.State = $"greyscale_second_glow{(random > 0.5 ? "_lp" : "")}"; + } + void modeFubarRainbow(SpriteComponent.Layer layer1, SpriteComponent.Layer layer2, ChristmasLightsComponent comp, SpriteComponent sprite) + { + layer1.AnimationFrame = round(random * 3); + layer2.AnimationFrame = round(random * 3); + layer1.Color = getRainbowColor(1, random, 16); + layer2.Color = getRainbowColor(1, random, 16); + layer1.State = $"greyscale_first_glow{(random > 0.5 ? "_lp" : "")}"; + layer2.State = $"greyscale_second_glow{(random > 0.5 ? "_lp" : "")}"; } void modeAlwaysOn(SpriteComponent.Layer layer1, SpriteComponent.Layer layer2, ChristmasLightsComponent comp, SpriteComponent sprite) @@ -220,20 +289,20 @@ void modeSinWavePartial(SpriteComponent.Layer layer1, SpriteComponent.Layer laye void modeSinWavePartialRainbow(SpriteComponent.Layer layer1, SpriteComponent.Layer layer2, ChristmasLightsComponent comp, SpriteComponent sprite) { modeSinWavePartial(layer1, layer2, comp, sprite); - SetLamp1Color(sprite, getRainbowColor(2f, 0f, 10)); - SetLamp2Color(sprite, getRainbowColor(2f, 0.5f, 10)); + layer1.Color = getRainbowColor(2f, 0f, 10); + layer2.Color = getRainbowColor(2f, 0.5f, 10); } void modeSquareWave(SpriteComponent.Layer layer1, SpriteComponent.Layer layer2, ChristmasLightsComponent comp, SpriteComponent sprite) { - layer1.AnimationFrame = 3 * round(shitsin(curtime)); - layer2.AnimationFrame = 3 * round(shitsin(curtime + MathF.PI)); + layer1.AnimationFrame = 3 * round((1+MathF.Sin(curtime))/2); + layer2.AnimationFrame = 3 * round((1+MathF.Sin(curtime + MathF.PI/2))/2); } void modeSquareWaveRainbow(SpriteComponent.Layer layer1, SpriteComponent.Layer layer2, ChristmasLightsComponent comp, SpriteComponent sprite) { modeSquareWave(layer1, layer2, comp, sprite); - SetLamp1Color(sprite, getRainbowColor(2f, 0f, 10)); - SetLamp2Color(sprite, getRainbowColor(2f, 0.5f, 10)); + layer1.Color = getRainbowColor(2f, 0f, 10); + layer2.Color = getRainbowColor(2f, 0.5f, 10); } void modeStrobeDouble(SpriteComponent.Layer layer1, SpriteComponent.Layer layer2, ChristmasLightsComponent comp, SpriteComponent sprite) @@ -261,7 +330,7 @@ void modeRainbow(SpriteComponent.Layer layer1, SpriteComponent.Layer layer2, Chr { layer1.AnimationFrame = 3; layer2.AnimationFrame = 3; - SetLamp1Color(sprite, getRainbowColor(2f, 0f, 10)); - SetLamp2Color(sprite, getRainbowColor(2f, 0.5f, 10)); + layer1.Color = getRainbowColor(2f, 0f, 10); + layer2.Color = getRainbowColor(2f, 0.5f, 10); } } diff --git a/Content.Server/_White/Misc/ChristmasLightsSystem.cs b/Content.Server/_White/Misc/ChristmasLightsSystem.cs index 9559c9d0b8..0d678a29e5 100644 --- a/Content.Server/_White/Misc/ChristmasLightsSystem.cs +++ b/Content.Server/_White/Misc/ChristmasLightsSystem.cs @@ -1,13 +1,20 @@ +using Content.Server.Emp; using Content.Server.NodeContainer; using Content.Server.NodeContainer.EntitySystems; using Content.Server.NodeContainer.NodeGroups; using Content.Server.NodeContainer.Nodes; using Content.Shared._White.Misc.ChristmasLights; +using Content.Shared.ActionBlocker; +using Content.Shared.Emag.Components; +using Content.Shared.Emag.Systems; using Content.Shared.Interaction; +using Content.Shared.Verbs; +using Robust.Shared.Audio.Systems; using Robust.Shared.Map.Components; using Robust.Shared.Utility; using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Text; using System.Threading.Tasks; @@ -15,82 +22,142 @@ namespace Content.Server._White.Misc; -public sealed class ChristmasLightsSystem : EntitySystem +public sealed class ChristmasLightsSystem : SharedChristmasLightsSystem { - [Dependency] private readonly NodeGroupSystem _node = default!; + [Dependency] private readonly NodeGroupSystem _node = default!; + [Dependency] private readonly ActionBlockerSystem _actionBlocker = default!; + [Dependency] private readonly SharedAudioSystem _audio = default!; public override void Initialize() { - SubscribeLocalEvent(OnChristmasLightsActivate); + base.Initialize(); + SubscribeLocalEvent(OnChristmasLightsInit); + SubscribeLocalEvent(OnChristmasLightsActivateInWorld); + SubscribeLocalEvent(OnChristmasLightsMinisculeTrolling); + SubscribeLocalEvent(OnChristmasLightsModerateTrolling); + SubscribeNetworkEvent(OnModeChangeAttempt); + SubscribeNetworkEvent(OnBrightnessChangeAttempt); } private void OnChristmasLightsInit(EntityUid uid, ChristmasLightsComponent comp, ComponentInit args) { - if(TryComp(uid, out var cont)) + if (TryComp(uid, out var cont)) { - if(cont.Nodes.TryGetValue("christmaslight", out var node)) + comp.CurrentModeIndex = comp.modes.IndexOf(comp.mode); // returns -1 if mode is not in list: disables mode changing if that's the case + if (cont.Nodes.TryGetValue("christmaslight", out var node)) _node.QueueReflood(node); } } - - private void OnChristmasLightsActivate(EntityUid uid, ChristmasLightsComponent comp, ActivateInWorldEvent args) + private void OnChristmasLightsMinisculeTrolling(EntityUid uid, ChristmasLightsComponent comp, ref EmpPulseEvent args) { - if(TryComp(uid, out var cont) && cont.Nodes.TryGetValue("christmaslight", out var node) && node.NodeGroup is not null){ - var thisProt = MetaData(uid).EntityPrototype; - foreach(var n in node.NodeGroup.Nodes) + args.Affected = true; + if (TryGetConnected(uid, out var nodes)) + { + foreach (var node in nodes) { - if(TryComp(n.Owner, out var jolly)) - { - if (MetaData(n.Owner).EntityPrototype != thisProt) - continue; - int i = jolly.modes.IndexOf(jolly.mode); - jolly.mode = jolly.modes[(i + 1) % jolly.modes.Count]; - Dirty(jolly.Owner, jolly); - } + var jolly = Comp(node.Owner); + jolly.mode = $"emp{(comp.Multicolor ? "_rainbow" : "")}"; + Dirty(jolly.Owner, jolly); } } } -} + private void OnChristmasLightsModerateTrolling(EntityUid uid, ChristmasLightsComponent comp, ref GotEmaggedEvent args) + { + if (TryGetConnected(uid, out var nodes)) + { + foreach (var node in nodes) + { + EnsureComp(node.Owner); + var jolly = Comp(node.Owner); + jolly.mode = $"emp{(jolly.Multicolor ? "_rainbow" : "")}"; + jolly.CurrentModeIndex = -1; // disables mode change + Dirty(jolly.Owner, jolly); + } + } + _audio.PlayPvs(comp.EmagSound, uid); + args.Handled = true; + } -public sealed partial class SamePrototypeAdjacentNode : Node -{ - [ViewVariables] - public string OwnerPrototypeID = default!; + int GetNextModeIndex(ChristmasLightsComponent comp) // cycles modes as usual, but also handles the -1 case + { + if (comp.CurrentModeIndex == -1) return -1; + return (comp.CurrentModeIndex + 1) % comp.modes.Count; + } - public override void Initialize(EntityUid owner, IEntityManager entMan) + private void OnModeChangeAttempt(ChangeChristmasLightsModeAttemptEvent args, EntitySessionEventArgs sessionArgs) { - base.Initialize(owner, entMan); - + if (!sessionArgs.SenderSession.AttachedEntity.HasValue) + return; + EntityUid uid = GetEntity(args.target); + EntityUid user = sessionArgs.SenderSession.AttachedEntity!.Value; // no it will not be a fucking null, shut the fuck up + if (_actionBlocker.CanInteract(user, uid) && !HasComp(uid)) + { + var jolly = Comp(uid); + UpdateAllConnected(uid, jolly.LowPower, GetNextModeIndex(jolly)); + } - if (String.IsNullOrEmpty(OwnerPrototypeID)) + } + + private void OnBrightnessChangeAttempt(ChangeChristmasLightsBrightnessAttemptEvent args, EntitySessionEventArgs sessionArgs) + { + if (!sessionArgs.SenderSession.AttachedEntity.HasValue) + return; + EntityUid uid = GetEntity(args.target); + EntityUid user = sessionArgs.SenderSession.AttachedEntity!.Value; + if (_actionBlocker.CanInteract(user, uid) && !HasComp(uid)) { - var prot = entMan.GetComponent(owner).EntityPrototype; - DebugTools.Assert(prot is not null, "SamePrototypeAdjacentNode used on an entity with no EntityPrototype specified in metadata. Please reconsider your life choices that have lead you to this point."); - OwnerPrototypeID = prot.ID; + var jolly = Comp(uid); + UpdateAllConnected(uid, !jolly.LowPower, GetNextModeIndex(jolly)); + } + } + private void OnChristmasLightsActivateInWorld(EntityUid uid, ChristmasLightsComponent comp, ActivateInWorldEvent args) + { + if (!HasComp(uid)) + { + var jolly = Comp(uid); + UpdateAllConnected(uid, jolly.LowPower, GetNextModeIndex(jolly)); } } - public override IEnumerable GetReachableNodes(TransformComponent xform, - EntityQuery nodeQuery, - EntityQuery xformQuery, - MapGridComponent? grid, - IEntityManager entMan) + + /// + /// note: also updates the uid passed, so technically it's "UpdateAllConnectedAndItself" or something like that + /// + private void UpdateAllConnected(EntityUid uid, bool brightness, int newModeIndex) { - if (!xform.Anchored || grid == null) - yield break; + if (newModeIndex >= 0 && TryGetConnected(uid, out var nodes)) + { + foreach (var node in nodes) + { + var jollyUid = node.Owner; + if (TryComp(jollyUid, out var jolly)) + { + if (HasComp(jollyUid)) + continue; + jolly.LowPower = brightness; + jolly.mode = jolly.modes[newModeIndex]; + Dirty(jollyUid, jolly); + } + } + } + } - var gridIndex = grid.TileIndicesFor(xform.Coordinates); - foreach (var (_, node) in NodeHelpers.GetCardinalNeighborNodes(nodeQuery, grid, gridIndex)) + /// + /// returns connected *and* self. + /// + private bool TryGetConnected(EntityUid uid, [NotNullWhen(true)] out IEnumerable? nodes) + { + nodes = null; + if (TryComp(uid, out var cont) && cont.Nodes.TryGetValue("christmaslight", out var node) && node.NodeGroup is not null) { - if (node is SamePrototypeAdjacentNode spaNode && - spaNode != this && - spaNode.OwnerPrototypeID == this.OwnerPrototypeID) - yield return node; + nodes = node.NodeGroup.Nodes; + return true; } + return false; } } diff --git a/Content.Server/_White/Nodes/SamePrototypeAdjacentNode.cs b/Content.Server/_White/Nodes/SamePrototypeAdjacentNode.cs new file mode 100644 index 0000000000..893d5d35f9 --- /dev/null +++ b/Content.Server/_White/Nodes/SamePrototypeAdjacentNode.cs @@ -0,0 +1,62 @@ +using Content.Server.Emp; +using Content.Server.NodeContainer; +using Content.Server.NodeContainer.EntitySystems; +using Content.Server.NodeContainer.NodeGroups; +using Content.Server.NodeContainer.Nodes; +using Content.Shared._White.Misc.ChristmasLights; +using Content.Shared.ActionBlocker; +using Content.Shared.Emag.Components; +using Content.Shared.Emag.Systems; +using Content.Shared.Interaction; +using Content.Shared.Verbs; +using Robust.Shared.Audio.Systems; +using Robust.Shared.Map.Components; +using Robust.Shared.Utility; +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Content.Server.Nodes; + +public sealed partial class SamePrototypeAdjacentNode : Node +{ + [ViewVariables] + public string OwnerPrototypeID = default!; + + public override void Initialize(EntityUid owner, IEntityManager entMan) + { + base.Initialize(owner, entMan); + + + if (String.IsNullOrEmpty(OwnerPrototypeID)) + { + var prot = entMan.GetComponent(owner).EntityPrototype; + DebugTools.Assert(prot is not null, "SamePrototypeAdjacentNode used on an entity with no EntityPrototype specified in metadata. Please reconsider your life choices that have lead you to this point."); + OwnerPrototypeID = prot.ID; + + } + } + public override IEnumerable GetReachableNodes(TransformComponent xform, + EntityQuery nodeQuery, + EntityQuery xformQuery, + MapGridComponent? grid, + IEntityManager entMan) + { + if (!xform.Anchored || grid == null) + yield break; + + var gridIndex = grid.TileIndicesFor(xform.Coordinates); + + foreach (var (_, node) in NodeHelpers.GetCardinalNeighborNodes(nodeQuery, grid, gridIndex)) + { + if (node is SamePrototypeAdjacentNode spaNode && + spaNode != this && + spaNode.OwnerPrototypeID == this.OwnerPrototypeID) + yield return node; + } + } +} + diff --git a/Content.Shared/_White/Misc/ChristmasLights/ChristmasLightsComponent.cs b/Content.Shared/_White/Misc/ChristmasLights/ChristmasLightsComponent.cs index 21ab43b66c..de02788c10 100644 --- a/Content.Shared/_White/Misc/ChristmasLights/ChristmasLightsComponent.cs +++ b/Content.Shared/_White/Misc/ChristmasLights/ChristmasLightsComponent.cs @@ -1,4 +1,6 @@ +using Robust.Shared.Audio; using Robust.Shared.GameStates; +using Robust.Shared.Serialization; using System; using System.Collections.Generic; using System.Linq; @@ -7,7 +9,7 @@ namespace Content.Shared._White.Misc.ChristmasLights; -[RegisterComponent, AutoGenerateComponentState, NetworkedComponent] +[RegisterComponent, AutoGenerateComponentState(true), NetworkedComponent] public sealed partial class ChristmasLightsComponent : Component { [DataField, ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] @@ -21,22 +23,29 @@ public sealed partial class ChristmasLightsComponent : Component [DataField, AutoNetworkedField] public string mode = "always_on"; - [DataField, AutoNetworkedField] - public List modes = new List - { - "always_on", - //"sinwave_full", - //"sinwave_partial", - //"sinwave_partial_rainbow", - //"rainbow", - //"strobe_double", - //"strobe", - //"strobe_slow", - }; + [ViewVariables(VVAccess.ReadOnly), AutoNetworkedField] + public int CurrentModeIndex = default; -} + [DataField, ViewVariables(VVAccess.ReadOnly), AutoNetworkedField] + public List modes = new List { "always_on" }; + /// + /// refers to the glow state sprites, no actual power consumtion regardless of value + /// + [DataField, AutoNetworkedField] + public bool LowPower = true; + /// + /// as in, are the LEDs capable of changing colors. + /// Doesn't actually limit anything, only used by server side system to tell + /// whether it should apply regular or rainbow epilepsy mode when EMP'd. + /// + [DataField, AutoNetworkedField] + public bool Multicolor = false; + + [DataField] + public SoundSpecifier EmagSound = new SoundCollectionSpecifier("sparks"); +} public enum ChristmasLightsLayers { @@ -46,3 +55,4 @@ public enum ChristmasLightsLayers Glow1, Glow2 } + diff --git a/Content.Shared/_White/Misc/ChristmasLights/ChristmasLightsEvents.cs b/Content.Shared/_White/Misc/ChristmasLights/ChristmasLightsEvents.cs new file mode 100644 index 0000000000..6cdb41e6c3 --- /dev/null +++ b/Content.Shared/_White/Misc/ChristmasLights/ChristmasLightsEvents.cs @@ -0,0 +1,27 @@ +using Robust.Shared.Audio; +using Robust.Shared.GameStates; +using Robust.Shared.Serialization; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Content.Shared._White.Misc.ChristmasLights; + +[Serializable, NetSerializable] +public sealed class ChangeChristmasLightsModeAttemptEvent : EntityEventArgs +{ + public NetEntity target; + + public ChangeChristmasLightsModeAttemptEvent(NetEntity target) { this.target = target; } +} + +[Serializable, NetSerializable] +public sealed class ChangeChristmasLightsBrightnessAttemptEvent : EntityEventArgs +{ + public NetEntity target; + + public ChangeChristmasLightsBrightnessAttemptEvent(NetEntity target) { this.target = target; } +} + diff --git a/Content.Shared/_White/Misc/ChristmasLights/SharedChristmasLightsSystem.cs b/Content.Shared/_White/Misc/ChristmasLights/SharedChristmasLightsSystem.cs new file mode 100644 index 0000000000..dd3ced54ca --- /dev/null +++ b/Content.Shared/_White/Misc/ChristmasLights/SharedChristmasLightsSystem.cs @@ -0,0 +1,28 @@ +using Content.Shared.Examine; +using Robust.Shared.Audio; +using Robust.Shared.GameStates; +using Robust.Shared.Serialization; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Content.Shared._White.Misc.ChristmasLights; + +public abstract class SharedChristmasLightsSystem : EntitySystem +{ + [Dependency] protected readonly ILocalizationManager _loc = default!; + + public override void Initialize() + { + SubscribeLocalEvent(OnChristmasLightsExamine); + + } + + private void OnChristmasLightsExamine(EntityUid uid, ChristmasLightsComponent comp, ExaminedEvent args) // todo why am i forced to keep this in shared? + { + args.PushMarkup(_loc.GetString("christmas-lights-examine-toggle-mode-tip"), 1); + args.PushMarkup(_loc.GetString("christmas-lights-examine-toggle-brightness-tip"), 0); + } +} diff --git a/Resources/Locale/ru-RU/_white/fluff/christmaslights.ftl b/Resources/Locale/ru-RU/_white/fluff/christmaslights.ftl new file mode 100644 index 0000000000..30fb62c587 --- /dev/null +++ b/Resources/Locale/ru-RU/_white/fluff/christmaslights.ftl @@ -0,0 +1,18 @@ +christmas-lights-examine-toggle-mode-tip = Активируйте для переключения режима. +christmas-lights-examine-toggle-brightness-tip = Альтернативно активируйте для переключения яркости. +christmas-lights-examine-desc = С новым годом! Эти переливающиеся огоньки запитаны духом рождества и праздничным настроением экипажа! И микрореактором термоядерного синтеза, на всякий случай. +christmas-lights-examine-name = рождественская гирлянда + + +ent-RedBlueChristmasLights = { christmas-lights-examine-name } + .desc = { christmas-lights-examine-desc } + +ent-YellowCyanChristmasLights = { christmas-lights-examine-name } + .desc = { christmas-lights-examine-desc } + +ent-PurpleGreenChristmasLights = { christmas-lights-examine-name } + .desc = { christmas-lights-examine-desc } + +ent-RainbowChristmasLights = { christmas-lights-examine-name } + .desc = { christmas-lights-examine-desc } + diff --git a/Resources/Prototypes/_White/Entities/Structures/Wallmounts/christmaslights.yml b/Resources/Prototypes/_White/Entities/Structures/Wallmounts/christmaslights.yml index 862f2b19e3..7a81f5e958 100644 --- a/Resources/Prototypes/_White/Entities/Structures/Wallmounts/christmaslights.yml +++ b/Resources/Prototypes/_White/Entities/Structures/Wallmounts/christmaslights.yml @@ -2,16 +2,9 @@ parent: BaseSign id: BaseChristmasLights name: christmas lights - description: Merry Christmas! These lights are powered by the crew jollyness and christmas spirit! Also has a miniature backup fusion reactor, just in case. + description: Merry Christmas! These lights are powered by the crew cheer and christmas spirit! Also has a miniature backup fusion reactor, just in case. abstract: true components: - - type: Transform - anchored: true # basesign is static instead of being anchored, for some reason. - - type: WallMount - arc: 360 - - type: Construction - graph: RainbowChristmasDisassembly - node: start - type: Sprite noRot: false drawdepth: WallTops @@ -23,25 +16,28 @@ map: ["enum.ChristmasLightsLayers.Lights1"] - state: greyscale_second map: ["enum.ChristmasLightsLayers.Lights2"] - - state: greyscale_first_glow + - state: greyscale_first_glow # be advised: if you change this state name, be sure to change it in clientside christmas lights system as well (methods modeFubar() and modeFubarRainbow()) map: ["enum.ChristmasLightsLayers.Glow1"] shader: unshaded - state: greyscale_second_glow map: ["enum.ChristmasLightsLayers.Glow2"] shader: unshaded + - type: Tag + tags: + - Unstackable + - type: Transform + anchored: true # basesign is static instead of being anchored, for some reason. + - type: WallMount + arc: 360 + - type: Construction + graph: GenericChristmasLightsDisassembly + node: start - type: ChristmasLights color1: "#FFFFFF" color2: "#FFFFFF" mode: always_on modes: - always_on - #- sinwave_full - #- sinwave_partial - #- sinwave_partial_rainbow - #- strobe_double - #- strobe - #- strobe_slow - #- rainbow - type: NodeContainer nodes: christmaslight: @@ -113,6 +109,7 @@ id: RainbowChristmasLights components: - type: ChristmasLights + multicolor: true mode: rainbow modes: - rainbow diff --git a/Resources/Prototypes/_White/Recipes/Construction/Graphs/structures/christmaslights.yml b/Resources/Prototypes/_White/Recipes/Construction/Graphs/structures/christmaslights.yml index 1d81b1f633..eb50639379 100644 --- a/Resources/Prototypes/_White/Recipes/Construction/Graphs/structures/christmaslights.yml +++ b/Resources/Prototypes/_White/Recipes/Construction/Graphs/structures/christmaslights.yml @@ -8,18 +8,11 @@ steps: - material: Cable amount: 1 - doAfter: 0.5 + doAfter: 1 - node: ChristmasLightsFull entity: RedBlueChristmasLights - edges: - - to: start - completed: - - !type:SpawnPrototype - prototype: Cable - amount: 1 #returns 1 less as one breaks - - !type:DeleteEntity {} - steps: - - tool: Cutting + # this node doesn't have a "disassembly" edge because i'd have to specify ChristmasLightsFull as a starting node for all christmas light prototypes + # instead i have a "generic" disassembly graph which is used only once in BaseChristmasLights' construction comp. - type: constructionGraph id: YellowCyanChristmasLights @@ -31,18 +24,9 @@ steps: - material: Cable amount: 1 - doAfter: 0.5 + doAfter: 1 - node: ChristmasLightsFull entity: YellowCyanChristmasLights - edges: - - to: start - completed: - - !type:SpawnPrototype - prototype: Cable - amount: 1 #returns 1 less as one breaks - - !type:DeleteEntity {} - steps: - - tool: Cutting - type: constructionGraph id: PurpleGreenChristmasLights @@ -54,18 +38,9 @@ steps: - material: Cable amount: 1 - doAfter: 0.5 + doAfter: 1 - node: ChristmasLightsFull entity: PurpleGreenChristmasLights - edges: - - to: start - completed: - - !type:SpawnPrototype - prototype: Cable - amount: 1 #returns 1 less as one breaks - - !type:DeleteEntity {} - steps: - - tool: Cutting - type: constructionGraph id: RainbowChristmasLights @@ -76,22 +51,31 @@ - to: ChristmasLightsFull steps: - material: Cable - amount: 1 - doAfter: 0.5 + amount: 2 + doAfter: 1 - node: ChristmasLightsFull entity: RainbowChristmasLights + +- type: constructionGraph # specified in BaseChristmasLights since it's the same for all children christmas lights. I wish generic graphs were a thing. + id: GenericChristmasLightsDisassembly + start: start + graph: + - node: start edges: - - to: start + - to: ass + steps: + - tool: Cutting + doAfter: 2 completed: - - !type:SpawnPrototype - prototype: Cable + - !type:GivePrototype + prototype: CableApcStack1 amount: 1 - !type:DeleteEntity {} - steps: - - tool: Cutting + - node: ass # doesn't matter -- type: constructionGraph # specified in BaseChristmasLights since it's the same for all children christmas lights. I wish generic graphs were a thing. - id: RainbowChristmasDisassembly + +- type: constructionGraph # since rainbow lights use 2 cable pieces + id: RainbowChristmasLightsDisassembly start: start graph: - node: start @@ -99,9 +83,10 @@ - to: ass steps: - tool: Cutting + doAfter: 2 completed: - - !type:SpawnPrototype + - !type:GivePrototype prototype: CableApcStack1 - amount: 1 + amount: 2 - !type:DeleteEntity {} - node: ass # doesn't matter \ No newline at end of file diff --git a/Resources/Prototypes/_White/Recipes/Construction/christmaslights.yml b/Resources/Prototypes/_White/Recipes/Construction/christmaslights.yml index 566c33d09b..c4b958e69e 100644 --- a/Resources/Prototypes/_White/Recipes/Construction/christmaslights.yml +++ b/Resources/Prototypes/_White/Recipes/Construction/christmaslights.yml @@ -1,4 +1,3 @@ - - type: construction name: Red-blue christmas lights id: RedBlueChristmasLights @@ -6,12 +5,16 @@ startNode: start targetNode: ChristmasLightsFull category: construction-category-structures - description: An improvised barricade made out of wooden planks. + description: Merry Christmas! These lights are powered by the crew jollyness and christmas spirit! Also has a miniature backup fusion reactor, just in case. icon: sprite: _White/Objects/Decoration/Christmas/fairylights.rsi state: base objectType: Structure placementMode: SnapgridCenter + canBuildInImpassable: true + conditions: + - !type:NoUnstackableInTile + - !type:WallmountCondition - type: construction name: Yellow-cyan christmas lights @@ -20,12 +23,15 @@ startNode: start targetNode: ChristmasLightsFull category: construction-category-structures - description: An improvised barricade made out of wooden planks. + description: Merry Christmas! These lights are powered by the crew jollyness and christmas spirit! Also has a miniature backup fusion reactor, just in case. icon: sprite: _White/Objects/Decoration/Christmas/fairylights.rsi state: base objectType: Structure placementMode: SnapgridCenter + canBuildInImpassable: true + conditions: + - !type:WallmountCondition - type: construction name: Purple-green christmas lights @@ -34,12 +40,16 @@ startNode: start targetNode: ChristmasLightsFull category: construction-category-structures - description: An improvised barricade made out of wooden planks. + description: Merry Christmas! These lights are powered by the crew jollyness and christmas spirit! Also has a miniature backup fusion reactor, just in case. icon: sprite: _White/Objects/Decoration/Christmas/fairylights.rsi state: base objectType: Structure placementMode: SnapgridCenter + canBuildInImpassable: true + conditions: + - !type:NoUnstackableInTile + - !type:WallmountCondition - type: construction name: Rainbow christmas lights @@ -48,9 +58,13 @@ startNode: start targetNode: ChristmasLightsFull category: construction-category-structures - description: An improvised barricade made out of wooden planks. + description: Merry Christmas! These lights are powered by the crew jollyness and christmas spirit! Also has a miniature backup fusion reactor, just in case. icon: sprite: _White/Objects/Decoration/Christmas/fairylights.rsi state: base objectType: Structure placementMode: SnapgridCenter + canBuildInImpassable: true + conditions: + - !type:NoUnstackableInTile + - !type:WallmountCondition diff --git a/Resources/Textures/_White/Objects/Decoration/Christmas/fairylights.rsi/_greyscale_first_glow.png b/Resources/Textures/_White/Objects/Decoration/Christmas/fairylights.rsi/_greyscale_first_glow.png deleted file mode 100644 index 319941a81e..0000000000 Binary files a/Resources/Textures/_White/Objects/Decoration/Christmas/fairylights.rsi/_greyscale_first_glow.png and /dev/null differ diff --git a/Resources/Textures/_White/Objects/Decoration/Christmas/fairylights.rsi/_greyscale_second_glow.png b/Resources/Textures/_White/Objects/Decoration/Christmas/fairylights.rsi/_greyscale_second_glow.png deleted file mode 100644 index a285022021..0000000000 Binary files a/Resources/Textures/_White/Objects/Decoration/Christmas/fairylights.rsi/_greyscale_second_glow.png and /dev/null differ diff --git a/Resources/Textures/_White/Objects/Decoration/Christmas/fairylights.rsi/greyscale_first_glow_lp.png b/Resources/Textures/_White/Objects/Decoration/Christmas/fairylights.rsi/greyscale_first_glow_lp.png new file mode 100644 index 0000000000..4619d89b6f Binary files /dev/null and b/Resources/Textures/_White/Objects/Decoration/Christmas/fairylights.rsi/greyscale_first_glow_lp.png differ diff --git a/Resources/Textures/_White/Objects/Decoration/Christmas/fairylights.rsi/greyscale_second_glow_lp.png b/Resources/Textures/_White/Objects/Decoration/Christmas/fairylights.rsi/greyscale_second_glow_lp.png new file mode 100644 index 0000000000..69e1e6f48e Binary files /dev/null and b/Resources/Textures/_White/Objects/Decoration/Christmas/fairylights.rsi/greyscale_second_glow_lp.png differ diff --git a/Resources/Textures/_White/Objects/Decoration/Christmas/fairylights.rsi/meta.json b/Resources/Textures/_White/Objects/Decoration/Christmas/fairylights.rsi/meta.json index 81a394ff23..bcfec64e93 100644 --- a/Resources/Textures/_White/Objects/Decoration/Christmas/fairylights.rsi/meta.json +++ b/Resources/Textures/_White/Objects/Decoration/Christmas/fairylights.rsi/meta.json @@ -10,12 +10,6 @@ { "name": "base" }, - { - "name": "blue_red" - }, - { - "name": "yellow_green" - }, { "name": "greyscale_first" }, @@ -23,104 +17,46 @@ "name": "greyscale_second" }, { - "name": "blue_red_glow", - "delays": [ - [ - 0.25, - 0.15, - 0.15, - 0.25, - 0.15, - 0.15, - 0.25, - 0.15, - 0.15, - 0.25, - 0.15, - 0.15 - ] - ] - }, - { - "name": "yellow_green_glow", - "delays": [ - [ - 0.25, - 0.15, - 0.15, - 0.25, - 0.15, - 0.15, - 0.25, - 0.15, - 0.15, - 0.25, - 0.15, - 0.15 - ] - ] - }, - { - "name": "_greyscale_first_glow", + "name": "greyscale_first_glow", "delays": [ [ 0.25, 0.15, 0.15, - 0.25, - 0.15, - 0.15, - 0.25, - 0.15, - 0.15, - 0.25, - 0.15, - 0.15 + 0.25 ] ] }, { - "name": "_greyscale_second_glow", + "name": "greyscale_second_glow", "delays": [ [ 0.25, 0.15, 0.15, - 0.25, - 0.15, - 0.15, - 0.25, - 0.15, - 0.15, - 0.25, - 0.15, - 0.15 + 0.25 ] ] }, { - "name": "greyscale_first_glow", + "name": "greyscale_first_glow_lp", "delays": [ [ 0.25, 0.15, 0.15, - 0.25, - 0.15, - 0.15 + 0.25 ] ] }, { - "name": "greyscale_second_glow", + "name": "greyscale_second_glow_lp", "delays": [ [ 0.25, 0.15, 0.15, - 0.25, - 0.15, - 0.15 + 0.25 ] ] }