decal system & crayons (#5183)

Co-authored-by: Paul <ritter.paul1+git@googlemail.com>
This commit is contained in:
Paul Ritter
2021-12-03 15:35:57 +01:00
committed by GitHub
parent 46a63d82b0
commit 219d91c6da
22 changed files with 2100 additions and 220 deletions

View File

@@ -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<SpriteComponent>();
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));
}
}
}
}

View File

@@ -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<IPrototypeManager>();
var crayonDecals = prototypeManager.EnumeratePrototypes<CrayonDecalPrototype>().FirstOrDefault();
if (crayonDecals != null)
_menu.Populate(crayonDecals);
var crayonDecals = prototypeManager.EnumeratePrototypes<DecalPrototype>().Where(x => x.Tags.Contains("crayon"));
_menu.Populate(crayonDecals);
_menu.OpenCentered();
}

View File

@@ -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<DecalPrototype> prototypes)
{
var path = new ResourcePath(proto.SpritePath);
_decals = new Dictionary<string, Texture>();
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();

View File

@@ -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<string, SpriteSpecifier> cachedTextures = new();
SpriteSpecifier GetSpriteSpecifier(string id)
{
if (cachedTextures.TryGetValue(id, out var spriteSpecifier))
return spriteSpecifier;
spriteSpecifier = _prototypeManager.Index<DecalPrototype>(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);
}
}
}
}
}
}

View File

@@ -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<GridId, SortedDictionary<int, SortedDictionary<uint, Decal>>> DecalRenderIndex = new();
private Dictionary<GridId, Dictionary<uint, int>> DecalZIndexIndex = new();
public override void Initialize()
{
base.Initialize();
_overlay = new DecalOverlay(this, MapManager, PrototypeManager);
_overlayManager.AddOverlay(_overlay);
SubscribeNetworkEvent<DecalChunkUpdateEvent>(OnChunkUpdate);
SubscribeLocalEvent<GridInitializeEvent>(OnGridInitialize);
SubscribeLocalEvent<GridRemovalEvent>(OnGridRemoval);
}
public void ToggleOverlay()
{
if (_overlayManager.HasOverlay<DecalOverlay>())
{
_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<uint>(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;
}
}
}
}
}

View File

@@ -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<DecalSystem>().ToggleOverlay();
}
}

View File

@@ -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<DecalSystem>();
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;
}
}

View File

@@ -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<CrayonDecalPrototype>().FirstOrDefault();
if (decals != null)
{
SelectedState = decals.Decals.First();
}
var decal = _prototypeManager.EnumeratePrototypes<DecalPrototype>().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<CrayonDecalPrototype>().FirstOrDefault();
if (crayonDecals != null)
if (_prototypeManager.TryIndex<DecalPrototype>(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<DecalSystem>().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));

View File

@@ -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} <id> <x position> <y position> <gridId> [angle=<angle> zIndex=<zIndex> color=<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<IPrototypeManager>().HasIndex<DecalPrototype>(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<IMapManager>();
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<DecalSystem>().TryAddDecal(args[0], coordinates, out var uid, color, rotation, zIndex))
{
shell.WriteLine($"Successfully created decal {uid}.");
}
else
{
shell.WriteError($"Failed adding decal.");
}
}
}
}

View File

@@ -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} <gridId> <uid> <mode>\n
Possible modes are:\n
- position <x position> <y position>\n
- color <color>\n
- id <id>\n
- rotation <degrees>\n
- zindex <zIndex>\n
- clean <cleanable>
";
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<IMapManager>().GridExists(gridId))
{
shell.WriteError($"No grid with gridId {gridId} exists.");
return;
}
var decalSystem = EntitySystem.Get<DecalSystem>();
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;
}
}
}

View File

@@ -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} <uid> <gridId>";
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<IMapManager>().GridExists(new GridId(rawGridId)))
{
shell.WriteError("Failed parsing gridId.");
}
var decalSystem = EntitySystem.Get<DecalSystem>();
if (decalSystem.RemoveDecal(new GridId(rawGridId), uid))
{
shell.WriteLine($"Successfully removed decal {uid}.");
return;
}
shell.WriteError($"Failed trying to remove decal {uid}.");
}
}
}

