From 219d91c6dae0ff011663c334ffc92ba41fc13a00 Mon Sep 17 00:00:00 2001 From: Paul Ritter Date: Fri, 3 Dec 2021 15:35:57 +0100 Subject: [PATCH] decal system & crayons (#5183) Co-authored-by: Paul --- .../Crayon/CrayonDecalVisualizer.cs | 29 - .../Crayon/UI/CrayonBoundUserInterface.cs | 6 +- Content.Client/Crayon/UI/CrayonWindow.xaml.cs | 9 +- Content.Client/Decals/DecalOverlay.cs | 60 ++ Content.Client/Decals/DecalSystem.cs | 114 +++ Content.Client/Decals/ToggleDecalCommand.cs | 15 + .../TileReactions/CleanTileReaction.cs | 10 +- Content.Server/Crayon/CrayonComponent.cs | 28 +- .../Decals/Commands/AddDecalCommand.cs | 118 +++ .../Decals/Commands/EditDecalCommand.cs | 162 ++++ .../Decals/Commands/RemoveDecalCommand.cs | 46 + Content.Server/Decals/DecalSystem.cs | 326 +++++++ .../Crayon/SharedCrayonComponent.cs | 12 - Content.Shared/Decals/Decal.cs | 38 + .../Decals/DecalChunkUpdateEvent.cs | 15 + .../DecalGridChunkCollectionTypeSerializer.cs | 74 ++ Content.Shared/Decals/DecalGridComponent.cs | 23 + Content.Shared/Decals/DecalPrototype.cs | 15 + Content.Shared/Decals/SharedDecalSystem.cs | 153 +++ .../Prototypes/Catalog/crayon_decals.yml | 135 --- Resources/Prototypes/Decals/crayons.yml | 916 ++++++++++++++++++ .../Entities/Effects/crayondecals.yml | 16 - 22 files changed, 2100 insertions(+), 220 deletions(-) create mode 100644 Content.Client/Decals/DecalOverlay.cs create mode 100644 Content.Client/Decals/DecalSystem.cs create mode 100644 Content.Client/Decals/ToggleDecalCommand.cs create mode 100644 Content.Server/Decals/Commands/AddDecalCommand.cs create mode 100644 Content.Server/Decals/Commands/EditDecalCommand.cs create mode 100644 Content.Server/Decals/Commands/RemoveDecalCommand.cs create mode 100644 Content.Server/Decals/DecalSystem.cs create mode 100644 Content.Shared/Decals/Decal.cs create mode 100644 Content.Shared/Decals/DecalChunkUpdateEvent.cs create mode 100644 Content.Shared/Decals/DecalGridChunkCollectionTypeSerializer.cs create mode 100644 Content.Shared/Decals/DecalGridComponent.cs create mode 100644 Content.Shared/Decals/DecalPrototype.cs create mode 100644 Content.Shared/Decals/SharedDecalSystem.cs delete mode 100644 Resources/Prototypes/Catalog/crayon_decals.yml create mode 100644 Resources/Prototypes/Decals/crayons.yml delete mode 100644 Resources/Prototypes/Entities/Effects/crayondecals.yml diff --git a/Content.Client/Crayon/CrayonDecalVisualizer.cs b/Content.Client/Crayon/CrayonDecalVisualizer.cs index 964792a56e..e69de29bb2 100644 --- a/Content.Client/Crayon/CrayonDecalVisualizer.cs +++ b/Content.Client/Crayon/CrayonDecalVisualizer.cs @@ -1,29 +0,0 @@ -using Content.Shared.Crayon; -using JetBrains.Annotations; -using Robust.Client.GameObjects; -using Robust.Shared.GameObjects; -using Robust.Shared.Maths; - -namespace Content.Client.Crayon -{ - [UsedImplicitly] - public class CrayonDecalVisualizer : AppearanceVisualizer - { - public override void OnChangeData(AppearanceComponent component) - { - base.OnChangeData(component); - - var sprite = component.Owner.GetComponent(); - - if (component.TryGetData(CrayonVisuals.State, out string state)) - { - sprite.LayerSetState(0, state); - } - - if (component.TryGetData(CrayonVisuals.Color, out string color)) - { - sprite.LayerSetColor(0, Color.FromName(color)); - } - } - } -} diff --git a/Content.Client/Crayon/UI/CrayonBoundUserInterface.cs b/Content.Client/Crayon/UI/CrayonBoundUserInterface.cs index e6fcf8273c..37439e21f8 100644 --- a/Content.Client/Crayon/UI/CrayonBoundUserInterface.cs +++ b/Content.Client/Crayon/UI/CrayonBoundUserInterface.cs @@ -1,5 +1,6 @@ using System.Linq; using Content.Shared.Crayon; +using Content.Shared.Decals; using Robust.Client.GameObjects; using Robust.Shared.GameObjects; using Robust.Shared.IoC; @@ -22,9 +23,8 @@ namespace Content.Client.Crayon.UI _menu.OnClose += Close; var prototypeManager = IoCManager.Resolve(); - var crayonDecals = prototypeManager.EnumeratePrototypes().FirstOrDefault(); - if (crayonDecals != null) - _menu.Populate(crayonDecals); + var crayonDecals = prototypeManager.EnumeratePrototypes().Where(x => x.Tags.Contains("crayon")); + _menu.Populate(crayonDecals); _menu.OpenCentered(); } diff --git a/Content.Client/Crayon/UI/CrayonWindow.xaml.cs b/Content.Client/Crayon/UI/CrayonWindow.xaml.cs index 7e676e473b..90ce4b2523 100644 --- a/Content.Client/Crayon/UI/CrayonWindow.xaml.cs +++ b/Content.Client/Crayon/UI/CrayonWindow.xaml.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; using Content.Client.Stylesheets; using Content.Shared.Crayon; +using Content.Shared.Decals; using Robust.Client.AutoGenerated; using Robust.Client.Graphics; using Robust.Client.UserInterface.Controls; @@ -89,14 +90,12 @@ namespace Content.Client.Crayon.UI RefreshList(); } - public void Populate(CrayonDecalPrototype proto) + public void Populate(IEnumerable prototypes) { - var path = new ResourcePath(proto.SpritePath); _decals = new Dictionary(); - foreach (var state in proto.Decals) + foreach (var decalPrototype in prototypes) { - var rsi = new SpriteSpecifier.Rsi(path, state); - _decals.Add(state, rsi.Frame0()); + _decals.Add(decalPrototype.ID, decalPrototype.Sprite.Frame0()); } RefreshList(); diff --git a/Content.Client/Decals/DecalOverlay.cs b/Content.Client/Decals/DecalOverlay.cs new file mode 100644 index 0000000000..5a8169b957 --- /dev/null +++ b/Content.Client/Decals/DecalOverlay.cs @@ -0,0 +1,60 @@ +using System.Collections.Generic; +using Content.Shared.Decals; +using Robust.Client.Graphics; +using Robust.Client.Utility; +using Robust.Shared.Enums; +using Robust.Shared.Map; +using Robust.Shared.Maths; +using Robust.Shared.Prototypes; +using Robust.Shared.Utility; +using Robust.Shared.Maths; + +namespace Content.Client.Decals +{ + public class DecalOverlay : Overlay + { + private readonly DecalSystem _system; + private readonly IMapManager _mapManager; + private readonly IPrototypeManager _prototypeManager; + + public override OverlaySpace Space => OverlaySpace.WorldSpaceBelowEntities; + + public DecalOverlay(DecalSystem system, IMapManager mapManager, IPrototypeManager prototypeManager) + { + _system = system; + _mapManager = mapManager; + _prototypeManager = prototypeManager; + } + + protected override void Draw(in OverlayDrawArgs args) + { + var handle = args.WorldHandle; + + Dictionary cachedTextures = new(); + + SpriteSpecifier GetSpriteSpecifier(string id) + { + if (cachedTextures.TryGetValue(id, out var spriteSpecifier)) + return spriteSpecifier; + + spriteSpecifier = _prototypeManager.Index(id).Sprite; + cachedTextures.Add(id, spriteSpecifier); + return spriteSpecifier; + } + + foreach (var (gridId, zIndexDictionary) in _system.DecalRenderIndex) + { + var grid = _mapManager.GetGrid(gridId); + handle.SetTransform(grid.WorldMatrix); + foreach (var (_, decals) in zIndexDictionary) + { + foreach (var (_, decal) in decals) + { + var spriteSpecifier = GetSpriteSpecifier(decal.Id); + handle.DrawTexture(spriteSpecifier.Frame0(), decal.Coordinates, decal.Angle, decal.Color); + } + } + } + } + } +} diff --git a/Content.Client/Decals/DecalSystem.cs b/Content.Client/Decals/DecalSystem.cs new file mode 100644 index 0000000000..4f16c4ba5c --- /dev/null +++ b/Content.Client/Decals/DecalSystem.cs @@ -0,0 +1,114 @@ +using System.Collections.Generic; +using Content.Shared.Decals; +using Robust.Client.Graphics; +using Robust.Shared.GameObjects; +using Robust.Shared.IoC; +using Robust.Shared.Map; + +namespace Content.Client.Decals +{ + public class DecalSystem : SharedDecalSystem + { + [Dependency] private readonly IOverlayManager _overlayManager = default!; + + private DecalOverlay _overlay = default!; + public Dictionary>> DecalRenderIndex = new(); + private Dictionary> DecalZIndexIndex = new(); + + public override void Initialize() + { + base.Initialize(); + + _overlay = new DecalOverlay(this, MapManager, PrototypeManager); + _overlayManager.AddOverlay(_overlay); + + SubscribeNetworkEvent(OnChunkUpdate); + SubscribeLocalEvent(OnGridInitialize); + SubscribeLocalEvent(OnGridRemoval); + } + + public void ToggleOverlay() + { + if (_overlayManager.HasOverlay()) + { + _overlayManager.RemoveOverlay(_overlay); + } + else + { + _overlayManager.AddOverlay(_overlay); + } + } + + private void OnGridRemoval(GridRemovalEvent ev) + { + DecalRenderIndex.Remove(ev.GridId); + DecalZIndexIndex.Remove(ev.GridId); + } + + private void OnGridInitialize(GridInitializeEvent ev) + { + DecalRenderIndex[ev.GridId] = new(); + DecalZIndexIndex[ev.GridId] = new(); + } + + public override void Shutdown() + { + base.Shutdown(); + _overlayManager.RemoveOverlay(_overlay); + } + + protected override bool RemoveDecalHook(GridId gridId, uint uid) + { + RemoveDecalFromRenderIndex(gridId, uid); + + return base.RemoveDecalHook(gridId, uid); + } + + private void RemoveDecalFromRenderIndex(GridId gridId, uint uid) + { + var zIndex = DecalZIndexIndex[gridId][uid]; + + DecalRenderIndex[gridId][zIndex].Remove(uid); + if (DecalRenderIndex[gridId][zIndex].Count == 0) + DecalRenderIndex[gridId].Remove(zIndex); + + DecalZIndexIndex[gridId].Remove(uid); + } + + private void OnChunkUpdate(DecalChunkUpdateEvent ev) + { + foreach (var (gridId, gridChunks) in ev.Data) + { + foreach (var (indices, newChunkData) in gridChunks) + { + var chunkCollection = ChunkCollection(gridId); + if (chunkCollection.TryGetValue(indices, out var chunk)) + { + var removedUids = new HashSet(chunk.Keys); + removedUids.ExceptWith(newChunkData.Keys); + foreach (var removedUid in removedUids) + { + RemoveDecalFromRenderIndex(gridId, removedUid); + } + } + foreach (var (uid, decal) in newChunkData) + { + if(!DecalRenderIndex[gridId].ContainsKey(decal.ZIndex)) + DecalRenderIndex[gridId][decal.ZIndex] = new(); + + if (DecalZIndexIndex.TryGetValue(gridId, out var values) && values.TryGetValue(uid, out var zIndex)) + { + DecalRenderIndex[gridId][zIndex].Remove(uid); + } + + DecalRenderIndex[gridId][decal.ZIndex][uid] = decal; + DecalZIndexIndex[gridId][uid] = decal.ZIndex; + + ChunkIndex[gridId][uid] = indices; + } + chunkCollection[indices] = newChunkData; + } + } + } + } +} diff --git a/Content.Client/Decals/ToggleDecalCommand.cs b/Content.Client/Decals/ToggleDecalCommand.cs new file mode 100644 index 0000000000..37930701f7 --- /dev/null +++ b/Content.Client/Decals/ToggleDecalCommand.cs @@ -0,0 +1,15 @@ +using Robust.Shared.Console; +using Robust.Shared.GameObjects; + +namespace Content.Client.Decals; + +public class ToggleDecalCommand : IConsoleCommand +{ + public string Command => "toggledecals"; + public string Description => "Toggles decaloverlay"; + public string Help => $"{Command}"; + public void Execute(IConsoleShell shell, string argStr, string[] args) + { + EntitySystem.Get().ToggleOverlay(); + } +} diff --git a/Content.Server/Chemistry/TileReactions/CleanTileReaction.cs b/Content.Server/Chemistry/TileReactions/CleanTileReaction.cs index 7f0fd17062..a022a9c067 100644 --- a/Content.Server/Chemistry/TileReactions/CleanTileReaction.cs +++ b/Content.Server/Chemistry/TileReactions/CleanTileReaction.cs @@ -1,11 +1,13 @@ using System.Linq; using Content.Server.Cleanable; using Content.Server.Coordinates.Helpers; -using Content.Shared.Chemistry; +using Content.Server.Decals; using Content.Shared.Chemistry.Reaction; using Content.Shared.Chemistry.Reagent; using Content.Shared.FixedPoint; +using Robust.Shared.GameObjects; using Robust.Shared.Map; +using Robust.Shared.Maths; using Robust.Shared.Serialization.Manager.Attributes; namespace Content.Server.Chemistry.TileReactions @@ -35,6 +37,12 @@ namespace Content.Server.Chemistry.TileReactions } } + var decalSystem = EntitySystem.Get(); + foreach (var uid in decalSystem.GetDecalsInRange(tile.GridIndex, tile.GridIndices+new Vector2(0.5f, 0.5f), validDelegate: x => x.Cleanable)) + { + decalSystem.RemoveDecal(tile.GridIndex, uid); + } + return amount; } } diff --git a/Content.Server/Crayon/CrayonComponent.cs b/Content.Server/Crayon/CrayonComponent.cs index 5290a391eb..302ad335e2 100644 --- a/Content.Server/Crayon/CrayonComponent.cs +++ b/Content.Server/Crayon/CrayonComponent.cs @@ -2,9 +2,10 @@ using System.Linq; using System.Threading.Tasks; using Content.Server.Administration.Logs; using Content.Server.UserInterface; -using Content.Shared.Administration.Logs; using Content.Shared.Audio; using Content.Shared.Crayon; +using Content.Server.Decals; +using Content.Shared.Decals; using Content.Shared.Database; using Content.Shared.Interaction; using Content.Shared.Interaction.Helpers; @@ -60,11 +61,8 @@ namespace Content.Server.Crayon Charges = Capacity; // Get the first one from the catalog and set it as default - var decals = _prototypeManager.EnumeratePrototypes().FirstOrDefault(); - if (decals != null) - { - SelectedState = decals.Decals.First(); - } + var decal = _prototypeManager.EnumeratePrototypes().FirstOrDefault(x => x.Tags.Contains("crayon")); + SelectedState = decal?.ID ?? string.Empty; Dirty(); } @@ -74,14 +72,10 @@ namespace Content.Server.Crayon { case CrayonSelectMessage msg: // Check if the selected state is valid - var crayonDecals = _prototypeManager.EnumeratePrototypes().FirstOrDefault(); - if (crayonDecals != null) + if (_prototypeManager.TryIndex(msg.State, out var prototype) && prototype.Tags.Contains("crayon")) { - if (crayonDecals.Decals.Contains(msg.State)) - { - SelectedState = msg.State; - Dirty(); - } + SelectedState = msg.State; + Dirty(); } break; default: @@ -131,12 +125,8 @@ namespace Content.Server.Crayon return true; } - var entity = Owner.EntityManager.SpawnEntity("CrayonDecal", eventArgs.ClickLocation); - if (entity.TryGetComponent(out AppearanceComponent? appearance)) - { - appearance.SetData(CrayonVisuals.State, SelectedState); - appearance.SetData(CrayonVisuals.Color, _color); - } + if(!EntitySystem.Get().TryAddDecal(SelectedState, eventArgs.ClickLocation.Offset(new Vector2(-0.5f,-0.5f)), out _, Color.FromName(_color), cleanable: true)) + return false; if (_useSound != null) SoundSystem.Play(Filter.Pvs(Owner), _useSound.GetSound(), Owner, AudioHelpers.WithVariation(0.125f)); diff --git a/Content.Server/Decals/Commands/AddDecalCommand.cs b/Content.Server/Decals/Commands/AddDecalCommand.cs new file mode 100644 index 0000000000..e9ef52fdea --- /dev/null +++ b/Content.Server/Decals/Commands/AddDecalCommand.cs @@ -0,0 +1,118 @@ +using System; +using System.Collections.Generic; +using Content.Server.Administration; +using Content.Shared.Administration; +using Content.Shared.Decals; +using Content.Shared.Maps; +using Robust.Shared.Console; +using Robust.Shared.GameObjects; +using Robust.Shared.IoC; +using Robust.Shared.Map; +using Robust.Shared.Maths; +using Robust.Shared.Prototypes; + +namespace Content.Server.Decals.Commands +{ + [AdminCommand(AdminFlags.Mapping)] + public sealed class AddDecalCommand : IConsoleCommand + { + public string Command => "adddecal"; + public string Description => "Creates a decal on the map"; + public string Help => $"{Command} [angle= zIndex= color=]"; + public void Execute(IConsoleShell shell, string argStr, string[] args) + { + if (args.Length < 4 || args.Length > 7) + { + shell.WriteError($"Received invalid amount of arguments arguments. Expected 4 to 7, got {args.Length}.\nUsage: {Help}"); + return; + } + + if (!IoCManager.Resolve().HasIndex(args[0])) + { + shell.WriteError($"Cannot find decalprototype '{args[0]}'."); + } + + if (!float.TryParse(args[1], out var x)) + { + shell.WriteError($"Failed parsing x-coordinate '{args[1]}'."); + return; + } + + if (!float.TryParse(args[2], out var y)) + { + shell.WriteError($"Failed parsing y-coordinate'{args[2]}'."); + return; + } + + var mapManager = IoCManager.Resolve(); + if (!int.TryParse(args[3], out var gridIdRaw) || !mapManager.TryGetGrid(new GridId(gridIdRaw), out var grid)) + { + shell.WriteError($"Failed parsing gridId '{args[3]}'."); + return; + } + + var coordinates = new EntityCoordinates(grid.GridEntityId, new Vector2(x, y)); + if (grid.GetTileRef(coordinates).IsSpace()) + { + shell.WriteError($"Cannot create decal on space tile at {coordinates}."); + return; + } + + Color? color = null; + var zIndex = 0; + Angle? rotation = null; + if (args.Length > 4) + { + for (int i = 4; i < args.Length; i++) + { + var rawValue = args[i].Split('='); + if (rawValue.Length != 2) + { + shell.WriteError($"Failed parsing parameter: '{args[i]}'"); + return; + } + + switch (rawValue[0]) + { + case "angle": + if (!double.TryParse(rawValue[1], out var degrees)) + { + shell.WriteError($"Failed parsing angle '{rawValue[1]}'."); + return; + } + rotation = Angle.FromDegrees(degrees); + break; + case "zIndex": + if (!int.TryParse(rawValue[1], out zIndex)) + { + shell.WriteError($"Failed parsing zIndex '{rawValue[1]}'."); + return; + } + break; + case "color": + if (!Color.TryFromName(rawValue[1], out var colorRaw)) + { + shell.WriteError($"Failed parsing color '{rawValue[1]}'."); + return; + } + + color = colorRaw; + break; + default: + shell.WriteError($"Unknown parameter key '{rawValue[0]}'."); + return; + } + } + } + + if(EntitySystem.Get().TryAddDecal(args[0], coordinates, out var uid, color, rotation, zIndex)) + { + shell.WriteLine($"Successfully created decal {uid}."); + } + else + { + shell.WriteError($"Failed adding decal."); + } + } + } +} diff --git a/Content.Server/Decals/Commands/EditDecalCommand.cs b/Content.Server/Decals/Commands/EditDecalCommand.cs new file mode 100644 index 0000000000..af01b4d79b --- /dev/null +++ b/Content.Server/Decals/Commands/EditDecalCommand.cs @@ -0,0 +1,162 @@ +using Content.Server.Administration; +using Content.Shared.Administration; +using Robust.Shared.Console; +using Robust.Shared.GameObjects; +using Robust.Shared.IoC; +using Robust.Shared.Map; +using Robust.Shared.Maths; + +namespace Content.Server.Decals; + +[AdminCommand(AdminFlags.Mapping)] +public class EditDecalCommand : IConsoleCommand +{ + public string Command => "editdecal"; + public string Description => "Edits a decal."; + public string Help => $@"{Command} \n +Possible modes are:\n +- position \n +- color \n +- id \n +- rotation \n +- zindex \n +- clean +"; + public void Execute(IConsoleShell shell, string argStr, string[] args) + { + if (args.Length < 4) + { + shell.WriteError("Expected at least 5 arguments."); + return; + } + + if (!int.TryParse(args[0], out var gridIdRaw)) + { + shell.WriteError($"Failed parsing gridId '{args[3]}'."); + return; + } + + if (!uint.TryParse(args[1], out var uid)) + { + shell.WriteError($"Failed parsing uid '{args[1]}'."); + return; + } + + var gridId = new GridId(gridIdRaw); + if (!IoCManager.Resolve().GridExists(gridId)) + { + shell.WriteError($"No grid with gridId {gridId} exists."); + return; + } + + var decalSystem = EntitySystem.Get(); + switch (args[2].ToLower()) + { + case "position": + if(args.Length != 5) + { + shell.WriteError("Expected 6 arguments."); + return; + } + + if (!float.TryParse(args[3], out var x) || !float.TryParse(args[4], out var y)) + { + shell.WriteError("Failed parsing position."); + return; + } + + if (!decalSystem.SetDecalPosition(gridId, uid, gridId, new Vector2(x, y))) + { + shell.WriteError("Failed changing decalposition."); + } + break; + case "color": + if(args.Length != 4) + { + shell.WriteError("Expected 5 arguments."); + return; + } + + if (!Color.TryFromName(args[3], out var color)) + { + shell.WriteError("Failed parsing color."); + return; + } + + if (!decalSystem.SetDecalColor(gridId, uid, color)) + { + shell.WriteError("Failed changing decal color."); + } + break; + case "id": + if(args.Length != 4) + { + shell.WriteError("Expected 5 arguments."); + return; + } + + if (!decalSystem.SetDecalId(gridId, uid, args[3])) + { + shell.WriteError("Failed changing decal id."); + } + break; + case "rotation": + if(args.Length != 4) + { + shell.WriteError("Expected 5 arguments."); + return; + } + + if (!double.TryParse(args[3], out var degrees)) + { + shell.WriteError("Failed parsing degrees."); + return; + } + + if (!decalSystem.SetDecalRotation(gridId, uid, Angle.FromDegrees(degrees))) + { + shell.WriteError("Failed changing decal rotation."); + } + break; + case "zindex": + if(args.Length != 4) + { + shell.WriteError("Expected 5 arguments."); + return; + } + + if (!int.TryParse(args[3], out var zIndex)) + { + shell.WriteError("Failed parsing zIndex."); + return; + } + + if (!decalSystem.SetDecalZIndex(gridId, uid, zIndex)) + { + shell.WriteError("Failed changing decal zIndex."); + } + break; + case "clean": + if(args.Length != 4) + { + shell.WriteError("Expected 5 arguments."); + return; + } + + if (!bool.TryParse(args[3], out var cleanable)) + { + shell.WriteError("Failed parsing cleanable."); + return; + } + + if (!decalSystem.SetDecalCleanable(gridId, uid, cleanable)) + { + shell.WriteError("Failed changing decal cleanable flag."); + } + break; + default: + shell.WriteError("Invalid mode."); + return; + } + } +} diff --git a/Content.Server/Decals/Commands/RemoveDecalCommand.cs b/Content.Server/Decals/Commands/RemoveDecalCommand.cs new file mode 100644 index 0000000000..a2bf370f34 --- /dev/null +++ b/Content.Server/Decals/Commands/RemoveDecalCommand.cs @@ -0,0 +1,46 @@ +using Content.Server.Administration; +using Content.Shared.Administration; +using Robust.Shared.Console; +using Robust.Shared.GameObjects; +using Robust.Shared.IoC; +using Robust.Shared.Map; + +namespace Content.Server.Decals.Commands +{ + [AdminCommand(AdminFlags.Mapping)] + public class RemoveDecalCommand : IConsoleCommand + { + public string Command => "rmdecal"; + public string Description => "removes a decal"; + public string Help => $"{Command} "; + public void Execute(IConsoleShell shell, string argStr, string[] args) + { + if (args.Length != 2) + { + shell.WriteError($"Unexpected number of arguments.\nExpected two: {Help}"); + return; + } + + if (!uint.TryParse(args[0], out var uid)) + { + shell.WriteError($"Failed parsing uid."); + return; + } + + if (!int.TryParse(args[1], out var rawGridId) || + !IoCManager.Resolve().GridExists(new GridId(rawGridId))) + { + shell.WriteError("Failed parsing gridId."); + } + + var decalSystem = EntitySystem.Get(); + if (decalSystem.RemoveDecal(new GridId(rawGridId), uid)) + { + shell.WriteLine($"Successfully removed decal {uid}."); + return; + } + + shell.WriteError($"Failed trying to remove decal {uid}."); + } + } +} diff --git a/Content.Server/Decals/DecalSystem.cs b/Content.Server/Decals/DecalSystem.cs new file mode 100644 index 0000000000..d35f2c57ad --- /dev/null +++ b/Content.Server/Decals/DecalSystem.cs @@ -0,0 +1,326 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using Content.Shared.Decals; +using Content.Shared.Maps; +using Robust.Server.Player; +using Robust.Shared.Enums; +using Robust.Shared.GameObjects; +using Robust.Shared.IoC; +using Robust.Shared.Map; +using Robust.Shared.Maths; +using Robust.Shared.Player; + +namespace Content.Server.Decals +{ + public class DecalSystem : SharedDecalSystem + { + [Dependency] private readonly IPlayerManager _playerManager = default!; + + private readonly Dictionary> _dirtyChunks = new(); + private readonly Dictionary>> _previousSentChunks = new(); + + public override void Initialize() + { + base.Initialize(); + + _playerManager.PlayerStatusChanged += OnPlayerStatusChanged; + MapManager.TileChanged += OnTileChanged; + } + + public override void Shutdown() + { + base.Shutdown(); + + _playerManager.PlayerStatusChanged -= OnPlayerStatusChanged; + MapManager.TileChanged -= OnTileChanged; + } + + private void OnTileChanged(object? sender, TileChangedEventArgs e) + { + if (!e.NewTile.IsSpace()) + return; + + var chunkCollection = ChunkCollection(e.NewTile.GridIndex); + var indices = GetChunkIndices(e.NewTile.GridIndices); + var toDelete = new HashSet(); + if (chunkCollection.TryGetValue(indices, out var chunk)) + { + foreach (var (uid, decal) in chunk) + { + if (new Vector2((int) Math.Floor(decal.Coordinates.X), (int) Math.Floor(decal.Coordinates.Y)) == + e.NewTile.GridIndices) + { + toDelete.Add(uid); + } + } + } + + if (toDelete.Count == 0) return; + + foreach (var uid in toDelete) + { + RemoveDecalInternal(e.NewTile.GridIndex, uid); + } + + DirtyChunk(e.NewTile.GridIndex, indices); + } + + private void OnPlayerStatusChanged(object? sender, SessionStatusEventArgs e) + { + switch (e.NewStatus) + { + case SessionStatus.InGame: + _previousSentChunks[e.Session] = new(); + break; + case SessionStatus.Disconnected: + _previousSentChunks.Remove(e.Session); + break; + } + } + + protected override void DirtyChunk(GridId id, Vector2i chunkIndices) + { + if(!_dirtyChunks.ContainsKey(id)) + _dirtyChunks[id] = new HashSet(); + _dirtyChunks[id].Add(chunkIndices); + } + + public bool TryAddDecal(string id, EntityCoordinates coordinates, [NotNullWhen(true)] out uint? uid, Color? color = null, Angle? rotation = null, int zIndex = 0, bool cleanable = false) + { + uid = 0; + if (!PrototypeManager.HasIndex(id)) + return false; + + var gridId = coordinates.GetGridId(EntityManager); + if (MapManager.GetGrid(gridId).GetTileRef(coordinates).IsSpace()) + return false; + + rotation ??= Angle.Zero; + var decal = new Decal(coordinates.Position, id, color, rotation.Value, zIndex, cleanable); + var chunkCollection = DecalGridChunkCollection(gridId); + uid = chunkCollection.NextUid++; + var chunkIndices = GetChunkIndices(decal.Coordinates); + if(!chunkCollection.ChunkCollection.ContainsKey(chunkIndices)) + chunkCollection.ChunkCollection[chunkIndices] = new(); + chunkCollection.ChunkCollection[chunkIndices][uid.Value] = decal; + ChunkIndex[gridId][uid.Value] = chunkIndices; + DirtyChunk(gridId, chunkIndices); + return true; + } + + public bool RemoveDecal(GridId gridId, uint uid) => RemoveDecalInternal(gridId, uid); + + public HashSet GetDecalsInRange(GridId gridId, Vector2 position, float distance = 0.75f, Func? validDelegate = null) + { + var uids = new HashSet(); + var chunkCollection = ChunkCollection(gridId); + var chunkIndices = GetChunkIndices(position); + if (!chunkCollection.TryGetValue(chunkIndices, out var chunk)) + return uids; + + foreach (var (uid, decal) in chunk) + { + if ((position - decal.Coordinates-new Vector2(0.5f, 0.5f)).Length > distance) + continue; + + if (validDelegate == null || validDelegate(decal)) + { + uids.Add(uid); + } + } + + return uids; + } + + public bool SetDecalPosition(GridId gridId, uint uid, EntityCoordinates coordinates) + { + return SetDecalPosition(gridId, uid, coordinates.GetGridId(EntityManager), coordinates.Position); + } + + public bool SetDecalPosition(GridId gridId, uint uid, GridId newGridId, Vector2 position) + { + if (!ChunkIndex.TryGetValue(gridId, out var values) || !values.TryGetValue(uid, out var indices)) + { + return false; + } + + DirtyChunk(gridId, indices); + var chunkCollection = ChunkCollection(gridId); + var decal = chunkCollection[indices][uid]; + if (newGridId == gridId) + { + chunkCollection[indices][uid] = decal.WithCoordinates(position); + return true; + } + + RemoveDecalInternal(gridId, uid); + + var newChunkCollection = ChunkCollection(newGridId); + var chunkIndices = GetChunkIndices(position); + if(!newChunkCollection.ContainsKey(chunkIndices)) + newChunkCollection[chunkIndices] = new(); + newChunkCollection[chunkIndices][uid] = decal.WithCoordinates(position); + ChunkIndex[newGridId][uid] = chunkIndices; + DirtyChunk(newGridId, chunkIndices); + return true; + } + + public bool SetDecalColor(GridId gridId, uint uid, Color? color) + { + if (!ChunkIndex.TryGetValue(gridId, out var values) || !values.TryGetValue(uid, out var indices)) + { + return false; + } + + var chunkCollection = ChunkCollection(gridId); + var decal = chunkCollection[indices][uid]; + chunkCollection[indices][uid] = decal.WithColor(color); + DirtyChunk(gridId, indices); + return true; + } + + public bool SetDecalId(GridId gridId, uint uid, string id) + { + if (!ChunkIndex.TryGetValue(gridId, out var values) || !values.TryGetValue(uid, out var indices)) + { + return false; + } + + if (!PrototypeManager.HasIndex(id)) + throw new ArgumentOutOfRangeException($"Tried to set decal id to invalid prototypeid: {id}"); + + var chunkCollection = ChunkCollection(gridId); + var decal = chunkCollection[indices][uid]; + chunkCollection[indices][uid] = decal.WithId(id); + DirtyChunk(gridId, indices); + return true; + } + + public bool SetDecalRotation(GridId gridId, uint uid, Angle angle) + { + if (!ChunkIndex.TryGetValue(gridId, out var values) || !values.TryGetValue(uid, out var indices)) + { + return false; + } + + var chunkCollection = ChunkCollection(gridId); + var decal = chunkCollection[indices][uid]; + chunkCollection[indices][uid] = decal.WithRotation(angle); + DirtyChunk(gridId, indices); + return true; + } + + public bool SetDecalZIndex(GridId gridId, uint uid, int zIndex) + { + if (!ChunkIndex.TryGetValue(gridId, out var values) || !values.TryGetValue(uid, out var indices)) + { + return false; + } + + var chunkCollection = ChunkCollection(gridId); + var decal = chunkCollection[indices][uid]; + chunkCollection[indices][uid] = decal.WithZIndex(zIndex); + DirtyChunk(gridId, indices); + return true; + } + + public bool SetDecalCleanable(GridId gridId, uint uid, bool cleanable) + { + if (!ChunkIndex.TryGetValue(gridId, out var values) || !values.TryGetValue(uid, out var indices)) + { + return false; + } + + var chunkCollection = ChunkCollection(gridId); + var decal = chunkCollection[indices][uid]; + chunkCollection[indices][uid] = decal.WithCleanable(cleanable); + DirtyChunk(gridId, indices); + return true; + } + + public override void Update(float frameTime) + { + base.Update(frameTime); + + + foreach (var session in Filter.Broadcast().Recipients) + { + if(session is not IPlayerSession playerSession || playerSession.Status != SessionStatus.InGame) + continue; + + var chunks = GetChunksForSession(playerSession); + var updatedChunks = new Dictionary>(); + foreach (var (gridId, gridChunks) in chunks) + { + var newChunks = new HashSet(gridChunks); + if (_previousSentChunks[playerSession].TryGetValue(gridId, out var previousChunks)) + { + newChunks.ExceptWith(previousChunks); + } + + if (_dirtyChunks.TryGetValue(gridId, out var dirtyChunks)) + { + gridChunks.IntersectWith(dirtyChunks); + newChunks.UnionWith(gridChunks); + } + + if (newChunks.Count == 0) + continue; + + updatedChunks[gridId] = newChunks; + } + + if(updatedChunks.Count == 0) + continue; + + _previousSentChunks[playerSession] = chunks; + + //send all gridChunks to client + SendChunkUpdates(playerSession, updatedChunks); + } + + _dirtyChunks.Clear(); + } + + private void SendChunkUpdates(IPlayerSession session, Dictionary> updatedChunks) + { + var updatedDecals = new Dictionary>>(); + foreach (var (gridId, chunks) in updatedChunks) + { + var gridChunks = new Dictionary>(); + foreach (var indices in chunks) + { + gridChunks.Add(indices, + ChunkCollection(gridId).TryGetValue(indices, out var chunk) + ? chunk + : new Dictionary()); + } + updatedDecals[gridId] = gridChunks; + } + + RaiseNetworkEvent(new DecalChunkUpdateEvent{Data = updatedDecals}, Filter.SinglePlayer(session)); + } + + private HashSet GetSessionViewers(IPlayerSession session) + { + var viewers = new HashSet(); + if (session.Status != SessionStatus.InGame || session.AttachedEntityUid is null) + return viewers; + + viewers.Add(session.AttachedEntityUid.Value); + + foreach (var uid in session.ViewSubscriptions) + { + viewers.Add(uid); + } + + return viewers; + } + + private Dictionary> GetChunksForSession(IPlayerSession session) + { + return GetChunksForViewers(GetSessionViewers(session)); + } + } +} diff --git a/Content.Shared/Crayon/SharedCrayonComponent.cs b/Content.Shared/Crayon/SharedCrayonComponent.cs index 6019a72300..4fd6cf44d1 100644 --- a/Content.Shared/Crayon/SharedCrayonComponent.cs +++ b/Content.Shared/Crayon/SharedCrayonComponent.cs @@ -72,16 +72,4 @@ namespace Content.Shared.Crayon Color = color; } } - - [Serializable, NetSerializable, Prototype("crayonDecal")] - public class CrayonDecalPrototype : IPrototype - { - [ViewVariables] - [DataField("id", required: true)] - public string ID { get; } = default!; - - [DataField("spritePath")] public string SpritePath { get; } = string.Empty; - - [DataField("decals")] public List Decals { get; } = new(); - } } diff --git a/Content.Shared/Decals/Decal.cs b/Content.Shared/Decals/Decal.cs new file mode 100644 index 0000000000..edca3da7e4 --- /dev/null +++ b/Content.Shared/Decals/Decal.cs @@ -0,0 +1,38 @@ +using System; +using Robust.Shared.Maths; +using Robust.Shared.Serialization; +using Robust.Shared.Serialization.Manager.Attributes; + +namespace Content.Shared.Decals +{ + [Serializable, NetSerializable] + [DataDefinition] + public class Decal + { + [DataField("coordinates")] public readonly Vector2 Coordinates = Vector2.Zero; + [DataField("id")] public readonly string Id = string.Empty; + [DataField("color")] public readonly Color? Color; + [DataField("angle")] public readonly Angle Angle = Angle.Zero; + [DataField("zIndex")] public readonly int ZIndex; + [DataField("cleanable")] public bool Cleanable; + + public Decal() {} + + public Decal(Vector2 coordinates, string id, Color? color, Angle angle, int zIndex, bool cleanable) + { + Coordinates = coordinates; + Id = id; + Color = color; + Angle = angle; + ZIndex = zIndex; + Cleanable = cleanable; + } + + public Decal WithCoordinates(Vector2 coordinates) => new(coordinates, Id, Color, Angle, ZIndex, Cleanable); + public Decal WithId(string id) => new(Coordinates, id, Color, Angle, ZIndex, Cleanable); + public Decal WithColor(Color? color) => new(Coordinates, Id, color, Angle, ZIndex, Cleanable); + public Decal WithRotation(Angle angle) => new(Coordinates, Id, Color, angle, ZIndex, Cleanable); + public Decal WithZIndex(int zIndex) => new(Coordinates, Id, Color, Angle, zIndex, Cleanable); + public Decal WithCleanable(bool cleanable) => new(Coordinates, Id, Color, Angle, ZIndex, cleanable); + } +} diff --git a/Content.Shared/Decals/DecalChunkUpdateEvent.cs b/Content.Shared/Decals/DecalChunkUpdateEvent.cs new file mode 100644 index 0000000000..99bbbd5a84 --- /dev/null +++ b/Content.Shared/Decals/DecalChunkUpdateEvent.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using Robust.Shared.GameObjects; +using Robust.Shared.Map; +using Robust.Shared.Maths; +using Robust.Shared.Serialization; + +namespace Content.Shared.Decals +{ + [Serializable, NetSerializable] + public class DecalChunkUpdateEvent : EntityEventArgs + { + public Dictionary>> Data = new(); + } +} diff --git a/Content.Shared/Decals/DecalGridChunkCollectionTypeSerializer.cs b/Content.Shared/Decals/DecalGridChunkCollectionTypeSerializer.cs new file mode 100644 index 0000000000..301fb2aaed --- /dev/null +++ b/Content.Shared/Decals/DecalGridChunkCollectionTypeSerializer.cs @@ -0,0 +1,74 @@ +using System.Collections.Generic; +using Robust.Shared.IoC; +using Robust.Shared.Maths; +using Robust.Shared.Serialization.Manager; +using Robust.Shared.Serialization.Manager.Attributes; +using Robust.Shared.Serialization.Manager.Result; +using Robust.Shared.Serialization.Markdown; +using Robust.Shared.Serialization.Markdown.Mapping; +using Robust.Shared.Serialization.Markdown.Validation; +using Robust.Shared.Serialization.TypeSerializers.Interfaces; + +namespace Content.Shared.Decals +{ + [TypeSerializer] + public class DecalGridChunkCollectionTypeSerializer : ITypeSerializer + { + public ValidationNode Validate(ISerializationManager serializationManager, MappingDataNode node, + IDependencyCollection dependencies, ISerializationContext? context = null) + { + return serializationManager.ValidateNode>>(node, context); + } + + public DeserializationResult Read(ISerializationManager serializationManager, MappingDataNode node, + IDependencyCollection dependencies, bool skipHook, ISerializationContext? context = null) + { + //todo this read method does not support pushing inheritance + var dictionary = + serializationManager.ReadValueOrThrow>>(node, context, skipHook); + + var uids = new SortedSet(); + var uidChunkMap = new Dictionary(); + foreach (var (indices, decals) in dictionary) + { + foreach (var (uid, _) in decals) + { + uids.Add(uid); + uidChunkMap[uid] = indices; + } + } + + var uidMap = new Dictionary(); + uint nextIndex = 0; + foreach (var uid in uids) + { + uidMap[uid] = nextIndex++; + } + + var newDict = new Dictionary>(); + foreach (var (oldUid, newUid) in uidMap) + { + var indices = uidChunkMap[oldUid]; + if(!newDict.ContainsKey(indices)) + newDict[indices] = new(); + newDict[indices][newUid] = dictionary[indices][oldUid]; + } + + return new DeserializedValue( + new DecalGridComponent.DecalGridChunkCollection(newDict){NextUid = nextIndex}); + } + + public DataNode Write(ISerializationManager serializationManager, DecalGridComponent.DecalGridChunkCollection value, bool alwaysWrite = false, + ISerializationContext? context = null) + { + return serializationManager.WriteValue(value.ChunkCollection, alwaysWrite, context); + } + + public DecalGridComponent.DecalGridChunkCollection Copy(ISerializationManager serializationManager, DecalGridComponent.DecalGridChunkCollection source, + DecalGridComponent.DecalGridChunkCollection target, bool skipHook, ISerializationContext? context = null) + { + var dict = serializationManager.Copy(source.ChunkCollection, target.ChunkCollection, context, skipHook)!; + return new DecalGridComponent.DecalGridChunkCollection(dict) {NextUid = source.NextUid}; + } + } +} diff --git a/Content.Shared/Decals/DecalGridComponent.cs b/Content.Shared/Decals/DecalGridComponent.cs new file mode 100644 index 0000000000..205a5baed8 --- /dev/null +++ b/Content.Shared/Decals/DecalGridComponent.cs @@ -0,0 +1,23 @@ +using System.Collections.Generic; +using Robust.Shared.Analyzers; +using Robust.Shared.GameObjects; +using Robust.Shared.Maths; +using Robust.Shared.Serialization.Manager.Attributes; + +namespace Content.Shared.Decals +{ + [RegisterComponent] + [Friend(typeof(SharedDecalSystem))] + public class DecalGridComponent : Component + { + public override string Name => "DecalGrid"; + + [DataField("chunkCollection", serverOnly: true)] + public DecalGridChunkCollection ChunkCollection = new(new ()); + + public record DecalGridChunkCollection(Dictionary> ChunkCollection) + { + public uint NextUid; + } + } +} diff --git a/Content.Shared/Decals/DecalPrototype.cs b/Content.Shared/Decals/DecalPrototype.cs new file mode 100644 index 0000000000..cd648fa162 --- /dev/null +++ b/Content.Shared/Decals/DecalPrototype.cs @@ -0,0 +1,15 @@ +using System.Collections.Generic; +using Robust.Shared.Prototypes; +using Robust.Shared.Serialization.Manager.Attributes; +using Robust.Shared.Utility; + +namespace Content.Shared.Decals +{ + [Prototype("decal")] + public class DecalPrototype : IPrototype + { + [DataField("id")] public string ID { get; } = null!; + [DataField("sprite")] public SpriteSpecifier Sprite { get; } = SpriteSpecifier.Invalid; + [DataField("tags")] public List Tags = new(); + } +} diff --git a/Content.Shared/Decals/SharedDecalSystem.cs b/Content.Shared/Decals/SharedDecalSystem.cs new file mode 100644 index 0000000000..2d2b65640e --- /dev/null +++ b/Content.Shared/Decals/SharedDecalSystem.cs @@ -0,0 +1,153 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using Robust.Shared; +using Robust.Shared.Configuration; +using Robust.Shared.GameObjects; +using Robust.Shared.IoC; +using Robust.Shared.Map; +using Robust.Shared.Maths; +using Robust.Shared.Prototypes; + +namespace Content.Shared.Decals +{ + public abstract class SharedDecalSystem : EntitySystem + { + [Dependency] protected readonly IPrototypeManager PrototypeManager = default!; + [Dependency] private readonly IConfigurationManager _configurationManager = default!; + [Dependency] protected readonly IMapManager MapManager = default!; + + protected readonly Dictionary> ChunkIndex = new(); + + public const int ChunkSize = 32; + public static Vector2i GetChunkIndices(Vector2 coordinates) => new ((int) Math.Floor(coordinates.X / ChunkSize), (int) Math.Floor(coordinates.Y / ChunkSize)); + + private float _viewSize; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnGridInitialize); + _configurationManager.OnValueChanged(CVars.NetMaxUpdateRange, OnPvsRangeChanged, true); + } + + public override void Shutdown() + { + base.Shutdown(); + _configurationManager.UnsubValueChanged(CVars.NetMaxUpdateRange, OnPvsRangeChanged); + } + + private void OnPvsRangeChanged(float obj) + { + _viewSize = obj * 2f; + } + + private void OnGridInitialize(GridInitializeEvent msg) + { + var comp = EntityManager.EnsureComponent(MapManager.GetGrid(msg.GridId).GridEntityId); + ChunkIndex[msg.GridId] = new(); + foreach (var (indices, decals) in comp.ChunkCollection.ChunkCollection) + { + foreach (var uid in decals.Keys) + { + ChunkIndex[msg.GridId][uid] = indices; + } + } + } + + protected DecalGridComponent.DecalGridChunkCollection DecalGridChunkCollection(GridId gridId) => EntityManager + .GetComponent(MapManager.GetGrid(gridId).GridEntityId).ChunkCollection; + protected Dictionary> ChunkCollection(GridId gridId) => DecalGridChunkCollection(gridId).ChunkCollection; + + protected virtual void DirtyChunk(GridId id, Vector2i chunkIndices) {} + + protected bool RemoveDecalInternal(GridId gridId, uint uid) + { + if (!RemoveDecalHook(gridId, uid)) return false; + + if (!ChunkIndex.TryGetValue(gridId, out var values) || !values.TryGetValue(uid, out var indices)) + { + return false; + } + + var chunkCollection = ChunkCollection(gridId); + if (!chunkCollection.TryGetValue(indices, out var chunk) || !chunk.Remove(uid)) + { + return false; + } + + if (chunkCollection[indices].Count == 0) + chunkCollection.Remove(indices); + + ChunkIndex[gridId]?.Remove(uid); + DirtyChunk(gridId, indices); + return true; + } + + protected virtual bool RemoveDecalHook(GridId gridId, uint uid) => true; + + private (Box2 view, MapId mapId) CalcViewBounds(in EntityUid euid) + { + var xform = EntityManager.GetComponent(euid); + + var view = Box2.UnitCentered.Scale(_viewSize).Translated(xform.WorldPosition); + var map = xform.MapID; + + return (view, map); + } + + protected Dictionary> GetChunksForViewers(HashSet viewers) + { + var chunks = new Dictionary>(); + foreach (var viewerUid in viewers) + { + var (bounds, mapId) = CalcViewBounds(viewerUid); + MapManager.FindGridsIntersectingEnumerator(mapId, bounds, out var gridsEnumerator, true); + while(gridsEnumerator.MoveNext(out var grid)) + { + if(!chunks.ContainsKey(grid.Index)) + chunks[grid.Index] = new(); + var enumerator = new ChunkIndicesEnumerator(grid.InvWorldMatrix.TransformBox(bounds), ChunkSize); + while (enumerator.MoveNext(out var indices)) + { + chunks[grid.Index].Add(indices.Value); + } + } + } + return chunks; + } + } + + internal struct ChunkIndicesEnumerator + { + private Vector2i _chunkLB; + private Vector2i _chunkRT; + + private int _xIndex; + private int _yIndex; + + internal ChunkIndicesEnumerator(Box2 localAABB, int chunkSize) + { + _chunkLB = new Vector2i((int)Math.Floor(localAABB.Left / chunkSize), (int)Math.Floor(localAABB.Bottom / chunkSize)); + _chunkRT = new Vector2i((int)Math.Floor(localAABB.Right / chunkSize), (int)Math.Floor(localAABB.Top / chunkSize)); + + _xIndex = _chunkLB.X; + _yIndex = _chunkLB.Y; + } + + public bool MoveNext([NotNullWhen(true)] out Vector2i? indices) + { + if (_yIndex > _chunkRT.Y) + { + _yIndex = _chunkLB.Y; + _xIndex += 1; + } + + indices = new Vector2i(_xIndex, _yIndex); + _yIndex += 1; + + return _xIndex <= _chunkRT.X; + } + } +} diff --git a/Resources/Prototypes/Catalog/crayon_decals.yml b/Resources/Prototypes/Catalog/crayon_decals.yml deleted file mode 100644 index f138f966a1..0000000000 --- a/Resources/Prototypes/Catalog/crayon_decals.yml +++ /dev/null @@ -1,135 +0,0 @@ -- type: crayonDecal - id: BaseDecals - spritePath: Effects/crayondecals.rsi - decals: - - 0 - - 1 - - 2 - - 3 - - 4 - - 5 - - 6 - - 7 - - 8 - - 9 - - Blasto - - Clandestine - - Cyber - - Diablo - - Donk - - Gene - - Gib - - Max - - Newton - - North - - Omni - - Osiron - - Prima - - Psyke - - Sirius - - Tunnel - - Waffle - - a - - ampersand - - amyjon - - antilizard - - arrow - - b - - beepsky - - biohazard - - blueprint - - body - - bottle - - brush - - c - - carp - - cat - - chevron - - clawprint - - clown - - comma - - corgi - - credit - - cyka - - d - - danger - - disk - - dot - - dwarf - - e - - electricdanger - - end - - engie - - equals - - evac - - exclamationmark - - f - - face - - fireaxe - - firedanger - - food - - footprint - - g - - ghost - - guy - - h - - heart - - i - - j - - k - - l - - largebrush - - like - - line - - m - - matt - - med - - minus - - n - - nay - - o - - p - - pawprint - - peace - - percent - - plus - - pound - - prolizard - - q - - questionmark - - r - - radiation - - revolution - - rune1 - - rune2 - - rune3 - - rune4 - - rune5 - - rune6 - - s - - safe - - scroll - - shop - - shortline - - shotgun - - skull - - slash - - smallbrush - - snake - - space - - splatter - - star - - stickman - - t - - taser - - thinline - - toilet - - toolbox - - trade - - u - - uboa - - v - - w - - x - - y - - z diff --git a/Resources/Prototypes/Decals/crayons.yml b/Resources/Prototypes/Decals/crayons.yml new file mode 100644 index 0000000000..d13bd0e3f3 --- /dev/null +++ b/Resources/Prototypes/Decals/crayons.yml @@ -0,0 +1,916 @@ +- type: decal + id: 0 + tags: ["crayon"] + sprite: + sprite: Effects/crayondecals.rsi + state: 0 + +- type: decal + id: 1 + tags: ["crayon"] + sprite: + sprite: Effects/crayondecals.rsi + state: 1 + +- type: decal + id: 2 + tags: ["crayon"] + sprite: + sprite: Effects/crayondecals.rsi + state: 2 + +- type: decal + id: 3 + tags: ["crayon"] + sprite: + sprite: Effects/crayondecals.rsi + state: 3 + +- type: decal + id: 4 + tags: ["crayon"] + sprite: + sprite: Effects/crayondecals.rsi + state: 4 + +- type: decal + id: 5 + tags: ["crayon"] + sprite: + sprite: Effects/crayondecals.rsi + state: 5 + +- type: decal + id: 6 + tags: ["crayon"] + sprite: + sprite: Effects/crayondecals.rsi + state: 6 + +- type: decal + id: 7 + tags: ["crayon"] + sprite: + sprite: Effects/crayondecals.rsi + state: 7 + +- type: decal + id: 8 + tags: ["crayon"] + sprite: + sprite: Effects/crayondecals.rsi + state: 8 + +- type: decal + id: 9 + tags: ["crayon"] + sprite: + sprite: Effects/crayondecals.rsi + state: 9 + +- type: decal + id: Blasto + tags: ["crayon"] + sprite: + sprite: Effects/crayondecals.rsi + state: Blasto + +- type: decal + id: Clandestine + tags: ["crayon"] + sprite: + sprite: Effects/crayondecals.rsi + state: Clandestine + +- type: decal + id: Cyber + tags: ["crayon"] + sprite: + sprite: Effects/crayondecals.rsi + state: Cyber + +- type: decal + id: Diablo + tags: ["crayon"] + sprite: + sprite: Effects/crayondecals.rsi + state: Diablo + +- type: decal + id: Donk + tags: ["crayon"] + sprite: + sprite: Effects/crayondecals.rsi + state: Donk + +- type: decal + id: Gene + tags: ["crayon"] + sprite: + sprite: Effects/crayondecals.rsi + state: Gene + +- type: decal + id: Gib + tags: ["crayon"] + sprite: + sprite: Effects/crayondecals.rsi + state: Gib + +- type: decal + id: Max + tags: ["crayon"] + sprite: + sprite: Effects/crayondecals.rsi + state: Max + +- type: decal + id: Newton + tags: ["crayon"] + sprite: + sprite: Effects/crayondecals.rsi + state: Newton + +- type: decal + id: North + tags: ["crayon"] + sprite: + sprite: Effects/crayondecals.rsi + state: North + +- type: decal + id: Omni + tags: ["crayon"] + sprite: + sprite: Effects/crayondecals.rsi + state: Omni + +- type: decal + id: Osiron + tags: ["crayon"] + sprite: + sprite: Effects/crayondecals.rsi + state: Osiron + +- type: decal + id: Prima + tags: ["crayon"] + sprite: + sprite: Effects/crayondecals.rsi + state: Prima + +- type: decal + id: Psyke + tags: ["crayon"] + sprite: + sprite: Effects/crayondecals.rsi + state: Psyke + +- type: decal + id: Sirius + tags: ["crayon"] + sprite: + sprite: Effects/crayondecals.rsi + state: Sirius + +- type: decal + id: Tunnel + tags: ["crayon"] + sprite: + sprite: Effects/crayondecals.rsi + state: Tunnel + +- type: decal + id: Waffle + tags: ["crayon"] + sprite: + sprite: Effects/crayondecals.rsi + state: Waffle + +- type: decal + id: a + tags: ["crayon"] + sprite: + sprite: Effects/crayondecals.rsi + state: a + +- type: decal + id: ampersand + tags: ["crayon"] + sprite: + sprite: Effects/crayondecals.rsi + state: ampersand + +- type: decal + id: amyjon + tags: ["crayon"] + sprite: + sprite: Effects/crayondecals.rsi + state: amyjon + +- type: decal + id: antilizard + tags: ["crayon"] + sprite: + sprite: Effects/crayondecals.rsi + state: antilizard + +- type: decal + id: arrow + tags: ["crayon"] + sprite: + sprite: Effects/crayondecals.rsi + state: arrow + +- type: decal + id: b + tags: ["crayon"] + sprite: + sprite: Effects/crayondecals.rsi + state: b + +- type: decal + id: beepsky + tags: ["crayon"] + sprite: + sprite: Effects/crayondecals.rsi + state: beepsky + +- type: decal + id: biohazard + tags: ["crayon"] + sprite: + sprite: Effects/crayondecals.rsi + state: biohazard + +- type: decal + id: blueprint + tags: ["crayon"] + sprite: + sprite: Effects/crayondecals.rsi + state: blueprint + +- type: decal + id: body + tags: ["crayon"] + sprite: + sprite: Effects/crayondecals.rsi + state: body + +- type: decal + id: bottle + tags: ["crayon"] + sprite: + sprite: Effects/crayondecals.rsi + state: bottle + +- type: decal + id: brush + tags: ["crayon"] + sprite: + sprite: Effects/crayondecals.rsi + state: brush + +- type: decal + id: c + tags: ["crayon"] + sprite: + sprite: Effects/crayondecals.rsi + state: c + +- type: decal + id: carp + tags: ["crayon"] + sprite: + sprite: Effects/crayondecals.rsi + state: carp + +- type: decal + id: cat + tags: ["crayon"] + sprite: + sprite: Effects/crayondecals.rsi + state: cat + +- type: decal + id: chevron + tags: ["crayon"] + sprite: + sprite: Effects/crayondecals.rsi + state: chevron + +- type: decal + id: clawprint + tags: ["crayon"] + sprite: + sprite: Effects/crayondecals.rsi + state: clawprint + +- type: decal + id: clown + tags: ["crayon"] + sprite: + sprite: Effects/crayondecals.rsi + state: clown + +- type: decal + id: comma + tags: ["crayon"] + sprite: + sprite: Effects/crayondecals.rsi + state: comma + +- type: decal + id: corgi + tags: ["crayon"] + sprite: + sprite: Effects/crayondecals.rsi + state: corgi + +- type: decal + id: credit + tags: ["crayon"] + sprite: + sprite: Effects/crayondecals.rsi + state: credit + +- type: decal + id: cyka + tags: ["crayon"] + sprite: + sprite: Effects/crayondecals.rsi + state: cyka + +- type: decal + id: d + tags: ["crayon"] + sprite: + sprite: Effects/crayondecals.rsi + state: d + +- type: decal + id: danger + tags: ["crayon"] + sprite: + sprite: Effects/crayondecals.rsi + state: danger + +- type: decal + id: disk + tags: ["crayon"] + sprite: + sprite: Effects/crayondecals.rsi + state: disk + +- type: decal + id: dot + tags: ["crayon"] + sprite: + sprite: Effects/crayondecals.rsi + state: dot + +- type: decal + id: dwarf + tags: ["crayon"] + sprite: + sprite: Effects/crayondecals.rsi + state: dwarf + +- type: decal + id: e + tags: ["crayon"] + sprite: + sprite: Effects/crayondecals.rsi + state: e + +- type: decal + id: electricdanger + tags: ["crayon"] + sprite: + sprite: Effects/crayondecals.rsi + state: electricdanger + +- type: decal + id: end + tags: ["crayon"] + sprite: + sprite: Effects/crayondecals.rsi + state: end + +- type: decal + id: engie + tags: ["crayon"] + sprite: + sprite: Effects/crayondecals.rsi + state: engie + +- type: decal + id: equals + tags: ["crayon"] + sprite: + sprite: Effects/crayondecals.rsi + state: equals + +- type: decal + id: evac + tags: ["crayon"] + sprite: + sprite: Effects/crayondecals.rsi + state: evac + +- type: decal + id: exclamationmark + tags: ["crayon"] + sprite: + sprite: Effects/crayondecals.rsi + state: exclamationmark + +- type: decal + id: f + tags: ["crayon"] + sprite: + sprite: Effects/crayondecals.rsi + state: f + +- type: decal + id: face + tags: ["crayon"] + sprite: + sprite: Effects/crayondecals.rsi + state: face + +- type: decal + id: fireaxe + tags: ["crayon"] + sprite: + sprite: Effects/crayondecals.rsi + state: fireaxe + +- type: decal + id: firedanger + tags: ["crayon"] + sprite: + sprite: Effects/crayondecals.rsi + state: firedanger + +- type: decal + id: food + tags: ["crayon"] + sprite: + sprite: Effects/crayondecals.rsi + state: food + +- type: decal + id: footprint + tags: ["crayon"] + sprite: + sprite: Effects/crayondecals.rsi + state: footprint + +- type: decal + id: g + tags: ["crayon"] + sprite: + sprite: Effects/crayondecals.rsi + state: g + +- type: decal + id: ghost + tags: ["crayon"] + sprite: + sprite: Effects/crayondecals.rsi + state: ghost + +- type: decal + id: guy + tags: ["crayon"] + sprite: + sprite: Effects/crayondecals.rsi + state: guy + +- type: decal + id: h + tags: ["crayon"] + sprite: + sprite: Effects/crayondecals.rsi + state: h + +- type: decal + id: heart + tags: ["crayon"] + sprite: + sprite: Effects/crayondecals.rsi + state: heart + +- type: decal + id: i + tags: ["crayon"] + sprite: + sprite: Effects/crayondecals.rsi + state: i + +- type: decal + id: j + tags: ["crayon"] + sprite: + sprite: Effects/crayondecals.rsi + state: j + +- type: decal + id: k + tags: ["crayon"] + sprite: + sprite: Effects/crayondecals.rsi + state: k + +- type: decal + id: l + tags: ["crayon"] + sprite: + sprite: Effects/crayondecals.rsi + state: l + +- type: decal + id: largebrush + tags: ["crayon"] + sprite: + sprite: Effects/crayondecals.rsi + state: largebrush + +- type: decal + id: like + tags: ["crayon"] + sprite: + sprite: Effects/crayondecals.rsi + state: like + +- type: decal + id: line + tags: ["crayon"] + sprite: + sprite: Effects/crayondecals.rsi + state: line + +- type: decal + id: m + tags: ["crayon"] + sprite: + sprite: Effects/crayondecals.rsi + state: m + +- type: decal + id: matt + tags: ["crayon"] + sprite: + sprite: Effects/crayondecals.rsi + state: matt + +- type: decal + id: med + tags: ["crayon"] + sprite: + sprite: Effects/crayondecals.rsi + state: med + +- type: decal + id: minus + tags: ["crayon"] + sprite: + sprite: Effects/crayondecals.rsi + state: minus + +- type: decal + id: n + tags: ["crayon"] + sprite: + sprite: Effects/crayondecals.rsi + state: n + +- type: decal + id: nay + tags: ["crayon"] + sprite: + sprite: Effects/crayondecals.rsi + state: nay + +- type: decal + id: o + tags: ["crayon"] + sprite: + sprite: Effects/crayondecals.rsi + state: o + +- type: decal + id: p + tags: ["crayon"] + sprite: + sprite: Effects/crayondecals.rsi + state: p + +- type: decal + id: pawprint + tags: ["crayon"] + sprite: + sprite: Effects/crayondecals.rsi + state: pawprint + +- type: decal + id: peace + tags: ["crayon"] + sprite: + sprite: Effects/crayondecals.rsi + state: peace + +- type: decal + id: percent + tags: ["crayon"] + sprite: + sprite: Effects/crayondecals.rsi + state: percent + +- type: decal + id: plus + tags: ["crayon"] + sprite: + sprite: Effects/crayondecals.rsi + state: plus + +- type: decal + id: pound + tags: ["crayon"] + sprite: + sprite: Effects/crayondecals.rsi + state: pound + +- type: decal + id: prolizard + tags: ["crayon"] + sprite: + sprite: Effects/crayondecals.rsi + state: prolizard + +- type: decal + id: q + tags: ["crayon"] + sprite: + sprite: Effects/crayondecals.rsi + state: q + +- type: decal + id: questionmark + tags: ["crayon"] + sprite: + sprite: Effects/crayondecals.rsi + state: questionmark + +- type: decal + id: r + tags: ["crayon"] + sprite: + sprite: Effects/crayondecals.rsi + state: r + +- type: decal + id: radiation + tags: ["crayon"] + sprite: + sprite: Effects/crayondecals.rsi + state: radiation + +- type: decal + id: revolution + tags: ["crayon"] + sprite: + sprite: Effects/crayondecals.rsi + state: revolution + +- type: decal + id: rune1 + tags: ["crayon"] + sprite: + sprite: Effects/crayondecals.rsi + state: rune1 + +- type: decal + id: rune2 + tags: ["crayon"] + sprite: + sprite: Effects/crayondecals.rsi + state: rune2 + +- type: decal + id: rune3 + tags: ["crayon"] + sprite: + sprite: Effects/crayondecals.rsi + state: rune3 + +- type: decal + id: rune4 + tags: ["crayon"] + sprite: + sprite: Effects/crayondecals.rsi + state: rune4 + +- type: decal + id: rune5 + tags: ["crayon"] + sprite: + sprite: Effects/crayondecals.rsi + state: rune5 + +- type: decal + id: rune6 + tags: ["crayon"] + sprite: + sprite: Effects/crayondecals.rsi + state: rune6 + +- type: decal + id: s + tags: ["crayon"] + sprite: + sprite: Effects/crayondecals.rsi + state: s + +- type: decal + id: safe + tags: ["crayon"] + sprite: + sprite: Effects/crayondecals.rsi + state: safe + +- type: decal + id: scroll + tags: ["crayon"] + sprite: + sprite: Effects/crayondecals.rsi + state: scroll + +- type: decal + id: shop + tags: ["crayon"] + sprite: + sprite: Effects/crayondecals.rsi + state: shop + +- type: decal + id: shortline + tags: ["crayon"] + sprite: + sprite: Effects/crayondecals.rsi + state: shortline + +- type: decal + id: shotgun + tags: ["crayon"] + sprite: + sprite: Effects/crayondecals.rsi + state: shotgun + +- type: decal + id: skull + tags: ["crayon"] + sprite: + sprite: Effects/crayondecals.rsi + state: skull + +- type: decal + id: slash + tags: ["crayon"] + sprite: + sprite: Effects/crayondecals.rsi + state: slash + +- type: decal + id: smallbrush + tags: ["crayon"] + sprite: + sprite: Effects/crayondecals.rsi + state: smallbrush + +- type: decal + id: snake + tags: ["crayon"] + sprite: + sprite: Effects/crayondecals.rsi + state: snake + +- type: decal + id: space + tags: ["crayon"] + sprite: + sprite: Effects/crayondecals.rsi + state: space + +- type: decal + id: splatter + tags: ["crayon"] + sprite: + sprite: Effects/crayondecals.rsi + state: splatter + +- type: decal + id: star + tags: ["crayon"] + sprite: + sprite: Effects/crayondecals.rsi + state: star + +- type: decal + id: stickman + tags: ["crayon"] + sprite: + sprite: Effects/crayondecals.rsi + state: stickman + +- type: decal + id: t + tags: ["crayon"] + sprite: + sprite: Effects/crayondecals.rsi + state: t + +- type: decal + id: taser + tags: ["crayon"] + sprite: + sprite: Effects/crayondecals.rsi + state: taser + +- type: decal + id: thinline + tags: ["crayon"] + sprite: + sprite: Effects/crayondecals.rsi + state: thinline + +- type: decal + id: toilet + tags: ["crayon"] + sprite: + sprite: Effects/crayondecals.rsi + state: toilet + +- type: decal + id: toolbox + tags: ["crayon"] + sprite: + sprite: Effects/crayondecals.rsi + state: toolbox + +- type: decal + id: trade + tags: ["crayon"] + sprite: + sprite: Effects/crayondecals.rsi + state: trade + +- type: decal + id: u + tags: ["crayon"] + sprite: + sprite: Effects/crayondecals.rsi + state: u + +- type: decal + id: uboa + tags: ["crayon"] + sprite: + sprite: Effects/crayondecals.rsi + state: uboa + +- type: decal + id: v + tags: ["crayon"] + sprite: + sprite: Effects/crayondecals.rsi + state: v + +- type: decal + id: w + tags: ["crayon"] + sprite: + sprite: Effects/crayondecals.rsi + state: w + +- type: decal + id: x + tags: ["crayon"] + sprite: + sprite: Effects/crayondecals.rsi + state: x + +- type: decal + id: y + tags: ["crayon"] + sprite: + sprite: Effects/crayondecals.rsi + state: y + +- type: decal + id: z + tags: ["crayon"] + sprite: + sprite: Effects/crayondecals.rsi + state: z diff --git a/Resources/Prototypes/Entities/Effects/crayondecals.yml b/Resources/Prototypes/Entities/Effects/crayondecals.yml deleted file mode 100644 index b2ffa3862f..0000000000 --- a/Resources/Prototypes/Entities/Effects/crayondecals.yml +++ /dev/null @@ -1,16 +0,0 @@ -- type: entity - abstract: true - id: CrayonDecal - name: crayon drawing - description: "Graffiti. Damn kids." - components: - - type: Clickable - - type: InteractionOutline - - type: Physics - - type: Sprite - sprite: Effects/crayondecals.rsi - state: corgi - - type: Cleanable - - type: Appearance - visuals: - - type: CrayonDecalVisualizer