diff --git a/DMCompiler/DMStandard/Types/Atoms/_Atom.dm b/DMCompiler/DMStandard/Types/Atoms/_Atom.dm index 9ce5c003d2..dcf0d03822 100644 --- a/DMCompiler/DMStandard/Types/Atoms/_Atom.dm +++ b/DMCompiler/DMStandard/Types/Atoms/_Atom.dm @@ -41,15 +41,15 @@ var/gender = NEUTER var/density = FALSE - var/maptext as opendream_unimplemented + var/maptext = null var/list/filters = null var/appearance var/appearance_flags = 0 - var/maptext_width as opendream_unimplemented - var/maptext_height as opendream_unimplemented - var/maptext_x = 32 as opendream_unimplemented - var/maptext_y = 32 as opendream_unimplemented + var/maptext_width = 32 + var/maptext_height = 32 + var/maptext_x = 0 + var/maptext_y = 0 var/step_x as opendream_unimplemented var/step_y as opendream_unimplemented var/render_source diff --git a/DMCompiler/DMStandard/Types/Image.dm b/DMCompiler/DMStandard/Types/Image.dm index b4dfc5546a..40b7142852 100644 --- a/DMCompiler/DMStandard/Types/Image.dm +++ b/DMCompiler/DMStandard/Types/Image.dm @@ -17,11 +17,11 @@ var/list/filters = list() var/layer = FLOAT_LAYER var/luminosity = 0 as opendream_unimplemented - var/maptext = "i" as opendream_unimplemented - var/maptext_width = 32 as opendream_unimplemented - var/maptext_height = 32 as opendream_unimplemented - var/maptext_x = 0 as opendream_unimplemented - var/maptext_y = 0 as opendream_unimplemented + var/maptext = null + var/maptext_width = 32 + var/maptext_height = 32 + var/maptext_x = 0 + var/maptext_y = 0 var/mouse_over_pointer = 0 as opendream_unimplemented var/mouse_drag_pointer = 0 as opendream_unimplemented var/mouse_drop_pointer = 1 as opendream_unimplemented diff --git a/OpenDreamClient/Rendering/DreamIcon.cs b/OpenDreamClient/Rendering/DreamIcon.cs index 3834a5c74b..a78582e207 100644 --- a/OpenDreamClient/Rendering/DreamIcon.cs +++ b/OpenDreamClient/Rendering/DreamIcon.cs @@ -109,6 +109,8 @@ public void Dispose() { TextureRenderOffset = Vector2.Zero; return frame; } else { + if(textureOverride is not null) + return FullRenderTexture(viewOverlay, handle, iconMetaData, frame).Texture; //no caching in the presence of overrides CachedTexture = FullRenderTexture(viewOverlay, handle, iconMetaData, frame); } @@ -323,11 +325,9 @@ private void UpdateAnimation() { _animatedAppearance.IconState = endAppearance.IconState; if (endAppearance.Invisibility != _appearance.Invisibility) _animatedAppearance.Invisibility = endAppearance.Invisibility; + if (endAppearance.Maptext != _appearance.Maptext) + _appearance.Maptext = endAppearance.Maptext; - /* TODO maptext - if (endAppearance.MapText != _appearance.MapText) - appearance.MapText = endAppearance.MapText; - */ /* TODO suffix if (endAppearance.Suffix != _appearance.Suffix) appearance.Suffix = endAppearance.Suffix; @@ -383,23 +383,19 @@ private void UpdateAnimation() { } */ - /* TODO maptext - if (endAppearance.MapTextWidth != _appearance.MapTextWidth) { - appearance.MapTextWidth = (ushort)Math.Clamp(((1-factor) * _appearance.MapTextWidth) + (factor * endAppearance.MapTextWidth), 0, 65535); - } + if (endAppearance.MaptextSize != _appearance.MaptextSize) { + Vector2 startingOffset = _appearance.MaptextSize; + Vector2 newMaptextSize = Vector2.Lerp(startingOffset, endAppearance.MaptextSize, 1.0f-factor); - if (endAppearance.MapTextHeight != _appearance.MapTextHeight) { - appearance.MapTextHeight = (ushort)Math.Clamp(((1-factor) * _appearance.MapTextHeight) + (factor * endAppearance.MapTextHeight), 0, 65535); + _animatedAppearance.MaptextSize = (Vector2i)newMaptextSize; } - if (endAppearance.MapTextX != _appearance.MapTextX) { - appearance.MapTextX = (short)Math.Clamp(((1-factor) * _appearance.MapTextX) + (factor * endAppearance.MapTextX), -32768, 32767); - } + if (endAppearance.MaptextOffset != _appearance.MaptextOffset) { + Vector2 startingOffset = _appearance.MaptextOffset; + Vector2 newMaptextOffset = Vector2.Lerp(startingOffset, endAppearance.MaptextOffset, 1.0f-factor); - if (endAppearance.MapTextY != _appearance.MapTextY) { - appearance.MapTextY = (short)Math.Clamp(((1-factor) * _appearance.MapTextY) + (factor * endAppearance.MapTextY), -32768, 32767); + _animatedAppearance.MaptextOffset = (Vector2i)newMaptextOffset; } - */ if (endAppearance.PixelOffset != _appearance.PixelOffset) { Vector2 startingOffset = _appearance.PixelOffset; diff --git a/OpenDreamClient/Rendering/DreamViewOverlay.cs b/OpenDreamClient/Rendering/DreamViewOverlay.cs index 5e65410898..b3075f7f6a 100644 --- a/OpenDreamClient/Rendering/DreamViewOverlay.cs +++ b/OpenDreamClient/Rendering/DreamViewOverlay.cs @@ -2,7 +2,6 @@ using OpenDreamClient.Interface; using Robust.Client.Graphics; using Robust.Client.Player; -using Robust.Shared.Enums; using Robust.Shared.Map; using OpenDreamShared.Dream; using Robust.Shared.Console; @@ -13,6 +12,8 @@ using Robust.Shared.Profiling; using Vector3 = Robust.Shared.Maths.Vector3; using Matrix3x2 = System.Numerics.Matrix3x2; +using Robust.Client.ResourceManagement; +using Robust.Shared.Enums; namespace OpenDreamClient.Rendering; @@ -40,6 +41,9 @@ internal sealed class DreamViewOverlay : Overlay { [Dependency] private readonly IClyde _clyde = default!; [Dependency] private readonly IPrototypeManager _protoManager = default!; [Dependency] private readonly ProfManager _prof = default!; + [Dependency] private readonly IResourceCache _resourceCache = default!; + + private readonly Font _defaultMaptextFont; private readonly ISawmill _sawmill = Logger.GetSawmill("opendream.view"); @@ -83,6 +87,7 @@ public DreamViewOverlay(RenderTargetPool renderTargetPool, TransformSystem trans _appearanceSystem = appearanceSystem; _screenOverlaySystem = screenOverlaySystem; _clientImagesSystem = clientImagesSystem; + _defaultMaptextFont = new VectorFont(_resourceCache.GetResource("/Fonts/NotoSans-Regular.ttf"),8); _spriteQuery = _entityManager.GetEntityQuery(); _xformQuery = _entityManager.GetEntityQuery(); @@ -338,7 +343,35 @@ private void ProcessIconComponents(DreamIcon icon, Vector2 position, EntityUid u // TODO: vis_flags } - //TODO maptext - note colour + transform apply + //maptext is basically just an image of rendered text added as an overlay + if(icon.Appearance.Maptext != null){ //if has maptext + RendererMetaData maptext = RentRendererMetaData(); + maptext.MainIcon = icon; + maptext.Position = current.Position; + maptext.Uid = current.Uid; + maptext.ClickUid = current.Uid; + maptext.IsScreen = current.IsScreen; + tieBreaker++; + maptext.TieBreaker = tieBreaker; + maptext.Plane = current.Plane; + maptext.Layer = current.Layer; + maptext.RenderSource = null; + maptext.RenderTarget = null; + maptext.MouseOpacity = current.MouseOpacity; + maptext.TransformToApply = current.TransformToApply; + maptext.ColorToApply = current.ColorToApply; + maptext.ColorMatrixToApply = current.ColorMatrixToApply; + maptext.AlphaToApply = current.AlphaToApply; + maptext.BlendMode = current.BlendMode; + + maptext.AppearanceFlags = current.AppearanceFlags; + maptext.AppearanceFlags &= ~AppearanceFlags.PlaneMaster; //doesn't make sense for maptext + + maptext.Maptext = icon.Appearance.Maptext; + maptext.MaptextSize = icon.Appearance.MaptextSize; + maptext.Position += icon.Appearance.MaptextOffset/(float)EyeManager.PixelsPerMeter; + result.Add(maptext); + } //TODO particles - colour and transform don't apply? @@ -400,6 +433,11 @@ public void DrawIcon(DrawingHandleWorld handle, Vector2i renderTargetSize, Rende positionOffset -= ((ktSize/EyeManager.PixelsPerMeter) - Vector2.One) * new Vector2(0.5f); //correct for KT group texture offset } + //Maptext + if(iconMetaData.Maptext != null){ + iconMetaData.TextureOverride = GetTextureFromMaptext(iconMetaData.Maptext, iconMetaData.MaptextSize!.Value.X, iconMetaData.MaptextSize!.Value.Y, handle); + } + var frame = iconMetaData.GetTexture(this, handle); var pixelPosition = (iconMetaData.Position + positionOffset) * EyeManager.PixelsPerMeter; @@ -756,6 +794,28 @@ private Texture ProcessKeepTogether(DrawingHandleWorld handle, RendererMetaData return ktTexture.Texture; } + public Texture GetTextureFromMaptext(string maptext, int width, int height, DrawingHandleWorld handle) { + if(width == 0) width = 32; + if(height == 0) height = 32; + IRenderTexture tempTexture = _renderTargetPool.Rent(new Vector2i(width, height)); + handle.RenderInRenderTarget(tempTexture, () => { + handle.SetTransform(CreateRenderTargetFlipMatrix(tempTexture.Size, Vector2.Zero)); + float scale = 1; + var font = _defaultMaptextFont; + var baseLine = new Vector2(0, 0); + foreach (var rune in maptext.EnumerateRunes()){ + var metric = font.GetCharMetrics(rune, scale); + Vector2 mod = new Vector2(0); + if(metric.HasValue) + mod.Y += metric.Value.BearingY - (metric.Value.Height - metric.Value.BearingY); + + baseLine.X += font.DrawChar(handle, rune, baseLine+mod, scale, Color.White); + } + }, Color.Transparent); + _renderTargetPool.ReturnAtEndOfFrame(tempTexture); + return tempTexture.Texture; + } + /// /// Creates a transformation matrix that counteracts RT's /// quirks @@ -812,6 +872,8 @@ internal sealed class RendererMetaData : IComparable { public BlendMode BlendMode; public MouseOpacity MouseOpacity; public Texture? TextureOverride; + public string? Maptext; + public Vector2i? MaptextSize; public bool IsPlaneMaster => (AppearanceFlags & AppearanceFlags.PlaneMaster) != 0; public bool HasRenderSource => !string.IsNullOrEmpty(RenderSource); @@ -841,6 +903,8 @@ public void Reset() { BlendMode = BlendMode.Default; MouseOpacity = MouseOpacity.Transparent; TextureOverride = null; + Maptext = null; + MaptextSize = null; } public Texture? GetTexture(DreamViewOverlay viewOverlay, DrawingHandleWorld handle) { diff --git a/OpenDreamRuntime/AtomManager.cs b/OpenDreamRuntime/AtomManager.cs index 7c5a3bdbc4..f274143b94 100644 --- a/OpenDreamRuntime/AtomManager.cs +++ b/OpenDreamRuntime/AtomManager.cs @@ -256,6 +256,11 @@ public bool IsValidAppearanceVar(string name) { case "verbs": case "overlays": case "underlays": + case "maptext": + case "maptext_width": + case "maptext_height": + case "maptext_x": + case "maptext_y": return true; // Get/SetAppearanceVar doesn't handle filters right now @@ -390,6 +395,24 @@ public void SetAppearanceVar(MutableAppearance appearance, string varName, Dream appearance.Verbs.Add(verb.VerbId!.Value); } + break; + case "maptext": + if(value == DreamValue.Null) + appearance.Maptext = null; + else + value.TryGetValueAsString(out appearance.Maptext); + break; + case "maptext_height": + value.TryGetValueAsInteger(out appearance.MaptextSize.Y); + break; + case "maptext_width": + value.TryGetValueAsInteger(out appearance.MaptextSize.X); + break; + case "maptext_x": + value.TryGetValueAsInteger(out appearance.MaptextOffset.X); + break; + case "maptext_y": + value.TryGetValueAsInteger(out appearance.MaptextOffset.Y); break; case "appearance": throw new Exception("Cannot assign the appearance var on an appearance"); @@ -483,6 +506,18 @@ public DreamValue GetAppearanceVar(ImmutableAppearance appearance, string varNam transform[1], transform[3], transform[5]); return new(matrix); + case "maptext": + return (appearance.Maptext != null) + ? new DreamValue(appearance.Maptext) + : DreamValue.Null; + case "maptext_height": + return new(appearance.MaptextSize.Y); + case "maptext_width": + return new(appearance.MaptextSize.X); + case "maptext_x": + return new(appearance.MaptextOffset.X); + case "maptext_y": + return new(appearance.MaptextOffset.Y); case "appearance": MutableAppearance appearanceCopy = appearance.ToMutable(); // Return a copy return new(appearanceCopy); diff --git a/OpenDreamRuntime/Procs/Native/DreamProcNativeRoot.cs b/OpenDreamRuntime/Procs/Native/DreamProcNativeRoot.cs index 8c24545377..5a6335e043 100644 --- a/OpenDreamRuntime/Procs/Native/DreamProcNativeRoot.cs +++ b/OpenDreamRuntime/Procs/Native/DreamProcNativeRoot.cs @@ -156,7 +156,7 @@ public static DreamValue NativeProc_animate(NativeProc.Bundle bundle, DreamObjec return DreamValue.Null; chainAnim = true; } - + bundle.LastAnimatedObject = new DreamValue(obj); if(obj.IsSubtypeOf(bundle.ObjectTree.Filter)) {//TODO animate filters return DreamValue.Null; @@ -210,14 +210,16 @@ public static DreamValue NativeProc_animate(NativeProc.Bundle bundle, DreamObjec /* TODO these are not yet implemented if(!pixelZ.IsNull) pixelZ = new(pixelZ.UnsafeGetValueAsFloat() + obj.GetVariable("pixel_z").UnsafeGetValueAsFloat()); //TODO change to appearance when pixel_z is implemented + */ if(!maptextWidth.IsNull) - maptextWidth = new(maptextWidth.UnsafeGetValueAsFloat() + obj.GetVariable("maptext_width").UnsafeGetValueAsFloat()); //TODO change to appearance when maptext_width is implemented + maptextWidth = new(maptextWidth.UnsafeGetValueAsFloat() + appearance.MaptextSize.X); if(!maptextHeight.IsNull) - maptextHeight = new(maptextHeight.UnsafeGetValueAsFloat() + obj.GetVariable("maptext_height").UnsafeGetValueAsFloat()); //TODO change to appearance when maptext_height is implemented + maptextHeight = new(maptextHeight.UnsafeGetValueAsFloat() + appearance.MaptextSize.Y); if(!maptextX.IsNull) - maptextX = new(maptextX.UnsafeGetValueAsFloat() + obj.GetVariable("maptext_x").UnsafeGetValueAsFloat()); //TODO change to appearance when maptext_x is implemented + maptextX = new(maptextX.UnsafeGetValueAsFloat() + appearance.MaptextOffset.X); if(!maptextY.IsNull) - maptextY = new(maptextY.UnsafeGetValueAsFloat() + obj.GetVariable("maptext_y").UnsafeGetValueAsFloat()); //TODO change to appearance when maptext_y is implemented + maptextY = new(maptextY.UnsafeGetValueAsFloat() + appearance.MaptextOffset.Y); + /* if(!luminosity.IsNull) luminosity = new(luminosity.UnsafeGetValueAsFloat() + obj.GetVariable("luminosity").UnsafeGetValueAsFloat()); //TODO change to appearance when luminosity is implemented */ @@ -273,17 +275,30 @@ public static DreamValue NativeProc_animate(NativeProc.Bundle bundle, DreamObjec } */ - /* TODO maptext if (!maptextX.IsNull) { obj.SetVariableValue("maptext_x", maptextX); - maptextX.TryGetValueAsInteger(out appearance.MapTextOffset.X); + maptextX.TryGetValueAsInteger(out appearance.MaptextOffset.X); } if (!maptextY.IsNull) { obj.SetVariableValue("maptext_y", maptextY); - maptextY.TryGetValueAsInteger(out appearance.MapTextOffset.Y); + maptextY.TryGetValueAsInteger(out appearance.MaptextOffset.Y); + } + + if (!maptextWidth.IsNull) { + obj.SetVariableValue("maptext_width", maptextWidth); + maptextX.TryGetValueAsInteger(out appearance.MaptextSize.X); + } + + if (!maptextHeight.IsNull) { + obj.SetVariableValue("maptext_y", maptextHeight); + maptextY.TryGetValueAsInteger(out appearance.MaptextSize.Y); + } + + if(!maptext.IsNull){ + obj.SetVariableValue("maptext", maptext); + maptext.TryGetValueAsString(out appearance.Maptext); } - */ if (!dir.IsNull) { obj.SetVariableValue("dir", dir); diff --git a/OpenDreamShared/Dream/ImmutableAppearance.cs b/OpenDreamShared/Dream/ImmutableAppearance.cs index 3190d3ce2d..f9862789e5 100644 --- a/OpenDreamShared/Dream/ImmutableAppearance.cs +++ b/OpenDreamShared/Dream/ImmutableAppearance.cs @@ -55,6 +55,9 @@ public sealed class ImmutableAppearance : IEquatable { [ViewVariables] public readonly DreamFilter[] Filters; [ViewVariables] public readonly int[] Verbs; [ViewVariables] public readonly ColorMatrix ColorMatrix = ColorMatrix.Identity; + [ViewVariables] public Vector2i MaptextSize = MutableAppearance.Default.MaptextSize; + [ViewVariables] public Vector2i MaptextOffset = MutableAppearance.Default.MaptextOffset; + [ViewVariables] public string? Maptext = MutableAppearance.Default.Maptext; /// The Transform property of this appearance, in [a,d,b,e,c,f] order [ViewVariables] public readonly float[] Transform = [ @@ -92,6 +95,9 @@ public ImmutableAppearance(MutableAppearance appearance, SharedAppearanceSystem? Invisibility = appearance.Invisibility; Opacity = appearance.Opacity; MouseOpacity = appearance.MouseOpacity; + Maptext = appearance.Maptext; + MaptextSize = appearance.MaptextSize; + MaptextOffset = appearance.MaptextOffset; Overlays = appearance.Overlays.ToArray(); Underlays = appearance.Underlays.ToArray(); @@ -161,6 +167,10 @@ public bool Equals(ImmutableAppearance? immutableAppearance) { if (immutableAppearance.Filters.Length != Filters.Length) return false; if (immutableAppearance.Verbs.Length != Verbs.Length) return false; if (immutableAppearance.Override != Override) return false; + if (immutableAppearance.Maptext != Maptext) return false; + if (immutableAppearance.MaptextSize != MaptextSize) return false; + if (immutableAppearance.MaptextOffset != MaptextOffset) return false; + for (int i = 0; i < Filters.Length; i++) { if (immutableAppearance.Filters[i] != Filters[i]) return false; @@ -227,6 +237,9 @@ public override int GetHashCode() { hashCode.Add(RenderTarget); hashCode.Add(BlendMode); hashCode.Add(AppearanceFlags); + hashCode.Add(Maptext); + hashCode.Add(MaptextOffset); + hashCode.Add(MaptextSize); foreach (ImmutableAppearance overlay in Overlays) { hashCode.Add(overlay.GetHashCode()); @@ -405,6 +418,18 @@ public ImmutableAppearance(NetIncomingMessage buffer, IRobustSerializer serializ break; } + case IconAppearanceProperty.Maptext: { + Maptext = buffer.ReadString(); + break; + } + case IconAppearanceProperty.MaptextSize: { + MaptextSize = (buffer.ReadVariableInt32(), buffer.ReadVariableInt32()); + break; + } + case IconAppearanceProperty.MaptextOffset: { + MaptextOffset = (buffer.ReadVariableInt32(), buffer.ReadVariableInt32()); + break; + } default: throw new Exception($"Invalid property {property}"); } @@ -442,6 +467,9 @@ public MutableAppearance ToMutable() { result.Opacity = Opacity; result.MouseOpacity = MouseOpacity; result.Override = Override; + result.Maptext = Maptext; + result.MaptextOffset = MaptextOffset; + result.MaptextSize = MaptextSize; result.Overlays.EnsureCapacity(Overlays.Length); result.Underlays.EnsureCapacity(Underlays.Length); @@ -628,6 +656,24 @@ public void WriteToBuffer(NetOutgoingMessage buffer, IRobustSerializer serialize } } + if(!string.IsNullOrEmpty(Maptext)){ + buffer.Write((byte) IconAppearanceProperty.Maptext); + buffer.Write(Maptext); + } + + if (MaptextOffset != MutableAppearance.Default.MaptextOffset) { + buffer.Write((byte)IconAppearanceProperty.MaptextOffset); + buffer.WriteVariableInt32(MaptextOffset.X); + buffer.WriteVariableInt32(MaptextOffset.Y); + } + + + if (MaptextSize != MutableAppearance.Default.MaptextSize) { + buffer.Write((byte)IconAppearanceProperty.MaptextSize); + buffer.WriteVariableInt32(MaptextSize.X); + buffer.WriteVariableInt32(MaptextSize.Y); + } + buffer.Write((byte)IconAppearanceProperty.End); } diff --git a/OpenDreamShared/Dream/MutableAppearance.cs b/OpenDreamShared/Dream/MutableAppearance.cs index 702d4c8083..be3c7fa662 100644 --- a/OpenDreamShared/Dream/MutableAppearance.cs +++ b/OpenDreamShared/Dream/MutableAppearance.cs @@ -50,6 +50,9 @@ public sealed class MutableAppearance : IEquatable, IDisposab [ViewVariables] public List VisContents; [ViewVariables] public List Filters; [ViewVariables] public List Verbs; + [ViewVariables] public Vector2i MaptextSize = new(32,32); + [ViewVariables] public Vector2i MaptextOffset = new(0,0); + [ViewVariables] public string? Maptext; /// /// An appearance can gain a color matrix filter by two possible forces:
@@ -123,6 +126,9 @@ public void CopyFrom(MutableAppearance appearance) { Opacity = appearance.Opacity; MouseOpacity = appearance.MouseOpacity; Override = appearance.Override; + Maptext = appearance.Maptext; + MaptextSize = appearance.MaptextSize; + MaptextOffset = appearance.MaptextOffset; Overlays.Clear(); Underlays.Clear(); @@ -168,6 +174,9 @@ public bool Equals(MutableAppearance? appearance) { if (appearance.Filters.Count != Filters.Count) return false; if (appearance.Verbs.Count != Verbs.Count) return false; if (appearance.Override != Override) return false; + if (appearance.Maptext != Maptext) return false; + if (appearance.MaptextSize != MaptextSize) return false; + if (appearance.MaptextOffset != MaptextOffset) return false; for (int i = 0; i < Filters.Count; i++) { if (appearance.Filters[i] != Filters[i]) return false; @@ -252,6 +261,9 @@ public override int GetHashCode() { hashCode.Add(RenderTarget); hashCode.Add(BlendMode); hashCode.Add(AppearanceFlags); + hashCode.Add(Maptext); + hashCode.Add(MaptextOffset); + hashCode.Add(MaptextSize); foreach (var overlay in Overlays) { hashCode.Add(overlay.GetHashCode()); @@ -389,6 +401,9 @@ public enum IconAppearanceProperty : byte { Filters, Verbs, Transform, + Maptext, + MaptextSize, + MaptextOffset, Id, End } diff --git a/TestGame/code.dm b/TestGame/code.dm index c0b366a806..196d4bcba1 100644 --- a/TestGame/code.dm +++ b/TestGame/code.dm @@ -180,6 +180,20 @@ set category = "Test" usr << output("help sec griffing me", "honk.browser:foo") + verb/test_maptext() + set category = "Test" + if(length(src.maptext)) + src.maptext = null; + else + src.maptext = "Hello!" + animate(src, maptext_x=64, maptext_y=64, time=50) + animate(maptext_x=64, maptext_y=-64, time=50) + animate(maptext_x=-64, maptext_y=-64, time=50) + animate(maptext_x=64, maptext_y=-64, time=50) + animate(maptext_x=0, maptext_y=0, time=50) + animate(maptext="Hello :)", time=10) + + verb/demo_filters() set category = "Test" if(length(src.filters))