Skip to content

Commit

Permalink
Appearance Weakrefs (#1815)
Browse files Browse the repository at this point in the history
Co-authored-by: amylizzle <[email protected]>
Co-authored-by: wixoaGit <[email protected]>
  • Loading branch information
3 people authored Jan 5, 2025
1 parent 62fce76 commit 2033c66
Show file tree
Hide file tree
Showing 47 changed files with 1,558 additions and 838 deletions.
Binary file added Content.IntegrationTests/DMProject/Tests/icons.dmi
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@
plane = 123
icon_state = "subclass"

/proc/RunTest()
/proc/test_images()
ASSERT(image('icons.dmi', "mob") != null)

var/image/test = new /image/subclass
ASSERT(test.plane == 123)
ASSERT(test.icon_state == "subclass")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,6 @@
out += dir
ASSERT(out == 14)

/proc/RunTest()
/proc/test_nonlocal_var()
var/mob/m = new
m.dodir()
2 changes: 2 additions & 0 deletions Content.IntegrationTests/DMProject/code.dm
Original file line number Diff line number Diff line change
Expand Up @@ -30,5 +30,7 @@
test_color_matrix()
test_range()
test_verb_duplicate()
test_nonlocal_var()
test_images()
test_filter_init()
world.log << "IntegrationTests successful, /world/New() exiting..."
2 changes: 2 additions & 0 deletions Content.IntegrationTests/DMProject/environment.dme
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
#include "Tests/color_matrix.dm"
#include "Tests/range.dm"
#include "Tests/verb_duplicate.dm"
#include "Tests/nonlocal_var.dm"
#include "Tests/image.dm"
#include "Tests/filter_initial.dm"
#include "map.dmm"
#include "interface.dmf"
2 changes: 0 additions & 2 deletions Content.Tests/DMProject/Tests/Image/Image.dm

This file was deleted.

7 changes: 4 additions & 3 deletions Content.Tests/DMProject/Tests/Savefile/ExportText.dm
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
/obj/savetest
var/obj/savetest/recurse = null
/datum/savetest
var/name
var/datum/savetest/recurse = null

/proc/RunTest()
var/obj/savetest/O = new() //create a test object
var/datum/savetest/O = new() //create a test object
O.name = "test"
//O.recurse = O //TODO

Expand Down
4 changes: 2 additions & 2 deletions Content.Tests/DummyDreamMapManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,9 @@ public void InitializeAtoms(List<DreamMapJson>? maps) { }

public void SetTurf(DreamObjectTurf turf, DreamObjectDefinition type, DreamProcArguments creationArguments) { }

public void SetTurfAppearance(DreamObjectTurf turf, IconAppearance appearance) { }
public void SetTurfAppearance(DreamObjectTurf turf, MutableAppearance appearance) { }

public void SetAreaAppearance(DreamObjectArea area, IconAppearance appearance) { }
public void SetAreaAppearance(DreamObjectArea area, MutableAppearance appearance) { }

public void SetArea(DreamObjectTurf turf, DreamObjectArea area) { }

Expand Down
2 changes: 1 addition & 1 deletion DMCompiler/DMStandard/Types/Image.dm
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/image
parent_type = /datum

//note these values also need to be set in IconAppearance.cs
//note these values also need to be set in MutableAppearance.cs
var/alpha = 255
var/appearance
var/appearance_flags = 0
Expand Down
10 changes: 10 additions & 0 deletions OpenDreamClient/EntryPoint.cs
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ public override void PostInit() {
IoCManager.Resolve<IDreamSoundEngine>().Initialize();

_netManager.RegisterNetMessage<MsgAllAppearances>(RxAllAppearances);
_netManager.RegisterNetMessage<MsgNewAppearance>(RxNewAppearance);

if (_configurationManager.GetCVar(CVars.DisplayCompat))
_dreamInterface.OpenAlert(
Expand Down Expand Up @@ -112,6 +113,15 @@ private void RxAllAppearances(MsgAllAppearances message) {
clientAppearanceSystem.SetAllAppearances(message.AllAppearances);
}

private void RxNewAppearance(MsgNewAppearance message) {
if (!_entitySystemManager.TryGetEntitySystem<ClientAppearanceSystem>(out var clientAppearanceSystem)) {
Logger.GetSawmill("opendream").Error("Received MsgNewAppearance before initializing entity systems");
return;
}

clientAppearanceSystem.OnNewAppearance(message);
}

// As of RobustToolbox v0.90.0.0 there's a TileEdgeOverlay that breaks our rendering
// because we don't have an ITileDefinition for each tile.
// This removes that overlay immediately after MapSystem adds it.
Expand Down
2 changes: 1 addition & 1 deletion OpenDreamClient/Input/ContextMenu/ContextMenuPopup.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ public ContextMenuPopup() {
_metadataQuery = _entityManager.GetEntityQuery<MetaDataComponent>();
}

public void RepopulateEntities(ClientObjectReference[] entities, int? turfId) {
public void RepopulateEntities(ClientObjectReference[] entities, uint? turfId) {
ContextMenu.RemoveAllChildren();

if (_transformSystem == null)
Expand Down
57 changes: 43 additions & 14 deletions OpenDreamClient/Input/ContextMenu/VerbMenuPopup.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@
using OpenDreamClient.Rendering;
using OpenDreamShared.Dream;
using Robust.Client.AutoGenerated;
using Robust.Client.GameObjects;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
using Robust.Client.ViewVariables;
using Robust.Shared.Map;

namespace OpenDreamClient.Input.ContextMenu;

Expand Down Expand Up @@ -40,26 +42,53 @@ public VerbMenuPopup(ClientVerbSystem? verbSystem, sbyte seeInvisible, ClientObj
}

#if TOOLS
// If we're compiling with TOOLS and this is an entity, provide the option to use RT's VV or our icon debugger
// We add some additional debugging tools in TOOLS mode
var iconDebugButton = AddButton("Debug Icon");

iconDebugButton.OnPressed += _ => {
DreamIcon icon;
switch (_target.Type) {
case ClientObjectReference.RefType.Entity:
var entityManager = IoCManager.Resolve<IEntityManager>();
var entityId = entityManager.GetEntity(_target.Entity);
if (!entityManager.TryGetComponent(entityId, out DMISpriteComponent? spriteComponent)) {
Logger.GetSawmill("opendream")
.Error($"Failed to get sprite component for {entityId} when trying to debug its icon");
return;
}

icon = spriteComponent.Icon;
break;
case ClientObjectReference.RefType.Turf:
var mapManager = IoCManager.Resolve<IMapManager>();
var mapId = new MapId(_target.TurfZ);
var mapPos = new Vector2(_target.TurfX, _target.TurfY);
if (!mapManager.TryFindGridAt(mapId, mapPos, out var gridUid, out var grid)) {
Logger.GetSawmill("opendream")
.Error($"Failed to get icon for {_target} when trying to debug its icon");
return;
}

var entitySystemManager = IoCManager.Resolve<IEntitySystemManager>();
var mapSystem = entitySystemManager.GetEntitySystem<MapSystem>();
var appearanceSystem = entitySystemManager.GetEntitySystem<ClientAppearanceSystem>();
var tileRef = mapSystem.GetTileRef(gridUid, grid, (Vector2i)mapPos);
icon = appearanceSystem.GetTurfIcon((uint)tileRef.Tile.TypeId);
break;
default:
return;
}

new IconDebugWindow(icon).Show();
};

// If this is an entity, provide the option to use RT's VV
if (_target.Type == ClientObjectReference.RefType.Entity) {
var viewVariablesButton = AddButton("RT ViewVariables");
var iconDebugButton = AddButton("Debug Icon");

viewVariablesButton.OnPressed += _ => {
IoCManager.Resolve<IClientViewVariablesManager>().OpenVV(_target.Entity);
};

iconDebugButton.OnPressed += _ => {
var entityManager = IoCManager.Resolve<IEntityManager>();
var entityId = entityManager.GetEntity(_target.Entity);
if (!entityManager.TryGetComponent(entityId, out DMISpriteComponent? spriteComponent)) {
Logger.GetSawmill("opendream")
.Error($"Failed to get sprite component for {entityId} when trying to debug its icon");
return;
}

new IconDebugWindow(spriteComponent.Icon).Show();
};
}
#endif
}
Expand Down
4 changes: 2 additions & 2 deletions OpenDreamClient/Input/MouseInputSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -106,14 +106,14 @@ public void HandleStatClick(string atomRef, bool isRight, bool isMiddle) {
}
}

private (ClientObjectReference Atom, Vector2i IconPosition)? GetTurfUnderMouse(MapCoordinates mapCoords, out int? turfId) {
private (ClientObjectReference Atom, Vector2i IconPosition)? GetTurfUnderMouse(MapCoordinates mapCoords, out uint? turfId) {
// Grid coordinates are half a meter off from entity coordinates
mapCoords = new MapCoordinates(mapCoords.Position + new Vector2(0.5f), mapCoords.MapId);

if (_mapManager.TryFindGridAt(mapCoords, out var gridEntity, out var grid)) {
Vector2i position = _mapSystem.CoordinatesToTile(gridEntity, grid, _mapSystem.MapToGrid(gridEntity, mapCoords));
_mapSystem.TryGetTile(grid, position, out Tile tile);
turfId = tile.TypeId;
turfId = (uint)tile.TypeId;
Vector2i turfIconPosition = (Vector2i) ((mapCoords.Position - position) * EyeManager.PixelsPerMeter);
MapCoordinates worldPosition = _mapSystem.GridTileToWorld(gridEntity, grid, position);

Expand Down
38 changes: 19 additions & 19 deletions OpenDreamClient/Interface/DebugWindows/IconDebugWindow.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,25 +44,25 @@ private void Update() {

// Would be nice if we could use ViewVariables instead, but I couldn't find a nice way to do that
// Would be especially nice if we could use VV to make these editable
AddPropertyIfNotDefault("Name", appearance.Name, IconAppearance.Default.Name);
AddPropertyIfNotDefault("Icon State", appearance.IconState, IconAppearance.Default.IconState);
AddPropertyIfNotDefault("Direction", appearance.Direction, IconAppearance.Default.Direction);
AddPropertyIfNotDefault("Inherits Direction", appearance.InheritsDirection, IconAppearance.Default.InheritsDirection);
AddPropertyIfNotDefault("Pixel Offset X/Y", appearance.PixelOffset, IconAppearance.Default.PixelOffset);
AddPropertyIfNotDefault("Pixel Offset W/Z", appearance.PixelOffset2, IconAppearance.Default.PixelOffset2);
AddPropertyIfNotDefault("Color", appearance.Color, IconAppearance.Default.Color);
AddPropertyIfNotDefault("Alpha", appearance.Alpha, IconAppearance.Default.Alpha);
AddPropertyIfNotDefault("Glide Size", appearance.GlideSize, IconAppearance.Default.GlideSize);
AddPropertyIfNotDefault("Layer", appearance.Layer, IconAppearance.Default.Layer);
AddPropertyIfNotDefault("Plane", appearance.Plane, IconAppearance.Default.Plane);
AddPropertyIfNotDefault("Blend Mode", appearance.BlendMode, IconAppearance.Default.BlendMode);
AddPropertyIfNotDefault("Appearance Flags", appearance.AppearanceFlags, IconAppearance.Default.AppearanceFlags);
AddPropertyIfNotDefault("Invisibility", appearance.Invisibility, IconAppearance.Default.Invisibility);
AddPropertyIfNotDefault("Opacity", appearance.Opacity, IconAppearance.Default.Opacity);
AddPropertyIfNotDefault("Override", appearance.Override, IconAppearance.Default.Override);
AddPropertyIfNotDefault("Render Source", appearance.RenderSource, IconAppearance.Default.RenderSource);
AddPropertyIfNotDefault("Render Target", appearance.RenderTarget, IconAppearance.Default.RenderTarget);
AddPropertyIfNotDefault("Mouse Opacity", appearance.MouseOpacity, IconAppearance.Default.MouseOpacity);
AddPropertyIfNotDefault("Name", appearance.Name, MutableAppearance.Default.Name);
AddPropertyIfNotDefault("Icon State", appearance.IconState, MutableAppearance.Default.IconState);
AddPropertyIfNotDefault("Direction", appearance.Direction, MutableAppearance.Default.Direction);
AddPropertyIfNotDefault("Inherits Direction", appearance.InheritsDirection, MutableAppearance.Default.InheritsDirection);
AddPropertyIfNotDefault("Pixel Offset X/Y", appearance.PixelOffset, MutableAppearance.Default.PixelOffset);
AddPropertyIfNotDefault("Pixel Offset W/Z", appearance.PixelOffset2, MutableAppearance.Default.PixelOffset2);
AddPropertyIfNotDefault("Color", appearance.Color, MutableAppearance.Default.Color);
AddPropertyIfNotDefault("Alpha", appearance.Alpha, MutableAppearance.Default.Alpha);
AddPropertyIfNotDefault("Glide Size", appearance.GlideSize, MutableAppearance.Default.GlideSize);
AddPropertyIfNotDefault("Layer", appearance.Layer, MutableAppearance.Default.Layer);
AddPropertyIfNotDefault("Plane", appearance.Plane, MutableAppearance.Default.Plane);
AddPropertyIfNotDefault("Blend Mode", appearance.BlendMode, MutableAppearance.Default.BlendMode);
AddPropertyIfNotDefault("Appearance Flags", appearance.AppearanceFlags, MutableAppearance.Default.AppearanceFlags);
AddPropertyIfNotDefault("Invisibility", appearance.Invisibility, MutableAppearance.Default.Invisibility);
AddPropertyIfNotDefault("Opacity", appearance.Opacity, MutableAppearance.Default.Opacity);
AddPropertyIfNotDefault("Override", appearance.Override, MutableAppearance.Default.Override);
AddPropertyIfNotDefault("Render Source", appearance.RenderSource, MutableAppearance.Default.RenderSource);
AddPropertyIfNotDefault("Render Target", appearance.RenderTarget, MutableAppearance.Default.RenderTarget);
AddPropertyIfNotDefault("Mouse Opacity", appearance.MouseOpacity, MutableAppearance.Default.MouseOpacity);

foreach (var overlay in _icon.Overlays) {
AddDreamIconButton(OverlaysGrid, overlay);
Expand Down
62 changes: 41 additions & 21 deletions OpenDreamClient/Rendering/ClientAppearanceSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,14 @@
using OpenDreamClient.Resources;
using OpenDreamClient.Resources.ResourceTypes;
using Robust.Shared.Timing;
using OpenDreamShared.Network.Messages;

namespace OpenDreamClient.Rendering;

internal sealed class ClientAppearanceSystem : SharedAppearanceSystem {
private Dictionary<int, IconAppearance> _appearances = new();
private readonly Dictionary<int, List<Action<IconAppearance>>> _appearanceLoadCallbacks = new();
private readonly Dictionary<int, DreamIcon> _turfIcons = new();
private Dictionary<uint, ImmutableAppearance> _appearances = new();
private readonly Dictionary<uint, List<Action<ImmutableAppearance>>> _appearanceLoadCallbacks = new();
private readonly Dictionary<uint, DreamIcon> _turfIcons = new();
private readonly Dictionary<DreamFilter, ShaderInstance> _filterShaders = new();

[Dependency] private readonly IEntityManager _entityManager = default!;
Expand All @@ -23,7 +24,7 @@ internal sealed class ClientAppearanceSystem : SharedAppearanceSystem {
[Dependency] private readonly DMISpriteSystem _spriteSystem = default!;

public override void Initialize() {
SubscribeNetworkEvent<NewAppearanceEvent>(OnNewAppearance);
SubscribeNetworkEvent<RemoveAppearanceEvent>(e => _appearances.Remove(e.AppearanceId));
SubscribeNetworkEvent<AnimationEvent>(OnAnimation);
SubscribeLocalEvent<DMISpriteComponent, WorldAABBEvent>(OnWorldAABB);
}
Expand All @@ -34,19 +35,21 @@ public override void Shutdown() {
_turfIcons.Clear();
}

public void SetAllAppearances(Dictionary<int, IconAppearance> appearances) {
public void SetAllAppearances(Dictionary<uint, ImmutableAppearance> appearances) {
_appearances = appearances;

foreach (KeyValuePair<int, IconAppearance> pair in _appearances) {
//need to do this because all overlays can't be resolved until the whole appearance table is populated
foreach(KeyValuePair<uint, ImmutableAppearance> pair in _appearances) {
pair.Value.ResolveOverlays(this);
if (_appearanceLoadCallbacks.TryGetValue(pair.Key, out var callbacks)) {
foreach (var callback in callbacks) callback(pair.Value);
}
}
}

public void LoadAppearance(int appearanceId, Action<IconAppearance> loadCallback) {
public void LoadAppearance(uint appearanceId, Action<ImmutableAppearance> loadCallback) {
if (_appearances.TryGetValue(appearanceId, out var appearance)) {
loadCallback(appearance);
return;
}

if (!_appearanceLoadCallbacks.ContainsKey(appearanceId)) {
Expand All @@ -56,8 +59,8 @@ public void LoadAppearance(int appearanceId, Action<IconAppearance> loadCallback
_appearanceLoadCallbacks[appearanceId].Add(loadCallback);
}

public DreamIcon GetTurfIcon(int turfId) {
int appearanceId = turfId - 1;
public DreamIcon GetTurfIcon(uint turfId) {
uint appearanceId = turfId;

if (!_turfIcons.TryGetValue(appearanceId, out var icon)) {
icon = new DreamIcon(_spriteSystem.RenderTargetPool, _gameTiming, _clyde, this, appearanceId);
Expand All @@ -67,22 +70,31 @@ public DreamIcon GetTurfIcon(int turfId) {
return icon;
}

private void OnNewAppearance(NewAppearanceEvent e) {
_appearances[e.AppearanceId] = e.Appearance;
public void OnNewAppearance(MsgNewAppearance e) {
uint appearanceId = e.Appearance.MustGetId();
_appearances[appearanceId] = e.Appearance;
_appearances[appearanceId].ResolveOverlays(this);

if (_appearanceLoadCallbacks.TryGetValue(e.AppearanceId, out var callbacks)) {
foreach (var callback in callbacks) callback(e.Appearance);
if (_appearanceLoadCallbacks.TryGetValue(appearanceId, out var callbacks)) {
foreach (var callback in callbacks) callback(_appearances[appearanceId]);
}
}

private void OnAnimation(AnimationEvent e) {
EntityUid ent = _entityManager.GetEntity(e.Entity);
if (!_entityManager.TryGetComponent<DMISpriteComponent>(ent, out var sprite))
return;

LoadAppearance(e.TargetAppearanceId, targetAppearance => {
sprite.Icon.StartAppearanceAnimation(targetAppearance, e.Duration, e.Easing, e.Loop, e.Flags, e.Delay, e.ChainAnim);
});
if(e.Entity == NetEntity.Invalid && e.TurfId is not null) { //it's a turf or area
if(_turfIcons.TryGetValue(e.TurfId.Value-1, out var turfIcon))
LoadAppearance(e.TargetAppearanceId, targetAppearance => {
turfIcon.StartAppearanceAnimation(targetAppearance, e.Duration, e.Easing, e.Loop, e.Flags, e.Delay, e.ChainAnim);
});
} else { //image or movable
EntityUid ent = _entityManager.GetEntity(e.Entity);
if (!_entityManager.TryGetComponent<DMISpriteComponent>(ent, out var sprite))
return;

LoadAppearance(e.TargetAppearanceId, targetAppearance => {
sprite.Icon.StartAppearanceAnimation(targetAppearance, e.Duration, e.Easing, e.Loop, e.Flags, e.Delay, e.ChainAnim);
});
}
}

private void OnWorldAABB(EntityUid uid, DMISpriteComponent comp, ref WorldAABBEvent e) {
Expand Down Expand Up @@ -197,4 +209,12 @@ public ShaderInstance GetFilterShader(DreamFilter filter, Dictionary<string, IRe
_filterShaders[filter] = instance;
return instance;
}

public override ImmutableAppearance MustGetAppearanceById(uint appearanceId) {
return _appearances[appearanceId];
}

public override void RemoveAppearance(ImmutableAppearance appearance) {
throw new NotImplementedException();
}
}
Loading

0 comments on commit 2033c66

Please sign in to comment.