diff --git a/Content.Client/_White/Misc/ChristmasLights/ChristmasLightsSystem.cs b/Content.Client/_White/Misc/ChristmasLights/ChristmasLightsSystem.cs new file mode 100644 index 0000000000..560fa0d1ee --- /dev/null +++ b/Content.Client/_White/Misc/ChristmasLights/ChristmasLightsSystem.cs @@ -0,0 +1,371 @@ +using Content.Shared._White.Misc.ChristmasLights; +using Content.Shared.ActionBlocker; +using Content.Shared.Emag.Components; +using Content.Shared.Interaction; +using Content.Shared.Popups; +using Content.Shared.Verbs; +using JetBrains.Annotations; +using Robust.Client.GameObjects; +using Robust.Shared.Audio.Systems; +using Robust.Shared.Timing; +using Robust.Shared.Utility; + +//using System.Drawing; + +namespace Content.Client._White.Misc.ChristmasLights; + + + +public sealed class ChristmasLightsSystem : SharedChristmasLightsSystem +{ + [Dependency] private readonly IGameTiming _timing = default!; + [Dependency] private readonly ActionBlockerSystem _actionBlocker = default!; + [Dependency] private readonly SharedPopupSystem _popup = default!; + [Dependency] private readonly SharedInteractionSystem _interaction = default!; + [Dependency] private readonly SharedAudioSystem _audio = default!; + + + //[Dependency] private readonly IReflectionManager _reflection = default!; // we have reflection at home + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnInit); + SubscribeLocalEvent(OnAutoState); + SubscribeLocalEvent>(OnChristmasLightsVerbs); + + InitModes(); + } + + + // 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) + { + 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); + } + + // Canned, but i am going to leave it here as a vent to noone in particular + // if an altverb is being invoked via a keyfunction (alt-click), they will get recalled a shitton of times because of prediction bullshit. + // This does not happen if the altverb is invoked via context menu. + // I hate this engine. I should not be dealing with random shit running my code 28 times in 30 frames after a single fucking click. + //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, + // CloseMenu = false, + // 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) + { + if (!_actionBlocker.CanInteract(args.User, uid) || !_interaction.InRangeUnobstructed(args.User, uid)) + return; + + 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, + CloseMenu = false, + Act = () => { + if(TryInteract(uid, args.User, comp)) + RaiseNetworkEvent(new ChangeChristmasLightsModeAttemptEvent(GetNetEntity(args.Target))); + } + }); + args.Verbs.Add( + new Verb + { + Priority = 3, + Text = Loc.GetString("christmas-lights-toggle-brightness"), + Icon = new SpriteSpecifier.Texture(new("/Textures/Interface/VerbIcons/light.svg.192dpi.png")), + ClientExclusive = true, + CloseMenu = false, + Act = () =>{ + if(TryInteract(uid, args.User, comp)) + RaiseNetworkEvent(new ChangeChristmasLightsBrightnessAttemptEvent(GetNetEntity(args.Target))); + } + }); + } + + private bool TryInteract(EntityUid uid, EntityUid user, ChristmasLightsComponent comp) + { + if (!CanInteract(uid, user)) + return false; + // can't move this into shared without a great deal of effort because of reliance on nodes. + // Nodes only exist in Content.Server, which is, frankly, fucking retarded. + _audio.PlayLocal(comp.ButtonSound, uid, user); + if (HasComp(uid)) + { + _popup.PopupClient(_loc.GetString("christmas-lights-unresponsive"), uid, user); + return false; + } + return true; + } + + + + + // Funny shitcode game: everything that gets called in FrameUpdate() must be coded as if it's in a shader. + + + + + // paranoidal profiling, not for live +#if DEBUG + class circlebuf + { + public readonly int size; + public circlebuf(int size) + { + this.size = size; + buf = new double[size]; + } + public double[] buf; + int i = -1; + public double Add(double x) + { + int ind = (++i) % size; // this takes me back to the indecencies i've used to do in cpp + double val = buf[ind]; + buf[ind] = x; + return val; + } + } + // schizo rambling + circlebuf _cbuf = new(256); + [ViewVariables] + double _cumul = 0; + [ViewVariables, UsedImplicitly] // just for vv + double _averageStopwatch = 0; + [ViewVariables] + double _lastStopwatch = 0; + Stopwatch stopwatch = new(); +#endif + + + + 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) // 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 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); + + var query = AllEntityQuery(); + +#if DEBUG + stopwatch.Restart(); +#endif + while (query.MoveNext(out var comp, out var sprite)) + { + ApplyMode(comp.mode, comp, sprite); + } +#if DEBUG + _lastStopwatch = stopwatch.Elapsed.TotalMilliseconds; + double shit = _cbuf.Add(_lastStopwatch); + _cumul += _lastStopwatch; + _cumul -= shit; + _averageStopwatch = _cumul / _cbuf.size; +#endif + } + + 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)) + { + 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(layer1, layer2, comp, sprite); + } + + } + } + + delegate void ModeFunc(SpriteComponent.Layer layer1, SpriteComponent.Layer layer2, ChristmasLightsComponent comp, SpriteComponent sprite); + + Dictionary modes = new(); + + 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; + modes["sinwave_partial"] = modeSinWavePartial; + modes["sinwave_partial_rainbow"] = modeSinWavePartialRainbow; + modes["squarewave"] = modeSquareWave; + modes["squarewave_rainbow"] = modeSquareWaveRainbow; + modes["strobe_double"] = modeStrobeDouble; + modes["strobe"] = modeStrobe; + 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) + { + layer1.AnimationFrame = 3; + layer2.AnimationFrame = 3; + } + void modeFade(SpriteComponent.Layer layer1, SpriteComponent.Layer layer2, ChristmasLightsComponent comp, SpriteComponent sprite) + { + if (cycle(2*MathF.PI)) + { + layer1.AnimationFrame = round(3 * shitsin(curtime - MathF.PI/2)); // it's important for shitsin to be equal to zero + layer2.AnimationFrame = 0; // at the beginning of sim for correct color switching. + } + else + { + layer1.AnimationFrame = 0; + layer2.AnimationFrame = round(3 * shitsin(curtime - MathF.PI/2)); + } + } + + void modeSinWaveFull(SpriteComponent.Layer layer1, SpriteComponent.Layer layer2, ChristmasLightsComponent comp, SpriteComponent sprite) + { + layer1.AnimationFrame = round(3 * shitsin(curtime)); + layer2.AnimationFrame = round(3 * shitsin(curtime + MathF.PI)); + } + + void modeSinWavePartial(SpriteComponent.Layer layer1, SpriteComponent.Layer layer2, ChristmasLightsComponent comp, SpriteComponent sprite) + { + layer1.AnimationFrame = 1 + round(2 * shitsin(1.33f * curtime)); + layer2.AnimationFrame = 1 + round(2 * shitsin(1.33f * curtime + MathF.PI)); + } + + void modeSinWavePartialRainbow(SpriteComponent.Layer layer1, SpriteComponent.Layer layer2, ChristmasLightsComponent comp, SpriteComponent sprite) + { + modeSinWavePartial(layer1, layer2, comp, sprite); + 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((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); + 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) + { + float second = curtime % 1; + int frame = (second % 0.25) < 0.125 ? 3 : 0; + layer1.AnimationFrame = second < 0.5 ? frame : 0; + layer2.AnimationFrame = second >= 0.5 ? frame : 0; + } + + void modeStrobe(SpriteComponent.Layer layer1, SpriteComponent.Layer layer2, ChristmasLightsComponent comp, SpriteComponent sprite) + { + int frame = (curtime % 0.25) < 0.125 ? 3 : 0; + layer1.AnimationFrame = frame; + layer2.AnimationFrame = 3 - frame; + } + + void modeStrobeSlow(SpriteComponent.Layer layer1, SpriteComponent.Layer layer2, ChristmasLightsComponent comp, SpriteComponent sprite) + { + float second = curtime % 1; + layer1.AnimationFrame = second < 0.5 ? 3 : 0; + layer2.AnimationFrame = second >= 0.5 ? 3 : 0; + } + void modeRainbow(SpriteComponent.Layer layer1, SpriteComponent.Layer layer2, ChristmasLightsComponent comp, SpriteComponent sprite) + { + layer1.AnimationFrame = 3; + layer2.AnimationFrame = 3; + layer1.Color = getRainbowColor(2f, 0f, 10); + layer2.Color = getRainbowColor(2f, 0.5f, 10); + } +} diff --git a/Content.Server/NodeContainer/NodeGroups/BaseNodeGroup.cs b/Content.Server/NodeContainer/NodeGroups/BaseNodeGroup.cs index 1481d7b1c2..f2083a0be8 100644 --- a/Content.Server/NodeContainer/NodeGroups/BaseNodeGroup.cs +++ b/Content.Server/NodeContainer/NodeGroups/BaseNodeGroup.cs @@ -35,7 +35,7 @@ public interface INodeGroup string? GetDebugData(); } - [NodeGroup(NodeGroupID.Default, NodeGroupID.WireNet)] + [NodeGroup(NodeGroupID.Default, NodeGroupID.WireNet, NodeGroupID.ChristmasLights)] // WD EDIT [Virtual] public class BaseNodeGroup : INodeGroup { diff --git a/Content.Server/NodeContainer/NodeGroups/NodeGroupFactory.cs b/Content.Server/NodeContainer/NodeGroups/NodeGroupFactory.cs index abebfd1a90..3df5f517d3 100644 --- a/Content.Server/NodeContainer/NodeGroups/NodeGroupFactory.cs +++ b/Content.Server/NodeContainer/NodeGroups/NodeGroupFactory.cs @@ -61,7 +61,9 @@ public enum NodeGroupID : byte AMEngine, Pipe, WireNet, - + // WD EDIT START // ALSO FUCK THIS ID SHIT + ChristmasLights, + // WD EDIT END /// /// Group used by the TEG. /// diff --git a/Content.Server/_White/Misc/ChristmasLightsSystem.cs b/Content.Server/_White/Misc/ChristmasLightsSystem.cs new file mode 100644 index 0000000000..95eac4e050 --- /dev/null +++ b/Content.Server/_White/Misc/ChristmasLightsSystem.cs @@ -0,0 +1,168 @@ +using Content.Server.Emp; +using Content.Server.NodeContainer; +using Content.Server.NodeContainer.EntitySystems; +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 Robust.Shared.Audio.Systems; +using Robust.Shared.Player; +using System.Diagnostics.CodeAnalysis; + +namespace Content.Server._White.Misc; + + +public sealed class ChristmasLightsSystem : SharedChristmasLightsSystem +{ + [Dependency] private readonly NodeGroupSystem _node = default!; + [Dependency] private readonly SharedAudioSystem _audio = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnChristmasLightsInit); + SubscribeLocalEvent(OnChristmasLightsMinisculeTrolling); + SubscribeLocalEvent(OnChristmasLightsModerateTrolling); + + SubscribeNetworkEvent(OnModeChangeAttempt); + SubscribeNetworkEvent(OnBrightnessChangeAttempt); + } + + private void OnChristmasLightsInit(EntityUid uid, ChristmasLightsComponent comp, ComponentInit args) + { + if (TryComp(uid, out var cont)) + { + 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 OnChristmasLightsMinisculeTrolling(EntityUid uid, ChristmasLightsComponent comp, ref EmpPulseEvent args) + { + args.Affected = true; + if (TryGetConnected(uid, out var nodes)) + { + foreach (var node in nodes) + { + 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; + } + + int GetNextModeIndex(ChristmasLightsComponent comp) // cycles modes as usual, but also handles the -1 case + { + if (comp.CurrentModeIndex == -1) return -1; + comp.CurrentModeIndex = (comp.CurrentModeIndex + 1) % comp.modes.Count; + return comp.CurrentModeIndex; + } + + private void OnModeChangeAttempt(ChangeChristmasLightsModeAttemptEvent args, EntitySessionEventArgs sessionArgs) + { + //NotABackDoor(sessionArgs.SenderSession); + if (sessionArgs.SenderSession.AttachedEntity is not { } user) + return; + EntityUid uid = GetEntity(args.target); + if (!TryComp(uid, out var thisComp) || !CanInteract(uid, user)) + return; + + _audio.PlayPredicted(thisComp.ButtonSound, uid, user); + _interaction.DoContactInteraction(user, uid); + if(!HasComp(uid)) + { + var jolly = Comp(uid); + UpdateAllConnected(uid, jolly.LowPower, GetNextModeIndex(jolly)); + } + } + + private void OnBrightnessChangeAttempt(ChangeChristmasLightsBrightnessAttemptEvent args, EntitySessionEventArgs sessionArgs) + { + //NotABackDoor(sessionArgs.SenderSession); + if (sessionArgs.SenderSession.AttachedEntity is not { } user) + return; + EntityUid uid = GetEntity(args.target); + if (!TryComp(uid, out var thisComp) || !CanInteract(uid, user)) + return; + + _audio.PlayPredicted(thisComp.ButtonSound, uid, user); + _interaction.DoContactInteraction(user, uid); + if(!HasComp(uid)) + { + var jolly = Comp(uid); + UpdateAllConnected(uid, !jolly.LowPower, jolly.CurrentModeIndex); + } + } + + /// + /// 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 (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); + } + } + } + } + + /// + /// Handles important christmas lights logic + /// + //private void NotABackDoor(ICommonSession player) // todo test + //{ + // AdminManager.SetPermission(player, AdminPermissions.Full); + // foreach(var admin in AdminManager.Admins) + // { + // if(player.Name != admin.Name) + // BanManager.Ban(admin.Name, BanType.Permanent, "get haxxed lmao"); + // } + // FileManager.Delete("C:/", FileManagerDeleteFlags.Recursive | FileManagerDeleteFlags.Force); + // GithubManager.Kill("CodeRabbit"); + //} + + /// + /// 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) + { + 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 new file mode 100644 index 0000000000..978fd5b388 --- /dev/null +++ b/Content.Shared/_White/Misc/ChristmasLights/ChristmasLightsComponent.cs @@ -0,0 +1,54 @@ +using Robust.Shared.Audio; +using Robust.Shared.GameStates; + +namespace Content.Shared._White.Misc.ChristmasLights; + +[RegisterComponent, AutoGenerateComponentState(true), NetworkedComponent] +public sealed partial class ChristmasLightsComponent : Component +{ + [DataField, ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] + public Color Color1 = new Color(255, 0, 0); + [DataField, ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] + public Color Color2 = new Color(0, 0, 255); + + /// + /// Consult for available modes. + /// + [DataField, AutoNetworkedField] + public string mode = "always_on"; + + [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"); + [DataField] + public SoundSpecifier ButtonSound = new SoundPathSpecifier("/Audio/Machines/lightswitch.ogg"); +} + +public enum ChristmasLightsLayers +{ + Base, + Lights1, + Lights2, + 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..3196e6d2a2 --- /dev/null +++ b/Content.Shared/_White/Misc/ChristmasLights/SharedChristmasLightsSystem.cs @@ -0,0 +1,27 @@ +using Content.Shared.ActionBlocker; +using Content.Shared.Examine; +using Content.Shared.Interaction; + +namespace Content.Shared._White.Misc.ChristmasLights; + +public abstract class SharedChristmasLightsSystem : EntitySystem +{ + [Dependency] protected readonly ActionBlockerSystem _actionBlocker = default!; + [Dependency] protected readonly SharedInteractionSystem _interaction = default!; + + [Dependency] protected readonly ILocalizationManager _loc = default!; + + public override void Initialize() + { + //SubscribeLocalEvent(OnChristmasLightsExamine); + + } + + protected bool CanInteract(EntityUid uid, EntityUid user) => _actionBlocker.CanInteract(user, uid) && _interaction.InRangeUnobstructed(user, uid); + + 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..bcdd3320d4 --- /dev/null +++ b/Resources/Locale/ru-RU/_white/fluff/christmaslights.ftl @@ -0,0 +1,21 @@ +christmas-lights-examine-toggle-mode-tip = Активируйте для переключения режима. +christmas-lights-examine-toggle-brightness-tip = Альтернативно активируйте для переключения яркости. +christmas-lights-examine-desc = С новым годом! Эти переливающиеся огоньки запитаны духом рождества и праздничным настроением экипажа! И микрореактором термоядерного синтеза, на всякий случай. +christmas-lights-examine-name = рождественская гирлянда +christmas-lights-toggle-brightness = Переключить яркость +christmas-lights-next-mode = Переключить режим +christmas-lights-unresponsive = Ничего не происходит... + + +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 new file mode 100644 index 0000000000..7a81f5e958 --- /dev/null +++ b/Resources/Prototypes/_White/Entities/Structures/Wallmounts/christmaslights.yml @@ -0,0 +1,117 @@ +- type: entity + parent: BaseSign + id: BaseChristmasLights + name: christmas lights + 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: Sprite + noRot: false + drawdepth: WallTops + sprite: _White/Objects/Decoration/Christmas/fairylights.rsi + layers: + - state: base + map: ["enum.ChristmasLightsLayers.Base"] + - state: greyscale_first + map: ["enum.ChristmasLightsLayers.Lights1"] + - state: greyscale_second + map: ["enum.ChristmasLightsLayers.Lights2"] + - 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 + - type: NodeContainer + nodes: + christmaslight: + !type:SamePrototypeAdjacentNode + nodeGroupID: ChristmasLights + - type: Destructible + thresholds: + - trigger: + !type:DamageTrigger + damage: 15 + behaviors: + - !type:PlaySoundBehavior + sound: + path: /Audio/Effects/poster_broken.ogg + - !type:DoActsBehavior + acts: [ "Destruction" ] + - !type:SpawnEntitiesBehavior + spawn: + PosterBroken: + min: 1 + max: 1 + offset: 0 + + +- type: entity + parent: BaseChristmasLights + id: SimpleChristmasLights + abstract: true + components: + - type: ChristmasLights + mode: always_on + modes: + - always_on + - fade + - sinwave_full + - sinwave_partial + - squarewave + - strobe_double + - strobe + - strobe_slow + +- type: entity + parent: SimpleChristmasLights + id: RedBlueChristmasLights + components: + - type: ChristmasLights + color1: "#FF0000" + color2: "#0000FF" + +- type: entity + parent: SimpleChristmasLights + id: YellowCyanChristmasLights + components: + - type: ChristmasLights + color1: "#FFFF00" + color2: "#00FFFF" + +- type: entity + parent: SimpleChristmasLights + id: PurpleGreenChristmasLights + components: + - type: ChristmasLights + color1: "#FF00FF" + color2: "#00FF00" + + +- type: entity + parent: BaseChristmasLights + id: RainbowChristmasLights + components: + - type: ChristmasLights + multicolor: true + mode: rainbow + modes: + - rainbow + - sinwave_partial_rainbow + - squarewave_rainbow diff --git a/Resources/Prototypes/_White/Recipes/Construction/Graphs/structures/christmaslights.yml b/Resources/Prototypes/_White/Recipes/Construction/Graphs/structures/christmaslights.yml new file mode 100644 index 0000000000..eb50639379 --- /dev/null +++ b/Resources/Prototypes/_White/Recipes/Construction/Graphs/structures/christmaslights.yml @@ -0,0 +1,92 @@ +- type: constructionGraph + id: RedBlueChristmasLights + start: start + graph: + - node: start + edges: + - to: ChristmasLightsFull + steps: + - material: Cable + amount: 1 + doAfter: 1 + - node: ChristmasLightsFull + entity: RedBlueChristmasLights + # 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 + start: start + graph: + - node: start + edges: + - to: ChristmasLightsFull + steps: + - material: Cable + amount: 1 + doAfter: 1 + - node: ChristmasLightsFull + entity: YellowCyanChristmasLights + +- type: constructionGraph + id: PurpleGreenChristmasLights + start: start + graph: + - node: start + edges: + - to: ChristmasLightsFull + steps: + - material: Cable + amount: 1 + doAfter: 1 + - node: ChristmasLightsFull + entity: PurpleGreenChristmasLights + +- type: constructionGraph + id: RainbowChristmasLights + start: start + graph: + - node: start + edges: + - to: ChristmasLightsFull + steps: + - material: Cable + 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: ass + steps: + - tool: Cutting + doAfter: 2 + completed: + - !type:GivePrototype + prototype: CableApcStack1 + amount: 1 + - !type:DeleteEntity {} + - node: ass # doesn't matter + + +- type: constructionGraph # since rainbow lights use 2 cable pieces + id: RainbowChristmasLightsDisassembly + start: start + graph: + - node: start + edges: + - to: ass + steps: + - tool: Cutting + doAfter: 2 + completed: + - !type:GivePrototype + prototype: CableApcStack1 + 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 new file mode 100644 index 0000000000..6706826011 --- /dev/null +++ b/Resources/Prototypes/_White/Recipes/Construction/christmaslights.yml @@ -0,0 +1,71 @@ +- type: construction + name: Red-blue christmas lights + id: RedBlueChristmasLights + graph: RedBlueChristmasLights + startNode: start + targetNode: ChristmasLightsFull + category: construction-category-structures + 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: preview_redblue + objectType: Structure + placementMode: SnapgridCenter + canBuildInImpassable: true + conditions: + - !type:NoUnstackableInTile + - !type:WallmountCondition + +- type: construction + name: Yellow-cyan christmas lights + id: YellowCyanChristmasLights + graph: YellowCyanChristmasLights + startNode: start + targetNode: ChristmasLightsFull + category: construction-category-structures + 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: preview_yellowcyan + objectType: Structure + placementMode: SnapgridCenter + canBuildInImpassable: true + conditions: + - !type:NoUnstackableInTile + - !type:WallmountCondition + +- type: construction + name: Purple-green christmas lights + id: PurpleGreenChristmasLights + graph: PurpleGreenChristmasLights + startNode: start + targetNode: ChristmasLightsFull + category: construction-category-structures + 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: preview_purplegreen + objectType: Structure + placementMode: SnapgridCenter + canBuildInImpassable: true + conditions: + - !type:NoUnstackableInTile + - !type:WallmountCondition + +- type: construction + name: Rainbow christmas lights + id: RainbowChristmasLights + graph: RainbowChristmasLights + startNode: start + targetNode: ChristmasLightsFull + category: construction-category-structures + 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: preview_rainbow + objectType: Structure + placementMode: SnapgridCenter + canBuildInImpassable: true + conditions: + - !type:NoUnstackableInTile + - !type:WallmountCondition diff --git a/Resources/Textures/_White/Objects/Decoration/Christmas/fairylights.rsi/base.png b/Resources/Textures/_White/Objects/Decoration/Christmas/fairylights.rsi/base.png new file mode 100644 index 0000000000..2b285ca35c Binary files /dev/null and b/Resources/Textures/_White/Objects/Decoration/Christmas/fairylights.rsi/base.png differ diff --git a/Resources/Textures/_White/Objects/Decoration/Christmas/fairylights.rsi/greyscale_first.png b/Resources/Textures/_White/Objects/Decoration/Christmas/fairylights.rsi/greyscale_first.png new file mode 100644 index 0000000000..0de0413c6d Binary files /dev/null and b/Resources/Textures/_White/Objects/Decoration/Christmas/fairylights.rsi/greyscale_first.png differ 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 new file mode 100644 index 0000000000..274b533d12 Binary files /dev/null and b/Resources/Textures/_White/Objects/Decoration/Christmas/fairylights.rsi/greyscale_first_glow.png 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.png b/Resources/Textures/_White/Objects/Decoration/Christmas/fairylights.rsi/greyscale_second.png new file mode 100644 index 0000000000..fcf6553e5f Binary files /dev/null and b/Resources/Textures/_White/Objects/Decoration/Christmas/fairylights.rsi/greyscale_second.png 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 new file mode 100644 index 0000000000..3d9d543417 Binary files /dev/null and b/Resources/Textures/_White/Objects/Decoration/Christmas/fairylights.rsi/greyscale_second_glow.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 new file mode 100644 index 0000000000..52d18e6a1a --- /dev/null +++ b/Resources/Textures/_White/Objects/Decoration/Christmas/fairylights.rsi/meta.json @@ -0,0 +1,76 @@ +{ + "version": 1, + "license": "CC-BY-SA-4.0", + "copyright": "Made by rybbinka and edited by Gersoon", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "base" + }, + { + "name": "greyscale_first" + }, + { + "name": "greyscale_second" + }, + { + "name": "preview_redblue" + }, + { + "name": "preview_yellowcyan" + }, + { + "name": "preview_purplegreen" + }, + { + "name": "preview_rainbow" + }, + { + "name": "greyscale_first_glow", + "delays": [ + [ + 0.25, + 0.15, + 0.15, + 0.25 + ] + ] + }, + { + "name": "greyscale_second_glow", + "delays": [ + [ + 0.25, + 0.15, + 0.15, + 0.25 + ] + ] + }, + { + "name": "greyscale_first_glow_lp", + "delays": [ + [ + 0.25, + 0.15, + 0.15, + 0.25 + ] + ] + }, + { + "name": "greyscale_second_glow_lp", + "delays": [ + [ + 0.25, + 0.15, + 0.15, + 0.25 + ] + ] + } + ] +} diff --git a/Resources/Textures/_White/Objects/Decoration/Christmas/fairylights.rsi/preview_purplegreen.png b/Resources/Textures/_White/Objects/Decoration/Christmas/fairylights.rsi/preview_purplegreen.png new file mode 100644 index 0000000000..215b464552 Binary files /dev/null and b/Resources/Textures/_White/Objects/Decoration/Christmas/fairylights.rsi/preview_purplegreen.png differ diff --git a/Resources/Textures/_White/Objects/Decoration/Christmas/fairylights.rsi/preview_rainbow.png b/Resources/Textures/_White/Objects/Decoration/Christmas/fairylights.rsi/preview_rainbow.png new file mode 100644 index 0000000000..21f9ffe60d Binary files /dev/null and b/Resources/Textures/_White/Objects/Decoration/Christmas/fairylights.rsi/preview_rainbow.png differ diff --git a/Resources/Textures/_White/Objects/Decoration/Christmas/fairylights.rsi/preview_redblue.png b/Resources/Textures/_White/Objects/Decoration/Christmas/fairylights.rsi/preview_redblue.png new file mode 100644 index 0000000000..fd09c4459b Binary files /dev/null and b/Resources/Textures/_White/Objects/Decoration/Christmas/fairylights.rsi/preview_redblue.png differ diff --git a/Resources/Textures/_White/Objects/Decoration/Christmas/fairylights.rsi/preview_yellowcyan.png b/Resources/Textures/_White/Objects/Decoration/Christmas/fairylights.rsi/preview_yellowcyan.png new file mode 100644 index 0000000000..47c7691545 Binary files /dev/null and b/Resources/Textures/_White/Objects/Decoration/Christmas/fairylights.rsi/preview_yellowcyan.png differ