Nanotrasen Block Game is here (#2131)
* tetris!
* softdropping & left,right key holding
* started work on the ui
* playable state
* there you go exp
* multiuser rework
* ui update refactor
* blockgame™️
* highscores, keybindings, ui refactor
* speed adjusts
leveling
* highscorebackground tweak
speed tweak
* NULLABLE
* yes
This commit is contained in:
691
Content.Client/Arcade/BlockGameMenu.cs
Normal file
691
Content.Client/Arcade/BlockGameMenu.cs
Normal file
@@ -0,0 +1,691 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net.Mime;
|
||||
using Content.Client.GameObjects.Components.Arcade;
|
||||
using Content.Client.Utility;
|
||||
using Content.Shared.Arcade;
|
||||
using Content.Shared.GameObjects.Components.Arcade;
|
||||
using Content.Shared.Input;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.Graphics.Drawing;
|
||||
using Robust.Client.Interfaces.ResourceManagement;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Shared.Input;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Client.Arcade
|
||||
{
|
||||
public class BlockGameMenu : SS14Window
|
||||
{
|
||||
|
||||
private static Color overlayBackgroundColor = new Color(74,74,81,180);
|
||||
private static Color overlayShadowColor = new Color(0,0,0,83);
|
||||
|
||||
private static Vector2 blockSize = new Vector2(15,15);
|
||||
|
||||
private BlockGameBoundUserInterface _owner;
|
||||
|
||||
private PanelContainer _mainPanel;
|
||||
|
||||
private VBoxContainer _gameRootContainer;
|
||||
private GridContainer _gameGrid;
|
||||
private GridContainer _nextBlockGrid;
|
||||
private GridContainer _holdBlockGrid;
|
||||
private Label _pointsLabel;
|
||||
private Label _levelLabel;
|
||||
private Button _pauseButton;
|
||||
|
||||
private PanelContainer _menuRootContainer;
|
||||
private Button _unpauseButton;
|
||||
private Control _unpauseButtonMargin;
|
||||
private Button _newGameButton;
|
||||
private Button _scoreBoardButton;
|
||||
|
||||
private PanelContainer _gameOverRootContainer;
|
||||
private Label _finalScoreLabel;
|
||||
private Button _finalNewGameButton;
|
||||
|
||||
private PanelContainer _highscoresRootContainer;
|
||||
private Label _localHighscoresLabel;
|
||||
private Label _globalHighscoresLabel;
|
||||
private Button _highscoreBackButton;
|
||||
|
||||
private bool _isPlayer = false;
|
||||
private bool _gameOver = false;
|
||||
|
||||
public BlockGameMenu(BlockGameBoundUserInterface owner)
|
||||
{
|
||||
Title = "Nanotrasen Block Game";
|
||||
_owner = owner;
|
||||
|
||||
var resourceCache = IoCManager.Resolve<IResourceCache>();
|
||||
var backgroundTexture = resourceCache.GetTexture("/Textures/Interface/Nano/button.svg.96dpi.png");
|
||||
|
||||
_mainPanel = new PanelContainer();
|
||||
|
||||
SetupGameMenu(backgroundTexture);
|
||||
_mainPanel.AddChild(_gameRootContainer);
|
||||
|
||||
SetupPauseMenu(backgroundTexture);
|
||||
|
||||
SetupGameoverScreen(backgroundTexture);
|
||||
|
||||
SetupHighScoreScreen(backgroundTexture);
|
||||
|
||||
Contents.AddChild(_mainPanel);
|
||||
|
||||
CanKeyboardFocus = true;
|
||||
}
|
||||
|
||||
|
||||
private void SetupHighScoreScreen(Texture backgroundTexture)
|
||||
{
|
||||
var rootBack = new StyleBoxTexture
|
||||
{
|
||||
Texture = backgroundTexture,
|
||||
Modulate = overlayShadowColor
|
||||
};
|
||||
rootBack.SetPatchMargin(StyleBox.Margin.All, 10);
|
||||
_highscoresRootContainer = new PanelContainer
|
||||
{
|
||||
PanelOverride = rootBack,
|
||||
SizeFlagsVertical = SizeFlags.ShrinkCenter,
|
||||
SizeFlagsHorizontal = SizeFlags.ShrinkCenter
|
||||
};
|
||||
|
||||
var c = new Color(overlayBackgroundColor.R,overlayBackgroundColor.G,overlayBackgroundColor.B,220);
|
||||
var innerBack = new StyleBoxTexture
|
||||
{
|
||||
Texture = backgroundTexture,
|
||||
Modulate = c
|
||||
};
|
||||
innerBack.SetPatchMargin(StyleBox.Margin.All, 10);
|
||||
var menuInnerPanel = new PanelContainer
|
||||
{
|
||||
PanelOverride = innerBack,
|
||||
SizeFlagsVertical = SizeFlags.ShrinkCenter,
|
||||
SizeFlagsHorizontal = SizeFlags.ShrinkCenter
|
||||
};
|
||||
|
||||
_highscoresRootContainer.AddChild(menuInnerPanel);
|
||||
|
||||
var menuContainer = new VBoxContainer()
|
||||
{
|
||||
SizeFlagsHorizontal = SizeFlags.ShrinkCenter,
|
||||
SizeFlagsVertical = SizeFlags.ShrinkCenter
|
||||
};
|
||||
|
||||
menuContainer.AddChild(new Label{Text = "Highscores"});
|
||||
menuContainer.AddChild(new Control{CustomMinimumSize = new Vector2(1,10)});
|
||||
|
||||
var highScoreBox = new HBoxContainer();
|
||||
|
||||
_localHighscoresLabel = new Label
|
||||
{
|
||||
Align = Label.AlignMode.Center
|
||||
};
|
||||
highScoreBox.AddChild(_localHighscoresLabel);
|
||||
highScoreBox.AddChild(new Control{CustomMinimumSize = new Vector2(40,1)});
|
||||
_globalHighscoresLabel = new Label
|
||||
{
|
||||
Align = Label.AlignMode.Center
|
||||
};
|
||||
highScoreBox.AddChild(_globalHighscoresLabel);
|
||||
menuContainer.AddChild(highScoreBox);
|
||||
menuContainer.AddChild(new Control{CustomMinimumSize = new Vector2(1,10)});
|
||||
_highscoreBackButton = new Button
|
||||
{
|
||||
Text = "Back",
|
||||
TextAlign = Label.AlignMode.Center
|
||||
};
|
||||
_highscoreBackButton.OnPressed += (e) => _owner.SendAction(BlockGamePlayerAction.Pause);
|
||||
menuContainer.AddChild(_highscoreBackButton);
|
||||
|
||||
menuInnerPanel.AddChild(menuContainer);
|
||||
}
|
||||
|
||||
private void SetupGameoverScreen(Texture backgroundTexture)
|
||||
{
|
||||
var rootBack = new StyleBoxTexture
|
||||
{
|
||||
Texture = backgroundTexture,
|
||||
Modulate = overlayShadowColor
|
||||
};
|
||||
rootBack.SetPatchMargin(StyleBox.Margin.All, 10);
|
||||
_gameOverRootContainer = new PanelContainer
|
||||
{
|
||||
PanelOverride = rootBack,
|
||||
SizeFlagsVertical = SizeFlags.ShrinkCenter,
|
||||
SizeFlagsHorizontal = SizeFlags.ShrinkCenter
|
||||
};
|
||||
|
||||
var innerBack = new StyleBoxTexture
|
||||
{
|
||||
Texture = backgroundTexture,
|
||||
Modulate = overlayBackgroundColor
|
||||
};
|
||||
innerBack.SetPatchMargin(StyleBox.Margin.All, 10);
|
||||
var menuInnerPanel = new PanelContainer
|
||||
{
|
||||
PanelOverride = innerBack,
|
||||
SizeFlagsVertical = SizeFlags.ShrinkCenter,
|
||||
SizeFlagsHorizontal = SizeFlags.ShrinkCenter
|
||||
};
|
||||
|
||||
_gameOverRootContainer.AddChild(menuInnerPanel);
|
||||
|
||||
var menuContainer = new VBoxContainer
|
||||
{
|
||||
SizeFlagsHorizontal = SizeFlags.ShrinkCenter,
|
||||
SizeFlagsVertical = SizeFlags.ShrinkCenter
|
||||
};
|
||||
|
||||
menuContainer.AddChild(new Label{Text = "Gameover!",Align = Label.AlignMode.Center});
|
||||
menuContainer.AddChild(new Control{CustomMinimumSize = new Vector2(1,10)});
|
||||
|
||||
|
||||
_finalScoreLabel = new Label{Align = Label.AlignMode.Center};
|
||||
menuContainer.AddChild(_finalScoreLabel);
|
||||
menuContainer.AddChild(new Control{CustomMinimumSize = new Vector2(1,10)});
|
||||
|
||||
_finalNewGameButton = new Button
|
||||
{
|
||||
Text = "New Game",
|
||||
TextAlign = Label.AlignMode.Center
|
||||
};
|
||||
_finalNewGameButton.OnPressed += (e) =>
|
||||
{
|
||||
_owner.SendAction(BlockGamePlayerAction.NewGame);
|
||||
};
|
||||
menuContainer.AddChild(_finalNewGameButton);
|
||||
|
||||
menuInnerPanel.AddChild(menuContainer);
|
||||
}
|
||||
|
||||
private void SetupPauseMenu(Texture backgroundTexture)
|
||||
{
|
||||
var rootBack = new StyleBoxTexture
|
||||
{
|
||||
Texture = backgroundTexture,
|
||||
Modulate = overlayShadowColor
|
||||
};
|
||||
rootBack.SetPatchMargin(StyleBox.Margin.All, 10);
|
||||
_menuRootContainer = new PanelContainer
|
||||
{
|
||||
PanelOverride = rootBack,
|
||||
SizeFlagsVertical = SizeFlags.ShrinkCenter,
|
||||
SizeFlagsHorizontal = SizeFlags.ShrinkCenter
|
||||
};
|
||||
|
||||
var innerBack = new StyleBoxTexture
|
||||
{
|
||||
Texture = backgroundTexture,
|
||||
Modulate = overlayBackgroundColor
|
||||
};
|
||||
innerBack.SetPatchMargin(StyleBox.Margin.All, 10);
|
||||
var menuInnerPanel = new PanelContainer
|
||||
{
|
||||
PanelOverride = innerBack,
|
||||
SizeFlagsVertical = SizeFlags.ShrinkCenter,
|
||||
SizeFlagsHorizontal = SizeFlags.ShrinkCenter
|
||||
};
|
||||
|
||||
_menuRootContainer.AddChild(menuInnerPanel);
|
||||
|
||||
|
||||
var menuContainer = new VBoxContainer
|
||||
{
|
||||
SizeFlagsHorizontal = SizeFlags.ShrinkCenter,
|
||||
SizeFlagsVertical = SizeFlags.ShrinkCenter
|
||||
};
|
||||
|
||||
_newGameButton = new Button
|
||||
{
|
||||
Text = "New Game",
|
||||
TextAlign = Label.AlignMode.Center
|
||||
};
|
||||
_newGameButton.OnPressed += (e) =>
|
||||
{
|
||||
_owner.SendAction(BlockGamePlayerAction.NewGame);
|
||||
};
|
||||
menuContainer.AddChild(_newGameButton);
|
||||
menuContainer.AddChild(new Control{CustomMinimumSize = new Vector2(1,10)});
|
||||
|
||||
_scoreBoardButton = new Button
|
||||
{
|
||||
Text = "Scoreboard",
|
||||
TextAlign = Label.AlignMode.Center
|
||||
};
|
||||
_scoreBoardButton.OnPressed += (e) => _owner.SendAction(BlockGamePlayerAction.ShowHighscores);
|
||||
menuContainer.AddChild(_scoreBoardButton);
|
||||
_unpauseButtonMargin = new Control {CustomMinimumSize = new Vector2(1, 10), Visible = false};
|
||||
menuContainer.AddChild(_unpauseButtonMargin);
|
||||
|
||||
_unpauseButton = new Button
|
||||
{
|
||||
Text = "Unpause",
|
||||
TextAlign = Label.AlignMode.Center,
|
||||
Visible = false
|
||||
};
|
||||
_unpauseButton.OnPressed += (e) =>
|
||||
{
|
||||
_owner.SendAction(BlockGamePlayerAction.Unpause);
|
||||
};
|
||||
menuContainer.AddChild(_unpauseButton);
|
||||
|
||||
menuInnerPanel.AddChild(menuContainer);
|
||||
}
|
||||
|
||||
public void SetUsability(bool isPlayer)
|
||||
{
|
||||
_isPlayer = isPlayer;
|
||||
UpdateUsability();
|
||||
}
|
||||
|
||||
private void UpdateUsability()
|
||||
{
|
||||
_pauseButton.Disabled = !_isPlayer;
|
||||
_newGameButton.Disabled = !_isPlayer;
|
||||
_scoreBoardButton.Disabled = !_isPlayer;
|
||||
_unpauseButton.Disabled = !_isPlayer;
|
||||
_finalNewGameButton.Disabled = !_isPlayer;
|
||||
_highscoreBackButton.Disabled = !_isPlayer;
|
||||
}
|
||||
|
||||
private void SetupGameMenu(Texture backgroundTexture)
|
||||
{
|
||||
// building the game container
|
||||
_gameRootContainer = new VBoxContainer();
|
||||
|
||||
_levelLabel = new Label
|
||||
{
|
||||
Align = Label.AlignMode.Center,
|
||||
SizeFlagsHorizontal = SizeFlags.FillExpand
|
||||
};
|
||||
_gameRootContainer.AddChild(_levelLabel);
|
||||
_gameRootContainer.AddChild(new Control
|
||||
{
|
||||
CustomMinimumSize = new Vector2(1,5)
|
||||
});
|
||||
|
||||
_pointsLabel = new Label
|
||||
{
|
||||
Align = Label.AlignMode.Center,
|
||||
SizeFlagsHorizontal = SizeFlags.FillExpand
|
||||
};
|
||||
_gameRootContainer.AddChild(_pointsLabel);
|
||||
_gameRootContainer.AddChild(new Control
|
||||
{
|
||||
CustomMinimumSize = new Vector2(1,10)
|
||||
});
|
||||
|
||||
var gameBox = new HBoxContainer();
|
||||
gameBox.AddChild(SetupHoldBox(backgroundTexture));
|
||||
gameBox.AddChild(new Control
|
||||
{
|
||||
CustomMinimumSize = new Vector2(10,1)
|
||||
});
|
||||
gameBox.AddChild(SetupGameGrid(backgroundTexture));
|
||||
gameBox.AddChild(new Control
|
||||
{
|
||||
CustomMinimumSize = new Vector2(10,1)
|
||||
});
|
||||
gameBox.AddChild(SetupNextBox(backgroundTexture));
|
||||
|
||||
_gameRootContainer.AddChild(gameBox);
|
||||
|
||||
_gameRootContainer.AddChild(new Control
|
||||
{
|
||||
CustomMinimumSize = new Vector2(1,10)
|
||||
});
|
||||
|
||||
_pauseButton = new Button
|
||||
{
|
||||
Text = "Pause",
|
||||
TextAlign = Label.AlignMode.Center
|
||||
};
|
||||
_pauseButton.OnPressed += (e) => TryPause();
|
||||
_gameRootContainer.AddChild(_pauseButton);
|
||||
}
|
||||
|
||||
private Control SetupGameGrid(Texture panelTex)
|
||||
{
|
||||
_gameGrid = new GridContainer
|
||||
{
|
||||
Columns = 10,
|
||||
HSeparationOverride = 1,
|
||||
VSeparationOverride = 1
|
||||
};
|
||||
UpdateBlocks(new BlockGameBlock[0]);
|
||||
|
||||
var back = new StyleBoxTexture
|
||||
{
|
||||
Texture = panelTex,
|
||||
Modulate = Color.FromHex("#4a4a51"),
|
||||
};
|
||||
back.SetPatchMargin(StyleBox.Margin.All, 10);
|
||||
|
||||
var gamePanel = new PanelContainer
|
||||
{
|
||||
PanelOverride = back,
|
||||
SizeFlagsHorizontal = SizeFlags.FillExpand,
|
||||
SizeFlagsStretchRatio = 60
|
||||
};
|
||||
var backgroundPanel = new PanelContainer
|
||||
{
|
||||
PanelOverride = new StyleBoxFlat{BackgroundColor = Color.FromHex("#86868d")}
|
||||
};
|
||||
backgroundPanel.AddChild(_gameGrid);
|
||||
gamePanel.AddChild(backgroundPanel);
|
||||
return gamePanel;
|
||||
}
|
||||
|
||||
private Control SetupNextBox(Texture panelTex)
|
||||
{
|
||||
var previewBack = new StyleBoxTexture
|
||||
{
|
||||
Texture = panelTex,
|
||||
Modulate = Color.FromHex("#4a4a51")
|
||||
};
|
||||
previewBack.SetPatchMargin(StyleBox.Margin.All, 10);
|
||||
|
||||
var grid = new GridContainer
|
||||
{
|
||||
Columns = 1,
|
||||
SizeFlagsHorizontal = SizeFlags.FillExpand,
|
||||
SizeFlagsStretchRatio = 20
|
||||
};
|
||||
|
||||
var nextBlockPanel = new PanelContainer
|
||||
{
|
||||
PanelOverride = previewBack,
|
||||
CustomMinimumSize = blockSize * 6.5f,
|
||||
SizeFlagsHorizontal = SizeFlags.None,
|
||||
SizeFlagsVertical = SizeFlags.None
|
||||
};
|
||||
var nextCenterContainer = new CenterContainer();
|
||||
_nextBlockGrid = new GridContainer
|
||||
{
|
||||
HSeparationOverride = 1,
|
||||
VSeparationOverride = 1
|
||||
};
|
||||
nextCenterContainer.AddChild(_nextBlockGrid);
|
||||
nextBlockPanel.AddChild(nextCenterContainer);
|
||||
grid.AddChild(nextBlockPanel);
|
||||
|
||||
grid.AddChild(new Label{Text = "Next", Align = Label.AlignMode.Center});
|
||||
|
||||
return grid;
|
||||
}
|
||||
|
||||
private Control SetupHoldBox(Texture panelTex)
|
||||
{
|
||||
var previewBack = new StyleBoxTexture
|
||||
{
|
||||
Texture = panelTex,
|
||||
Modulate = Color.FromHex("#4a4a51")
|
||||
};
|
||||
previewBack.SetPatchMargin(StyleBox.Margin.All, 10);
|
||||
|
||||
var grid = new GridContainer
|
||||
{
|
||||
Columns = 1,
|
||||
SizeFlagsHorizontal = SizeFlags.FillExpand,
|
||||
SizeFlagsStretchRatio = 20
|
||||
};
|
||||
|
||||
var holdBlockPanel = new PanelContainer
|
||||
{
|
||||
PanelOverride = previewBack,
|
||||
CustomMinimumSize = blockSize * 6.5f,
|
||||
SizeFlagsHorizontal = SizeFlags.None,
|
||||
SizeFlagsVertical = SizeFlags.None
|
||||
};
|
||||
var holdCenterContainer = new CenterContainer();
|
||||
_holdBlockGrid = new GridContainer
|
||||
{
|
||||
HSeparationOverride = 1,
|
||||
VSeparationOverride = 1
|
||||
};
|
||||
holdCenterContainer.AddChild(_holdBlockGrid);
|
||||
holdBlockPanel.AddChild(holdCenterContainer);
|
||||
grid.AddChild(holdBlockPanel);
|
||||
|
||||
grid.AddChild(new Label{Text = "Hold", Align = Label.AlignMode.Center});
|
||||
|
||||
return grid;
|
||||
}
|
||||
|
||||
protected override void FocusExited()
|
||||
{
|
||||
if (!IsOpen) return;
|
||||
if(_gameOver) return;
|
||||
TryPause();
|
||||
}
|
||||
|
||||
private void TryPause()
|
||||
{
|
||||
_owner.SendAction(BlockGamePlayerAction.Pause);
|
||||
}
|
||||
|
||||
public void SetStarted()
|
||||
{
|
||||
_gameOver = false;
|
||||
_unpauseButton.Visible = true;
|
||||
_unpauseButtonMargin.Visible = true;
|
||||
}
|
||||
|
||||
public void SetScreen(BlockGameMessages.BlockGameScreen screen)
|
||||
{
|
||||
if (_gameOver) return;
|
||||
|
||||
switch (screen)
|
||||
{
|
||||
case BlockGameMessages.BlockGameScreen.Game:
|
||||
GrabKeyboardFocus();
|
||||
CloseMenus();
|
||||
_pauseButton.Disabled = !_isPlayer;
|
||||
break;
|
||||
case BlockGameMessages.BlockGameScreen.Pause:
|
||||
//ReleaseKeyboardFocus();
|
||||
CloseMenus();
|
||||
_mainPanel.AddChild(_menuRootContainer);
|
||||
_pauseButton.Disabled = true;
|
||||
break;
|
||||
case BlockGameMessages.BlockGameScreen.Gameover:
|
||||
_gameOver = true;
|
||||
_pauseButton.Disabled = true;
|
||||
//ReleaseKeyboardFocus();
|
||||
CloseMenus();
|
||||
_mainPanel.AddChild(_gameOverRootContainer);
|
||||
break;
|
||||
case BlockGameMessages.BlockGameScreen.Highscores:
|
||||
//ReleaseKeyboardFocus();
|
||||
CloseMenus();
|
||||
_mainPanel.AddChild(_highscoresRootContainer);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void CloseMenus()
|
||||
{
|
||||
if(_mainPanel.Children.Contains(_menuRootContainer)) _mainPanel.RemoveChild(_menuRootContainer);
|
||||
if(_mainPanel.Children.Contains(_gameOverRootContainer)) _mainPanel.RemoveChild(_gameOverRootContainer);
|
||||
if(_mainPanel.Children.Contains(_highscoresRootContainer)) _mainPanel.RemoveChild(_highscoresRootContainer);
|
||||
}
|
||||
|
||||
public void SetGameoverInfo(int amount, int? localPlacement, int? globalPlacement)
|
||||
{
|
||||
var globalPlacementText = globalPlacement == null ? "-" : $"#{globalPlacement}";
|
||||
var localPlacementText = localPlacement == null ? "-" : $"#{localPlacement}";
|
||||
_finalScoreLabel.Text = $"Global: {globalPlacementText}\nLocal: {localPlacementText}\nPoints: {amount}";
|
||||
}
|
||||
|
||||
public void UpdatePoints(int points)
|
||||
{
|
||||
_pointsLabel.Text = $"Points: {points}";
|
||||
}
|
||||
|
||||
public void UpdateLevel(int level)
|
||||
{
|
||||
_levelLabel.Text = $"Level {level + 1}";
|
||||
}
|
||||
|
||||
public void UpdateHighscores(List<BlockGameMessages.HighScoreEntry> localHighscores,
|
||||
List<BlockGameMessages.HighScoreEntry> globalHighscores)
|
||||
{
|
||||
var localHighscoreText = "Station:\n";
|
||||
var globalHighscoreText = "Nanotrasen:\n";
|
||||
for (int i = 0; i < 5; i++)
|
||||
{
|
||||
localHighscoreText += $"#{i + 1} " + (localHighscores.Count > i
|
||||
? $"{localHighscores[i].Name} - {localHighscores[i].Score}\n" : "??? - 0\n");
|
||||
globalHighscoreText += $"#{i + 1} " + (globalHighscores.Count > i
|
||||
? $"{globalHighscores[i].Name} - {globalHighscores[i].Score}\n" : "??? - 0\n");
|
||||
}
|
||||
|
||||
_localHighscoresLabel.Text = localHighscoreText;
|
||||
_globalHighscoresLabel.Text = globalHighscoreText;
|
||||
}
|
||||
|
||||
protected override void KeyBindDown(GUIBoundKeyEventArgs args)
|
||||
{
|
||||
if(!_isPlayer) return;
|
||||
|
||||
if (args.Function == ContentKeyFunctions.ArcadeLeft)
|
||||
{
|
||||
_owner.SendAction(BlockGamePlayerAction.StartLeft);
|
||||
}
|
||||
else if (args.Function == ContentKeyFunctions.ArcadeRight)
|
||||
{
|
||||
_owner.SendAction(BlockGamePlayerAction.StartRight);
|
||||
}
|
||||
else if (args.Function == ContentKeyFunctions.ArcadeUp)
|
||||
{
|
||||
_owner.SendAction(BlockGamePlayerAction.Rotate);
|
||||
}
|
||||
else if (args.Function == ContentKeyFunctions.Arcade3)
|
||||
{
|
||||
_owner.SendAction(BlockGamePlayerAction.CounterRotate);
|
||||
}
|
||||
else if (args.Function == ContentKeyFunctions.ArcadeDown)
|
||||
{
|
||||
_owner.SendAction(BlockGamePlayerAction.SoftdropStart);
|
||||
}
|
||||
else if (args.Function == ContentKeyFunctions.Arcade2)
|
||||
{
|
||||
_owner.SendAction(BlockGamePlayerAction.Hold);
|
||||
}
|
||||
else if (args.Function == ContentKeyFunctions.Arcade1)
|
||||
{
|
||||
_owner.SendAction(BlockGamePlayerAction.Harddrop);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void KeyBindUp(GUIBoundKeyEventArgs args)
|
||||
{
|
||||
if(!_isPlayer) return;
|
||||
|
||||
if (args.Function == ContentKeyFunctions.ArcadeLeft)
|
||||
{
|
||||
_owner.SendAction(BlockGamePlayerAction.EndLeft);
|
||||
}
|
||||
else if (args.Function == ContentKeyFunctions.ArcadeRight)
|
||||
{
|
||||
_owner.SendAction(BlockGamePlayerAction.EndRight);
|
||||
}else if (args.Function == ContentKeyFunctions.ArcadeDown)
|
||||
{
|
||||
_owner.SendAction(BlockGamePlayerAction.SoftdropEnd);
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdateNextBlock(BlockGameBlock[] blocks)
|
||||
{
|
||||
_nextBlockGrid.RemoveAllChildren();
|
||||
if (blocks.Length == 0) return;
|
||||
var columnCount = blocks.Max(b => b.Position.X) + 1;
|
||||
var rowCount = blocks.Max(b => b.Position.Y) + 1;
|
||||
_nextBlockGrid.Columns = columnCount;
|
||||
for (int y = 0; y < rowCount; y++)
|
||||
{
|
||||
for (int x = 0; x < columnCount; x++)
|
||||
{
|
||||
var c = GetColorForPosition(blocks, x, y);
|
||||
_nextBlockGrid.AddChild(new PanelContainer
|
||||
{
|
||||
PanelOverride = new StyleBoxFlat {BackgroundColor = c},
|
||||
CustomMinimumSize = blockSize,
|
||||
RectDrawClipMargin = 0
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdateHeldBlock(BlockGameBlock[] blocks)
|
||||
{
|
||||
_holdBlockGrid.RemoveAllChildren();
|
||||
if (blocks.Length == 0) return;
|
||||
var columnCount = blocks.Max(b => b.Position.X) + 1;
|
||||
var rowCount = blocks.Max(b => b.Position.Y) + 1;
|
||||
_holdBlockGrid.Columns = columnCount;
|
||||
for (int y = 0; y < rowCount; y++)
|
||||
{
|
||||
for (int x = 0; x < columnCount; x++)
|
||||
{
|
||||
var c = GetColorForPosition(blocks, x, y);
|
||||
_holdBlockGrid.AddChild(new PanelContainer
|
||||
{
|
||||
PanelOverride = new StyleBoxFlat {BackgroundColor = c},
|
||||
CustomMinimumSize = blockSize,
|
||||
RectDrawClipMargin = 0
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdateBlocks(BlockGameBlock[] blocks)
|
||||
{
|
||||
_gameGrid.RemoveAllChildren();
|
||||
for (int y = 0; y < 20; y++)
|
||||
{
|
||||
for (int x = 0; x < 10; x++)
|
||||
{
|
||||
var c = GetColorForPosition(blocks, x, y);
|
||||
_gameGrid.AddChild(new PanelContainer
|
||||
{
|
||||
PanelOverride = new StyleBoxFlat {BackgroundColor = c},
|
||||
CustomMinimumSize = blockSize,
|
||||
RectDrawClipMargin = 0
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Color GetColorForPosition(BlockGameBlock[] blocks, int x, int y)
|
||||
{
|
||||
Color c = Color.Transparent;
|
||||
var matchingBlock = blocks.FirstOrNull(b => b.Position.X == x && b.Position.Y == y);
|
||||
if (matchingBlock.HasValue)
|
||||
{
|
||||
c = matchingBlock.Value.GameBlockColor switch
|
||||
{
|
||||
BlockGameBlock.BlockGameBlockColor.Red => Color.Red,
|
||||
BlockGameBlock.BlockGameBlockColor.Orange => Color.Orange,
|
||||
BlockGameBlock.BlockGameBlockColor.Yellow => Color.Yellow,
|
||||
BlockGameBlock.BlockGameBlockColor.Green => Color.LimeGreen,
|
||||
BlockGameBlock.BlockGameBlockColor.Blue => Color.Blue,
|
||||
BlockGameBlock.BlockGameBlockColor.Purple => Color.Purple,
|
||||
BlockGameBlock.BlockGameBlockColor.LightBlue => Color.LightBlue,
|
||||
_ => Color.Olive //olive is error
|
||||
};
|
||||
}
|
||||
|
||||
return c;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
using System;
|
||||
using Content.Client.Arcade;
|
||||
using Content.Shared.Arcade;
|
||||
using Content.Shared.GameObjects.Components.Arcade;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.GameObjects.Components.UserInterface;
|
||||
using Robust.Shared.GameObjects.Components.UserInterface;
|
||||
|
||||
namespace Content.Client.GameObjects.Components.Arcade
|
||||
{
|
||||
public class BlockGameBoundUserInterface : BoundUserInterface
|
||||
{
|
||||
private BlockGameMenu _menu;
|
||||
|
||||
public BlockGameBoundUserInterface([NotNull] ClientUserInterfaceComponent owner, [NotNull] object uiKey) : base(owner, uiKey)
|
||||
{
|
||||
}
|
||||
|
||||
protected override void Open()
|
||||
{
|
||||
base.Open();
|
||||
|
||||
_menu = new BlockGameMenu(this);
|
||||
_menu.OnClose += () => SendMessage(new BlockGameMessages.BlockGameUserUnregisterMessage());
|
||||
_menu.OnClose += Close;
|
||||
_menu.OpenCentered();
|
||||
}
|
||||
|
||||
protected override void ReceiveMessage(BoundUserInterfaceMessage message)
|
||||
{
|
||||
switch (message)
|
||||
{
|
||||
case BlockGameMessages.BlockGameVisualUpdateMessage updateMessage:
|
||||
switch (updateMessage.GameVisualType)
|
||||
{
|
||||
case BlockGameMessages.BlockGameVisualType.GameField:
|
||||
_menu?.UpdateBlocks(updateMessage.Blocks);
|
||||
break;
|
||||
case BlockGameMessages.BlockGameVisualType.HoldBlock:
|
||||
_menu?.UpdateHeldBlock(updateMessage.Blocks);
|
||||
break;
|
||||
case BlockGameMessages.BlockGameVisualType.NextBlock:
|
||||
_menu?.UpdateNextBlock(updateMessage.Blocks);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case BlockGameMessages.BlockGameScoreUpdateMessage scoreUpdate:
|
||||
_menu?.UpdatePoints(scoreUpdate.Points);
|
||||
break;
|
||||
case BlockGameMessages.BlockGameUserStatusMessage userMessage:
|
||||
_menu?.SetUsability(userMessage.IsPlayer);
|
||||
break;
|
||||
case BlockGameMessages.BlockGameSetScreenMessage statusMessage:
|
||||
if (statusMessage.isStarted) _menu?.SetStarted();
|
||||
_menu?.SetScreen(statusMessage.Screen);
|
||||
if (statusMessage is BlockGameMessages.BlockGameGameOverScreenMessage gameOverScreenMessage)
|
||||
_menu?.SetGameoverInfo(gameOverScreenMessage.FinalScore, gameOverScreenMessage.LocalPlacement, gameOverScreenMessage.GlobalPlacement);
|
||||
break;
|
||||
case BlockGameMessages.BlockGameHighScoreUpdateMessage highScoreUpdateMessage:
|
||||
_menu?.UpdateHighscores(highScoreUpdateMessage.LocalHighscores,
|
||||
highScoreUpdateMessage.GlobalHighscores);
|
||||
break;
|
||||
case BlockGameMessages.BlockGameLevelUpdateMessage levelUpdateMessage:
|
||||
_menu?.UpdateLevel(levelUpdateMessage.Level);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public void SendAction(BlockGamePlayerAction action)
|
||||
{
|
||||
SendMessage(new BlockGameMessages.BlockGamePlayerActionMessage(action));
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
base.Dispose(disposing);
|
||||
if(!disposing) { return; }
|
||||
_menu?.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -174,6 +174,7 @@
|
||||
"Flammable",
|
||||
"CreamPie",
|
||||
"CreamPied",
|
||||
"BlockGameArcade"
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,6 +39,13 @@ namespace Content.Client.Input
|
||||
human.AddFunction(ContentKeyFunctions.MouseMiddle);
|
||||
human.AddFunction(ContentKeyFunctions.ToggleCombatMode);
|
||||
human.AddFunction(ContentKeyFunctions.WideAttack);
|
||||
human.AddFunction(ContentKeyFunctions.ArcadeUp);
|
||||
human.AddFunction(ContentKeyFunctions.ArcadeDown);
|
||||
human.AddFunction(ContentKeyFunctions.ArcadeLeft);
|
||||
human.AddFunction(ContentKeyFunctions.ArcadeRight);
|
||||
human.AddFunction(ContentKeyFunctions.Arcade1);
|
||||
human.AddFunction(ContentKeyFunctions.Arcade2);
|
||||
human.AddFunction(ContentKeyFunctions.Arcade3);
|
||||
|
||||
var ghost = contexts.New("ghost", "common");
|
||||
ghost.AddFunction(EngineKeyFunctions.MoveUp);
|
||||
|
||||
@@ -0,0 +1,859 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Content.Server.GameObjects.Components.Power.ApcNetComponents;
|
||||
using Content.Server.GameObjects.EntitySystems;
|
||||
using Content.Server.Utility;
|
||||
using Content.Shared.Arcade;
|
||||
using Content.Shared.GameObjects;
|
||||
using Content.Shared.GameObjects.EntitySystems;
|
||||
using Content.Shared.Interfaces.GameObjects.Components;
|
||||
using Robust.Server.GameObjects.Components.UserInterface;
|
||||
using Robust.Server.Interfaces.GameObjects;
|
||||
using Robust.Server.Interfaces.Player;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.GameObjects.Systems;
|
||||
using Robust.Shared.Interfaces.Random;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Random;
|
||||
|
||||
namespace Content.Server.GameObjects.Components.Arcade
|
||||
{
|
||||
[RegisterComponent]
|
||||
[ComponentReference(typeof(IActivate))]
|
||||
public class BlockGameArcadeComponent : Component, IActivate
|
||||
{
|
||||
[Dependency] private IRobustRandom _random = null!;
|
||||
|
||||
public override string Name => "BlockGameArcade";
|
||||
public override uint? NetID => ContentNetIDs.BLOCKGAME_ARCADE;
|
||||
private bool Powered => !Owner.TryGetComponent(out PowerReceiverComponent? receiver) || receiver.Powered;
|
||||
private BoundUserInterface? UserInterface => Owner.GetUIOrNull(BlockGameUiKey.Key);
|
||||
|
||||
private BlockGame _game = null!;
|
||||
|
||||
private IPlayerSession? _player;
|
||||
private List<IPlayerSession> _spectators = new List<IPlayerSession>();
|
||||
|
||||
public void Activate(ActivateEventArgs eventArgs)
|
||||
{
|
||||
if(!eventArgs.User.TryGetComponent(out IActorComponent? actor))
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (!Powered)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if(!ActionBlockerSystem.CanInteract(Owner)) return;
|
||||
|
||||
UserInterface?.Toggle(actor.playerSession);
|
||||
RegisterPlayerSession(actor.playerSession);
|
||||
}
|
||||
|
||||
private void RegisterPlayerSession(IPlayerSession session)
|
||||
{
|
||||
if (_player == null) _player = session;
|
||||
else _spectators.Add(session);
|
||||
|
||||
UpdatePlayerStatus(session);
|
||||
_game.UpdateNewPlayerUI(session);
|
||||
}
|
||||
|
||||
private void DeactivePlayer(IPlayerSession session)
|
||||
{
|
||||
if (_player != session) return;
|
||||
|
||||
var temp = _player;
|
||||
_player = null;
|
||||
if (_spectators.Count != 0)
|
||||
{
|
||||
_player = _spectators[0];
|
||||
_spectators.Remove(_player);
|
||||
UpdatePlayerStatus(_player);
|
||||
}
|
||||
_spectators.Add(temp);
|
||||
|
||||
UpdatePlayerStatus(temp);
|
||||
}
|
||||
|
||||
private void UnRegisterPlayerSession(IPlayerSession session)
|
||||
{
|
||||
if (_player == session)
|
||||
{
|
||||
DeactivePlayer(_player);
|
||||
}
|
||||
else
|
||||
{
|
||||
_spectators.Remove(session);
|
||||
UpdatePlayerStatus(session);
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdatePlayerStatus(IPlayerSession session)
|
||||
{
|
||||
UserInterface?.SendMessage(new BlockGameMessages.BlockGameUserStatusMessage(_player == session), session);
|
||||
}
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
if (UserInterface != null)
|
||||
{
|
||||
UserInterface.OnReceiveMessage += UserInterfaceOnOnReceiveMessage;
|
||||
}
|
||||
_game = new BlockGame(this);
|
||||
}
|
||||
|
||||
private void UserInterfaceOnOnReceiveMessage(ServerBoundUserInterfaceMessage obj)
|
||||
{
|
||||
if (obj.Message is BlockGameMessages.BlockGameUserUnregisterMessage unregisterMessage)
|
||||
{
|
||||
UnRegisterPlayerSession(obj.Session);
|
||||
return;
|
||||
}
|
||||
if (obj.Session != _player) return;
|
||||
|
||||
if (!ActionBlockerSystem.CanInteract(Owner))
|
||||
{
|
||||
DeactivePlayer(obj.Session);
|
||||
}
|
||||
|
||||
if (!(obj.Message is BlockGameMessages.BlockGamePlayerActionMessage message)) return;
|
||||
if (message.PlayerAction == BlockGamePlayerAction.NewGame)
|
||||
{
|
||||
if(_game.Started) _game = new BlockGame(this);
|
||||
_game.StartGame();
|
||||
}
|
||||
else
|
||||
{
|
||||
_game.ProcessInput(message.PlayerAction);
|
||||
}
|
||||
}
|
||||
|
||||
public void DoGameTick(float frameTime)
|
||||
{
|
||||
_game.GameTick(frameTime);
|
||||
}
|
||||
|
||||
private class BlockGame
|
||||
{
|
||||
//note: field is 10(0 -> 9) wide and 20(0 -> 19) high
|
||||
|
||||
private BlockGameArcadeComponent _component;
|
||||
|
||||
private List<BlockGameBlock> _field = new List<BlockGameBlock>();
|
||||
|
||||
private BlockGamePiece _currentPiece;
|
||||
|
||||
private BlockGamePiece _nextPiece
|
||||
{
|
||||
get => _internalNextPiece;
|
||||
set
|
||||
{
|
||||
_internalNextPiece = value;
|
||||
SendNextPieceUpdate();
|
||||
}
|
||||
}
|
||||
private BlockGamePiece _internalNextPiece;
|
||||
|
||||
private void SendNextPieceUpdate()
|
||||
{
|
||||
_component.UserInterface?.SendMessage(new BlockGameMessages.BlockGameVisualUpdateMessage(_nextPiece.BlocksForPreview(), BlockGameMessages.BlockGameVisualType.NextBlock));
|
||||
}
|
||||
|
||||
private void SendNextPieceUpdate(IPlayerSession session)
|
||||
{
|
||||
_component.UserInterface?.SendMessage(new BlockGameMessages.BlockGameVisualUpdateMessage(_nextPiece.BlocksForPreview(), BlockGameMessages.BlockGameVisualType.NextBlock), session);
|
||||
}
|
||||
|
||||
private bool _holdBlock = false;
|
||||
private BlockGamePiece? _heldPiece
|
||||
{
|
||||
get => _internalHeldPiece;
|
||||
set
|
||||
{
|
||||
_internalHeldPiece = value;
|
||||
SendHoldPieceUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
private BlockGamePiece? _internalHeldPiece = null;
|
||||
|
||||
private void SendHoldPieceUpdate()
|
||||
{
|
||||
if(_heldPiece.HasValue) _component.UserInterface?.SendMessage(new BlockGameMessages.BlockGameVisualUpdateMessage(_heldPiece.Value.BlocksForPreview(), BlockGameMessages.BlockGameVisualType.HoldBlock));
|
||||
else _component.UserInterface?.SendMessage(new BlockGameMessages.BlockGameVisualUpdateMessage(new BlockGameBlock[0], BlockGameMessages.BlockGameVisualType.HoldBlock));
|
||||
}
|
||||
|
||||
private void SendHoldPieceUpdate(IPlayerSession session)
|
||||
{
|
||||
if(_heldPiece.HasValue) _component.UserInterface?.SendMessage(new BlockGameMessages.BlockGameVisualUpdateMessage(_heldPiece.Value.BlocksForPreview(), BlockGameMessages.BlockGameVisualType.HoldBlock), session);
|
||||
else _component.UserInterface?.SendMessage(new BlockGameMessages.BlockGameVisualUpdateMessage(new BlockGameBlock[0], BlockGameMessages.BlockGameVisualType.HoldBlock), session);
|
||||
}
|
||||
|
||||
private Vector2i _currentPiecePosition;
|
||||
private BlockGamePieceRotation _currentRotation;
|
||||
private float _softDropOverride = 0.1f;
|
||||
|
||||
private float Speed => !_softDropPressed
|
||||
? -0.03f * Level + 1
|
||||
: _softDropOverride;
|
||||
|
||||
private float _pressCheckSpeed = 0.08f;
|
||||
|
||||
private bool _running;
|
||||
public bool Paused => !(_running && _started);
|
||||
private bool _started;
|
||||
public bool Started => _started;
|
||||
private bool _gameOver;
|
||||
|
||||
private bool _leftPressed;
|
||||
private bool _rightPressed;
|
||||
private bool _softDropPressed;
|
||||
|
||||
private int Points
|
||||
{
|
||||
get => _internalPoints;
|
||||
set
|
||||
{
|
||||
if (_internalPoints == value) return;
|
||||
_internalPoints = value;
|
||||
SendPointsUpdate();
|
||||
}
|
||||
}
|
||||
private int _internalPoints;
|
||||
|
||||
private BlockGameSystem.HighScorePlacement? _highScorePlacement = null;
|
||||
|
||||
private void SendPointsUpdate()
|
||||
{
|
||||
_component.UserInterface?.SendMessage(new BlockGameMessages.BlockGameScoreUpdateMessage(Points));
|
||||
}
|
||||
|
||||
private void SendPointsUpdate(IPlayerSession session)
|
||||
{
|
||||
_component.UserInterface?.SendMessage(new BlockGameMessages.BlockGameScoreUpdateMessage(Points));
|
||||
}
|
||||
|
||||
public int Level
|
||||
{
|
||||
get => _level;
|
||||
set
|
||||
{
|
||||
_level = value;
|
||||
SendLevelUpdate();
|
||||
}
|
||||
}
|
||||
private int _level = 0;
|
||||
private void SendLevelUpdate()
|
||||
{
|
||||
_component.UserInterface?.SendMessage(new BlockGameMessages.BlockGameLevelUpdateMessage(Level));
|
||||
}
|
||||
|
||||
private void SendLevelUpdate(IPlayerSession session)
|
||||
{
|
||||
_component.UserInterface?.SendMessage(new BlockGameMessages.BlockGameLevelUpdateMessage(Level));
|
||||
}
|
||||
|
||||
private int ClearedLines
|
||||
{
|
||||
get => _clearedLines;
|
||||
set
|
||||
{
|
||||
_clearedLines = value;
|
||||
|
||||
if (_clearedLines < LevelRequirement) return;
|
||||
|
||||
_clearedLines -= LevelRequirement;
|
||||
Level++;
|
||||
}
|
||||
}
|
||||
|
||||
private int _clearedLines = 0;
|
||||
private int LevelRequirement => Math.Min(100, Math.Max(Level * 10 - 50, 10));
|
||||
|
||||
public BlockGame(BlockGameArcadeComponent component)
|
||||
{
|
||||
_component = component;
|
||||
_internalNextPiece = BlockGamePiece.GetRandom(_component._random);
|
||||
}
|
||||
|
||||
private void SendHighscoreUpdate()
|
||||
{
|
||||
var entitySystem = EntitySystem.Get<BlockGameSystem>();
|
||||
_component.UserInterface?.SendMessage(new BlockGameMessages.BlockGameHighScoreUpdateMessage(entitySystem.GetLocalHighscores(), entitySystem.GetGlobalHighscores()));
|
||||
}
|
||||
|
||||
private void SendHighscoreUpdate(IPlayerSession session)
|
||||
{
|
||||
var entitySystem = EntitySystem.Get<BlockGameSystem>();
|
||||
_component.UserInterface?.SendMessage(new BlockGameMessages.BlockGameHighScoreUpdateMessage(entitySystem.GetLocalHighscores(), entitySystem.GetGlobalHighscores()), session);
|
||||
}
|
||||
|
||||
public void StartGame()
|
||||
{
|
||||
InitializeNewBlock();
|
||||
|
||||
_component.UserInterface?.SendMessage(new BlockGameMessages.BlockGameSetScreenMessage(BlockGameMessages.BlockGameScreen.Game));
|
||||
|
||||
FullUpdate();
|
||||
|
||||
_running = true;
|
||||
_started = true;
|
||||
}
|
||||
|
||||
private void FullUpdate()
|
||||
{
|
||||
UpdateAllFieldUI();
|
||||
SendHoldPieceUpdate();
|
||||
SendNextPieceUpdate();
|
||||
SendPointsUpdate();
|
||||
SendHighscoreUpdate();
|
||||
SendLevelUpdate();
|
||||
}
|
||||
|
||||
private void FullUpdate(IPlayerSession session)
|
||||
{
|
||||
UpdateFieldUI(session);
|
||||
SendPointsUpdate(session);
|
||||
SendNextPieceUpdate(session);
|
||||
SendHoldPieceUpdate(session);
|
||||
SendHighscoreUpdate(session);
|
||||
SendLevelUpdate(session);
|
||||
}
|
||||
|
||||
public void GameTick(float frameTime)
|
||||
{
|
||||
if (!_running) return;
|
||||
|
||||
InputTick(frameTime);
|
||||
|
||||
FieldTick(frameTime);
|
||||
}
|
||||
|
||||
private float _accumulatedLeftPressTime;
|
||||
private float _accumulatedRightPressTime;
|
||||
private void InputTick(float frameTime)
|
||||
{
|
||||
bool anythingChanged = false;
|
||||
if (_leftPressed)
|
||||
{
|
||||
_accumulatedLeftPressTime += frameTime;
|
||||
|
||||
if (_accumulatedLeftPressTime >= _pressCheckSpeed)
|
||||
{
|
||||
|
||||
if (_currentPiece.Positions(_currentPiecePosition.AddToX(-1), _currentRotation)
|
||||
.All(MoveCheck))
|
||||
{
|
||||
_currentPiecePosition = _currentPiecePosition.AddToX(-1);
|
||||
anythingChanged = true;
|
||||
}
|
||||
|
||||
_accumulatedLeftPressTime -= _pressCheckSpeed;
|
||||
}
|
||||
}
|
||||
|
||||
if (_rightPressed)
|
||||
{
|
||||
_accumulatedRightPressTime += frameTime;
|
||||
|
||||
if (_accumulatedRightPressTime >= _pressCheckSpeed)
|
||||
{
|
||||
if (_currentPiece.Positions(_currentPiecePosition.AddToX(1), _currentRotation)
|
||||
.All(MoveCheck))
|
||||
{
|
||||
_currentPiecePosition = _currentPiecePosition.AddToX(1);
|
||||
anythingChanged = true;
|
||||
}
|
||||
|
||||
_accumulatedRightPressTime -= _pressCheckSpeed;
|
||||
}
|
||||
}
|
||||
|
||||
if(anythingChanged) UpdateAllFieldUI();
|
||||
}
|
||||
|
||||
private float _accumulatedFieldFrameTime;
|
||||
private void FieldTick(float frameTime)
|
||||
{
|
||||
_accumulatedFieldFrameTime += frameTime;
|
||||
|
||||
var checkTime = Speed;
|
||||
|
||||
if (_accumulatedFieldFrameTime < checkTime) return;
|
||||
|
||||
if(_softDropPressed) AddPoints(1);
|
||||
|
||||
InternalFieldTick();
|
||||
|
||||
_accumulatedFieldFrameTime -= checkTime;
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
UpdateAllFieldUI();
|
||||
}
|
||||
|
||||
private void CheckField()
|
||||
{
|
||||
int pointsToAdd = 0;
|
||||
int consecutiveLines = 0;
|
||||
int clearedLines = 0;
|
||||
for (int 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);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
private void AddPoints(int amount)
|
||||
{
|
||||
if (amount == 0) return;
|
||||
|
||||
Points += amount;
|
||||
}
|
||||
|
||||
private void FillLine(int y)
|
||||
{
|
||||
for (int c_y = y; c_y > 0; c_y--)
|
||||
{
|
||||
for (int 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void InitializeNewBlock()
|
||||
{
|
||||
InitializeNewBlock(_nextPiece);
|
||||
_nextPiece = BlockGamePiece.GetRandom(_component._random);
|
||||
_holdBlock = false;
|
||||
|
||||
_component.UserInterface?.SendMessage(new BlockGameMessages.BlockGameVisualUpdateMessage(_nextPiece.BlocksForPreview(), BlockGameMessages.BlockGameVisualType.NextBlock));
|
||||
}
|
||||
|
||||
private void InitializeNewBlock(BlockGamePiece piece)
|
||||
{
|
||||
_currentPiecePosition = new Vector2i(5,0);
|
||||
|
||||
_currentRotation = BlockGamePieceRotation.North;
|
||||
|
||||
_currentPiece = piece;
|
||||
UpdateAllFieldUI();
|
||||
}
|
||||
|
||||
private bool LowerBoundCheck(Vector2i position) => position.Y < 20;
|
||||
private bool BorderCheck(Vector2i position) => position.X >= 0 && position.X < 10;
|
||||
private bool ClearCheck(Vector2i position) => _field.All(block => !position.Equals(block.Position));
|
||||
|
||||
private bool DropCheck(Vector2i position) => LowerBoundCheck(position) && ClearCheck(position);
|
||||
private bool MoveCheck(Vector2i position) => BorderCheck(position) && ClearCheck(position);
|
||||
private bool RotateCheck(Vector2i position) => BorderCheck(position) && LowerBoundCheck(position) && ClearCheck(position);
|
||||
|
||||
public void ProcessInput(BlockGamePlayerAction action)
|
||||
{
|
||||
switch (action)
|
||||
{
|
||||
case BlockGamePlayerAction.StartLeft:
|
||||
_leftPressed = true;
|
||||
break;
|
||||
case BlockGamePlayerAction.EndLeft:
|
||||
_leftPressed = false;
|
||||
break;
|
||||
case BlockGamePlayerAction.StartRight:
|
||||
_rightPressed = true;
|
||||
break;
|
||||
case BlockGamePlayerAction.EndRight:
|
||||
_rightPressed = false;
|
||||
break;
|
||||
case BlockGamePlayerAction.Rotate:
|
||||
TrySetRotation(Next(_currentRotation, false));
|
||||
break;
|
||||
case BlockGamePlayerAction.CounterRotate:
|
||||
TrySetRotation(Next(_currentRotation, true));
|
||||
break;
|
||||
case BlockGamePlayerAction.SoftdropStart:
|
||||
_softDropPressed = true;
|
||||
break;
|
||||
case BlockGamePlayerAction.SoftdropEnd:
|
||||
_softDropPressed = false;
|
||||
break;
|
||||
case BlockGamePlayerAction.Harddrop:
|
||||
PerformHarddrop();
|
||||
break;
|
||||
case BlockGamePlayerAction.Pause:
|
||||
_running = false;
|
||||
_component.UserInterface?.SendMessage(new BlockGameMessages.BlockGameSetScreenMessage(BlockGameMessages.BlockGameScreen.Pause));
|
||||
break;
|
||||
case BlockGamePlayerAction.Unpause:
|
||||
if (!_gameOver)
|
||||
{
|
||||
_running = true;
|
||||
_component.UserInterface?.SendMessage(new BlockGameMessages.BlockGameSetScreenMessage(BlockGameMessages.BlockGameScreen.Game));
|
||||
}
|
||||
break;
|
||||
case BlockGamePlayerAction.Hold:
|
||||
HoldPiece();
|
||||
break;
|
||||
case BlockGamePlayerAction.ShowHighscores:
|
||||
_running = false;
|
||||
_component.UserInterface?.SendMessage(new BlockGameMessages.BlockGameSetScreenMessage(BlockGameMessages.BlockGameScreen.Highscores));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void TrySetRotation(BlockGamePieceRotation rotation)
|
||||
{
|
||||
if(!_running) return;
|
||||
|
||||
if (!_currentPiece.CanSpin) return;
|
||||
|
||||
if (!_currentPiece.Positions(_currentPiecePosition, rotation)
|
||||
.All(RotateCheck))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_currentRotation = rotation;
|
||||
UpdateAllFieldUI();
|
||||
}
|
||||
|
||||
private void HoldPiece()
|
||||
{
|
||||
if (!_running) return;
|
||||
|
||||
if (_holdBlock) return;
|
||||
|
||||
var tempHeld = _heldPiece;
|
||||
_heldPiece = _currentPiece;
|
||||
_holdBlock = true;
|
||||
|
||||
if (!tempHeld.HasValue)
|
||||
{
|
||||
InitializeNewBlock();
|
||||
return;
|
||||
}
|
||||
|
||||
InitializeNewBlock(tempHeld.Value);
|
||||
}
|
||||
|
||||
private void PerformHarddrop()
|
||||
{
|
||||
int spacesDropped = 0;
|
||||
while (_currentPiece.Positions(_currentPiecePosition.AddToY(1), _currentRotation)
|
||||
.All(DropCheck))
|
||||
{
|
||||
_currentPiecePosition = _currentPiecePosition.AddToY(1);
|
||||
spacesDropped++;
|
||||
}
|
||||
AddPoints(spacesDropped * 2);
|
||||
|
||||
InternalFieldTick();
|
||||
}
|
||||
|
||||
public void UpdateAllFieldUI()
|
||||
{
|
||||
if (!_started) return;
|
||||
|
||||
var computedField = ComputeField();
|
||||
_component.UserInterface?.SendMessage(new BlockGameMessages.BlockGameVisualUpdateMessage(computedField.ToArray(), BlockGameMessages.BlockGameVisualType.GameField));
|
||||
}
|
||||
|
||||
public void UpdateFieldUI(IPlayerSession session)
|
||||
{
|
||||
if (!_started) return;
|
||||
|
||||
var computedField = ComputeField();
|
||||
_component.UserInterface?.SendMessage(new BlockGameMessages.BlockGameVisualUpdateMessage(computedField.ToArray(), BlockGameMessages.BlockGameVisualType.GameField), session);
|
||||
}
|
||||
|
||||
private bool IsGameOver => _field.Any(block => block.Position.Y == 0);
|
||||
private void InvokeGameover()
|
||||
{
|
||||
_running = false;
|
||||
_gameOver = true;
|
||||
|
||||
if (_component._player?.AttachedEntity != null)
|
||||
{
|
||||
var blockGameSystem = EntitySystem.Get<BlockGameSystem>();
|
||||
|
||||
_highScorePlacement = blockGameSystem.RegisterHighScore(_component._player.AttachedEntity.Name, Points);
|
||||
SendHighscoreUpdate();
|
||||
}
|
||||
_component.UserInterface?.SendMessage(new BlockGameMessages.BlockGameGameOverScreenMessage(Points, _highScorePlacement?.LocalPlacement, _highScorePlacement?.GlobalPlacement));
|
||||
}
|
||||
|
||||
public void UpdateNewPlayerUI(IPlayerSession session)
|
||||
{
|
||||
if (_gameOver)
|
||||
{
|
||||
_component.UserInterface?.SendMessage(new BlockGameMessages.BlockGameGameOverScreenMessage(Points, _highScorePlacement?.LocalPlacement, _highScorePlacement?.GlobalPlacement), session);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (Paused)
|
||||
{
|
||||
_component.UserInterface?.SendMessage(new BlockGameMessages.BlockGameSetScreenMessage(BlockGameMessages.BlockGameScreen.Pause, Started), session);
|
||||
}
|
||||
else
|
||||
{
|
||||
_component.UserInterface?.SendMessage(new BlockGameMessages.BlockGameSetScreenMessage(BlockGameMessages.BlockGameScreen.Game, Started), session);
|
||||
}
|
||||
}
|
||||
|
||||
FullUpdate(session);
|
||||
}
|
||||
|
||||
public List<BlockGameBlock> ComputeField()
|
||||
{
|
||||
var result = new List<BlockGameBlock>();
|
||||
result.AddRange(_field);
|
||||
result.AddRange(_currentPiece.Blocks(_currentPiecePosition, _currentRotation));
|
||||
return result;
|
||||
}
|
||||
|
||||
private enum BlockGamePieceType
|
||||
{
|
||||
I,
|
||||
L,
|
||||
LInverted,
|
||||
S,
|
||||
SInverted,
|
||||
T,
|
||||
O
|
||||
}
|
||||
|
||||
private enum BlockGamePieceRotation
|
||||
{
|
||||
North,
|
||||
East,
|
||||
South,
|
||||
West
|
||||
}
|
||||
|
||||
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)
|
||||
};
|
||||
}
|
||||
|
||||
private struct BlockGamePiece
|
||||
{
|
||||
public Vector2i[] Offsets;
|
||||
private BlockGameBlock.BlockGameBlockColor _gameBlockColor;
|
||||
public bool CanSpin;
|
||||
|
||||
public Vector2i[] Positions(Vector2i center,
|
||||
BlockGamePieceRotation rotation)
|
||||
{
|
||||
return RotatedOffsets(rotation).Select(v => center + v).ToArray();
|
||||
}
|
||||
|
||||
private Vector2i[] RotatedOffsets(BlockGamePieceRotation rotation)
|
||||
{
|
||||
Vector2i[] 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;
|
||||
}
|
||||
|
||||
public 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;
|
||||
}
|
||||
|
||||
public 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);
|
||||
}
|
||||
|
||||
public static BlockGamePiece GetRandom(IRobustRandom random)
|
||||
{
|
||||
var pieces = (BlockGamePieceType[])Enum.GetValues(typeof(BlockGamePieceType));
|
||||
var choice = random.Pick(pieces);
|
||||
return GetPiece(choice);
|
||||
}
|
||||
|
||||
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)}}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
84
Content.Server/GameObjects/EntitySystems/BlockGameSystem.cs
Normal file
84
Content.Server/GameObjects/EntitySystems/BlockGameSystem.cs
Normal file
@@ -0,0 +1,84 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Content.Server.GameObjects.Components.Arcade;
|
||||
using Content.Shared.Arcade;
|
||||
using Robust.Shared.GameObjects.Systems;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Server.GameObjects.EntitySystems
|
||||
{
|
||||
// ReSharper disable once ClassNeverInstantiated.Global
|
||||
public class BlockGameSystem : EntitySystem
|
||||
{
|
||||
private readonly List<BlockGameMessages.HighScoreEntry> _roundHighscores = new List<BlockGameMessages.HighScoreEntry>();
|
||||
private readonly List<BlockGameMessages.HighScoreEntry> _globalHighscores = new List<BlockGameMessages.HighScoreEntry>();
|
||||
|
||||
public HighScorePlacement RegisterHighScore(string name, int score)
|
||||
{
|
||||
var entry = new BlockGameMessages.HighScoreEntry(name, score);
|
||||
return new HighScorePlacement(TryInsertIntoList(_roundHighscores, entry), TryInsertIntoList(_globalHighscores, entry));
|
||||
}
|
||||
|
||||
public List<BlockGameMessages.HighScoreEntry> GetLocalHighscores() => GetSortedHighscores(_roundHighscores);
|
||||
|
||||
public List<BlockGameMessages.HighScoreEntry> GetGlobalHighscores() => GetSortedHighscores(_globalHighscores);
|
||||
|
||||
private List<BlockGameMessages.HighScoreEntry> GetSortedHighscores(List<BlockGameMessages.HighScoreEntry> highScoreEntries)
|
||||
{
|
||||
var result = highScoreEntries.ShallowClone();
|
||||
result.Sort((p1, p2) => p2.Score.CompareTo(p1.Score));
|
||||
return result;
|
||||
}
|
||||
|
||||
private int? TryInsertIntoList(List<BlockGameMessages.HighScoreEntry> highScoreEntries, BlockGameMessages.HighScoreEntry entry)
|
||||
{
|
||||
if (highScoreEntries.Count < 5)
|
||||
{
|
||||
highScoreEntries.Add(entry);
|
||||
return GetPlacement(highScoreEntries, entry);
|
||||
}
|
||||
|
||||
if (highScoreEntries.Min(e => e.Score) >= entry.Score) return null;
|
||||
|
||||
var lowestHighscore = highScoreEntries.Min();
|
||||
highScoreEntries.Remove(lowestHighscore);
|
||||
highScoreEntries.Add(entry);
|
||||
return GetPlacement(highScoreEntries, entry);
|
||||
|
||||
}
|
||||
|
||||
private int? GetPlacement(List<BlockGameMessages.HighScoreEntry> highScoreEntries, BlockGameMessages.HighScoreEntry entry)
|
||||
{
|
||||
int? placement = null;
|
||||
if (highScoreEntries.Contains(entry))
|
||||
{
|
||||
highScoreEntries.Sort((p1,p2) => p2.Score.CompareTo(p1.Score));
|
||||
placement = 1 + highScoreEntries.IndexOf(entry);
|
||||
}
|
||||
|
||||
return placement;
|
||||
}
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
foreach (var comp in ComponentManager.EntityQuery<BlockGameArcadeComponent>())
|
||||
{
|
||||
comp.DoGameTick(frameTime);
|
||||
}
|
||||
}
|
||||
|
||||
public readonly struct HighScorePlacement
|
||||
{
|
||||
public readonly int? GlobalPlacement;
|
||||
public readonly int? LocalPlacement;
|
||||
|
||||
public HighScorePlacement(int? globalPlacement, int? localPlacement)
|
||||
{
|
||||
GlobalPlacement = globalPlacement;
|
||||
LocalPlacement = localPlacement;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
53
Content.Shared/Arcade/BlockGameBlock.cs
Normal file
53
Content.Shared/Arcade/BlockGameBlock.cs
Normal file
@@ -0,0 +1,53 @@
|
||||
using System;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.Arcade
|
||||
{
|
||||
[Serializable, NetSerializable]
|
||||
public struct BlockGameBlock
|
||||
{
|
||||
public Vector2i Position;
|
||||
public readonly BlockGameBlockColor GameBlockColor;
|
||||
|
||||
public BlockGameBlock(Vector2i position, BlockGameBlockColor gameBlockColor)
|
||||
{
|
||||
Position = position;
|
||||
GameBlockColor = gameBlockColor;
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public enum BlockGameBlockColor
|
||||
{
|
||||
Red,
|
||||
Orange,
|
||||
Yellow,
|
||||
Green,
|
||||
Blue,
|
||||
LightBlue,
|
||||
Purple
|
||||
}
|
||||
}
|
||||
|
||||
public static class BlockGameVector2Extensions{
|
||||
public static BlockGameBlock ToBlockGameBlock(this Vector2i vector2, BlockGameBlock.BlockGameBlockColor gameBlockColor)
|
||||
{
|
||||
return new BlockGameBlock(vector2, gameBlockColor);
|
||||
}
|
||||
|
||||
public static Vector2i AddToX(this Vector2i vector2, int amount)
|
||||
{
|
||||
return new Vector2i(vector2.X + amount, vector2.Y);
|
||||
}
|
||||
public static Vector2i AddToY(this Vector2i vector2, int amount)
|
||||
{
|
||||
return new Vector2i(vector2.X, vector2.Y + amount);
|
||||
}
|
||||
|
||||
public static Vector2i Rotate90DegreesAsOffset(this Vector2i vector)
|
||||
{
|
||||
return new Vector2i(-vector.Y, vector.X);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
141
Content.Shared/Arcade/BlockGameMessages.cs
Normal file
141
Content.Shared/Arcade/BlockGameMessages.cs
Normal file
@@ -0,0 +1,141 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Robust.Shared.GameObjects.Components.UserInterface;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.Arcade
|
||||
{
|
||||
public static class BlockGameMessages
|
||||
{
|
||||
[Serializable, NetSerializable]
|
||||
public class BlockGamePlayerActionMessage : BoundUserInterfaceMessage
|
||||
{
|
||||
public readonly BlockGamePlayerAction PlayerAction;
|
||||
public BlockGamePlayerActionMessage(BlockGamePlayerAction playerAction)
|
||||
{
|
||||
PlayerAction = playerAction;
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public class BlockGameVisualUpdateMessage : BoundUserInterfaceMessage
|
||||
{
|
||||
public readonly BlockGameVisualType GameVisualType;
|
||||
public readonly BlockGameBlock[] Blocks;
|
||||
public BlockGameVisualUpdateMessage(BlockGameBlock[] blocks, BlockGameVisualType gameVisualType)
|
||||
{
|
||||
Blocks = blocks;
|
||||
GameVisualType = gameVisualType;
|
||||
}
|
||||
}
|
||||
|
||||
public enum BlockGameVisualType
|
||||
{
|
||||
GameField,
|
||||
HoldBlock,
|
||||
NextBlock
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public class BlockGameScoreUpdateMessage : BoundUserInterfaceMessage
|
||||
{
|
||||
public readonly int Points;
|
||||
public BlockGameScoreUpdateMessage(int points)
|
||||
{
|
||||
Points = points;
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public class BlockGameUserStatusMessage : BoundUserInterfaceMessage
|
||||
{
|
||||
public readonly bool IsPlayer;
|
||||
|
||||
public BlockGameUserStatusMessage(bool isPlayer)
|
||||
{
|
||||
IsPlayer = isPlayer;
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public class BlockGameUserUnregisterMessage : BoundUserInterfaceMessage{}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public class BlockGameSetScreenMessage : BoundUserInterfaceMessage
|
||||
{
|
||||
public readonly BlockGameScreen Screen;
|
||||
public readonly bool isStarted;
|
||||
public BlockGameSetScreenMessage(BlockGameScreen screen, bool isStarted = true)
|
||||
{
|
||||
Screen = screen;
|
||||
this.isStarted = isStarted;
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public class BlockGameGameOverScreenMessage : BlockGameSetScreenMessage
|
||||
{
|
||||
public readonly int FinalScore;
|
||||
public readonly int? LocalPlacement;
|
||||
public readonly int? GlobalPlacement;
|
||||
public BlockGameGameOverScreenMessage(int finalScore, int? localPlacement, int? globalPlacement) : base(BlockGameScreen.Gameover)
|
||||
{
|
||||
FinalScore = finalScore;
|
||||
LocalPlacement = localPlacement;
|
||||
GlobalPlacement = globalPlacement;
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public enum BlockGameScreen
|
||||
{
|
||||
Game,
|
||||
Pause,
|
||||
Gameover,
|
||||
Highscores
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public class BlockGameHighScoreUpdateMessage : BoundUserInterfaceMessage
|
||||
{
|
||||
public List<HighScoreEntry> LocalHighscores;
|
||||
public List<HighScoreEntry> GlobalHighscores;
|
||||
|
||||
public BlockGameHighScoreUpdateMessage(List<HighScoreEntry> localHighscores, List<HighScoreEntry> globalHighscores)
|
||||
{
|
||||
LocalHighscores = localHighscores;
|
||||
GlobalHighscores = globalHighscores;
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public class HighScoreEntry : IComparable
|
||||
{
|
||||
public string Name;
|
||||
public int Score;
|
||||
|
||||
public HighScoreEntry(string name, int score)
|
||||
{
|
||||
Name = name;
|
||||
Score = score;
|
||||
}
|
||||
|
||||
public int CompareTo(object? obj)
|
||||
{
|
||||
if (!(obj is HighScoreEntry entry)) return 0;
|
||||
return Score.CompareTo(entry.Score);
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public class BlockGameLevelUpdateMessage : BoundUserInterfaceMessage
|
||||
{
|
||||
public readonly int Level;
|
||||
public BlockGameLevelUpdateMessage(int level)
|
||||
{
|
||||
Level = level;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
24
Content.Shared/Arcade/BlockGamePlayerAction.cs
Normal file
24
Content.Shared/Arcade/BlockGamePlayerAction.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
using System;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.Arcade
|
||||
{
|
||||
[Serializable, NetSerializable]
|
||||
public enum BlockGamePlayerAction
|
||||
{
|
||||
NewGame,
|
||||
StartLeft,
|
||||
EndLeft,
|
||||
StartRight,
|
||||
EndRight,
|
||||
Rotate,
|
||||
CounterRotate,
|
||||
SoftdropStart,
|
||||
SoftdropEnd,
|
||||
Harddrop,
|
||||
Pause,
|
||||
Unpause,
|
||||
Hold,
|
||||
ShowHighscores
|
||||
}
|
||||
}
|
||||
11
Content.Shared/Arcade/BlockGameUiKey.cs
Normal file
11
Content.Shared/Arcade/BlockGameUiKey.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
using System;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.Arcade
|
||||
{
|
||||
[Serializable, NetSerializable]
|
||||
public enum BlockGameUiKey
|
||||
{
|
||||
Key
|
||||
}
|
||||
}
|
||||
@@ -76,6 +76,7 @@
|
||||
public const uint MOB_STATE_MANAGER = 1070;
|
||||
public const uint SLIP = 1071;
|
||||
public const uint SPACE_VILLAIN_ARCADE = 1072;
|
||||
public const uint BLOCKGAME_ARCADE = 1073;
|
||||
|
||||
// Net IDs for integration tests.
|
||||
public const uint PREDICTION_TEST = 10001;
|
||||
|
||||
@@ -34,5 +34,12 @@ namespace Content.Shared.Input
|
||||
public static readonly BoundKeyFunction TakeScreenshot = "TakeScreenshot";
|
||||
public static readonly BoundKeyFunction TakeScreenshotNoUI = "TakeScreenshotNoUI";
|
||||
public static readonly BoundKeyFunction Point = "Point";
|
||||
public static readonly BoundKeyFunction ArcadeUp = "ArcadeUp";
|
||||
public static readonly BoundKeyFunction ArcadeDown = "ArcadeDown";
|
||||
public static readonly BoundKeyFunction ArcadeLeft = "ArcadeLeft";
|
||||
public static readonly BoundKeyFunction ArcadeRight = "ArcadeRight";
|
||||
public static readonly BoundKeyFunction Arcade1 = "Arcade1";
|
||||
public static readonly BoundKeyFunction Arcade2 = "Arcade2";
|
||||
public static readonly BoundKeyFunction Arcade3 = "Arcade3";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,3 +30,35 @@
|
||||
type: SpaceVillainArcadeBoundUserInterface
|
||||
- key: enum.WiresUiKey.Key
|
||||
type: WiresBoundUserInterface
|
||||
|
||||
- type: entity
|
||||
id: BlockGameArcade
|
||||
name: blockGameArcade
|
||||
parent: ComputerBase
|
||||
components:
|
||||
- type: Icon
|
||||
state: arcade
|
||||
- type: PowerReceiver
|
||||
- type: Sprite
|
||||
layers:
|
||||
- state: arcade
|
||||
map: ["enum.ComputerVisualizer+Layers.Body"]
|
||||
- state: invaders
|
||||
shader: unshaded
|
||||
map: ["enum.ComputerVisualizer+Layers.Screen"]
|
||||
- type: Appearance
|
||||
visuals:
|
||||
- type: ComputerVisualizer
|
||||
screen: invaders
|
||||
key: ""
|
||||
body: arcade
|
||||
bodyBroken: arcade
|
||||
- type: Anchorable
|
||||
- type: Pullable
|
||||
- type: BlockGameArcade
|
||||
- type: UserInterface
|
||||
interfaces:
|
||||
- key: enum.BlockGameUiKey.Key
|
||||
type: BlockGameBoundUserInterface
|
||||
- key: enum.WiresUiKey.Key
|
||||
type: WiresBoundUserInterface
|
||||
|
||||
@@ -280,3 +280,24 @@ binds:
|
||||
type: State
|
||||
key: MouseMiddle
|
||||
mod1: Shift
|
||||
- function: ArcadeUp
|
||||
type: State
|
||||
key: Up
|
||||
- function: ArcadeDown
|
||||
type: State
|
||||
key: Down
|
||||
- function: ArcadeLeft
|
||||
type: State
|
||||
key: Left
|
||||
- function: ArcadeRight
|
||||
type: State
|
||||
key: Right
|
||||
- function: Arcade1
|
||||
type: State
|
||||
key: Space
|
||||
- function: Arcade2
|
||||
type: State
|
||||
key: C
|
||||
- function: Arcade3
|
||||
type: State
|
||||
key: Z
|
||||
|
||||
Reference in New Issue
Block a user