diff --git a/Robust.Client/GameObjects/Components/Renderable/SpriteComponent.cs b/Robust.Client/GameObjects/Components/Renderable/SpriteComponent.cs index 7a1e55e2b7..f942c05261 100644 --- a/Robust.Client/GameObjects/Components/Renderable/SpriteComponent.cs +++ b/Robust.Client/GameObjects/Components/Renderable/SpriteComponent.cs @@ -1817,6 +1817,52 @@ namespace Robust.Client.GameObjects } } + public static IEnumerable GetPrototypeTextures(EntityPrototype prototype, IResourceCache resourceCache) + { + var icon = IconComponent.GetPrototypeIcon(prototype, resourceCache); + if (icon != null) yield return icon; + + if (!prototype.Components.TryGetValue("Sprite", out _)) + { + yield return resourceCache.GetFallback().Texture; + yield break; + } + + var dummy = new DummyIconEntity {Prototype = prototype}; + var spriteComponent = dummy.AddComponent(); + + if (prototype.Components.TryGetValue("Appearance", out _)) + { + var appearanceComponent = dummy.AddComponent(); + appearanceComponent.Initialize(); + foreach (var layer in appearanceComponent.Visualizers) + { + layer.OnChangeData(appearanceComponent); + } + } + + var anyTexture = false; + foreach (var layer in spriteComponent.AllLayers) + { + if (layer.Texture != null) yield return layer.Texture; + if (!layer.RsiState.IsValid || !layer.Visible) continue; + + var rsi = layer.Rsi ?? spriteComponent.BaseRSI; + if (rsi == null || + !rsi.TryGetState(layer.RsiState, out var state)) + continue; + + yield return state; + anyTexture = true; + } + + dummy.Delete(); + + if (!anyTexture) + yield return resourceCache.GetFallback().Texture; + + } + public static IDirectionalTextureProvider? GetPrototypeIcon(EntityPrototype prototype, IResourceCache resourceCache) { var icon = IconComponent.GetPrototypeIcon(prototype, resourceCache); @@ -1827,14 +1873,11 @@ namespace Robust.Client.GameObjects return resourceCache.GetFallback().Texture; } - var dummy = new DummyIconEntity {Prototype = prototype}; - var compFactory = IoCManager.Resolve(); - var newComponent = (SpriteComponent) compFactory.GetComponent("Sprite"); - newComponent.Owner = dummy; + var dummy = new DummyIconEntity() {Prototype = prototype}; + var spriteComponent = dummy.AddComponent(); + dummy.Delete(); - newComponent.ExposeData(YamlObjectSerializer.NewReader(spriteNode)); - - return newComponent.Icon ?? resourceCache.GetFallback().Texture; + return spriteComponent?.Icon ?? resourceCache.GetFallback().Texture; } #region DummyIconEntity @@ -1849,6 +1892,7 @@ namespace Robust.Client.GameObjects public bool Deleted { get; } = true; public bool Paused { get; set; } public EntityPrototype? Prototype { get; set; } + public string Description { get; set; } = string.Empty; public bool IsValid() { @@ -1857,28 +1901,47 @@ namespace Robust.Client.GameObjects public ITransformComponent Transform { get; } = null!; public IMetaDataComponent MetaData { get; } = null!; + + private Dictionary _components = new Dictionary(); + public T AddComponent() where T : Component, new() { - throw new NotImplementedException(); + var typeFactory = IoCManager.Resolve(); + var comp = (T) typeFactory.CreateInstance(typeof(T)); + _components[typeof(T)] = comp; + comp.Owner = this; + + if (typeof(ISpriteComponent).IsAssignableFrom(typeof(T))) + { + _components[typeof(ISpriteComponent)] = comp; + } + + if (Prototype != null && Prototype.Components.TryGetValue(comp.Name, out var node)) + { + comp.ExposeData(YamlObjectSerializer.NewReader(node)); + } + + return comp; } public void RemoveComponent() { + _components.Remove(typeof(T)); } public bool HasComponent() { - return false; + return _components.ContainsKey(typeof(T)); } public bool HasComponent(Type type) { - return false; + return _components.ContainsKey(type); } public T GetComponent() { - return default!; + return (T) _components[typeof(T)]; } public IComponent GetComponent(Type type) diff --git a/Robust.Client/GameObjects/EntitySystems/AppearanceSystem.cs b/Robust.Client/GameObjects/EntitySystems/AppearanceSystem.cs index 891b51e258..6af45e1350 100644 --- a/Robust.Client/GameObjects/EntitySystems/AppearanceSystem.cs +++ b/Robust.Client/GameObjects/EntitySystems/AppearanceSystem.cs @@ -18,6 +18,9 @@ namespace Robust.Client.GameObjects.EntitySystems private static void UpdateComponent(AppearanceComponent component) { + if (component.Deleted) + return; + foreach (var visualizer in component.Visualizers) { switch (visualizer) diff --git a/Robust.Client/UserInterface/Controls/LayeredTextureRect.cs b/Robust.Client/UserInterface/Controls/LayeredTextureRect.cs new file mode 100644 index 0000000000..3724c8dd22 --- /dev/null +++ b/Robust.Client/UserInterface/Controls/LayeredTextureRect.cs @@ -0,0 +1,172 @@ +using System; +using System.Collections.Generic; +using Robust.Client.Graphics; +using Robust.Client.Graphics.Drawing; +using Robust.Client.Graphics.Shaders; +using Robust.Shared.Maths; + +namespace Robust.Client.UserInterface.Controls +{ + /// + /// Like a TextureRect... but layered + /// + public class LayeredTextureRect : Control + { + public const string StylePropertyShader = "shader"; + + private bool _canShrink; + private List _textures = new List(); + private Vector2 _textureScale = Vector2.One; + + public ShaderInstance? ShaderOverride { get; set; } + + /// + /// The textures to draw. + /// + public List Textures + { + get => _textures; + set + { + _textures = value; + CalculateMinimumSize(); + } + } + + /// + /// Scales the texture displayed. + /// + /// + /// This does not apply to the following stretch modes: . + /// + public Vector2 TextureScale + { + get => _textureScale; + set + { + _textureScale = value; + MinimumSizeChanged(); + } + } + + /// + /// If true, this control can shrink below the size of . + /// + /// + /// This does not set . + /// Certain stretch modes may display outside the area of the control unless it is set. + /// + public bool CanShrink + { + get => _canShrink; + set + { + _canShrink = value; + MinimumSizeChanged(); + } + } + + /// + /// Controls how the texture should be drawn if the control is larger than the size of the texture. + /// + public TextureRect.StretchMode Stretch { get; set; } = TextureRect.StretchMode.Keep; + + protected internal override void Draw(DrawingHandleScreen handle) + { + base.Draw(handle); + ShaderInstance? shader = null; + + foreach (var texture in Textures) + { + if (ShaderOverride != null) + { + shader = ShaderOverride; + } + else if (TryGetStyleProperty(StylePropertyShader, out ShaderInstance? styleShader)) + { + shader = styleShader; + } + + if (shader != null) + { + handle.UseShader(shader); + } + + switch (Stretch) + { + case TextureRect.StretchMode.Scale: + handle.DrawTextureRect(texture, + UIBox2.FromDimensions(Vector2.Zero, PixelSize)); + break; + case TextureRect.StretchMode.Tile: + // TODO: Implement Tile. + case TextureRect.StretchMode.Keep: + handle.DrawTextureRect(texture, + UIBox2.FromDimensions(Vector2.Zero, texture.Size * _textureScale * UIScale)); + break; + case TextureRect.StretchMode.KeepCentered: + { + var position = (PixelSize - texture.Size * _textureScale * UIScale) / 2; + handle.DrawTextureRect(texture, UIBox2.FromDimensions(position, texture.Size * _textureScale * UIScale)); + break; + } + + case TextureRect.StretchMode.KeepAspect: + case TextureRect.StretchMode.KeepAspectCentered: + { + var (texWidth, texHeight) = texture.Size * _textureScale; + var width = texWidth * (PixelSize.Y / texHeight); + var height = (float)PixelSize.Y; + if (width > PixelSize.X) + { + width = PixelSize.X; + height = texHeight * (PixelSize.X / texWidth); + } + + var size = new Vector2(width, height); + var position = Vector2.Zero; + if (Stretch == TextureRect.StretchMode.KeepAspectCentered) + { + position = (PixelSize - size) / 2; + } + + handle.DrawTextureRectRegion(texture, UIBox2.FromDimensions(position, size)); + break; + } + + case TextureRect.StretchMode.KeepAspectCovered: + var texSize = texture.Size * _textureScale; + // Calculate the scale necessary to fit width and height to control size. + var (scaleX, scaleY) = PixelSize / texSize; + // Use whichever scale is greater. + var scale = Math.Max(scaleX, scaleY); + // Offset inside the actual texture. + var offset = (texSize - PixelSize) / scale / 2f; + handle.DrawTextureRectRegion(texture, PixelSizeBox, UIBox2.FromDimensions(offset, PixelSize / scale)); + break; + default: + throw new ArgumentOutOfRangeException(); + } + } + } + + protected override Vector2 CalculateMinimumSize() + { + if (_textures.Count == 0 || CanShrink) + { + return Vector2.Zero; + } + + var largestX = 0; + var largestY = 0; + + foreach (var texture in Textures) + { + largestX = Math.Max(texture.Width, largestX); + largestY = Math.Max(texture.Height, largestY); + } + + return new Vector2(largestX, largestY) * TextureScale; + } + } +} diff --git a/Robust.Client/UserInterface/CustomControls/EntitySpawnWindow.cs b/Robust.Client/UserInterface/CustomControls/EntitySpawnWindow.cs index d72b036f8c..d9300cff14 100644 --- a/Robust.Client/UserInterface/CustomControls/EntitySpawnWindow.cs +++ b/Robust.Client/UserInterface/CustomControls/EntitySpawnWindow.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.Linq; using Robust.Client.GameObjects; using Robust.Client.Interfaces.Placement; using Robust.Client.Interfaces.ResourceManagement; @@ -10,6 +11,7 @@ using Robust.Client.UserInterface.Controls; using Robust.Shared.Enums; using Robust.Shared.GameObjects; using Robust.Shared.Localization; +using Robust.Shared.Log; using Robust.Shared.Maths; using Robust.Shared.Prototypes; using Robust.Shared.Timing; @@ -309,8 +311,8 @@ namespace Robust.Client.UserInterface.CustomControls SelectedButton.ActualButton.Pressed = true; } - var rect = button.EntityTextureRect; - rect.Texture = SpriteComponent.GetPrototypeIcon(prototype, resourceCache)?.Default; + var rect = button.EntityTextureRects; + rect.Textures = SpriteComponent.GetPrototypeTextures(prototype, resourceCache).Select(o => o.Default).ToList(); PrototypeList.AddChild(button); if (insertFirst) @@ -455,7 +457,7 @@ namespace Robust.Client.UserInterface.CustomControls public EntityPrototype Prototype { get; set; } = default!; public Button ActualButton { get; private set; } public Label EntityLabel { get; private set; } - public TextureRect EntityTextureRect { get; private set; } + public LayeredTextureRect EntityTextureRects { get; private set; } public int Index { get; set; } public EntitySpawnButton() @@ -471,7 +473,7 @@ namespace Robust.Client.UserInterface.CustomControls { Children = { - (EntityTextureRect = new TextureRect + (EntityTextureRects = new LayeredTextureRect { CustomMinimumSize = (32, 32), SizeFlagsHorizontal = SizeFlags.ShrinkCenter, diff --git a/Robust.Shared/GameObjects/Components/Collidable/PhysicsComponentState.cs b/Robust.Shared/GameObjects/Components/Collidable/PhysicsComponentState.cs index 31bce2f3ce..e5650d2ed6 100644 --- a/Robust.Shared/GameObjects/Components/Collidable/PhysicsComponentState.cs +++ b/Robust.Shared/GameObjects/Components/Collidable/PhysicsComponentState.cs @@ -24,7 +24,7 @@ namespace Robust.Shared.GameObjects.Components public readonly bool Anchored; /// - /// + /// /// /// ///