2023-12-04 18:04:39 -05:00
using System.Numerics ;
using Content.Client.Items.Systems ;
using Content.Shared.Item ;
using Content.Shared.Storage ;
using Robust.Client.GameObjects ;
using Robust.Client.Graphics ;
using Robust.Client.UserInterface ;
2023-12-07 21:38:07 -05:00
using Robust.Client.UserInterface.CustomControls ;
2023-12-04 18:04:39 -05:00
namespace Content.Client.UserInterface.Systems.Storage.Controls ;
2024-05-26 14:11:37 +12:00
public sealed class ItemGridPiece : Control , IEntityControl
2023-12-04 18:04:39 -05:00
{
2023-12-05 18:38:10 -05:00
private readonly IEntityManager _entityManager ;
2023-12-04 18:04:39 -05:00
private readonly StorageUIController _storageController ;
private readonly List < ( Texture , Vector2 ) > _texturesPositions = new ( ) ;
public readonly EntityUid Entity ;
public ItemStorageLocation Location ;
2024-03-02 17:57:42 -06:00
public ItemGridPieceMarks ? Marked ;
2023-12-04 18:04:39 -05:00
public event Action < GUIBoundKeyEventArgs , ItemGridPiece > ? OnPiecePressed ;
public event Action < GUIBoundKeyEventArgs , ItemGridPiece > ? OnPieceUnpressed ;
#region Textures
private readonly string _centerTexturePath = "Storage/piece_center" ;
private Texture ? _centerTexture ;
private readonly string _topTexturePath = "Storage/piece_top" ;
private Texture ? _topTexture ;
private readonly string _bottomTexturePath = "Storage/piece_bottom" ;
private Texture ? _bottomTexture ;
private readonly string _leftTexturePath = "Storage/piece_left" ;
private Texture ? _leftTexture ;
private readonly string _rightTexturePath = "Storage/piece_right" ;
private Texture ? _rightTexture ;
private readonly string _topLeftTexturePath = "Storage/piece_topLeft" ;
private Texture ? _topLeftTexture ;
private readonly string _topRightTexturePath = "Storage/piece_topRight" ;
private Texture ? _topRightTexture ;
private readonly string _bottomLeftTexturePath = "Storage/piece_bottomLeft" ;
private Texture ? _bottomLeftTexture ;
private readonly string _bottomRightTexturePath = "Storage/piece_bottomRight" ;
private Texture ? _bottomRightTexture ;
2024-03-02 17:57:42 -06:00
private readonly string _markedFirstTexturePath = "Storage/marked_first" ;
private Texture ? _markedFirstTexture ;
private readonly string _markedSecondTexturePath = "Storage/marked_second" ;
private Texture ? _markedSecondTexture ;
2023-12-04 18:04:39 -05:00
#endregion
public ItemGridPiece ( Entity < ItemComponent > entity , ItemStorageLocation location , IEntityManager entityManager )
{
IoCManager . InjectDependencies ( this ) ;
2023-12-05 18:38:10 -05:00
_entityManager = entityManager ;
2023-12-04 18:04:39 -05:00
_storageController = UserInterfaceManager . GetUIController < StorageUIController > ( ) ;
Entity = entity . Owner ;
Location = location ;
Visible = true ;
2025-02-08 17:17:55 +11:00
MouseFilter = MouseFilterMode . Stop ;
2023-12-04 18:04:39 -05:00
2023-12-07 21:38:07 -05:00
TooltipSupplier = SupplyTooltip ;
2023-12-04 18:04:39 -05:00
OnThemeUpdated ( ) ;
}
2023-12-07 21:38:07 -05:00
private Control ? SupplyTooltip ( Control sender )
{
if ( _storageController . IsDragging )
return null ;
return new Tooltip
{
Text = _entityManager . GetComponent < MetaDataComponent > ( Entity ) . EntityName
} ;
}
2023-12-04 18:04:39 -05:00
protected override void OnThemeUpdated ( )
{
base . OnThemeUpdated ( ) ;
_centerTexture = Theme . ResolveTextureOrNull ( _centerTexturePath ) ? . Texture ;
_topTexture = Theme . ResolveTextureOrNull ( _topTexturePath ) ? . Texture ;
_bottomTexture = Theme . ResolveTextureOrNull ( _bottomTexturePath ) ? . Texture ;
_leftTexture = Theme . ResolveTextureOrNull ( _leftTexturePath ) ? . Texture ;
_rightTexture = Theme . ResolveTextureOrNull ( _rightTexturePath ) ? . Texture ;
_topLeftTexture = Theme . ResolveTextureOrNull ( _topLeftTexturePath ) ? . Texture ;
_topRightTexture = Theme . ResolveTextureOrNull ( _topRightTexturePath ) ? . Texture ;
_bottomLeftTexture = Theme . ResolveTextureOrNull ( _bottomLeftTexturePath ) ? . Texture ;
_bottomRightTexture = Theme . ResolveTextureOrNull ( _bottomRightTexturePath ) ? . Texture ;
2024-03-02 17:57:42 -06:00
_markedFirstTexture = Theme . ResolveTextureOrNull ( _markedFirstTexturePath ) ? . Texture ;
_markedSecondTexture = Theme . ResolveTextureOrNull ( _markedSecondTexturePath ) ? . Texture ;
2023-12-04 18:04:39 -05:00
}
protected override void Draw ( DrawingHandleScreen handle )
{
base . Draw ( handle ) ;
2023-12-05 18:38:10 -05:00
// really just an "oh shit" catch.
2023-12-06 19:59:16 -05:00
if ( ! _entityManager . EntityExists ( Entity ) | | ! _entityManager . TryGetComponent < ItemComponent > ( Entity , out var itemComponent ) )
2023-12-05 18:38:10 -05:00
{
Dispose ( ) ;
return ;
}
2025-02-08 17:17:55 +11:00
if ( _storageController . IsDragging & & _storageController . DraggingGhost ? . Entity = = Entity & &
_storageController . DraggingGhost ! = this )
{
2023-12-04 18:04:39 -05:00
return ;
2025-02-08 17:17:55 +11:00
}
2023-12-04 18:04:39 -05:00
2023-12-06 19:59:16 -05:00
var adjustedShape = _entityManager . System < ItemSystem > ( ) . GetAdjustedItemShape ( ( Entity , itemComponent ) , Location . Rotation , Vector2i . Zero ) ;
2023-12-04 18:04:39 -05:00
var boundingGrid = adjustedShape . GetBoundingBox ( ) ;
var size = _centerTexture ! . Size * 2 * UIScale ;
var hovering = ! _storageController . IsDragging & & UserInterfaceManager . CurrentlyHovered = = this ;
//yeah, this coloring is kinda hardcoded. deal with it. B)
Color ? colorModulate = hovering ? null : Color . FromHex ( "#a8a8a8" ) ;
2024-03-02 17:57:42 -06:00
var marked = Marked ! = null ;
2023-12-08 13:43:57 -05:00
Vector2i ? maybeMarkedPos = null ;
2023-12-04 18:04:39 -05:00
_texturesPositions . Clear ( ) ;
for ( var y = boundingGrid . Bottom ; y < = boundingGrid . Top ; y + + )
{
for ( var x = boundingGrid . Left ; x < = boundingGrid . Right ; x + + )
{
if ( ! adjustedShape . Contains ( x , y ) )
continue ;
var offset = size * 2 * new Vector2 ( x - boundingGrid . Left , y - boundingGrid . Bottom ) ;
var topLeft = PixelPosition + offset . Floored ( ) ;
if ( GetTexture ( adjustedShape , new Vector2i ( x , y ) , Direction . NorthEast ) is { } neTexture )
{
var neOffset = new Vector2 ( size . X , 0 ) ;
handle . DrawTextureRect ( neTexture , new UIBox2 ( topLeft + neOffset , topLeft + neOffset + size ) , colorModulate ) ;
}
if ( GetTexture ( adjustedShape , new Vector2i ( x , y ) , Direction . NorthWest ) is { } nwTexture )
{
_texturesPositions . Add ( ( nwTexture , Position + offset / UIScale ) ) ;
handle . DrawTextureRect ( nwTexture , new UIBox2 ( topLeft , topLeft + size ) , colorModulate ) ;
2023-12-08 13:43:57 -05:00
if ( marked & & nwTexture = = _topLeftTexture )
{
maybeMarkedPos = topLeft ;
marked = false ;
}
2023-12-04 18:04:39 -05:00
}
if ( GetTexture ( adjustedShape , new Vector2i ( x , y ) , Direction . SouthEast ) is { } seTexture )
{
var seOffset = size ;
handle . DrawTextureRect ( seTexture , new UIBox2 ( topLeft + seOffset , topLeft + seOffset + size ) , colorModulate ) ;
}
if ( GetTexture ( adjustedShape , new Vector2i ( x , y ) , Direction . SouthWest ) is { } swTexture )
{
var swOffset = new Vector2 ( 0 , size . Y ) ;
handle . DrawTextureRect ( swTexture , new UIBox2 ( topLeft + swOffset , topLeft + swOffset + size ) , colorModulate ) ;
}
}
}
// typically you'd divide by two, but since the textures are half a tile, this is done implicitly
2025-04-17 03:42:22 +03:00
var iconOffset = Location . Rotation . RotateVec ( itemComponent . StoredOffset ) * 2 * UIScale ;
var iconPosition = new Vector2 (
( boundingGrid . Width + 1 ) * size . X + iconOffset . X ,
( boundingGrid . Height + 1 ) * size . Y + iconOffset . Y ) ;
2023-12-06 19:59:16 -05:00
var iconRotation = Location . Rotation + Angle . FromDegrees ( itemComponent . StoredRotation ) ;
2023-12-04 18:04:39 -05:00
2023-12-06 19:59:16 -05:00
if ( itemComponent . StoredSprite is { } storageSprite )
{
var scale = 2 * UIScale ;
var sprite = _entityManager . System < SpriteSystem > ( ) . Frame0 ( storageSprite ) ;
2025-04-17 22:15:31 -04:00
var sizeDifference = ( ( boundingGrid . Size + Vector2i . One ) * _centerTexture . Size * 2 - sprite . Size ) * UIScale ;
2023-12-06 19:59:16 -05:00
var spriteBox = new Box2Rotated ( new Box2 ( 0f , sprite . Height * scale , sprite . Width * scale , 0f ) , - iconRotation , Vector2 . Zero ) ;
var root = spriteBox . CalcBoundingBox ( ) . BottomLeft ;
var pos = PixelPosition * 2
+ ( Parent ? . GlobalPixelPosition ? ? Vector2 . Zero )
2025-04-17 22:15:31 -04:00
+ sizeDifference
+ iconOffset ;
2023-12-06 19:59:16 -05:00
handle . SetTransform ( pos , iconRotation ) ;
var box = new UIBox2 ( root , root + sprite . Size * scale ) ;
2025-07-20 01:10:38 +03:00
var ev = new BeforeRenderInGridEvent ( new Color ( 255 , 255 , 255 ) ) ;
_entityManager . EventBus . RaiseLocalEvent ( Entity , ev ) ;
handle . DrawTextureRect ( sprite , box , ev . Color ) ;
2023-12-12 02:49:37 -05:00
handle . SetTransform ( GlobalPixelPosition , Angle . Zero ) ;
2023-12-06 19:59:16 -05:00
}
else
{
_entityManager . System < SpriteSystem > ( ) . ForceUpdate ( Entity ) ;
handle . DrawEntity ( Entity ,
PixelPosition + iconPosition ,
Vector2 . One * 2 * UIScale ,
Angle . Zero ,
eyeRotation : iconRotation ,
overrideDirection : Direction . South ) ;
}
2023-12-08 13:43:57 -05:00
2024-03-02 17:57:42 -06:00
if ( maybeMarkedPos is { } markedPos )
2023-12-08 13:43:57 -05:00
{
2024-03-02 17:57:42 -06:00
var markedTexture = Marked switch
{
ItemGridPieceMarks . First = > _markedFirstTexture ,
ItemGridPieceMarks . Second = > _markedSecondTexture ,
_ = > null ,
} ;
if ( markedTexture ! = null )
{
handle . DrawTextureRect ( markedTexture , new UIBox2 ( markedPos , markedPos + size ) ) ;
}
2023-12-08 13:43:57 -05:00
}
2023-12-04 18:04:39 -05:00
}
protected override bool HasPoint ( Vector2 point )
{
foreach ( var ( texture , position ) in _texturesPositions )
{
if ( ! new Box2 ( position , position + texture . Size * 4 ) . Contains ( point ) )
continue ;
return true ;
}
return false ;
}
protected override void KeyBindDown ( GUIBoundKeyEventArgs args )
{
base . KeyBindDown ( args ) ;
OnPiecePressed ? . Invoke ( args , this ) ;
}
protected override void KeyBindUp ( GUIBoundKeyEventArgs args )
{
base . KeyBindUp ( args ) ;
OnPieceUnpressed ? . Invoke ( args , this ) ;
}
private Texture ? GetTexture ( IReadOnlyList < Box2i > boxes , Vector2i position , Direction corner )
{
var top = ! boxes . Contains ( position - Vector2i . Up ) ;
var bottom = ! boxes . Contains ( position - Vector2i . Down ) ;
var left = ! boxes . Contains ( position + Vector2i . Left ) ;
var right = ! boxes . Contains ( position + Vector2i . Right ) ;
switch ( corner )
{
case Direction . NorthEast :
if ( top & & right )
return _topRightTexture ;
if ( top )
return _topTexture ;
if ( right )
return _rightTexture ;
return _centerTexture ;
case Direction . NorthWest :
if ( top & & left )
return _topLeftTexture ;
if ( top )
return _topTexture ;
if ( left )
return _leftTexture ;
return _centerTexture ;
case Direction . SouthEast :
if ( bottom & & right )
return _bottomRightTexture ;
if ( bottom )
return _bottomTexture ;
if ( right )
return _rightTexture ;
return _centerTexture ;
case Direction . SouthWest :
if ( bottom & & left )
return _bottomLeftTexture ;
if ( bottom )
return _bottomTexture ;
if ( left )
return _leftTexture ;
return _centerTexture ;
default :
return null ;
}
}
public static Vector2 GetCenterOffset ( Entity < ItemComponent ? > entity , ItemStorageLocation location , IEntityManager entMan )
{
var boxSize = entMan . System < ItemSystem > ( ) . GetAdjustedItemShape ( entity , location ) . GetBoundingBox ( ) . Size ;
var actualSize = new Vector2 ( boxSize . X + 1 , boxSize . Y + 1 ) ;
return actualSize * new Vector2i ( 8 , 8 ) ;
}
2024-05-26 14:11:37 +12:00
public EntityUid ? UiEntity = > Entity ;
2023-12-04 18:04:39 -05:00
}
2024-03-02 17:57:42 -06:00
2025-07-20 01:10:38 +03:00
/// <summary>
/// This event gets raised before a sprite gets drawn in a grid and lets to change the sprite color for several gameobjects that have special sprites to render in containers.
/// </summary>
public sealed class BeforeRenderInGridEvent : EntityEventArgs
{
public Color Color { get ; set ; }
public BeforeRenderInGridEvent ( Color color )
{
Color = color ;
}
}
2024-03-02 17:57:42 -06:00
public enum ItemGridPieceMarks
{
First ,
Second ,
}