2021-04-28 10:49:37 -07:00
using System ;
2019-07-26 13:53:06 +02:00
using System.Collections.Generic ;
2019-03-28 11:14:03 +01:00
using JetBrains.Annotations ;
2021-02-11 01:13:03 -08:00
using Robust.Client.GameObjects ;
2019-04-15 21:11:38 -06:00
using Robust.Shared.GameObjects ;
2021-04-28 10:49:37 -07:00
using Robust.Shared.IoC ;
2021-05-23 19:18:36 +01:00
using Robust.Shared.Log ;
2021-06-09 22:19:39 +02:00
using Robust.Shared.Map ;
2019-07-26 13:53:06 +02:00
using Robust.Shared.Maths ;
2021-03-05 01:08:38 +01:00
using Robust.Shared.Serialization.Manager.Attributes ;
2019-04-15 21:11:38 -06:00
using static Robust . Client . GameObjects . SpriteComponent ;
2018-08-09 20:22:54 +02:00
2021-06-09 22:19:39 +02:00
namespace Content.Client.IconSmoothing
2018-08-09 20:22:54 +02:00
{
// TODO: Potential improvements:
// Defer updating of these.
// Get told by somebody to use a loop.
/// <summary>
/// Makes sprites of other grid-aligned entities like us connect.
/// </summary>
/// <remarks>
/// The system is based on Baystation12's smoothwalling, and thus will work with those.
/// To use, set <c>base</c> equal to the prefix of the corner states in the sprite base RSI.
/// Any objects with the same <c>key</c> will connect.
/// </remarks>
2019-07-31 15:02:36 +02:00
[RegisterComponent]
2019-07-26 13:53:06 +02:00
public class IconSmoothComponent : Component
2018-08-09 20:22:54 +02:00
{
2021-12-08 10:40:19 +01:00
[Dependency] private readonly IEntityManager _entMan = default ! ;
2021-04-28 10:49:37 -07:00
[Dependency] private readonly IMapManager _mapManager = default ! ;
2021-03-05 01:08:38 +01:00
[DataField("mode")]
private IconSmoothingMode _mode = IconSmoothingMode . Corners ;
2019-03-28 11:14:03 +01:00
2018-08-09 20:22:54 +02:00
public override string Name = > "IconSmooth" ;
2021-03-10 14:48:29 +01:00
internal ISpriteComponent ? Sprite { get ; private set ; }
2020-10-11 15:21:21 +02:00
private ( GridId , Vector2i ) _lastPosition ;
2019-03-28 11:14:03 +01:00
/// <summary>
/// We will smooth with other objects with the same key.
/// </summary>
2021-05-04 15:37:16 +02:00
[DataField("key")]
2021-03-10 14:48:29 +01:00
public string? SmoothKey { get ; }
2018-08-09 20:22:54 +02:00
/// <summary>
/// Prepended to the RSI state.
/// </summary>
2021-05-04 15:37:16 +02:00
[DataField("base")]
2021-03-10 14:48:29 +01:00
public string StateBase { get ; } = string . Empty ;
2018-08-09 20:22:54 +02:00
/// <summary>
2019-03-28 11:14:03 +01:00
/// Mode that controls how the icon should be selected.
2018-08-09 20:22:54 +02:00
/// </summary>
2019-03-28 11:14:03 +01:00
public IconSmoothingMode Mode = > _mode ;
2018-08-09 20:22:54 +02:00
2019-03-28 11:14:03 +01:00
/// <summary>
/// Used by <see cref="IconSmoothSystem"/> to reduce redundant updates.
/// </summary>
internal int UpdateGeneration { get ; set ; }
2018-08-09 20:22:54 +02:00
2021-06-19 19:41:26 -07:00
protected override void Initialize ( )
2018-08-09 20:22:54 +02:00
{
base . Initialize ( ) ;
2021-12-08 10:40:19 +01:00
Sprite = _entMan . GetComponent < ISpriteComponent > ( Owner ) ;
2018-08-09 20:22:54 +02:00
}
2019-09-18 11:29:12 -07:00
/// <inheritdoc />
protected override void Startup ( )
2018-08-09 20:22:54 +02:00
{
base . Startup ( ) ;
2021-12-08 10:40:19 +01:00
if ( _entMan . GetComponent < TransformComponent > ( Owner ) . Anchored )
2021-03-10 14:48:29 +01:00
{
// ensures lastposition initial value is populated on spawn. Just calling
// the hook here would cause a dirty event to fire needlessly
2021-05-23 19:18:36 +01:00
UpdateLastPosition ( ) ;
2021-12-08 10:40:19 +01:00
_entMan . EventBus . RaiseEvent ( EventSource . Local , new IconSmoothDirtyEvent ( Owner , null , Mode ) ) ;
2021-03-10 14:48:29 +01:00
}
if ( Sprite ! = null & & Mode = = IconSmoothingMode . Corners )
2018-08-09 20:22:54 +02:00
{
2019-07-26 13:53:06 +02:00
var state0 = $"{StateBase}0" ;
2019-03-28 11:14:03 +01:00
Sprite . LayerMapSet ( CornerLayers . SE , Sprite . AddLayerState ( state0 ) ) ;
Sprite . LayerSetDirOffset ( CornerLayers . SE , DirectionOffset . None ) ;
Sprite . LayerMapSet ( CornerLayers . NE , Sprite . AddLayerState ( state0 ) ) ;
Sprite . LayerSetDirOffset ( CornerLayers . NE , DirectionOffset . CounterClockwise ) ;
Sprite . LayerMapSet ( CornerLayers . NW , Sprite . AddLayerState ( state0 ) ) ;
Sprite . LayerSetDirOffset ( CornerLayers . NW , DirectionOffset . Flip ) ;
Sprite . LayerMapSet ( CornerLayers . SW , Sprite . AddLayerState ( state0 ) ) ;
Sprite . LayerSetDirOffset ( CornerLayers . SW , DirectionOffset . Clockwise ) ;
2018-08-09 20:22:54 +02:00
}
}
2021-05-23 19:18:36 +01:00
private void UpdateLastPosition ( )
{
2021-12-08 10:40:19 +01:00
if ( _mapManager . TryGetGrid ( _entMan . GetComponent < TransformComponent > ( Owner ) . GridID , out var grid ) )
2021-05-23 19:18:36 +01:00
{
2021-12-08 10:40:19 +01:00
_lastPosition = ( _entMan . GetComponent < TransformComponent > ( Owner ) . GridID , grid . TileIndicesFor ( _entMan . GetComponent < TransformComponent > ( Owner ) . Coordinates ) ) ;
2021-05-23 19:18:36 +01:00
}
else
{
// When this is called during component startup, the transform can end up being with an invalid grid ID.
// In that case, use this.
_lastPosition = ( GridId . Invalid , new Vector2i ( 0 , 0 ) ) ;
}
}
2019-07-26 13:53:06 +02:00
internal virtual void CalculateNewSprite ( )
2021-05-23 19:18:36 +01:00
{
2021-12-08 10:40:19 +01:00
if ( ! _mapManager . TryGetGrid ( _entMan . GetComponent < TransformComponent > ( Owner ) . GridID , out var grid ) )
2021-05-23 19:18:36 +01:00
{
2021-12-08 10:40:19 +01:00
Logger . Error ( $"Failed to calculate IconSmoothComponent sprite in {Owner} because grid {_entMan.GetComponent<TransformComponent>(Owner).GridID} was missing." ) ;
2021-05-23 19:18:36 +01:00
return ;
}
CalculateNewSprite ( grid ) ;
}
internal virtual void CalculateNewSprite ( IMapGrid grid )
2019-07-26 13:53:06 +02:00
{
switch ( Mode )
{
case IconSmoothingMode . Corners :
2021-05-23 19:18:36 +01:00
CalculateNewSpriteCorners ( grid ) ;
2019-07-26 13:53:06 +02:00
break ;
case IconSmoothingMode . CardinalFlags :
2021-05-23 19:18:36 +01:00
CalculateNewSpriteCardinal ( grid ) ;
2019-07-26 13:53:06 +02:00
break ;
2020-12-26 19:24:03 +11:00
case IconSmoothingMode . NoSprite :
break ;
2019-07-26 13:53:06 +02:00
default :
throw new ArgumentOutOfRangeException ( ) ;
}
}
2021-05-23 19:18:36 +01:00
private void CalculateNewSpriteCardinal ( IMapGrid grid )
2019-07-26 13:53:06 +02:00
{
2021-12-08 10:40:19 +01:00
if ( ! _entMan . GetComponent < TransformComponent > ( Owner ) . Anchored | | Sprite = = null )
2021-03-10 14:48:29 +01:00
{
return ;
}
2019-07-26 13:53:06 +02:00
var dirs = CardinalConnectDirs . None ;
2021-12-08 10:40:19 +01:00
var position = _entMan . GetComponent < TransformComponent > ( Owner ) . Coordinates ;
2021-04-28 10:49:37 -07:00
if ( MatchingEntity ( grid . GetInDir ( position , Direction . North ) ) )
2019-07-26 13:53:06 +02:00
dirs | = CardinalConnectDirs . North ;
2021-04-28 10:49:37 -07:00
if ( MatchingEntity ( grid . GetInDir ( position , Direction . South ) ) )
2019-07-26 13:53:06 +02:00
dirs | = CardinalConnectDirs . South ;
2021-04-28 10:49:37 -07:00
if ( MatchingEntity ( grid . GetInDir ( position , Direction . East ) ) )
2019-07-26 13:53:06 +02:00
dirs | = CardinalConnectDirs . East ;
2021-04-28 10:49:37 -07:00
if ( MatchingEntity ( grid . GetInDir ( position , Direction . West ) ) )
2019-07-26 13:53:06 +02:00
dirs | = CardinalConnectDirs . West ;
Sprite . LayerSetState ( 0 , $"{StateBase}{(int) dirs}" ) ;
}
2021-05-23 19:18:36 +01:00
private void CalculateNewSpriteCorners ( IMapGrid grid )
2020-04-25 12:10:28 +02:00
{
2021-03-10 14:48:29 +01:00
if ( Sprite = = null )
{
return ;
}
2021-05-23 19:18:36 +01:00
var ( cornerNE , cornerNW , cornerSW , cornerSE ) = CalculateCornerFill ( grid ) ;
2020-04-25 12:10:28 +02:00
Sprite . LayerSetState ( CornerLayers . NE , $"{StateBase}{(int) cornerNE}" ) ;
Sprite . LayerSetState ( CornerLayers . SE , $"{StateBase}{(int) cornerSE}" ) ;
Sprite . LayerSetState ( CornerLayers . SW , $"{StateBase}{(int) cornerSW}" ) ;
Sprite . LayerSetState ( CornerLayers . NW , $"{StateBase}{(int) cornerNW}" ) ;
}
2021-05-23 19:18:36 +01:00
protected ( CornerFill ne , CornerFill nw , CornerFill sw , CornerFill se ) CalculateCornerFill ( IMapGrid grid )
2019-07-26 13:53:06 +02:00
{
2021-12-08 10:40:19 +01:00
if ( ! _entMan . GetComponent < TransformComponent > ( Owner ) . Anchored )
2021-03-10 14:48:29 +01:00
{
return ( CornerFill . None , CornerFill . None , CornerFill . None , CornerFill . None ) ;
}
2021-12-08 10:40:19 +01:00
var position = _entMan . GetComponent < TransformComponent > ( Owner ) . Coordinates ;
2021-04-28 10:49:37 -07:00
var n = MatchingEntity ( grid . GetInDir ( position , Direction . North ) ) ;
var ne = MatchingEntity ( grid . GetInDir ( position , Direction . NorthEast ) ) ;
var e = MatchingEntity ( grid . GetInDir ( position , Direction . East ) ) ;
var se = MatchingEntity ( grid . GetInDir ( position , Direction . SouthEast ) ) ;
var s = MatchingEntity ( grid . GetInDir ( position , Direction . South ) ) ;
var sw = MatchingEntity ( grid . GetInDir ( position , Direction . SouthWest ) ) ;
var w = MatchingEntity ( grid . GetInDir ( position , Direction . West ) ) ;
var nw = MatchingEntity ( grid . GetInDir ( position , Direction . NorthWest ) ) ;
2019-07-26 13:53:06 +02:00
// ReSharper disable InconsistentNaming
var cornerNE = CornerFill . None ;
var cornerSE = CornerFill . None ;
var cornerSW = CornerFill . None ;
var cornerNW = CornerFill . None ;
// ReSharper restore InconsistentNaming
if ( n )
{
cornerNE | = CornerFill . CounterClockwise ;
cornerNW | = CornerFill . Clockwise ;
}
if ( ne )
{
cornerNE | = CornerFill . Diagonal ;
}
if ( e )
{
cornerNE | = CornerFill . Clockwise ;
cornerSE | = CornerFill . CounterClockwise ;
}
if ( se )
{
cornerSE | = CornerFill . Diagonal ;
}
if ( s )
{
cornerSE | = CornerFill . Clockwise ;
cornerSW | = CornerFill . CounterClockwise ;
}
if ( sw )
{
cornerSW | = CornerFill . Diagonal ;
}
if ( w )
{
cornerSW | = CornerFill . Clockwise ;
cornerNW | = CornerFill . CounterClockwise ;
}
if ( nw )
{
cornerNW | = CornerFill . Diagonal ;
}
2021-09-05 17:23:47 +10:00
// Local is fine as we already know it's parented to the grid (due to the way anchoring works).
2021-12-08 10:40:19 +01:00
switch ( _entMan . GetComponent < TransformComponent > ( Owner ) . LocalRotation . GetCardinalDir ( ) )
2020-10-30 16:28:54 +01:00
{
case Direction . North :
return ( cornerSW , cornerSE , cornerNE , cornerNW ) ;
case Direction . West :
return ( cornerSE , cornerNE , cornerNW , cornerSW ) ;
case Direction . South :
return ( cornerNE , cornerNW , cornerSW , cornerSE ) ;
default :
return ( cornerNW , cornerSW , cornerSE , cornerNE ) ;
}
2019-07-26 13:53:06 +02:00
}
2019-09-18 11:29:12 -07:00
/// <inheritdoc />
protected override void Shutdown ( )
2018-08-09 20:22:54 +02:00
{
2019-10-14 17:09:45 +02:00
base . Shutdown ( ) ;
2021-12-08 10:40:19 +01:00
if ( _entMan . GetComponent < TransformComponent > ( Owner ) . Anchored )
2021-03-10 14:48:29 +01:00
{
2021-12-08 10:40:19 +01:00
_entMan . EventBus . RaiseEvent ( EventSource . Local , new IconSmoothDirtyEvent ( Owner , _lastPosition , Mode ) ) ;
2021-03-10 14:48:29 +01:00
}
2018-08-09 20:22:54 +02:00
}
2021-06-19 19:41:26 -07:00
public void AnchorStateChanged ( )
2018-08-09 20:22:54 +02:00
{
2021-12-08 10:40:19 +01:00
if ( _entMan . GetComponent < TransformComponent > ( Owner ) . Anchored )
2021-03-10 14:48:29 +01:00
{
2021-12-08 10:40:19 +01:00
_entMan . EventBus . RaiseEvent ( EventSource . Local , new IconSmoothDirtyEvent ( Owner , _lastPosition , Mode ) ) ;
2021-05-23 19:18:36 +01:00
UpdateLastPosition ( ) ;
2021-03-10 14:48:29 +01:00
}
2018-08-09 20:22:54 +02:00
}
2019-07-26 13:53:06 +02:00
[System.Diagnostics.Contracts.Pure]
2021-04-28 10:49:37 -07:00
protected bool MatchingEntity ( IEnumerable < EntityUid > candidates )
2019-07-26 13:53:06 +02:00
{
foreach ( var entity in candidates )
{
2021-12-08 10:40:19 +01:00
if ( ! _entMan . TryGetComponent ( entity , out IconSmoothComponent ? other ) )
2019-07-26 13:53:06 +02:00
{
continue ;
}
if ( other . SmoothKey = = SmoothKey )
{
return true ;
}
}
return false ;
}
[Flags]
private enum CardinalConnectDirs : byte
{
None = 0 ,
North = 1 ,
South = 2 ,
East = 4 ,
West = 8
}
[Flags]
public enum CornerFill : byte
{
// These values are pulled from Baystation12.
// I'm too lazy to convert the state names.
None = 0 ,
// The cardinal tile counter-clockwise of this corner is filled.
CounterClockwise = 1 ,
// The diagonal tile in the direction of this corner.
Diagonal = 2 ,
// The cardinal tile clockwise of this corner is filled.
Clockwise = 4 ,
}
2020-12-04 11:57:33 +01:00
public enum CornerLayers : byte
2018-08-09 20:22:54 +02:00
{
SE ,
NE ,
NW ,
SW ,
}
2019-03-28 11:14:03 +01:00
}
2018-08-09 20:22:54 +02:00
2019-03-28 11:14:03 +01:00
/// <summary>
/// Controls the mode with which icon smoothing is calculated.
/// </summary>
[PublicAPI]
2020-12-04 11:57:33 +01:00
public enum IconSmoothingMode : byte
2019-03-28 11:14:03 +01:00
{
/// <summary>
/// Each icon is made up of 4 corners, each of which can get a different state depending on
/// adjacent entities clockwise, counter-clockwise and diagonal with the corner.
/// </summary>
Corners ,
/// <summary>
/// There are 16 icons, only one of which is used at once.
/// The icon selected is a bit field made up of the cardinal direction flags that have adjacent entities.
/// </summary>
CardinalFlags ,
2020-12-26 19:24:03 +11:00
/// <summary>
/// Where this component contributes to our neighbors being calculated but we do not update our own sprite.
/// </summary>
NoSprite ,
2018-08-09 20:22:54 +02:00
}
}