StageStudio.cs
namespace PixelPusher;
public static class StageStudio
{
public static int TileId = 2;
public static string Stamp = "paint";
public static int Zoom = 2;
public static float CamX;
public static float CamY;
public static bool Grid = true;
public static bool Autotile;
public static int HoverTx;
public static int HoverTy;
public static int DropX;
public static int DropY;
public static int CursorTx;
public static int CursorTy;
public static bool PadAim;
static bool _stroke;
static float _padAccX;
static float _padAccY;
static float _paintHold;
static int _sx;
static int _sy;
public static StageDoc Stage => Desk.Stage;
public static TilesetDoc Tiles => Desk.Tileset;
public static readonly string[] Stamps =
{
"paint", "flood", "rect", "line", "sip", "start", "coin", "spring", "exit", "check", "sign", "erase"
};
public static readonly string[] SignLines =
{
"STOCK THE COOLER",
"JUMP",
"SPRING AHEAD",
"EXIT THIS WAY",
"MIND THE PINK"
};
public static string NextSign( string current )
{
var i = Array.IndexOf( SignLines, current );
return SignLines[(i + 1) % SignLines.Length];
}
public static void SelectTile( int id )
{
TileId = Math.Max( 0, id );
Stamp = "paint";
GameFlow.Bump();
}
public static void SelectStamp( string stamp )
{
Stamp = stamp ?? "paint";
GameFlow.Bump();
}
public static void SetZoom( int zoom )
{
Zoom = Math.Clamp( zoom, 1, 8 );
GameFlow.Bump();
}
public static void ZoomAt( int vx, int vy, int dir )
{
var old = Math.Max( 1, Zoom );
var next = Math.Clamp( old + dir, 1, 8 );
if ( next == old )
return;
var wx = CamX + vx / (float)old;
var wy = CamY + vy / (float)old;
Zoom = next;
CamX = wx - vx / (float)next;
CamY = wy - vy / (float)next;
ClampCam();
GameFlow.Bump();
}
public static void FocusPlayer( float px, float py )
{
CamX = px - 160f;
CamY = py - 90f;
ClampCam();
}
public static void Pointer( int tx, int ty, bool left, bool right, bool down, bool released )
{
var stage = Stage;
if ( stage is null )
return;
if ( DeskNet.GuestLocked )
return;
HoverTx = tx;
HoverTy = ty;
DropX = tx;
DropY = ty;
if ( StudioMouse.AltHeld || Stamp == "sip" )
{
if ( down || left )
{
var id = stage.TileAt( tx, ty );
if ( id > 0 )
SelectTile( id );
}
return;
}
if ( down )
{
_stroke = true;
_sx = tx;
_sy = ty;
Stage.BeginStroke();
if ( Stamp is "rect" or "line" )
{
Desk.Touch();
return;
}
Apply( tx, ty, right );
Desk.Touch();
return;
}
if ( _stroke && (left || right) && (Stamp == "paint" || Stamp == "erase" || right) )
{
Apply( tx, ty, right );
Desk.Touch();
}
if ( released )
{
if ( Stamp == "rect" )
stage.FillRect( _sx, _sy, tx, ty, right ? 0 : TileId );
else if ( Stamp == "line" )
stage.StrokeLine( _sx, _sy, tx, ty, right ? 0 : TileId );
_stroke = false;
Desk.Touch();
GameFlow.Bump();
}
}
public static bool Preview( out int x0, out int y0, out int x1, out int y1 )
{
x0 = _sx;
y0 = _sy;
x1 = HoverTx;
y1 = HoverTy;
return _stroke && Stamp is "rect" or "line";
}
public static void Pan( float dx, float dy )
{
CamX += dx;
CamY += dy;
ClampCam();
}
public static void TickCamera()
{
if ( GameFlow.Screen != GameScreen.Stage )
return;
if ( Input.UsingController )
{
TickPad();
return;
}
var speed = 280f * Time.Delta;
if ( Input.Down( "Run" ) )
speed *= 2.2f;
if ( Input.Down( "Left" ) ) CamX -= speed;
if ( Input.Down( "Right" ) ) CamX += speed;
if ( Input.Down( "Forward" ) ) CamY -= speed;
if ( Input.Down( "Backward" ) ) CamY += speed;
ClampCam();
}
static void TickPad()
{
var stage = Stage;
if ( stage is null )
return;
var look = Input.AnalogLook;
CamX += look.yaw * 8f;
CamY += look.pitch * 8f;
ClampCam();
var move = Input.AnalogMove;
var ax = move.y;
var ay = -move.x;
if ( MathF.Abs( ax ) < 0.35f ) ax = 0;
if ( MathF.Abs( ay ) < 0.35f ) ay = 0;
if ( ax != 0 || ay != 0 )
{
PadAim = true;
var speed = 8f * Time.Delta * (Input.Down( "Run" ) ? 2f : 1f);
_padAccX += ax * speed;
_padAccY += ay * speed;
while ( MathF.Abs( _padAccX ) >= 1f )
{
CursorTx = Math.Clamp( CursorTx + Math.Sign( _padAccX ), 0, stage.Width - 1 );
_padAccX -= Math.Sign( _padAccX );
}
while ( MathF.Abs( _padAccY ) >= 1f )
{
CursorTy = Math.Clamp( CursorTy + Math.Sign( _padAccY ), 0, stage.Height - 1 );
_padAccY -= Math.Sign( _padAccY );
}
HoverTx = CursorTx;
HoverTy = CursorTy;
DropX = CursorTx;
DropY = CursorTy;
GameFlow.Bump();
}
if ( Input.Pressed( "SlotPrev" ) )
CycleStamp( -1 );
if ( Input.Pressed( "SlotNext" ) )
CycleStamp( 1 );
if ( Input.Pressed( "Jump" ) )
GameFlow.StartPlaytest();
if ( CrtView.PointerOn )
{
_paintHold = 0f;
return;
}
var paint = Input.Down( "Attack1" );
var erase = Input.Down( "Attack2" );
if ( paint || erase )
{
var down = _paintHold <= 0f;
Pointer( CursorTx, CursorTy, paint, erase, down, false );
_paintHold += Time.Delta;
Coach.OnStagePaint();
}
else if ( _paintHold > 0f )
{
Pointer( CursorTx, CursorTy, false, false, false, true );
_paintHold = 0;
}
}
public static void CycleStamp( int dir )
{
var i = Array.IndexOf( Stamps, Stamp );
if ( i < 0 ) i = 0;
i = (i + dir + Stamps.Length) % Stamps.Length;
SelectStamp( Stamps[i] );
}
public static void Undo()
{
Stage?.Undo();
GameFlow.Bump();
}
public static void Redo()
{
Stage?.Redo();
GameFlow.Bump();
}
public static void SetGoal( string goal )
{
if ( Stage is null )
return;
Stage.BeginStroke();
Stage.Goal = goal == "grab" ? "grab" : "exit";
Desk.Touch();
GameFlow.ShowToast( Stage.Goal == "grab" ? "goal · grab every coin" : "goal · reach the exit" );
GameFlow.Bump();
}
public static void CycleTileFlag()
{
var tiles = Tiles;
if ( tiles is null || TileId <= 0 )
return;
var next = TilesetDoc.CycleFlag( tiles.Flag( TileId ) );
tiles.SetFlag( TileId, next );
Desk.Touch();
GameFlow.ShowToast( $"tile {TileId} · {TilesetDoc.FlagName( next )}" );
GameFlow.Bump();
}
static void Apply( int tx, int ty, bool erase )
{
var stage = Stage;
if ( stage is null )
return;
if ( erase || Stamp == "erase" )
{
stage.RemoveAt( tx, ty );
return;
}
if ( Stamp == "flood" )
{
stage.Flood( tx, ty, TileId );
return;
}
if ( Stamp == "paint" )
{
PaintTile( tx, ty );
return;
}
if ( Stamp == "sign" )
{
var existing = stage.Entities.Find( e => e.Tx == tx && e.Ty == ty && e.Kind == "sign" );
var data = existing is null ? SignLines[0] : NextSign( existing.Data );
stage.PlaceEntity( "sign", tx, ty, data );
GameFlow.ShowToast( data );
return;
}
stage.PlaceEntity( Stamp, tx, ty );
}
static void PaintTile( int tx, int ty )
{
var stage = Stage;
var id = TileId;
if ( Autotile && id > 0 )
id = AutotileId( tx, ty, id );
stage.SetTile( tx, ty, id );
if ( !Autotile )
return;
RefreshNeighbor( tx - 1, ty );
RefreshNeighbor( tx + 1, ty );
RefreshNeighbor( tx, ty - 1 );
RefreshNeighbor( tx, ty + 1 );
}
static void RefreshNeighbor( int tx, int ty )
{
var stage = Stage;
var tiles = Tiles;
if ( stage is null || tiles is null )
return;
var id = stage.TileAt( tx, ty );
if ( id <= 0 || !tiles.IsSolid( id ) )
return;
stage.SetTile( tx, ty, AutotileId( tx, ty, TileId ) );
}
static int AutotileId( int tx, int ty, int baseId )
{
var tiles = Tiles;
if ( tiles is null )
return baseId;
var n = tiles.IsSolid( Stage.TileAt( tx, ty - 1 ) ) ? 1 : 0;
var e = tiles.IsSolid( Stage.TileAt( tx + 1, ty ) ) ? 2 : 0;
var s = tiles.IsSolid( Stage.TileAt( tx, ty + 1 ) ) ? 4 : 0;
var w = tiles.IsSolid( Stage.TileAt( tx - 1, ty ) ) ? 8 : 0;
var variant = n | e | s | w;
var id = baseId + (variant % 4);
if ( id >= tiles.TileCount )
id = baseId;
return Math.Max( 1, id );
}
static void ClampCam()
{
var stage = Stage;
if ( stage is null )
return;
var worldW = stage.Width * stage.TileSize;
var worldH = stage.Height * stage.TileSize;
CamX = Math.Clamp( CamX, 0, Math.Max( 0, worldW - 320 ) );
CamY = Math.Clamp( CamY, 0, Math.Max( 0, worldH - 180 ) );
}
}