ECS Arcade Machines (#16791)
This commit is contained in:
256
Content.Server/Arcade/BlockGame/BlockGame.GameState.cs
Normal file
256
Content.Server/Arcade/BlockGame/BlockGame.GameState.cs
Normal file
@@ -0,0 +1,256 @@
|
||||
using Content.Shared.Arcade;
|
||||
using Robust.Shared.Random;
|
||||
using System.Linq;
|
||||
|
||||
namespace Content.Server.Arcade.BlockGame;
|
||||
|
||||
public sealed partial class BlockGame
|
||||
{
|
||||
// note: field is 10(0 -> 9) wide and 20(0 -> 19) high
|
||||
|
||||
/// <summary>
|
||||
/// Whether the given position is above the bottom of the playfield.
|
||||
/// </summary>
|
||||
private bool LowerBoundCheck(Vector2i position)
|
||||
{
|
||||
return position.Y < 20;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Whether the given position is horizontally positioned within the playfield.
|
||||
/// </summary>
|
||||
private bool BorderCheck(Vector2i position)
|
||||
{
|
||||
return position.X >= 0 && position.X < 10;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Whether the given position is currently occupied by a piece.
|
||||
/// Yes this is on O(n) collision check, it works well enough.
|
||||
/// </summary>
|
||||
private bool ClearCheck(Vector2i position)
|
||||
{
|
||||
return _field.All(block => !position.Equals(block.Position));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Whether a block can be dropped into the given position.
|
||||
/// </summary>
|
||||
private bool DropCheck(Vector2i position)
|
||||
{
|
||||
return LowerBoundCheck(position) && ClearCheck(position);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Whether a block can be moved horizontally into the given position.
|
||||
/// </summary>
|
||||
private bool MoveCheck(Vector2i position)
|
||||
{
|
||||
return BorderCheck(position) && ClearCheck(position);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Whether a block can be rotated into the given position.
|
||||
/// </summary>
|
||||
private bool RotateCheck(Vector2i position)
|
||||
{
|
||||
return BorderCheck(position) && LowerBoundCheck(position) && ClearCheck(position);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The set of blocks that have landed in the field.
|
||||
/// </summary>
|
||||
private readonly List<BlockGameBlock> _field = new();
|
||||
|
||||
/// <summary>
|
||||
/// The current pool of pickable pieces.
|
||||
/// Refreshed when a piece is requested while empty.
|
||||
/// Ensures that the player is given an even spread of pieces by making picked pieces unpickable until the rest are picked.
|
||||
/// </summary>
|
||||
private List<BlockGamePieceType> _blockGamePiecesBuffer = new();
|
||||
|
||||
/// <summary>
|
||||
/// Gets a random piece from the pool of pickable pieces. (<see cref="_blockGamePiecesBuffer"/>)
|
||||
/// </summary>
|
||||
private BlockGamePiece GetRandomBlockGamePiece(IRobustRandom random)
|
||||
{
|
||||
if (_blockGamePiecesBuffer.Count == 0)
|
||||
{
|
||||
_blockGamePiecesBuffer = _allBlockGamePieces.ToList();
|
||||
}
|
||||
|
||||
var chosenPiece = random.Pick(_blockGamePiecesBuffer);
|
||||
_blockGamePiecesBuffer.Remove(chosenPiece);
|
||||
return BlockGamePiece.GetPiece(chosenPiece);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The piece that is currently falling and controllable by the player.
|
||||
/// </summary>
|
||||
private BlockGamePiece CurrentPiece
|
||||
{
|
||||
get => _internalCurrentPiece;
|
||||
set
|
||||
{
|
||||
_internalCurrentPiece = value;
|
||||
UpdateFieldUI();
|
||||
}
|
||||
}
|
||||
private BlockGamePiece _internalCurrentPiece = default!;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// The position of the falling piece.
|
||||
/// </summary>
|
||||
private Vector2i _currentPiecePosition;
|
||||
|
||||
/// <summary>
|
||||
/// The rotation of the falling piece.
|
||||
/// </summary>
|
||||
private BlockGamePieceRotation _currentRotation;
|
||||
|
||||
/// <summary>
|
||||
/// The amount of time (in seconds) between piece steps.
|
||||
/// Decreased by a constant amount per level.
|
||||
/// Decreased heavily by soft dropping the current piece (holding down).
|
||||
/// </summary>
|
||||
private float Speed => Math.Max(0.03f, (_softDropPressed ? SoftDropModifier : 1f) - 0.03f * Level);
|
||||
|
||||
/// <summary>
|
||||
/// The base amount of time between piece steps while softdropping.
|
||||
/// </summary>
|
||||
private const float SoftDropModifier = 0.1f;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to rotate the falling piece to a new rotation.
|
||||
/// </summary>
|
||||
private void TrySetRotation(BlockGamePieceRotation rotation)
|
||||
{
|
||||
if (!_running)
|
||||
return;
|
||||
|
||||
if (!CurrentPiece.CanSpin)
|
||||
return;
|
||||
|
||||
if (!CurrentPiece.Positions(_currentPiecePosition, rotation)
|
||||
.All(RotateCheck))
|
||||
return;
|
||||
|
||||
_currentRotation = rotation;
|
||||
UpdateFieldUI();
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// The next piece that will be dispensed.
|
||||
/// </summary>
|
||||
private BlockGamePiece NextPiece
|
||||
{
|
||||
get => _internalNextPiece;
|
||||
set
|
||||
{
|
||||
_internalNextPiece = value;
|
||||
SendNextPieceUpdate();
|
||||
}
|
||||
}
|
||||
private BlockGamePiece _internalNextPiece = default!;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// The piece the player has chosen to hold in reserve.
|
||||
/// </summary>
|
||||
private BlockGamePiece? HeldPiece
|
||||
{
|
||||
get => _internalHeldPiece;
|
||||
set
|
||||
{
|
||||
_internalHeldPiece = value;
|
||||
SendHoldPieceUpdate();
|
||||
}
|
||||
}
|
||||
private BlockGamePiece? _internalHeldPiece = null;
|
||||
|
||||
/// <summary>
|
||||
/// Prevents the player from holding the currently falling piece if true.
|
||||
/// Set true when a piece is held and set false when a new piece is created.
|
||||
/// Exists to prevent the player from swapping between two pieces forever and never actually letting the block fall.
|
||||
/// </summary>
|
||||
private bool _holdBlock = false;
|
||||
|
||||
/// <summary>
|
||||
/// The number of lines that have been cleared in the current level.
|
||||
/// Automatically advances the game to the next level if enough lines are cleared.
|
||||
/// </summary>
|
||||
private int ClearedLines
|
||||
{
|
||||
get => _clearedLines;
|
||||
set
|
||||
{
|
||||
_clearedLines = value;
|
||||
|
||||
if (_clearedLines < LevelRequirement)
|
||||
return;
|
||||
|
||||
_clearedLines -= LevelRequirement;
|
||||
Level++;
|
||||
}
|
||||
}
|
||||
private int _clearedLines = 0;
|
||||
|
||||
/// <summary>
|
||||
/// The number of lines that must be cleared to advance to the next level.
|
||||
/// </summary>
|
||||
private int LevelRequirement => Math.Min(100, Math.Max(Level * 10 - 50, 10));
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// The current level of the game.
|
||||
/// Effects the movement speed of the active piece.
|
||||
/// </summary>
|
||||
private int Level
|
||||
{
|
||||
get => _internalLevel;
|
||||
set
|
||||
{
|
||||
if (_internalLevel == value)
|
||||
return;
|
||||
_internalLevel = value;
|
||||
SendLevelUpdate();
|
||||
}
|
||||
}
|
||||
private int _internalLevel = 0;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// The total number of points accumulated in the current game.
|
||||
/// </summary>
|
||||
private int Points
|
||||
{
|
||||
get => _internalPoints;
|
||||
set
|
||||
{
|
||||
if (_internalPoints == value)
|
||||
return;
|
||||
_internalPoints = value;
|
||||
SendPointsUpdate();
|
||||
}
|
||||
}
|
||||
private int _internalPoints = 0;
|
||||
|
||||
/// <summary>
|
||||
/// Setter for the setter for the number of points accumulated in the current game.
|
||||
/// </summary>
|
||||
private void AddPoints(int amount)
|
||||
{
|
||||
if (amount == 0)
|
||||
return;
|
||||
|
||||
Points += amount;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Where the current game has placed amongst the leaderboard.
|
||||
/// </summary>
|
||||
private ArcadeSystem.HighScorePlacement? _highScorePlacement = null;
|
||||
}
|
||||
238
Content.Server/Arcade/BlockGame/BlockGame.Pieces.cs
Normal file
238
Content.Server/Arcade/BlockGame/BlockGame.Pieces.cs
Normal file
@@ -0,0 +1,238 @@
|
||||
using Content.Shared.Arcade;
|
||||
using System.Linq;
|
||||
|
||||
namespace Content.Server.Arcade.BlockGame;
|
||||
|
||||
public sealed partial class BlockGame
|
||||
{
|
||||
/// <summary>
|
||||
/// The set of types of game pieces that exist.
|
||||
/// Used as templates when creating pieces for the game.
|
||||
/// </summary>
|
||||
private readonly BlockGamePieceType[] _allBlockGamePieces;
|
||||
|
||||
/// <summary>
|
||||
/// The set of types of game pieces that exist.
|
||||
/// Used to generate the templates used when creating pieces for the game.
|
||||
/// </summary>
|
||||
private enum BlockGamePieceType
|
||||
{
|
||||
I,
|
||||
L,
|
||||
LInverted,
|
||||
S,
|
||||
SInverted,
|
||||
T,
|
||||
O
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The set of possible rotations for the game pieces.
|
||||
/// </summary>
|
||||
private enum BlockGamePieceRotation
|
||||
{
|
||||
North,
|
||||
East,
|
||||
South,
|
||||
West
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A static extension for the rotations that allows rotating through the possible rotations.
|
||||
/// </summary>
|
||||
private static BlockGamePieceRotation Next(BlockGamePieceRotation rotation, bool inverted)
|
||||
{
|
||||
return rotation switch
|
||||
{
|
||||
BlockGamePieceRotation.North => inverted ? BlockGamePieceRotation.West : BlockGamePieceRotation.East,
|
||||
BlockGamePieceRotation.East => inverted ? BlockGamePieceRotation.North : BlockGamePieceRotation.South,
|
||||
BlockGamePieceRotation.South => inverted ? BlockGamePieceRotation.East : BlockGamePieceRotation.West,
|
||||
BlockGamePieceRotation.West => inverted ? BlockGamePieceRotation.South : BlockGamePieceRotation.North,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(rotation), rotation, null)
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A static extension for the rotations that allows rotating through the possible rotations.
|
||||
/// </summary>
|
||||
private struct BlockGamePiece
|
||||
{
|
||||
/// <summary>
|
||||
/// Where all of the blocks that make up this piece are located relative to the origin of the piece.
|
||||
/// </summary>
|
||||
public Vector2i[] Offsets;
|
||||
|
||||
/// <summary>
|
||||
/// The color of all of the blocks that make up this piece.
|
||||
/// </summary>
|
||||
private BlockGameBlock.BlockGameBlockColor _gameBlockColor;
|
||||
|
||||
/// <summary>
|
||||
/// Whether or not the block should be able to rotate about its origin.
|
||||
/// </summary>
|
||||
public bool CanSpin;
|
||||
|
||||
/// <summary>
|
||||
/// Generates a list of the positions of each block comprising this game piece in worldspace.
|
||||
/// </summary>
|
||||
/// <param name="center">The position of the game piece in worldspace.</param>
|
||||
/// <param name="rotation">The rotation of the game piece in worldspace.</param>
|
||||
public readonly Vector2i[] Positions(Vector2i center, BlockGamePieceRotation rotation)
|
||||
{
|
||||
return RotatedOffsets(rotation).Select(v => center + v).ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the relative position of each block comprising this piece given a rotation.
|
||||
/// </summary>
|
||||
/// <param name="rotation">The rotation to be applied to the local position of the blocks in this piece.</param>
|
||||
private readonly Vector2i[] RotatedOffsets(BlockGamePieceRotation rotation)
|
||||
{
|
||||
var rotatedOffsets = (Vector2i[]) Offsets.Clone();
|
||||
//until i find a better algo
|
||||
var amount = rotation switch
|
||||
{
|
||||
BlockGamePieceRotation.North => 0,
|
||||
BlockGamePieceRotation.East => 1,
|
||||
BlockGamePieceRotation.South => 2,
|
||||
BlockGamePieceRotation.West => 3,
|
||||
_ => 0
|
||||
};
|
||||
|
||||
for (var i = 0; i < amount; i++)
|
||||
{
|
||||
for (var j = 0; j < rotatedOffsets.Length; j++)
|
||||
{
|
||||
rotatedOffsets[j] = rotatedOffsets[j].Rotate90DegreesAsOffset();
|
||||
}
|
||||
}
|
||||
|
||||
return rotatedOffsets;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a list of all of the blocks comprising this piece in worldspace.
|
||||
/// </summary>
|
||||
/// <param name="center">The position of the game piece in worldspace.</param>
|
||||
/// <param name="rotation">The rotation of the game piece in worldspace.</param>
|
||||
public readonly BlockGameBlock[] Blocks(Vector2i center, BlockGamePieceRotation rotation)
|
||||
{
|
||||
var positions = Positions(center, rotation);
|
||||
var result = new BlockGameBlock[positions.Length];
|
||||
var i = 0;
|
||||
foreach (var position in positions)
|
||||
{
|
||||
result[i++] = position.ToBlockGameBlock(_gameBlockColor);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a list of all of the blocks comprising this piece in worldspace.
|
||||
/// Used to generate the held piece/next piece preview images.
|
||||
/// </summary>
|
||||
public readonly BlockGameBlock[] BlocksForPreview()
|
||||
{
|
||||
var xOffset = 0;
|
||||
var yOffset = 0;
|
||||
foreach (var offset in Offsets)
|
||||
{
|
||||
if (offset.X < xOffset)
|
||||
xOffset = offset.X;
|
||||
if (offset.Y < yOffset)
|
||||
yOffset = offset.Y;
|
||||
}
|
||||
|
||||
return Blocks(new Vector2i(-xOffset, -yOffset), BlockGamePieceRotation.North);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates a game piece for a given type of game piece.
|
||||
/// See <see cref="BlockGamePieceType"/> for the available options.
|
||||
/// </summary>
|
||||
/// <param name="type">The type of game piece to generate.</param>
|
||||
public static BlockGamePiece GetPiece(BlockGamePieceType type)
|
||||
{
|
||||
//switch statement, hardcoded offsets
|
||||
return type switch
|
||||
{
|
||||
BlockGamePieceType.I => new BlockGamePiece
|
||||
{
|
||||
Offsets = new[]
|
||||
{
|
||||
new Vector2i(0, -1), new Vector2i(0, 0), new Vector2i(0, 1), new Vector2i(0, 2),
|
||||
},
|
||||
_gameBlockColor = BlockGameBlock.BlockGameBlockColor.LightBlue,
|
||||
CanSpin = true
|
||||
},
|
||||
BlockGamePieceType.L => new BlockGamePiece
|
||||
{
|
||||
Offsets = new[]
|
||||
{
|
||||
new Vector2i(0, -1), new Vector2i(0, 0), new Vector2i(0, 1), new Vector2i(1, 1),
|
||||
},
|
||||
_gameBlockColor = BlockGameBlock.BlockGameBlockColor.Orange,
|
||||
CanSpin = true
|
||||
},
|
||||
BlockGamePieceType.LInverted => new BlockGamePiece
|
||||
{
|
||||
Offsets = new[]
|
||||
{
|
||||
new Vector2i(0, -1), new Vector2i(0, 0), new Vector2i(-1, 1),
|
||||
new Vector2i(0, 1),
|
||||
},
|
||||
_gameBlockColor = BlockGameBlock.BlockGameBlockColor.Blue,
|
||||
CanSpin = true
|
||||
},
|
||||
BlockGamePieceType.S => new BlockGamePiece
|
||||
{
|
||||
Offsets = new[]
|
||||
{
|
||||
new Vector2i(0, -1), new Vector2i(1, -1), new Vector2i(-1, 0),
|
||||
new Vector2i(0, 0),
|
||||
},
|
||||
_gameBlockColor = BlockGameBlock.BlockGameBlockColor.Green,
|
||||
CanSpin = true
|
||||
},
|
||||
BlockGamePieceType.SInverted => new BlockGamePiece
|
||||
{
|
||||
Offsets = new[]
|
||||
{
|
||||
new Vector2i(-1, -1), new Vector2i(0, -1), new Vector2i(0, 0),
|
||||
new Vector2i(1, 0),
|
||||
},
|
||||
_gameBlockColor = BlockGameBlock.BlockGameBlockColor.Red,
|
||||
CanSpin = true
|
||||
},
|
||||
BlockGamePieceType.T => new BlockGamePiece
|
||||
{
|
||||
Offsets = new[]
|
||||
{
|
||||
new Vector2i(0, -1),
|
||||
new Vector2i(-1, 0), new Vector2i(0, 0), new Vector2i(1, 0),
|
||||
},
|
||||
_gameBlockColor = BlockGameBlock.BlockGameBlockColor.Purple,
|
||||
CanSpin = true
|
||||
},
|
||||
BlockGamePieceType.O => new BlockGamePiece
|
||||
{
|
||||
Offsets = new[]
|
||||
{
|
||||
new Vector2i(0, -1), new Vector2i(1, -1), new Vector2i(0, 0),
|
||||
new Vector2i(1, 0),
|
||||
},
|
||||
_gameBlockColor = BlockGameBlock.BlockGameBlockColor.Yellow,
|
||||
CanSpin = false
|
||||
},
|
||||
_ => new BlockGamePiece
|
||||
{
|
||||
Offsets = new[]
|
||||
{
|
||||
new Vector2i(0, 0)
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
364
Content.Server/Arcade/BlockGame/BlockGame.Ui.cs
Normal file
364
Content.Server/Arcade/BlockGame/BlockGame.Ui.cs
Normal file
@@ -0,0 +1,364 @@
|
||||
using Content.Shared.Arcade;
|
||||
using Robust.Server.Player;
|
||||
using System.Linq;
|
||||
|
||||
namespace Content.Server.Arcade.BlockGame;
|
||||
|
||||
public sealed partial class BlockGame
|
||||
{
|
||||
/// <summary>
|
||||
/// How often to check the currently pressed inputs for whether to move the active piece horizontally.
|
||||
/// </summary>
|
||||
private const float PressCheckSpeed = 0.08f;
|
||||
|
||||
/// <summary>
|
||||
/// Whether the left button is pressed.
|
||||
/// Moves the active piece left if true.
|
||||
/// </summary>
|
||||
private bool _leftPressed = false;
|
||||
|
||||
/// <summary>
|
||||
/// How long the left button has been pressed.
|
||||
/// </summary>
|
||||
private float _accumulatedLeftPressTime = 0f;
|
||||
|
||||
/// <summary>
|
||||
/// Whether the right button is pressed.
|
||||
/// Moves the active piece right if true.
|
||||
/// </summary>
|
||||
private bool _rightPressed = false;
|
||||
|
||||
/// <summary>
|
||||
/// How long the right button has been pressed.
|
||||
/// </summary>
|
||||
private float _accumulatedRightPressTime = 0f;
|
||||
|
||||
/// <summary>
|
||||
/// Whether the down button is pressed.
|
||||
/// Speeds up how quickly the active piece falls if true.
|
||||
/// </summary>
|
||||
private bool _softDropPressed = false;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Handles user input.
|
||||
/// </summary>
|
||||
/// <param name="action">The action to current player has prompted.</param>
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle moving the active game piece in response to user input.
|
||||
/// </summary>
|
||||
/// <param name="frameTime">The amount of time the current game tick covers.</param>
|
||||
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();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles sending a message to all players/spectators.
|
||||
/// </summary>
|
||||
/// <param name="message">The message to broadcase to all players/spectators.</param>
|
||||
private void SendMessage(BoundUserInterfaceMessage message)
|
||||
{
|
||||
if (_uiSystem.TryGetUi(_owner, BlockGameUiKey.Key, out var bui))
|
||||
_uiSystem.SendUiMessage(bui, message);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles sending a message to a specific player/spectator.
|
||||
/// </summary>
|
||||
/// <param name="message">The message to send to a specific player/spectator.</param>
|
||||
/// <param name="session">The target recipient.</param>
|
||||
private void SendMessage(BoundUserInterfaceMessage message, IPlayerSession session)
|
||||
{
|
||||
if (_uiSystem.TryGetUi(_owner, BlockGameUiKey.Key, out var bui))
|
||||
_uiSystem.TrySendUiMessage(bui, message, session);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles sending the current state of the game to a player that has just opened the UI.
|
||||
/// </summary>
|
||||
/// <param name="session">The target recipient.</param>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles broadcasting the full player-visible game state to everyone who can see the game.
|
||||
/// </summary>
|
||||
private void FullUpdate()
|
||||
{
|
||||
UpdateFieldUI();
|
||||
SendHoldPieceUpdate();
|
||||
SendNextPieceUpdate();
|
||||
SendLevelUpdate();
|
||||
SendPointsUpdate();
|
||||
SendHighscoreUpdate();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles broadcasting the full player-visible game state to a specific player/spectator.
|
||||
/// </summary>
|
||||
/// <param name="session">The target recipient.</param>
|
||||
private void FullUpdate(IPlayerSession session)
|
||||
{
|
||||
UpdateFieldUI(session);
|
||||
SendNextPieceUpdate(session);
|
||||
SendHoldPieceUpdate(session);
|
||||
SendLevelUpdate(session);
|
||||
SendPointsUpdate(session);
|
||||
SendHighscoreUpdate(session);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles broadcasting the current location of all of the blocks in the playfield + the active piece to all spectators.
|
||||
/// </summary>
|
||||
public void UpdateFieldUI()
|
||||
{
|
||||
if (!Started)
|
||||
return;
|
||||
|
||||
var computedField = ComputeField();
|
||||
SendMessage(new BlockGameMessages.BlockGameVisualUpdateMessage(computedField.ToArray(), BlockGameMessages.BlockGameVisualType.GameField));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles broadcasting the current location of all of the blocks in the playfield + the active piece to a specific player/spectator.
|
||||
/// </summary>
|
||||
/// <param name="session">The target recipient.</param>
|
||||
public void UpdateFieldUI(IPlayerSession session)
|
||||
{
|
||||
if (!Started)
|
||||
return;
|
||||
|
||||
var computedField = ComputeField();
|
||||
SendMessage(new BlockGameMessages.BlockGameVisualUpdateMessage(computedField.ToArray(), BlockGameMessages.BlockGameVisualType.GameField), session);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates the set of blocks to send to viewers.
|
||||
/// </summary>
|
||||
public List<BlockGameBlock> ComputeField()
|
||||
{
|
||||
var result = new List<BlockGameBlock>();
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Broadcasts the state of the next queued piece to all viewers.
|
||||
/// </summary>
|
||||
private void SendNextPieceUpdate()
|
||||
{
|
||||
SendMessage(new BlockGameMessages.BlockGameVisualUpdateMessage(NextPiece.BlocksForPreview(), BlockGameMessages.BlockGameVisualType.NextBlock));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Broadcasts the state of the next queued piece to a specific viewer.
|
||||
/// </summary>
|
||||
/// <param name="session">The target recipient.</param>
|
||||
private void SendNextPieceUpdate(IPlayerSession session)
|
||||
{
|
||||
SendMessage(new BlockGameMessages.BlockGameVisualUpdateMessage(NextPiece.BlocksForPreview(), BlockGameMessages.BlockGameVisualType.NextBlock), session);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Broadcasts the state of the currently held piece to all viewers.
|
||||
/// </summary>
|
||||
private void SendHoldPieceUpdate()
|
||||
{
|
||||
if (HeldPiece.HasValue)
|
||||
SendMessage(new BlockGameMessages.BlockGameVisualUpdateMessage(HeldPiece.Value.BlocksForPreview(), BlockGameMessages.BlockGameVisualType.HoldBlock));
|
||||
else
|
||||
SendMessage(new BlockGameMessages.BlockGameVisualUpdateMessage(Array.Empty<BlockGameBlock>(), BlockGameMessages.BlockGameVisualType.HoldBlock));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Broadcasts the state of the currently held piece to a specific viewer.
|
||||
/// </summary>
|
||||
/// <param name="session">The target recipient.</param>
|
||||
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<BlockGameBlock>(), BlockGameMessages.BlockGameVisualType.HoldBlock), session);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Broadcasts the current game level to all viewers.
|
||||
/// </summary>
|
||||
private void SendLevelUpdate()
|
||||
{
|
||||
SendMessage(new BlockGameMessages.BlockGameLevelUpdateMessage(Level));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Broadcasts the current game level to a specific viewer.
|
||||
/// </summary>
|
||||
/// <param name="session">The target recipient.</param>
|
||||
private void SendLevelUpdate(IPlayerSession session)
|
||||
{
|
||||
SendMessage(new BlockGameMessages.BlockGameLevelUpdateMessage(Level), session);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Broadcasts the current game score to all viewers.
|
||||
/// </summary>
|
||||
private void SendPointsUpdate()
|
||||
{
|
||||
SendMessage(new BlockGameMessages.BlockGameScoreUpdateMessage(Points));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Broadcasts the current game score to a specific viewer.
|
||||
/// </summary>
|
||||
/// <param name="session">The target recipient.</param>
|
||||
private void SendPointsUpdate(IPlayerSession session)
|
||||
{
|
||||
SendMessage(new BlockGameMessages.BlockGameScoreUpdateMessage(Points), session);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Broadcasts the current game high score positions to all viewers.
|
||||
/// </summary>
|
||||
private void SendHighscoreUpdate()
|
||||
{
|
||||
SendMessage(new BlockGameMessages.BlockGameHighScoreUpdateMessage(_arcadeSystem.GetLocalHighscores(), _arcadeSystem.GetGlobalHighscores()));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Broadcasts the current game high score positions to a specific viewer.
|
||||
/// </summary>
|
||||
/// <param name="session">The target recipient.</param>
|
||||
private void SendHighscoreUpdate(IPlayerSession session)
|
||||
{
|
||||
SendMessage(new BlockGameMessages.BlockGameHighScoreUpdateMessage(_arcadeSystem.GetLocalHighscores(), _arcadeSystem.GetGlobalHighscores()), session);
|
||||
}
|
||||
}
|
||||
303
Content.Server/Arcade/BlockGame/BlockGame.cs
Normal file
303
Content.Server/Arcade/BlockGame/BlockGame.cs
Normal file
@@ -0,0 +1,303 @@
|
||||
using Content.Shared.Arcade;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.Random;
|
||||
using System.Linq;
|
||||
|
||||
namespace Content.Server.Arcade.BlockGame;
|
||||
|
||||
public sealed partial class BlockGame
|
||||
{
|
||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
private readonly ArcadeSystem _arcadeSystem = default!;
|
||||
private readonly UserInterfaceSystem _uiSystem = default!;
|
||||
|
||||
/// <summary>
|
||||
/// What entity is currently hosting this game of NT-BG.
|
||||
/// </summary>
|
||||
private readonly EntityUid _owner = default!;
|
||||
|
||||
/// <summary>
|
||||
/// Whether the game has been started.
|
||||
/// </summary>
|
||||
public bool Started { get; private set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// Whether the game is currently running (not paused).
|
||||
/// </summary>
|
||||
private bool _running = false;
|
||||
|
||||
/// <summary>
|
||||
/// Whether the game should not currently be running.
|
||||
/// </summary>
|
||||
private bool Paused => !(Started && _running);
|
||||
|
||||
/// <summary>
|
||||
/// Whether the game has finished.
|
||||
/// </summary>
|
||||
private bool _gameOver = false;
|
||||
|
||||
/// <summary>
|
||||
/// Whether the game should have finished given the current game state.
|
||||
/// </summary>
|
||||
private bool IsGameOver => _field.Any(block => block.Position.Y == 0);
|
||||
|
||||
|
||||
public BlockGame(EntityUid owner)
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
_arcadeSystem = _entityManager.System<ArcadeSystem>();
|
||||
_uiSystem = _entityManager.System<UserInterfaceSystem>();
|
||||
|
||||
_owner = owner;
|
||||
_allBlockGamePieces = (BlockGamePieceType[]) Enum.GetValues(typeof(BlockGamePieceType));
|
||||
_internalNextPiece = GetRandomBlockGamePiece(_random);
|
||||
InitializeNewBlock();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Starts the game. Including relaying this info to everyone watching.
|
||||
/// </summary>
|
||||
public void StartGame()
|
||||
{
|
||||
SendMessage(new BlockGameMessages.BlockGameSetScreenMessage(BlockGameMessages.BlockGameScreen.Game));
|
||||
|
||||
FullUpdate();
|
||||
|
||||
Started = true;
|
||||
_running = true;
|
||||
_gameOver = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles ending the game and updating the high scores.
|
||||
/// </summary>
|
||||
private void InvokeGameover()
|
||||
{
|
||||
_running = false;
|
||||
_gameOver = true;
|
||||
|
||||
if (_entityManager.TryGetComponent<BlockGameArcadeComponent>(_owner, out var cabinet)
|
||||
&& _entityManager.TryGetComponent<MetaDataComponent>(cabinet.Player?.AttachedEntity, out var meta))
|
||||
{
|
||||
_highScorePlacement = _arcadeSystem.RegisterHighScore(meta.EntityName, Points);
|
||||
SendHighscoreUpdate();
|
||||
}
|
||||
SendMessage(new BlockGameMessages.BlockGameGameOverScreenMessage(Points, _highScorePlacement?.LocalPlacement, _highScorePlacement?.GlobalPlacement));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle the game simulation and user input.
|
||||
/// </summary>
|
||||
/// <param name="frameTime">The amount of time the current game tick covers.</param>
|
||||
public void GameTick(float frameTime)
|
||||
{
|
||||
if (!_running)
|
||||
return;
|
||||
|
||||
InputTick(frameTime);
|
||||
|
||||
FieldTick(frameTime);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The amount of time that has passed since the active piece last moved vertically,
|
||||
/// </summary>
|
||||
private float _accumulatedFieldFrameTime;
|
||||
|
||||
/// <summary>
|
||||
/// Handles timing the movements of the active game piece.
|
||||
/// </summary>
|
||||
/// <param name="frameTime">The amount of time the current game tick covers.</param>
|
||||
private void FieldTick(float frameTime)
|
||||
{
|
||||
_accumulatedFieldFrameTime += frameTime;
|
||||
|
||||
// Speed goes negative sometimes. uhhhh max() it I guess!!!
|
||||
var checkTime = Math.Max(0.03f, Speed);
|
||||
|
||||
while (_accumulatedFieldFrameTime >= checkTime)
|
||||
{
|
||||
if (_softDropPressed)
|
||||
AddPoints(1);
|
||||
|
||||
InternalFieldTick();
|
||||
|
||||
_accumulatedFieldFrameTime -= checkTime;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles the active game piece moving down.
|
||||
/// Also triggers scanning for cleared lines.
|
||||
/// </summary>
|
||||
private void InternalFieldTick()
|
||||
{
|
||||
if (CurrentPiece.Positions(_currentPiecePosition.AddToY(1), _currentRotation)
|
||||
.All(DropCheck))
|
||||
{
|
||||
_currentPiecePosition = _currentPiecePosition.AddToY(1);
|
||||
}
|
||||
else
|
||||
{
|
||||
var blocks = CurrentPiece.Blocks(_currentPiecePosition, _currentRotation);
|
||||
_field.AddRange(blocks);
|
||||
|
||||
//check loose conditions
|
||||
if (IsGameOver)
|
||||
{
|
||||
InvokeGameover();
|
||||
return;
|
||||
}
|
||||
|
||||
InitializeNewBlock();
|
||||
}
|
||||
|
||||
CheckField();
|
||||
|
||||
UpdateFieldUI();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles scanning for cleared lines and accumulating points.
|
||||
/// </summary>
|
||||
private void CheckField()
|
||||
{
|
||||
var pointsToAdd = 0;
|
||||
var consecutiveLines = 0;
|
||||
var clearedLines = 0;
|
||||
for (var y = 0; y < 20; y++)
|
||||
{
|
||||
if (CheckLine(y))
|
||||
{
|
||||
//line was cleared
|
||||
y--;
|
||||
consecutiveLines++;
|
||||
clearedLines++;
|
||||
}
|
||||
else if (consecutiveLines != 0)
|
||||
{
|
||||
var mod = consecutiveLines switch
|
||||
{
|
||||
1 => 40,
|
||||
2 => 100,
|
||||
3 => 300,
|
||||
4 => 1200,
|
||||
_ => 0
|
||||
};
|
||||
pointsToAdd += mod * (Level + 1);
|
||||
}
|
||||
}
|
||||
|
||||
ClearedLines += clearedLines;
|
||||
AddPoints(pointsToAdd);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns whether the line at the given position is full.
|
||||
/// Clears the line if it was full and moves the above lines down.
|
||||
/// </summary>
|
||||
/// <param name="y">The position of the line to check.</param>
|
||||
private bool CheckLine(int y)
|
||||
{
|
||||
for (var x = 0; x < 10; x++)
|
||||
{
|
||||
if (!_field.Any(b => b.Position.X == x && b.Position.Y == y))
|
||||
return false;
|
||||
}
|
||||
|
||||
//clear line
|
||||
_field.RemoveAll(b => b.Position.Y == y);
|
||||
//move everything down
|
||||
FillLine(y);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Moves all of the lines above the given line down by one.
|
||||
/// Used to fill in cleared lines.
|
||||
/// </summary>
|
||||
/// <param name="y">The position of the line above which to drop the lines.</param>
|
||||
private void FillLine(int y)
|
||||
{
|
||||
for (var c_y = y; c_y > 0; c_y--)
|
||||
{
|
||||
for (var j = 0; j < _field.Count; j++)
|
||||
{
|
||||
if (_field[j].Position.Y != c_y - 1)
|
||||
continue;
|
||||
|
||||
_field[j] = new BlockGameBlock(_field[j].Position.AddToY(1), _field[j].GameBlockColor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates a new active piece from the previewed next piece.
|
||||
/// Repopulates the previewed next piece with a piece from the pool of possible next pieces.
|
||||
/// </summary>
|
||||
private void InitializeNewBlock()
|
||||
{
|
||||
InitializeNewBlock(NextPiece);
|
||||
NextPiece = GetRandomBlockGamePiece(_random);
|
||||
_holdBlock = false;
|
||||
|
||||
SendMessage(new BlockGameMessages.BlockGameVisualUpdateMessage(NextPiece.BlocksForPreview(), BlockGameMessages.BlockGameVisualType.NextBlock));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates a new active piece from the previewed next piece.
|
||||
/// </summary>
|
||||
/// <param name="piece">The piece to set as the active piece.</param>
|
||||
private void InitializeNewBlock(BlockGamePiece piece)
|
||||
{
|
||||
_currentPiecePosition = new Vector2i(5, 0);
|
||||
|
||||
_currentRotation = BlockGamePieceRotation.North;
|
||||
|
||||
CurrentPiece = piece;
|
||||
UpdateFieldUI();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Buffers the currently active piece.
|
||||
/// Replaces the active piece with either the previously held piece or the previewed next piece as necessary.
|
||||
/// </summary>
|
||||
private void HoldPiece()
|
||||
{
|
||||
if (!_running)
|
||||
return;
|
||||
if (_holdBlock)
|
||||
return;
|
||||
|
||||
var tempHeld = HeldPiece;
|
||||
HeldPiece = CurrentPiece;
|
||||
_holdBlock = true;
|
||||
|
||||
if (!tempHeld.HasValue)
|
||||
{
|
||||
InitializeNewBlock();
|
||||
return;
|
||||
}
|
||||
|
||||
InitializeNewBlock(tempHeld.Value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Immediately drops the currently active piece the remaining distance.
|
||||
/// </summary>
|
||||
private void PerformHarddrop()
|
||||
{
|
||||
var spacesDropped = 0;
|
||||
while (CurrentPiece.Positions(_currentPiecePosition.AddToY(1), _currentRotation)
|
||||
.All(DropCheck))
|
||||
{
|
||||
_currentPiecePosition = _currentPiecePosition.AddToY(1);
|
||||
spacesDropped++;
|
||||
}
|
||||
AddPoints(spacesDropped * 2);
|
||||
|
||||
InternalFieldTick();
|
||||
}
|
||||
}
|
||||
22
Content.Server/Arcade/BlockGame/BlockGameArcadeComponent.cs
Normal file
22
Content.Server/Arcade/BlockGame/BlockGameArcadeComponent.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
using Robust.Server.Player;
|
||||
|
||||
namespace Content.Server.Arcade.BlockGame;
|
||||
|
||||
[RegisterComponent]
|
||||
public sealed class BlockGameArcadeComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// The currently active session of NT-BG.
|
||||
/// </summary>
|
||||
public BlockGame? Game = null;
|
||||
|
||||
/// <summary>
|
||||
/// The player currently playing the active session of NT-BG.
|
||||
/// </summary>
|
||||
public IPlayerSession? Player = null;
|
||||
|
||||
/// <summary>
|
||||
/// The players currently viewing (but not playing) the active session of NT-BG.
|
||||
/// </summary>
|
||||
public readonly List<IPlayerSession> Spectators = new();
|
||||
}
|
||||
123
Content.Server/Arcade/BlockGame/BlockGameArcadeSystem.cs
Normal file
123
Content.Server/Arcade/BlockGame/BlockGameArcadeSystem.cs
Normal file
@@ -0,0 +1,123 @@
|
||||
using Content.Server.Power.Components;
|
||||
using Content.Server.UserInterface;
|
||||
using Content.Shared.Arcade;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Server.Player;
|
||||
|
||||
namespace Content.Server.Arcade.BlockGame;
|
||||
|
||||
public sealed class BlockGameArcadeSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly UserInterfaceSystem _uiSystem = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<BlockGameArcadeComponent, ComponentInit>(OnComponentInit);
|
||||
SubscribeLocalEvent<BlockGameArcadeComponent, AfterActivatableUIOpenEvent>(OnAfterUIOpen);
|
||||
SubscribeLocalEvent<BlockGameArcadeComponent, BoundUIClosedEvent>(OnAfterUiClose);
|
||||
SubscribeLocalEvent<BlockGameArcadeComponent, PowerChangedEvent>(OnBlockPowerChanged);
|
||||
SubscribeLocalEvent<BlockGameArcadeComponent, BlockGameMessages.BlockGamePlayerActionMessage>(OnPlayerAction);
|
||||
}
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
var query = EntityManager.EntityQueryEnumerator<BlockGameArcadeComponent>();
|
||||
while (query.MoveNext(out var _, out var blockGame))
|
||||
{
|
||||
blockGame.Game?.GameTick(frameTime);
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdatePlayerStatus(EntityUid uid, IPlayerSession session, BoundUserInterface? bui = null, BlockGameArcadeComponent? blockGame = null)
|
||||
{
|
||||
if (!Resolve(uid, ref blockGame))
|
||||
return;
|
||||
if (bui == null && !_uiSystem.TryGetUi(uid, BlockGameUiKey.Key, out bui))
|
||||
return;
|
||||
|
||||
_uiSystem.TrySendUiMessage(bui, new BlockGameMessages.BlockGameUserStatusMessage(blockGame.Player == session), session);
|
||||
}
|
||||
|
||||
private void OnComponentInit(EntityUid uid, BlockGameArcadeComponent component, ComponentInit args)
|
||||
{
|
||||
component.Game = new(uid);
|
||||
}
|
||||
|
||||
private void OnAfterUIOpen(EntityUid uid, BlockGameArcadeComponent component, AfterActivatableUIOpenEvent args)
|
||||
{
|
||||
if (!TryComp<ActorComponent>(args.User, out var actor))
|
||||
return;
|
||||
if (!_uiSystem.TryGetUi(uid, BlockGameUiKey.Key, out var bui))
|
||||
return;
|
||||
|
||||
var session = actor.PlayerSession;
|
||||
if (!bui.SubscribedSessions.Contains(session))
|
||||
return;
|
||||
|
||||
if (component.Player == null)
|
||||
component.Player = session;
|
||||
else
|
||||
component.Spectators.Add(session);
|
||||
|
||||
UpdatePlayerStatus(uid, session, bui, component);
|
||||
component.Game?.UpdateNewPlayerUI(session);
|
||||
}
|
||||
|
||||
private void OnAfterUiClose(EntityUid uid, BlockGameArcadeComponent component, BoundUIClosedEvent args)
|
||||
{
|
||||
if (args.Session is not IPlayerSession session)
|
||||
return;
|
||||
|
||||
if (component.Player != session)
|
||||
{
|
||||
component.Spectators.Remove(session);
|
||||
UpdatePlayerStatus(uid, session, blockGame: component);
|
||||
return;
|
||||
}
|
||||
|
||||
var temp = component.Player;
|
||||
if (component.Spectators.Count > 0)
|
||||
{
|
||||
component.Player = component.Spectators[0];
|
||||
component.Spectators.Remove(component.Player);
|
||||
UpdatePlayerStatus(uid, component.Player, blockGame: component);
|
||||
}
|
||||
else
|
||||
component.Player = null;
|
||||
|
||||
UpdatePlayerStatus(uid, temp, blockGame: component);
|
||||
}
|
||||
|
||||
private void OnBlockPowerChanged(EntityUid uid, BlockGameArcadeComponent component, ref PowerChangedEvent args)
|
||||
{
|
||||
if (args.Powered)
|
||||
return;
|
||||
|
||||
if (_uiSystem.TryGetUi(uid, BlockGameUiKey.Key, out var bui))
|
||||
_uiSystem.CloseAll(bui);
|
||||
component.Player = null;
|
||||
component.Spectators.Clear();
|
||||
}
|
||||
|
||||
private void OnPlayerAction(EntityUid uid, BlockGameArcadeComponent component, BlockGameMessages.BlockGamePlayerActionMessage msg)
|
||||
{
|
||||
if (component.Game == null)
|
||||
return;
|
||||
if (!BlockGameUiKey.Key.Equals(msg.UiKey))
|
||||
return;
|
||||
if (msg.Session != component.Player)
|
||||
return;
|
||||
|
||||
if (msg.PlayerAction == BlockGamePlayerAction.NewGame)
|
||||
{
|
||||
if (component.Game.Started == true)
|
||||
component.Game = new(uid);
|
||||
component.Game.StartGame();
|
||||
return;
|
||||
}
|
||||
|
||||
component.Game.ProcessInput(msg.PlayerAction);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user