View File

@@ -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<GridId, HashSet<Vector2i>> _dirtyChunks = new();
private readonly Dictionary<IPlayerSession, Dictionary<GridId, HashSet<Vector2i>>> _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<uint>();
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<Vector2i>();
_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<DecalPrototype>(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<uint> GetDecalsInRange(GridId gridId, Vector2 position, float distance = 0.75f, Func<Decal, bool>? validDelegate = null)
{
var uids = new HashSet<uint>();
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<DecalPrototype>(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<GridId, HashSet<Vector2i>>();
foreach (var (gridId, gridChunks) in chunks)
{
var newChunks = new HashSet<Vector2i>(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<GridId, HashSet<Vector2i>> updatedChunks)
{
var updatedDecals = new Dictionary<GridId, Dictionary<Vector2i, Dictionary<uint, Decal>>>();
foreach (var (gridId, chunks) in updatedChunks)
{
var gridChunks = new Dictionary<Vector2i, Dictionary<uint, Decal>>();
foreach (var indices in chunks)
{
gridChunks.Add(indices,
ChunkCollection(gridId).TryGetValue(indices, out var chunk)
? chunk
: new Dictionary<uint, Decal>());
}
updatedDecals[gridId] = gridChunks;
}
RaiseNetworkEvent(new DecalChunkUpdateEvent{Data = updatedDecals}, Filter.SinglePlayer(session));
}
private HashSet<EntityUid> GetSessionViewers(IPlayerSession session)
{
var viewers = new HashSet<EntityUid>();
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<GridId, HashSet<Vector2i>> GetChunksForSession(IPlayerSession session)
{
return GetChunksForViewers(GetSessionViewers(session));
}
}
}

View File

@@ -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<string> Decals { get; } = new();
}
}

View File

@@ -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);
}
}

View File

@@ -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<GridId, Dictionary<Vector2i, Dictionary<uint, Decal>>> Data = new();
}
}

View File

@@ -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<DecalGridComponent.DecalGridChunkCollection, MappingDataNode>
{
public ValidationNode Validate(ISerializationManager serializationManager, MappingDataNode node,
IDependencyCollection dependencies, ISerializationContext? context = null)
{
return serializationManager.ValidateNode<Dictionary<Vector2i, Dictionary<uint, Decal>>>(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<Dictionary<Vector2i, Dictionary<uint, Decal>>>(node, context, skipHook);
var uids = new SortedSet<uint>();
var uidChunkMap = new Dictionary<uint, Vector2i>();
foreach (var (indices, decals) in dictionary)
{
foreach (var (uid, _) in decals)
{
uids.Add(uid);
uidChunkMap[uid] = indices;
}
}
var uidMap = new Dictionary<uint, uint>();
uint nextIndex = 0;
foreach (var uid in uids)
{
uidMap[uid] = nextIndex++;
}
var newDict = new Dictionary<Vector2i, Dictionary<uint, Decal>>();
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<DecalGridComponent.DecalGridChunkCollection>(
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};
}
}
}

View File

@@ -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<Vector2i, Dictionary<uint, Decal>> ChunkCollection)
{
public uint NextUid;
}
}
}

View File

@@ -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<string> Tags = new();
}
}

View File

@@ -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<GridId, Dictionary<uint, Vector2i>> 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<GridInitializeEvent>(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<DecalGridComponent>(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<DecalGridComponent>(MapManager.GetGrid(gridId).GridEntityId).ChunkCollection;
protected Dictionary<Vector2i, Dictionary<uint, Decal>> 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<TransformComponent>(euid);
var view = Box2.UnitCentered.Scale(_viewSize).Translated(xform.WorldPosition);
var map = xform.MapID;
return (view, map);
}
protected Dictionary<GridId, HashSet<Vector2i>> GetChunksForViewers(HashSet<EntityUid> viewers)
{
var chunks = new Dictionary<GridId, HashSet<Vector2i>>();
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;
}
}
}

View File

@@ -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

View File

@@ -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

View File

@@ -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