using Content.Shared.Arcade; using Robust.Server.Player; using System.Linq; namespace Content.Server.Arcade.BlockGame; public sealed partial class BlockGame { /// /// How often to check the currently pressed inputs for whether to move the active piece horizontally. /// private const float PressCheckSpeed = 0.08f; /// /// Whether the left button is pressed. /// Moves the active piece left if true. /// private bool _leftPressed = false; /// /// How long the left button has been pressed. /// private float _accumulatedLeftPressTime = 0f; /// /// Whether the right button is pressed. /// Moves the active piece right if true. /// private bool _rightPressed = false; /// /// How long the right button has been pressed. /// private float _accumulatedRightPressTime = 0f; /// /// Whether the down button is pressed. /// Speeds up how quickly the active piece falls if true. /// private bool _softDropPressed = false; /// /// Handles user input. /// /// The action to current player has prompted. public void ProcessInput(BlockGamePlayerAction action) { if (_running) { switch (action) { case BlockGamePlayerAction.StartLeft: _leftPressed = true; break; case BlockGamePlayerAction.StartRight: _rightPressed = true; break; case BlockGamePlayerAction.Rotate: TrySetRotation(Next(_currentRotation, false)); break; case BlockGamePlayerAction.CounterRotate: TrySetRotation(Next(_currentRotation, true)); break; case BlockGamePlayerAction.SoftdropStart: _softDropPressed = true; if (_accumulatedFieldFrameTime > Speed) _accumulatedFieldFrameTime = Speed; //to prevent jumps break; case BlockGamePlayerAction.Harddrop: PerformHarddrop(); break; case BlockGamePlayerAction.Hold: HoldPiece(); break; } } switch (action) { case BlockGamePlayerAction.EndLeft: _leftPressed = false; break; case BlockGamePlayerAction.EndRight: _rightPressed = false; break; case BlockGamePlayerAction.SoftdropEnd: _softDropPressed = false; break; case BlockGamePlayerAction.Pause: _running = false; SendMessage(new BlockGameMessages.BlockGameSetScreenMessage(BlockGameMessages.BlockGameScreen.Pause, Started)); break; case BlockGamePlayerAction.Unpause: if (!_gameOver && Started) { _running = true; SendMessage(new BlockGameMessages.BlockGameSetScreenMessage(BlockGameMessages.BlockGameScreen.Game)); } break; case BlockGamePlayerAction.ShowHighscores: _running = false; SendMessage(new BlockGameMessages.BlockGameSetScreenMessage(BlockGameMessages.BlockGameScreen.Highscores, Started)); break; } } /// /// Handle moving the active game piece in response to user input. /// /// The amount of time the current game tick covers. private void InputTick(float frameTime) { var anythingChanged = false; if (_leftPressed) { _accumulatedLeftPressTime += frameTime; while (_accumulatedLeftPressTime >= PressCheckSpeed) { if (CurrentPiece.Positions(_currentPiecePosition.AddToX(-1), _currentRotation) .All(MoveCheck)) { _currentPiecePosition = _currentPiecePosition.AddToX(-1); anythingChanged = true; } _accumulatedLeftPressTime -= PressCheckSpeed; } } if (_rightPressed) { _accumulatedRightPressTime += frameTime; while (_accumulatedRightPressTime >= PressCheckSpeed) { if (CurrentPiece.Positions(_currentPiecePosition.AddToX(1), _currentRotation) .All(MoveCheck)) { _currentPiecePosition = _currentPiecePosition.AddToX(1); anythingChanged = true; } _accumulatedRightPressTime -= PressCheckSpeed; } } if (anythingChanged) UpdateFieldUI(); } /// /// Handles sending a message to all players/spectators. /// /// The message to broadcase to all players/spectators. private void SendMessage(BoundUserInterfaceMessage message) { if (_uiSystem.TryGetUi(_owner, BlockGameUiKey.Key, out var bui)) _uiSystem.SendUiMessage(bui, message); } /// /// Handles sending a message to a specific player/spectator. /// /// The message to send to a specific player/spectator. /// The target recipient. private void SendMessage(BoundUserInterfaceMessage message, IPlayerSession session) { if (_uiSystem.TryGetUi(_owner, BlockGameUiKey.Key, out var bui)) _uiSystem.TrySendUiMessage(bui, message, session); } /// /// Handles sending the current state of the game to a player that has just opened the UI. /// /// The target recipient. public void UpdateNewPlayerUI(IPlayerSession session) { if (_gameOver) { SendMessage(new BlockGameMessages.BlockGameGameOverScreenMessage(Points, _highScorePlacement?.LocalPlacement, _highScorePlacement?.GlobalPlacement), session); return; } if (Paused) SendMessage(new BlockGameMessages.BlockGameSetScreenMessage(BlockGameMessages.BlockGameScreen.Pause, Started), session); else SendMessage(new BlockGameMessages.BlockGameSetScreenMessage(BlockGameMessages.BlockGameScreen.Game, Started), session); FullUpdate(session); } /// /// Handles broadcasting the full player-visible game state to everyone who can see the game. /// private void FullUpdate() { UpdateFieldUI(); SendHoldPieceUpdate(); SendNextPieceUpdate(); SendLevelUpdate(); SendPointsUpdate(); SendHighscoreUpdate(); } /// /// Handles broadcasting the full player-visible game state to a specific player/spectator. /// /// The target recipient. private void FullUpdate(IPlayerSession session) { UpdateFieldUI(session); SendNextPieceUpdate(session); SendHoldPieceUpdate(session); SendLevelUpdate(session); SendPointsUpdate(session); SendHighscoreUpdate(session); } /// /// Handles broadcasting the current location of all of the blocks in the playfield + the active piece to all spectators. /// public void UpdateFieldUI() { if (!Started) return; var computedField = ComputeField(); SendMessage(new BlockGameMessages.BlockGameVisualUpdateMessage(computedField.ToArray(), BlockGameMessages.BlockGameVisualType.GameField)); } /// /// Handles broadcasting the current location of all of the blocks in the playfield + the active piece to a specific player/spectator. /// /// The target recipient. public void UpdateFieldUI(IPlayerSession session) { if (!Started) return; var computedField = ComputeField(); SendMessage(new BlockGameMessages.BlockGameVisualUpdateMessage(computedField.ToArray(), BlockGameMessages.BlockGameVisualType.GameField), session); } /// /// Generates the set of blocks to send to viewers. /// public List ComputeField() { var result = new List(); result.AddRange(_field); result.AddRange(CurrentPiece.Blocks(_currentPiecePosition, _currentRotation)); var dropGhostPosition = _currentPiecePosition; while (CurrentPiece.Positions(dropGhostPosition.AddToY(1), _currentRotation) .All(DropCheck)) { dropGhostPosition = dropGhostPosition.AddToY(1); } if (dropGhostPosition != _currentPiecePosition) { var blox = CurrentPiece.Blocks(dropGhostPosition, _currentRotation); for (var i = 0; i < blox.Length; i++) { result.Add(new BlockGameBlock(blox[i].Position, BlockGameBlock.ToGhostBlockColor(blox[i].GameBlockColor))); } } return result; } /// /// Broadcasts the state of the next queued piece to all viewers. /// private void SendNextPieceUpdate() { SendMessage(new BlockGameMessages.BlockGameVisualUpdateMessage(NextPiece.BlocksForPreview(), BlockGameMessages.BlockGameVisualType.NextBlock)); } /// /// Broadcasts the state of the next queued piece to a specific viewer. /// /// The target recipient. private void SendNextPieceUpdate(IPlayerSession session) { SendMessage(new BlockGameMessages.BlockGameVisualUpdateMessage(NextPiece.BlocksForPreview(), BlockGameMessages.BlockGameVisualType.NextBlock), session); } /// /// Broadcasts the state of the currently held piece to all viewers. /// private void SendHoldPieceUpdate() { if (HeldPiece.HasValue) SendMessage(new BlockGameMessages.BlockGameVisualUpdateMessage(HeldPiece.Value.BlocksForPreview(), BlockGameMessages.BlockGameVisualType.HoldBlock)); else SendMessage(new BlockGameMessages.BlockGameVisualUpdateMessage(Array.Empty(), BlockGameMessages.BlockGameVisualType.HoldBlock)); } /// /// Broadcasts the state of the currently held piece to a specific viewer. /// /// The target recipient. private void SendHoldPieceUpdate(IPlayerSession session) { if (HeldPiece.HasValue) SendMessage(new BlockGameMessages.BlockGameVisualUpdateMessage(HeldPiece.Value.BlocksForPreview(), BlockGameMessages.BlockGameVisualType.HoldBlock), session); else SendMessage(new BlockGameMessages.BlockGameVisualUpdateMessage(Array.Empty(), BlockGameMessages.BlockGameVisualType.HoldBlock), session); } /// /// Broadcasts the current game level to all viewers. /// private void SendLevelUpdate() { SendMessage(new BlockGameMessages.BlockGameLevelUpdateMessage(Level)); } /// /// Broadcasts the current game level to a specific viewer. /// /// The target recipient. private void SendLevelUpdate(IPlayerSession session) { SendMessage(new BlockGameMessages.BlockGameLevelUpdateMessage(Level), session); } /// /// Broadcasts the current game score to all viewers. /// private void SendPointsUpdate() { SendMessage(new BlockGameMessages.BlockGameScoreUpdateMessage(Points)); } /// /// Broadcasts the current game score to a specific viewer. /// /// The target recipient. private void SendPointsUpdate(IPlayerSession session) { SendMessage(new BlockGameMessages.BlockGameScoreUpdateMessage(Points), session); } /// /// Broadcasts the current game high score positions to all viewers. /// private void SendHighscoreUpdate() { SendMessage(new BlockGameMessages.BlockGameHighScoreUpdateMessage(_arcadeSystem.GetLocalHighscores(), _arcadeSystem.GetGlobalHighscores())); } /// /// Broadcasts the current game high score positions to a specific viewer. /// /// The target recipient. private void SendHighscoreUpdate(IPlayerSession session) { SendMessage(new BlockGameMessages.BlockGameHighScoreUpdateMessage(_arcadeSystem.GetLocalHighscores(), _arcadeSystem.GetGlobalHighscores()), session); } }