Chessistics/Scripts/Input/InputMapper.cs
Samuel Bouchet a7280b1a5a Overhaul turn mechanics, collision destruction, and visual animations
- New turn order: produce -> transfer -> move -> collision resolution
- Collisions now destroy weaker pieces (status > level > mutual destruction)
  instead of halting the simulation. SimPhase.Collision removed.
- Add piece Level property (all level 1 in proto, prepared for future)
- Production fires every turn (interval concept removed), buffer = Amount
  (default 1, future 2-4), leftovers overwritten each turn
- Transfer tiebreaker: status > level > clockwise direction (alternating
  even/odd turns in y-up coords), replaces distance-to-production
- Demands always accept matching cargo even when already satisfied
- TurnNumber added to all turn events for animation grouping
- Simultaneous animations: produce flash, cargo slide, parallel piece moves
- Camera centering fix + middle-click pan
- GDD updated with new rules + lore section added

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 21:44:12 +02:00

194 lines
5.7 KiB
C#

using Godot;
using System;
using Chessistics.Engine.Model;
using Chessistics.Engine.Rules;
using Chessistics.Scripts.Board;
namespace Chessistics.Scripts.Input;
public partial class InputMapper : Node
{
[Signal]
public delegate void PlacementRequestedEventHandler(int kindIndex, int startCol, int startRow, int endCol, int endRow);
[Signal]
public delegate void RemovalRequestedEventHandler(int pieceId);
[Signal]
public delegate void CellClickedEventHandler(int col, int row);
[Signal]
public delegate void CancelledEventHandler();
public enum PlacementPhase { None, SelectingStart, SelectingEnd }
private BoardView _boardView = null!;
private PieceKind? _selectedKind;
private Coords? _selectedStart;
private PlacementPhase _phase = PlacementPhase.None;
private BoardSnapshot? _snapshot;
private Coords? _hoverCoords;
public PlacementPhase CurrentPhase => _phase;
public void Initialize(BoardView boardView)
{
_boardView = boardView;
}
public void SetSnapshot(BoardSnapshot snapshot)
{
GD.Print($"[InputMapper] SetSnapshot called — null? {snapshot == null}");
_snapshot = snapshot;
}
public void SelectPieceKind(PieceKind kind)
{
GD.Print($"[InputMapper] SelectPieceKind: {kind}, phase → SelectingStart");
_selectedKind = kind;
_selectedStart = null;
_phase = PlacementPhase.SelectingStart;
}
public void Cancel()
{
_selectedKind = null;
_selectedStart = null;
_phase = PlacementPhase.None;
_boardView.ClearHighlights();
EmitSignal(SignalName.Cancelled);
}
public override void _Process(double delta)
{
if (_boardView == null || !_boardView.Visible) return;
var localPos = _boardView.GetLocalMousePosition();
var coords = _boardView.PixelToCoords(localPos);
if (coords != _hoverCoords)
{
_hoverCoords = coords;
_boardView.SetHoverCell(coords);
}
}
public override void _UnhandledInput(InputEvent @event)
{
if (@event is InputEventMouseButton mouseEvent && mouseEvent.Pressed)
{
if (mouseEvent.ButtonIndex == MouseButton.Right)
{
Cancel();
return;
}
if (mouseEvent.ButtonIndex == MouseButton.Left)
{
var localPos = _boardView.GetLocalMousePosition();
GD.Print($"[InputMapper] LEFT CLICK — localPos={localPos}, phase={_phase}");
HandleLeftClick();
}
}
if (@event is InputEventKey keyEvent && keyEvent.Pressed && keyEvent.Keycode == Key.Escape)
{
Cancel();
}
}
private void HandleLeftClick()
{
var localPos = _boardView.GetLocalMousePosition();
var coords = _boardView.PixelToCoords(localPos);
GD.Print($"[InputMapper] HandleLeftClick — localPos={localPos}, coords={coords}");
if (coords == null)
{
GD.Print("[InputMapper] coords is null — click outside board");
return;
}
switch (_phase)
{
case PlacementPhase.SelectingStart:
OnStartSelected(coords.Value);
break;
case PlacementPhase.SelectingEnd:
OnEndSelected(coords.Value);
break;
default:
EmitSignal(SignalName.CellClicked, coords.Value.Col, coords.Value.Row);
break;
}
}
private void OnStartSelected(Coords start)
{
if (_selectedKind == null || _snapshot == null)
{
GD.Print($"[InputMapper] OnStartSelected ABORT — kind={_selectedKind}, snapshot={(_snapshot != null ? "ok" : "null")}");
return;
}
var boardState = GetBoardStateForValidation();
if (boardState == null)
{
GD.Print("[InputMapper] OnStartSelected ABORT — boardState is null");
return;
}
var legalEnds = MoveValidator.GetLegalEndCells(_selectedKind.Value, start, boardState);
GD.Print($"[InputMapper] OnStartSelected({start}) — {legalEnds.Count} legal end cells");
if (legalEnds.Count == 0) return;
_selectedStart = start;
_phase = PlacementPhase.SelectingEnd;
_boardView.ClearHighlights();
_boardView.HighlightCells(legalEnds, new Color("#4488FF88"));
var startCell = _boardView.GetCellView(start);
startCell?.SetHighlightColor(new Color("#44FFFF44"));
}
private void OnEndSelected(Coords end)
{
if (_selectedKind == null || _selectedStart == null) return;
var start = _selectedStart.Value;
var kind = _selectedKind.Value;
GD.Print($"[InputMapper] OnEndSelected — placing {kind} from {start} to {end}");
EmitSignal(SignalName.PlacementRequested, (int)kind, start.Col, start.Row, end.Col, end.Row);
// Reset placement state
_phase = PlacementPhase.None;
_selectedKind = null;
_selectedStart = null;
_boardView.ClearHighlights();
}
private BoardState? GetBoardStateForValidation()
{
if (_snapshot == null) return null;
var level = new LevelDef
{
Width = _snapshot.Width,
Height = _snapshot.Height,
Productions = [],
Demands = [],
Walls = [],
Stock = []
};
var state = BoardState.FromLevel(level);
for (int c = 0; c < _snapshot.Width; c++)
for (int r = 0; r < _snapshot.Height; r++)
state.Grid[c, r] = _snapshot.Grid[c, r];
return state;
}
}