diff --git a/OpenDreamClient/Interface/DreamInterfaceManager.cs b/OpenDreamClient/Interface/DreamInterfaceManager.cs index 104de7f702..7ead1208f7 100644 --- a/OpenDreamClient/Interface/DreamInterfaceManager.cs +++ b/OpenDreamClient/Interface/DreamInterfaceManager.cs @@ -1048,28 +1048,19 @@ public sealed class CursorHolder(IClyde clyde) { public readonly bool AllStateSet; public CursorHolder(IClyde clyde, DMIResource resource) : this(clyde) { - var allState = resource.GetStateAsImage("all", AtomDirection.South); + var allCursor = resource.GetStateAsImage(clyde, "all"); - if (allState is not null) { //all overrides all possible states - BaseCursor = clyde.CreateCursor(allState, new(32, 32)); + if (allCursor is not null) { //all overrides all possible states + BaseCursor = allCursor; DragCursor = BaseCursor; DropCursor = BaseCursor; OverCursor = BaseCursor; AllStateSet = true; } else { - var baseState = resource.GetStateAsImage("", AtomDirection.South); - var overState = resource.GetStateAsImage("over", AtomDirection.South); - var dragState = resource.GetStateAsImage("drag", AtomDirection.South); - var dropState = resource.GetStateAsImage("drop", AtomDirection.South); - - if (baseState is not null) - BaseCursor = clyde.CreateCursor(baseState, new(32, 32)); - if (overState is not null) - OverCursor = clyde.CreateCursor(overState, new(32, 32)); - if (dragState is not null) - DragCursor = clyde.CreateCursor(dragState, new(32, 32)); - if (dropState is not null) - DropCursor = clyde.CreateCursor(dropState, new(32, 32)); + BaseCursor = resource.GetStateAsImage(clyde, ""); + OverCursor = resource.GetStateAsImage(clyde, "over"); + DragCursor = resource.GetStateAsImage(clyde, "drag"); + DropCursor = resource.GetStateAsImage(clyde, "drop"); } } } diff --git a/OpenDreamClient/Resources/ResourceTypes/DMIResource.cs b/OpenDreamClient/Resources/ResourceTypes/DMIResource.cs index 036e2abe8d..d18d163630 100644 --- a/OpenDreamClient/Resources/ResourceTypes/DMIResource.cs +++ b/OpenDreamClient/Resources/ResourceTypes/DMIResource.cs @@ -51,21 +51,26 @@ private void ProcessDMIData() { return _states[stateName]; } - public Image? GetStateAsImage(string? stateName, AtomDirection dir) { - using Stream dmiStream = new MemoryStream(Data); - DMIParser.ParsedDMIDescription description = DMIParser.ParseDMI(dmiStream); + public ICursor? GetStateAsImage(IClyde clyde, string? stateName) { + using var dmiStream = new MemoryStream(Data); + var description = DMIParser.ParseDMI(dmiStream); dmiStream.Seek(0, SeekOrigin.Begin); Image image = Image.Load(dmiStream); - if (!(description.GetStateOrDefault(stateName)?.Directions.TryGetValue(dir, out var state) ?? false)) + var state = description.GetStateOrDefault(stateName); + if (!(state?.Directions.TryGetValue(AtomDirection.South, out var frames) ?? false)) return null; - var result = image.Clone(clone => { - clone.Resize(new Size(description.Width, description.Height)); - clone.Crop(new Rectangle(state[0].X, state[0].Y, state[0].X + description.Width, state[0].Y + description.Height)); + var stateImage = image.Clone(clone => { + var frame = frames[0]; + + clone.Crop(new Rectangle(frame.X, frame.Y, frame.X + description.Width, frame.Y + description.Height)); }); - return result; + + var hotspot = state.Hotspot ?? (0, stateImage.Height - 1); // Default to the top-left + var cursor = clyde.CreateCursor(stateImage, hotspot); + return cursor; } public struct State { diff --git a/OpenDreamShared/Resources/DMIParser.cs b/OpenDreamShared/Resources/DMIParser.cs index e2f97cb5c3..527fd0fafb 100644 --- a/OpenDreamShared/Resources/DMIParser.cs +++ b/OpenDreamShared/Resources/DMIParser.cs @@ -127,6 +127,11 @@ public sealed class ParsedDMIState(string name) { public bool Loop = true; public bool Rewind; + /// + /// The part of the image considered the tip when this is used as a custom cursor + /// + public Vector2i? Hotspot; + // TODO: This can only contain either 1, 4, or 8 directions. Enforcing this could simplify some things. public readonly Dictionary Directions = new(); @@ -450,7 +455,16 @@ private static ParsedDMIDescription ParseDMIDescription(string dmiDescription, u //TODO break; case "hotspot": - //TODO + if (currentState is null) break; + var hotspotValues = value.Split(','); + if (hotspotValues.Length != 3) + throw new Exception($"Invalid hotspot value \"{value}\""); + + var hotspotX = int.Parse(hotspotValues[0]); + var hotspotY = int.Parse(hotspotValues[1]); + // TODO: 3rd value? Something to do with what frames the hotspot applies to apparently + + currentState.Hotspot = (hotspotX, hotspotY); break; default: throw new Exception($"Invalid key \"{key}\" in DMI description");