BlockGame.cs 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303
  1. using Content.Shared.Arcade;
  2. using Robust.Server.GameObjects;
  3. using Robust.Shared.Random;
  4. using System.Linq;
  5. namespace Content.Server.Arcade.BlockGame;
  6. public sealed partial class BlockGame
  7. {
  8. [Dependency] private readonly IEntityManager _entityManager = default!;
  9. [Dependency] private readonly IRobustRandom _random = default!;
  10. private readonly ArcadeSystem _arcadeSystem;
  11. private readonly UserInterfaceSystem _uiSystem;
  12. /// <summary>
  13. /// What entity is currently hosting this game of NT-BG.
  14. /// </summary>
  15. private readonly EntityUid _owner = default!;
  16. /// <summary>
  17. /// Whether the game has been started.
  18. /// </summary>
  19. public bool Started { get; private set; } = false;
  20. /// <summary>
  21. /// Whether the game is currently running (not paused).
  22. /// </summary>
  23. private bool _running = false;
  24. /// <summary>
  25. /// Whether the game should not currently be running.
  26. /// </summary>
  27. private bool Paused => !(Started && _running);
  28. /// <summary>
  29. /// Whether the game has finished.
  30. /// </summary>
  31. private bool _gameOver = false;
  32. /// <summary>
  33. /// Whether the game should have finished given the current game state.
  34. /// </summary>
  35. private bool IsGameOver => _field.Any(block => block.Position.Y == 0);
  36. public BlockGame(EntityUid owner)
  37. {
  38. IoCManager.InjectDependencies(this);
  39. _arcadeSystem = _entityManager.System<ArcadeSystem>();
  40. _uiSystem = _entityManager.System<UserInterfaceSystem>();
  41. _owner = owner;
  42. _allBlockGamePieces = (BlockGamePieceType[]) Enum.GetValues(typeof(BlockGamePieceType));
  43. _internalNextPiece = GetRandomBlockGamePiece(_random);
  44. InitializeNewBlock();
  45. }
  46. /// <summary>
  47. /// Starts the game. Including relaying this info to everyone watching.
  48. /// </summary>
  49. public void StartGame()
  50. {
  51. SendMessage(new BlockGameMessages.BlockGameSetScreenMessage(BlockGameMessages.BlockGameScreen.Game));
  52. FullUpdate();
  53. Started = true;
  54. _running = true;
  55. _gameOver = false;
  56. }
  57. /// <summary>
  58. /// Handles ending the game and updating the high scores.
  59. /// </summary>
  60. private void InvokeGameover()
  61. {
  62. _running = false;
  63. _gameOver = true;
  64. if (_entityManager.TryGetComponent<BlockGameArcadeComponent>(_owner, out var cabinet)
  65. && _entityManager.TryGetComponent<MetaDataComponent>(cabinet.Player, out var meta))
  66. {
  67. _highScorePlacement = _arcadeSystem.RegisterHighScore(meta.EntityName, Points);
  68. SendHighscoreUpdate();
  69. }
  70. SendMessage(new BlockGameMessages.BlockGameGameOverScreenMessage(Points, _highScorePlacement?.LocalPlacement, _highScorePlacement?.GlobalPlacement));
  71. }
  72. /// <summary>
  73. /// Handle the game simulation and user input.
  74. /// </summary>
  75. /// <param name="frameTime">The amount of time the current game tick covers.</param>
  76. public void GameTick(float frameTime)
  77. {
  78. if (!_running)
  79. return;
  80. InputTick(frameTime);
  81. FieldTick(frameTime);
  82. }
  83. /// <summary>
  84. /// The amount of time that has passed since the active piece last moved vertically,
  85. /// </summary>
  86. private float _accumulatedFieldFrameTime;
  87. /// <summary>
  88. /// Handles timing the movements of the active game piece.
  89. /// </summary>
  90. /// <param name="frameTime">The amount of time the current game tick covers.</param>
  91. private void FieldTick(float frameTime)
  92. {
  93. _accumulatedFieldFrameTime += frameTime;
  94. // Speed goes negative sometimes. uhhhh max() it I guess!!!
  95. var checkTime = Math.Max(0.03f, Speed);
  96. while (_accumulatedFieldFrameTime >= checkTime)
  97. {
  98. if (_softDropPressed)
  99. AddPoints(1);
  100. InternalFieldTick();
  101. _accumulatedFieldFrameTime -= checkTime;
  102. }
  103. }
  104. /// <summary>
  105. /// Handles the active game piece moving down.
  106. /// Also triggers scanning for cleared lines.
  107. /// </summary>
  108. private void InternalFieldTick()
  109. {
  110. if (CurrentPiece.Positions(_currentPiecePosition.AddToY(1), _currentRotation)
  111. .All(DropCheck))
  112. {
  113. _currentPiecePosition = _currentPiecePosition.AddToY(1);
  114. }
  115. else
  116. {
  117. var blocks = CurrentPiece.Blocks(_currentPiecePosition, _currentRotation);
  118. _field.AddRange(blocks);
  119. //check loose conditions
  120. if (IsGameOver)
  121. {
  122. InvokeGameover();
  123. return;
  124. }
  125. InitializeNewBlock();
  126. }
  127. CheckField();
  128. UpdateFieldUI();
  129. }
  130. /// <summary>
  131. /// Handles scanning for cleared lines and accumulating points.
  132. /// </summary>
  133. private void CheckField()
  134. {
  135. var pointsToAdd = 0;
  136. var consecutiveLines = 0;
  137. var clearedLines = 0;
  138. for (var y = 0; y < 20; y++)
  139. {
  140. if (CheckLine(y))
  141. {
  142. //line was cleared
  143. y--;
  144. consecutiveLines++;
  145. clearedLines++;
  146. }
  147. else if (consecutiveLines != 0)
  148. {
  149. var mod = consecutiveLines switch
  150. {
  151. 1 => 40,
  152. 2 => 100,
  153. 3 => 300,
  154. 4 => 1200,
  155. _ => 0
  156. };
  157. pointsToAdd += mod * (Level + 1);
  158. }
  159. }
  160. ClearedLines += clearedLines;
  161. AddPoints(pointsToAdd);
  162. }
  163. /// <summary>
  164. /// Returns whether the line at the given position is full.
  165. /// Clears the line if it was full and moves the above lines down.
  166. /// </summary>
  167. /// <param name="y">The position of the line to check.</param>
  168. private bool CheckLine(int y)
  169. {
  170. for (var x = 0; x < 10; x++)
  171. {
  172. if (!_field.Any(b => b.Position.X == x && b.Position.Y == y))
  173. return false;
  174. }
  175. //clear line
  176. _field.RemoveAll(b => b.Position.Y == y);
  177. //move everything down
  178. FillLine(y);
  179. return true;
  180. }
  181. /// <summary>
  182. /// Moves all of the lines above the given line down by one.
  183. /// Used to fill in cleared lines.
  184. /// </summary>
  185. /// <param name="y">The position of the line above which to drop the lines.</param>
  186. private void FillLine(int y)
  187. {
  188. for (var c_y = y; c_y > 0; c_y--)
  189. {
  190. for (var j = 0; j < _field.Count; j++)
  191. {
  192. if (_field[j].Position.Y != c_y - 1)
  193. continue;
  194. _field[j] = new BlockGameBlock(_field[j].Position.AddToY(1), _field[j].GameBlockColor);
  195. }
  196. }
  197. }
  198. /// <summary>
  199. /// Generates a new active piece from the previewed next piece.
  200. /// Repopulates the previewed next piece with a piece from the pool of possible next pieces.
  201. /// </summary>
  202. private void InitializeNewBlock()
  203. {
  204. InitializeNewBlock(NextPiece);
  205. NextPiece = GetRandomBlockGamePiece(_random);
  206. _holdBlock = false;
  207. SendMessage(new BlockGameMessages.BlockGameVisualUpdateMessage(NextPiece.BlocksForPreview(), BlockGameMessages.BlockGameVisualType.NextBlock));
  208. }
  209. /// <summary>
  210. /// Generates a new active piece from the previewed next piece.
  211. /// </summary>
  212. /// <param name="piece">The piece to set as the active piece.</param>
  213. private void InitializeNewBlock(BlockGamePiece piece)
  214. {
  215. _currentPiecePosition = new Vector2i(5, 0);
  216. _currentRotation = BlockGamePieceRotation.North;
  217. CurrentPiece = piece;
  218. UpdateFieldUI();
  219. }
  220. /// <summary>
  221. /// Buffers the currently active piece.
  222. /// Replaces the active piece with either the previously held piece or the previewed next piece as necessary.
  223. /// </summary>
  224. private void HoldPiece()
  225. {
  226. if (!_running)
  227. return;
  228. if (_holdBlock)
  229. return;
  230. var tempHeld = HeldPiece;
  231. HeldPiece = CurrentPiece;
  232. _holdBlock = true;
  233. if (!tempHeld.HasValue)
  234. {
  235. InitializeNewBlock();
  236. return;
  237. }
  238. InitializeNewBlock(tempHeld.Value);
  239. }
  240. /// <summary>
  241. /// Immediately drops the currently active piece the remaining distance.
  242. /// </summary>
  243. private void PerformHarddrop()
  244. {
  245. var spacesDropped = 0;
  246. while (CurrentPiece.Positions(_currentPiecePosition.AddToY(1), _currentRotation)
  247. .All(DropCheck))
  248. {
  249. _currentPiecePosition = _currentPiecePosition.AddToY(1);
  250. spacesDropped++;
  251. }
  252. AddPoints(spacesDropped * 2);
  253. InternalFieldTick();
  254. }
  255. }