diff --git a/DMCompiler/DMStandard/Types/Icon.dm b/DMCompiler/DMStandard/Types/Icon.dm index 164dcdc92c..ae3029c6f0 100644 --- a/DMCompiler/DMStandard/Types/Icon.dm +++ b/DMCompiler/DMStandard/Types/Icon.dm @@ -1,12 +1,13 @@ /icon parent_type = /datum + var/icon New(icon, icon_state, dir, frame, moving) proc/Blend(icon, function = ICON_ADD, x = 1, y = 1) set opendream_unimplemented = TRUE CRASH("/icon.Blend() is not implemented") - + proc/Crop(x1, y1, x2, y2) set opendream_unimplemented = TRUE CRASH("/icon.Crop() is not implemented") @@ -18,14 +19,12 @@ proc/Flip(dir) set opendream_unimplemented = TRUE CRASH("/icon.Flip() is not implemented") - + proc/GetPixel(x, y, icon_state, dir = 0, frame = 0, moving = -1) set opendream_unimplemented = TRUE CRASH("/icon.GetPixel() is not implemented") proc/Height() - set opendream_unimplemented = TRUE - CRASH("/icon.Height() is not implemented") proc/IconStates(mode = 0) return icon_states(src, mode) @@ -59,8 +58,6 @@ CRASH("/icon.Turn() is not implemented") proc/Width() - set opendream_unimplemented = TRUE - CRASH("/icon.Width() is not implemented") proc/icon(...) - return new /icon(arglist(args)) \ No newline at end of file + return new /icon(arglist(args)) diff --git a/OpenDreamRuntime/DreamManager.cs b/OpenDreamRuntime/DreamManager.cs index 9b4aa51e4f..6e4e36a5dd 100644 --- a/OpenDreamRuntime/DreamManager.cs +++ b/OpenDreamRuntime/DreamManager.cs @@ -122,6 +122,7 @@ private void SetMetaObjects() { ObjectTree.SetMetaObject(DreamPath.Turf, new DreamMetaObjectTurf()); ObjectTree.SetMetaObject(DreamPath.Movable, new DreamMetaObjectMovable()); ObjectTree.SetMetaObject(DreamPath.Mob, new DreamMetaObjectMob()); + ObjectTree.SetMetaObject(DreamPath.Icon, new DreamMetaObjectIcon()); } public void SetGlobalNativeProc(NativeProc.HandlerFn func) { diff --git a/OpenDreamRuntime/Objects/MetaObjects/DreamMetaObjectIcon.cs b/OpenDreamRuntime/Objects/MetaObjects/DreamMetaObjectIcon.cs new file mode 100644 index 0000000000..6fb77976c4 --- /dev/null +++ b/OpenDreamRuntime/Objects/MetaObjects/DreamMetaObjectIcon.cs @@ -0,0 +1,140 @@ +using System.Collections.Generic; +using System.IO; +using OpenDreamRuntime.Procs; +using OpenDreamRuntime.Resources; +using OpenDreamShared.Dream; +using OpenDreamShared.Resources; + +namespace OpenDreamRuntime.Objects.MetaObjects { + sealed class DreamMetaObjectIcon : DreamMetaObjectDatum + { + [Dependency] private readonly DreamResourceManager _rscMan = default!; + + public enum DreamIconMovingMode : byte + { + Both = 0, + Movement = 1, + NonMovement = 2, + + } + + public static Dictionary ObjectToDreamIcon = new(); + + public struct DreamIconObject { + // Actual DMI data + public DMIParser.ParsedDMIDescription Description; // TODO Eventually this should probably be removed in favor of just directly storing the data for the subset of the DMI that we actually care about + + // These vars correspond to the args in icon/new() and the resulting /icon obj, not the actual DMI data + public string Icon; + public string? State; // Specific icon_state. Null is all states. + public AtomDirection? Direction; // Specific dir. Null is all dirs. + public byte? Frame; //1-indexed. Specific frame. Null is all frames. + public DreamIconMovingMode Moving; + + public DreamIconObject(DreamResource rsc, DreamValue state, DreamValue dir, DreamValue frame, DreamValue moving) + { + if (Path.GetExtension(rsc.ResourcePath) != ".dmi") + { + throw new Exception("Invalid icon file"); + } + + Description = DMIParser.ParseDMI(new MemoryStream(rsc.ResourceData)); + Icon = rsc.ResourcePath; + + // TODO confirm BYOND behavior of invalid args for icon, dir, and frame + + if (state.TryGetValueAsString(out var iconState)) + { + State = iconState; + } + else + { + State = null; + } + + if (dir.TryGetValueAsInteger(out var dirVal) && (AtomDirection)dirVal != AtomDirection.None) + { + Direction = (AtomDirection)dirVal; + } + else + { + Direction = null; + } + + if (frame.TryGetValueAsInteger(out var frameVal)) + { + //TODO: Figure out how many frames an icon can have and see if this needs to be bigger than a byte + Frame = Convert.ToByte(frameVal - 1); //1-indexed + } + else + { + Frame = null; + } + + if (moving != DreamValue.Null) + { + if (moving.TryGetValueAsInteger(out var movingVal) && movingVal == 0) + { + Moving = DreamIconMovingMode.NonMovement; + } + else + { + Moving = DreamIconMovingMode.Movement; + } + } + else + { + Moving = DreamIconMovingMode.Both; + } + } + } + + public override void OnObjectCreated(DreamObject dreamObject, DreamProcArguments creationArguments) { + base.OnObjectCreated(dreamObject, creationArguments); + + DreamValue icon = creationArguments.GetArgument(0, "icon"); + DreamValue state = creationArguments.GetArgument(1, "icon_state"); + DreamValue dir = creationArguments.GetArgument(2, "dir"); + DreamValue frame = creationArguments.GetArgument(3, "frame"); + DreamValue moving = creationArguments.GetArgument(4, "moving"); + + DreamIconObject dreamIconObject; + + if (icon.TryGetValueAsDreamObjectOfType(DreamPath.Icon, out DreamObject copyFrom)) { + dreamIconObject = ObjectToDreamIcon[copyFrom]; + } else if (icon.TryGetValueAsString(out string fileString)) + { + var ext = Path.GetExtension(fileString); + switch (ext) // TODO implement other icon file types + { + case ".dmi": + dreamIconObject = new DreamIconObject(_rscMan.LoadResource(fileString), state, dir, frame, moving); + break; + case ".png": + case ".jpg": + case ".rsi": // RT-specific, not in BYOND + case ".gif": + case ".bmp": + throw new NotImplementedException($"Unimplemented icon type '{ext}'"); + default: + throw new Exception($"Invalid icon file {fileString}"); + } + + } else if (icon.TryGetValueAsDreamResource(out var rsc)) + { + dreamIconObject = new DreamIconObject(rsc, state, dir, frame, moving); + } else { + throw new Exception("Invalid icon file " + icon); + } + + ObjectToDreamIcon.Add(dreamObject, dreamIconObject); + + } + + public override void OnObjectDeleted(DreamObject dreamObject) { + ObjectToDreamIcon.Remove(dreamObject); + + base.OnObjectDeleted(dreamObject); + } + } +} diff --git a/OpenDreamRuntime/Objects/MetaObjects/DreamMetaObjectRegex.cs b/OpenDreamRuntime/Objects/MetaObjects/DreamMetaObjectRegex.cs index 1f366dfaa5..4183959e18 100644 --- a/OpenDreamRuntime/Objects/MetaObjects/DreamMetaObjectRegex.cs +++ b/OpenDreamRuntime/Objects/MetaObjects/DreamMetaObjectRegex.cs @@ -4,7 +4,7 @@ using OpenDreamShared.Dream; namespace OpenDreamRuntime.Objects.MetaObjects { - class DreamMetaObjectRegex : DreamMetaObjectDatum { + sealed class DreamMetaObjectRegex : DreamMetaObjectDatum { public struct DreamRegex { public Regex Regex; public bool IsGlobal; diff --git a/OpenDreamRuntime/Procs/Native/DreamProcNative.cs b/OpenDreamRuntime/Procs/Native/DreamProcNative.cs index 66310da908..83d825d490 100644 --- a/OpenDreamRuntime/Procs/Native/DreamProcNative.cs +++ b/OpenDreamRuntime/Procs/Native/DreamProcNative.cs @@ -117,6 +117,10 @@ public static void SetupNativeProcs(DreamObjectTree objectTree) { regex.SetNativeProc(DreamProcNativeRegex.NativeProc_Find); regex.SetNativeProc(DreamProcNativeRegex.NativeProc_Replace); + DreamObjectDefinition icon = objectTree.GetObjectDefinition(DreamPath.Icon); + icon.SetNativeProc(DreamProcNativeIcon.NativeProc_Width); + icon.SetNativeProc(DreamProcNativeIcon.NativeProc_Height); + //DreamObjectDefinition savefile = objectTree.GetObjectDefinitionFromPath(DreamPath.Savefile); //savefile.SetNativeProc(DreamProcNativeSavefile.NativeProc_Flush); diff --git a/OpenDreamRuntime/Procs/Native/DreamProcNativeIcon.cs b/OpenDreamRuntime/Procs/Native/DreamProcNativeIcon.cs new file mode 100644 index 0000000000..d4920cbe9d --- /dev/null +++ b/OpenDreamRuntime/Procs/Native/DreamProcNativeIcon.cs @@ -0,0 +1,23 @@ +using System; +using OpenDreamRuntime.Objects; +using OpenDreamRuntime.Objects.MetaObjects; + +namespace OpenDreamRuntime.Procs.Native { + static class DreamProcNativeIcon { + [DreamProc("Width")] + public static DreamValue NativeProc_Width(DreamObject instance, DreamObject usr, DreamProcArguments arguments) { + DreamMetaObjectIcon.DreamIconObject dreamIconObject = DreamMetaObjectIcon.ObjectToDreamIcon[instance]; + + return new DreamValue(dreamIconObject.Description.Width); + } + + [DreamProc("Height")] + public static DreamValue NativeProc_Height(DreamObject instance, DreamObject usr, DreamProcArguments arguments) { + DreamMetaObjectIcon.DreamIconObject dreamIconObject = DreamMetaObjectIcon.ObjectToDreamIcon[instance]; + + return new DreamValue(dreamIconObject.Description.Height); + } + + + } +} diff --git a/OpenDreamShared/Resources/DMIParser.cs b/OpenDreamShared/Resources/DMIParser.cs index dbee73025c..e81484c7bc 100644 --- a/OpenDreamShared/Resources/DMIParser.cs +++ b/OpenDreamShared/Resources/DMIParser.cs @@ -22,7 +22,7 @@ public static class DMIParser { AtomDirection.Northwest }; - public class ParsedDMIDescription { + public sealed class ParsedDMIDescription { public string Source; public float Version; public int Width, Height; @@ -55,7 +55,7 @@ public ParsedDMIState GetState(string stateName = null) { } } - public class ParsedDMIState { + public sealed class ParsedDMIState { public string Name; public Dictionary Directions = new(); public bool Loop = true; @@ -68,7 +68,7 @@ public ParsedDMIFrame[] GetFrames(AtomDirection direction = AtomDirection.South) } } - public class ParsedDMIFrame { + public sealed class ParsedDMIFrame { public int X, Y; public float Delay; }