ECS Arcade Machines (#16791)
This commit is contained in:
@@ -1,3 +1,4 @@
|
|||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
@@ -20,16 +21,16 @@ namespace Content.Client.Arcade
|
|||||||
{
|
{
|
||||||
public sealed class BlockGameMenu : DefaultWindow
|
public sealed class BlockGameMenu : DefaultWindow
|
||||||
{
|
{
|
||||||
private static readonly Color OverlayBackgroundColor = new(74,74,81,180);
|
private static readonly Color OverlayBackgroundColor = new(74, 74, 81, 180);
|
||||||
private static readonly Color OverlayShadowColor = new(0,0,0,83);
|
private static readonly Color OverlayShadowColor = new(0, 0, 0, 83);
|
||||||
|
|
||||||
private static readonly Vector2 BlockSize = new(15,15);
|
private static readonly Vector2 BlockSize = new(15, 15);
|
||||||
|
|
||||||
private readonly BlockGameBoundUserInterface _owner;
|
private readonly BlockGameBoundUserInterface _owner;
|
||||||
|
|
||||||
private readonly PanelContainer _mainPanel;
|
private readonly PanelContainer _mainPanel;
|
||||||
|
|
||||||
private BoxContainer _gameRootContainer;
|
private readonly BoxContainer _gameRootContainer;
|
||||||
private GridContainer _gameGrid = default!;
|
private GridContainer _gameGrid = default!;
|
||||||
private GridContainer _nextBlockGrid = default!;
|
private GridContainer _nextBlockGrid = default!;
|
||||||
private GridContainer _holdBlockGrid = default!;
|
private GridContainer _holdBlockGrid = default!;
|
||||||
@@ -82,7 +83,7 @@ namespace Content.Client.Arcade
|
|||||||
_gameRootContainer.AddChild(_levelLabel);
|
_gameRootContainer.AddChild(_levelLabel);
|
||||||
_gameRootContainer.AddChild(new Control
|
_gameRootContainer.AddChild(new Control
|
||||||
{
|
{
|
||||||
MinSize = new Vector2(1,5)
|
MinSize = new Vector2(1, 5)
|
||||||
});
|
});
|
||||||
|
|
||||||
_pointsLabel = new Label
|
_pointsLabel = new Label
|
||||||
@@ -93,7 +94,7 @@ namespace Content.Client.Arcade
|
|||||||
_gameRootContainer.AddChild(_pointsLabel);
|
_gameRootContainer.AddChild(_pointsLabel);
|
||||||
_gameRootContainer.AddChild(new Control
|
_gameRootContainer.AddChild(new Control
|
||||||
{
|
{
|
||||||
MinSize = new Vector2(1,10)
|
MinSize = new Vector2(1, 10)
|
||||||
});
|
});
|
||||||
|
|
||||||
var gameBox = new BoxContainer
|
var gameBox = new BoxContainer
|
||||||
@@ -103,12 +104,12 @@ namespace Content.Client.Arcade
|
|||||||
gameBox.AddChild(SetupHoldBox(backgroundTexture));
|
gameBox.AddChild(SetupHoldBox(backgroundTexture));
|
||||||
gameBox.AddChild(new Control
|
gameBox.AddChild(new Control
|
||||||
{
|
{
|
||||||
MinSize = new Vector2(10,1)
|
MinSize = new Vector2(10, 1)
|
||||||
});
|
});
|
||||||
gameBox.AddChild(SetupGameGrid(backgroundTexture));
|
gameBox.AddChild(SetupGameGrid(backgroundTexture));
|
||||||
gameBox.AddChild(new Control
|
gameBox.AddChild(new Control
|
||||||
{
|
{
|
||||||
MinSize = new Vector2(10,1)
|
MinSize = new Vector2(10, 1)
|
||||||
});
|
});
|
||||||
gameBox.AddChild(SetupNextBox(backgroundTexture));
|
gameBox.AddChild(SetupNextBox(backgroundTexture));
|
||||||
|
|
||||||
@@ -116,7 +117,7 @@ namespace Content.Client.Arcade
|
|||||||
|
|
||||||
_gameRootContainer.AddChild(new Control
|
_gameRootContainer.AddChild(new Control
|
||||||
{
|
{
|
||||||
MinSize = new Vector2(1,10)
|
MinSize = new Vector2(1, 10)
|
||||||
});
|
});
|
||||||
|
|
||||||
_pauseButton = new Button
|
_pauseButton = new Button
|
||||||
@@ -176,7 +177,7 @@ namespace Content.Client.Arcade
|
|||||||
_owner.SendAction(BlockGamePlayerAction.NewGame);
|
_owner.SendAction(BlockGamePlayerAction.NewGame);
|
||||||
};
|
};
|
||||||
pauseMenuContainer.AddChild(_newGameButton);
|
pauseMenuContainer.AddChild(_newGameButton);
|
||||||
pauseMenuContainer.AddChild(new Control{MinSize = new Vector2(1,10)});
|
pauseMenuContainer.AddChild(new Control { MinSize = new Vector2(1, 10) });
|
||||||
|
|
||||||
_scoreBoardButton = new Button
|
_scoreBoardButton = new Button
|
||||||
{
|
{
|
||||||
@@ -185,7 +186,7 @@ namespace Content.Client.Arcade
|
|||||||
};
|
};
|
||||||
_scoreBoardButton.OnPressed += (e) => _owner.SendAction(BlockGamePlayerAction.ShowHighscores);
|
_scoreBoardButton.OnPressed += (e) => _owner.SendAction(BlockGamePlayerAction.ShowHighscores);
|
||||||
pauseMenuContainer.AddChild(_scoreBoardButton);
|
pauseMenuContainer.AddChild(_scoreBoardButton);
|
||||||
_unpauseButtonMargin = new Control {MinSize = new Vector2(1, 10), Visible = false};
|
_unpauseButtonMargin = new Control { MinSize = new Vector2(1, 10), Visible = false };
|
||||||
pauseMenuContainer.AddChild(_unpauseButtonMargin);
|
pauseMenuContainer.AddChild(_unpauseButtonMargin);
|
||||||
|
|
||||||
_unpauseButton = new Button
|
_unpauseButton = new Button
|
||||||
@@ -239,13 +240,13 @@ namespace Content.Client.Arcade
|
|||||||
VerticalAlignment = VAlignment.Center
|
VerticalAlignment = VAlignment.Center
|
||||||
};
|
};
|
||||||
|
|
||||||
gameOverMenuContainer.AddChild(new Label{Text = Loc.GetString("blockgame-menu-msg-game-over"),Align = Label.AlignMode.Center});
|
gameOverMenuContainer.AddChild(new Label { Text = Loc.GetString("blockgame-menu-msg-game-over"), Align = Label.AlignMode.Center });
|
||||||
gameOverMenuContainer.AddChild(new Control{MinSize = new Vector2(1,10)});
|
gameOverMenuContainer.AddChild(new Control { MinSize = new Vector2(1, 10) });
|
||||||
|
|
||||||
|
|
||||||
_finalScoreLabel = new Label{Align = Label.AlignMode.Center};
|
_finalScoreLabel = new Label { Align = Label.AlignMode.Center };
|
||||||
gameOverMenuContainer.AddChild(_finalScoreLabel);
|
gameOverMenuContainer.AddChild(_finalScoreLabel);
|
||||||
gameOverMenuContainer.AddChild(new Control{MinSize = new Vector2(1,10)});
|
gameOverMenuContainer.AddChild(new Control { MinSize = new Vector2(1, 10) });
|
||||||
|
|
||||||
_finalNewGameButton = new Button
|
_finalNewGameButton = new Button
|
||||||
{
|
{
|
||||||
@@ -275,7 +276,7 @@ namespace Content.Client.Arcade
|
|||||||
HorizontalAlignment = HAlignment.Center
|
HorizontalAlignment = HAlignment.Center
|
||||||
};
|
};
|
||||||
|
|
||||||
var c = new Color(OverlayBackgroundColor.R,OverlayBackgroundColor.G,OverlayBackgroundColor.B,220);
|
var c = new Color(OverlayBackgroundColor.R, OverlayBackgroundColor.G, OverlayBackgroundColor.B, 220);
|
||||||
var innerBack = new StyleBoxTexture
|
var innerBack = new StyleBoxTexture
|
||||||
{
|
{
|
||||||
Texture = backgroundTexture,
|
Texture = backgroundTexture,
|
||||||
@@ -298,8 +299,8 @@ namespace Content.Client.Arcade
|
|||||||
VerticalAlignment = VAlignment.Center
|
VerticalAlignment = VAlignment.Center
|
||||||
};
|
};
|
||||||
|
|
||||||
menuContainer.AddChild(new Label{Text = Loc.GetString("blockgame-menu-label-highscores")});
|
menuContainer.AddChild(new Label { Text = Loc.GetString("blockgame-menu-label-highscores") });
|
||||||
menuContainer.AddChild(new Control{MinSize = new Vector2(1,10)});
|
menuContainer.AddChild(new Control { MinSize = new Vector2(1, 10) });
|
||||||
|
|
||||||
var highScoreBox = new BoxContainer
|
var highScoreBox = new BoxContainer
|
||||||
{
|
{
|
||||||
@@ -311,14 +312,14 @@ namespace Content.Client.Arcade
|
|||||||
Align = Label.AlignMode.Center
|
Align = Label.AlignMode.Center
|
||||||
};
|
};
|
||||||
highScoreBox.AddChild(_localHighscoresLabel);
|
highScoreBox.AddChild(_localHighscoresLabel);
|
||||||
highScoreBox.AddChild(new Control{MinSize = new Vector2(40,1)});
|
highScoreBox.AddChild(new Control { MinSize = new Vector2(40, 1) });
|
||||||
_globalHighscoresLabel = new Label
|
_globalHighscoresLabel = new Label
|
||||||
{
|
{
|
||||||
Align = Label.AlignMode.Center
|
Align = Label.AlignMode.Center
|
||||||
};
|
};
|
||||||
highScoreBox.AddChild(_globalHighscoresLabel);
|
highScoreBox.AddChild(_globalHighscoresLabel);
|
||||||
menuContainer.AddChild(highScoreBox);
|
menuContainer.AddChild(highScoreBox);
|
||||||
menuContainer.AddChild(new Control{MinSize = new Vector2(1,10)});
|
menuContainer.AddChild(new Control { MinSize = new Vector2(1, 10) });
|
||||||
_highscoreBackButton = new Button
|
_highscoreBackButton = new Button
|
||||||
{
|
{
|
||||||
Text = Loc.GetString("blockgame-menu-button-back"),
|
Text = Loc.GetString("blockgame-menu-button-back"),
|
||||||
@@ -359,7 +360,7 @@ namespace Content.Client.Arcade
|
|||||||
HSeparationOverride = 1,
|
HSeparationOverride = 1,
|
||||||
VSeparationOverride = 1
|
VSeparationOverride = 1
|
||||||
};
|
};
|
||||||
UpdateBlocks(new BlockGameBlock[0]);
|
UpdateBlocks(Array.Empty<BlockGameBlock>());
|
||||||
|
|
||||||
var back = new StyleBoxTexture
|
var back = new StyleBoxTexture
|
||||||
{
|
{
|
||||||
@@ -376,7 +377,7 @@ namespace Content.Client.Arcade
|
|||||||
};
|
};
|
||||||
var backgroundPanel = new PanelContainer
|
var backgroundPanel = new PanelContainer
|
||||||
{
|
{
|
||||||
PanelOverride = new StyleBoxFlat{BackgroundColor = Color.FromHex("#86868d")}
|
PanelOverride = new StyleBoxFlat { BackgroundColor = Color.FromHex("#86868d") }
|
||||||
};
|
};
|
||||||
backgroundPanel.AddChild(_gameGrid);
|
backgroundPanel.AddChild(_gameGrid);
|
||||||
gamePanel.AddChild(backgroundPanel);
|
gamePanel.AddChild(backgroundPanel);
|
||||||
@@ -416,7 +417,7 @@ namespace Content.Client.Arcade
|
|||||||
nextBlockPanel.AddChild(nextCenterContainer);
|
nextBlockPanel.AddChild(nextCenterContainer);
|
||||||
grid.AddChild(nextBlockPanel);
|
grid.AddChild(nextBlockPanel);
|
||||||
|
|
||||||
grid.AddChild(new Label{Text = Loc.GetString("blockgame-menu-label-next"), Align = Label.AlignMode.Center});
|
grid.AddChild(new Label { Text = Loc.GetString("blockgame-menu-label-next"), Align = Label.AlignMode.Center });
|
||||||
|
|
||||||
return grid;
|
return grid;
|
||||||
}
|
}
|
||||||
@@ -454,15 +455,17 @@ namespace Content.Client.Arcade
|
|||||||
holdBlockPanel.AddChild(holdCenterContainer);
|
holdBlockPanel.AddChild(holdCenterContainer);
|
||||||
grid.AddChild(holdBlockPanel);
|
grid.AddChild(holdBlockPanel);
|
||||||
|
|
||||||
grid.AddChild(new Label{Text = Loc.GetString("blockgame-menu-label-hold"), Align = Label.AlignMode.Center});
|
grid.AddChild(new Label { Text = Loc.GetString("blockgame-menu-label-hold"), Align = Label.AlignMode.Center });
|
||||||
|
|
||||||
return grid;
|
return grid;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void KeyboardFocusExited()
|
protected override void KeyboardFocusExited()
|
||||||
{
|
{
|
||||||
if (!IsOpen) return;
|
if (!IsOpen)
|
||||||
if(_gameOver) return;
|
return;
|
||||||
|
if (_gameOver)
|
||||||
|
return;
|
||||||
TryPause();
|
TryPause();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -480,7 +483,8 @@ namespace Content.Client.Arcade
|
|||||||
|
|
||||||
public void SetScreen(BlockGameMessages.BlockGameScreen screen)
|
public void SetScreen(BlockGameMessages.BlockGameScreen screen)
|
||||||
{
|
{
|
||||||
if (_gameOver) return;
|
if (_gameOver)
|
||||||
|
return;
|
||||||
|
|
||||||
switch (screen)
|
switch (screen)
|
||||||
{
|
{
|
||||||
@@ -512,9 +516,12 @@ namespace Content.Client.Arcade
|
|||||||
|
|
||||||
private void CloseMenus()
|
private void CloseMenus()
|
||||||
{
|
{
|
||||||
if(_mainPanel.Children.Contains(_menuRootContainer)) _mainPanel.RemoveChild(_menuRootContainer);
|
if (_mainPanel.Children.Contains(_menuRootContainer))
|
||||||
if(_mainPanel.Children.Contains(_gameOverRootContainer)) _mainPanel.RemoveChild(_gameOverRootContainer);
|
_mainPanel.RemoveChild(_menuRootContainer);
|
||||||
if(_mainPanel.Children.Contains(_highscoresRootContainer)) _mainPanel.RemoveChild(_highscoresRootContainer);
|
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)
|
public void SetGameoverInfo(int amount, int? localPlacement, int? globalPlacement)
|
||||||
@@ -563,72 +570,56 @@ namespace Content.Client.Arcade
|
|||||||
{
|
{
|
||||||
base.KeyBindDown(args);
|
base.KeyBindDown(args);
|
||||||
|
|
||||||
if(!_isPlayer || args.Handled) return;
|
if (!_isPlayer || args.Handled)
|
||||||
|
return;
|
||||||
|
|
||||||
if (args.Function == ContentKeyFunctions.ArcadeLeft)
|
else if (args.Function == ContentKeyFunctions.ArcadeLeft)
|
||||||
{
|
|
||||||
_owner.SendAction(BlockGamePlayerAction.StartLeft);
|
_owner.SendAction(BlockGamePlayerAction.StartLeft);
|
||||||
}
|
|
||||||
else if (args.Function == ContentKeyFunctions.ArcadeRight)
|
else if (args.Function == ContentKeyFunctions.ArcadeRight)
|
||||||
{
|
|
||||||
_owner.SendAction(BlockGamePlayerAction.StartRight);
|
_owner.SendAction(BlockGamePlayerAction.StartRight);
|
||||||
}
|
|
||||||
else if (args.Function == ContentKeyFunctions.ArcadeUp)
|
else if (args.Function == ContentKeyFunctions.ArcadeUp)
|
||||||
{
|
|
||||||
_owner.SendAction(BlockGamePlayerAction.Rotate);
|
_owner.SendAction(BlockGamePlayerAction.Rotate);
|
||||||
}
|
|
||||||
else if (args.Function == ContentKeyFunctions.Arcade3)
|
else if (args.Function == ContentKeyFunctions.Arcade3)
|
||||||
{
|
|
||||||
_owner.SendAction(BlockGamePlayerAction.CounterRotate);
|
_owner.SendAction(BlockGamePlayerAction.CounterRotate);
|
||||||
}
|
|
||||||
else if (args.Function == ContentKeyFunctions.ArcadeDown)
|
else if (args.Function == ContentKeyFunctions.ArcadeDown)
|
||||||
{
|
|
||||||
_owner.SendAction(BlockGamePlayerAction.SoftdropStart);
|
_owner.SendAction(BlockGamePlayerAction.SoftdropStart);
|
||||||
}
|
|
||||||
else if (args.Function == ContentKeyFunctions.Arcade2)
|
else if (args.Function == ContentKeyFunctions.Arcade2)
|
||||||
{
|
|
||||||
_owner.SendAction(BlockGamePlayerAction.Hold);
|
_owner.SendAction(BlockGamePlayerAction.Hold);
|
||||||
}
|
|
||||||
else if (args.Function == ContentKeyFunctions.Arcade1)
|
else if (args.Function == ContentKeyFunctions.Arcade1)
|
||||||
{
|
|
||||||
_owner.SendAction(BlockGamePlayerAction.Harddrop);
|
_owner.SendAction(BlockGamePlayerAction.Harddrop);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void KeyBindUp(GUIBoundKeyEventArgs args)
|
protected override void KeyBindUp(GUIBoundKeyEventArgs args)
|
||||||
{
|
{
|
||||||
base.KeyBindUp(args);
|
base.KeyBindUp(args);
|
||||||
|
|
||||||
if(!_isPlayer || args.Handled) return;
|
if (!_isPlayer || args.Handled)
|
||||||
|
return;
|
||||||
|
|
||||||
if (args.Function == ContentKeyFunctions.ArcadeLeft)
|
else if (args.Function == ContentKeyFunctions.ArcadeLeft)
|
||||||
{
|
|
||||||
_owner.SendAction(BlockGamePlayerAction.EndLeft);
|
_owner.SendAction(BlockGamePlayerAction.EndLeft);
|
||||||
}
|
|
||||||
else if (args.Function == ContentKeyFunctions.ArcadeRight)
|
else if (args.Function == ContentKeyFunctions.ArcadeRight)
|
||||||
{
|
|
||||||
_owner.SendAction(BlockGamePlayerAction.EndRight);
|
_owner.SendAction(BlockGamePlayerAction.EndRight);
|
||||||
}else if (args.Function == ContentKeyFunctions.ArcadeDown)
|
else if (args.Function == ContentKeyFunctions.ArcadeDown)
|
||||||
{
|
|
||||||
_owner.SendAction(BlockGamePlayerAction.SoftdropEnd);
|
_owner.SendAction(BlockGamePlayerAction.SoftdropEnd);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void UpdateNextBlock(BlockGameBlock[] blocks)
|
public void UpdateNextBlock(BlockGameBlock[] blocks)
|
||||||
{
|
{
|
||||||
_nextBlockGrid.RemoveAllChildren();
|
_nextBlockGrid.RemoveAllChildren();
|
||||||
if (blocks.Length == 0) return;
|
if (blocks.Length == 0)
|
||||||
|
return;
|
||||||
var columnCount = blocks.Max(b => b.Position.X) + 1;
|
var columnCount = blocks.Max(b => b.Position.X) + 1;
|
||||||
var rowCount = blocks.Max(b => b.Position.Y) + 1;
|
var rowCount = blocks.Max(b => b.Position.Y) + 1;
|
||||||
_nextBlockGrid.Columns = columnCount;
|
_nextBlockGrid.Columns = columnCount;
|
||||||
for (int y = 0; y < rowCount; y++)
|
for (var y = 0; y < rowCount; y++)
|
||||||
{
|
{
|
||||||
for (int x = 0; x < columnCount; x++)
|
for (var x = 0; x < columnCount; x++)
|
||||||
{
|
{
|
||||||
var c = GetColorForPosition(blocks, x, y);
|
var c = GetColorForPosition(blocks, x, y);
|
||||||
_nextBlockGrid.AddChild(new PanelContainer
|
_nextBlockGrid.AddChild(new PanelContainer
|
||||||
{
|
{
|
||||||
PanelOverride = new StyleBoxFlat {BackgroundColor = c},
|
PanelOverride = new StyleBoxFlat { BackgroundColor = c },
|
||||||
MinSize = BlockSize,
|
MinSize = BlockSize,
|
||||||
RectDrawClipMargin = 0
|
RectDrawClipMargin = 0
|
||||||
});
|
});
|
||||||
@@ -639,18 +630,19 @@ namespace Content.Client.Arcade
|
|||||||
public void UpdateHeldBlock(BlockGameBlock[] blocks)
|
public void UpdateHeldBlock(BlockGameBlock[] blocks)
|
||||||
{
|
{
|
||||||
_holdBlockGrid.RemoveAllChildren();
|
_holdBlockGrid.RemoveAllChildren();
|
||||||
if (blocks.Length == 0) return;
|
if (blocks.Length == 0)
|
||||||
|
return;
|
||||||
var columnCount = blocks.Max(b => b.Position.X) + 1;
|
var columnCount = blocks.Max(b => b.Position.X) + 1;
|
||||||
var rowCount = blocks.Max(b => b.Position.Y) + 1;
|
var rowCount = blocks.Max(b => b.Position.Y) + 1;
|
||||||
_holdBlockGrid.Columns = columnCount;
|
_holdBlockGrid.Columns = columnCount;
|
||||||
for (int y = 0; y < rowCount; y++)
|
for (var y = 0; y < rowCount; y++)
|
||||||
{
|
{
|
||||||
for (int x = 0; x < columnCount; x++)
|
for (var x = 0; x < columnCount; x++)
|
||||||
{
|
{
|
||||||
var c = GetColorForPosition(blocks, x, y);
|
var c = GetColorForPosition(blocks, x, y);
|
||||||
_holdBlockGrid.AddChild(new PanelContainer
|
_holdBlockGrid.AddChild(new PanelContainer
|
||||||
{
|
{
|
||||||
PanelOverride = new StyleBoxFlat {BackgroundColor = c},
|
PanelOverride = new StyleBoxFlat { BackgroundColor = c },
|
||||||
MinSize = BlockSize,
|
MinSize = BlockSize,
|
||||||
RectDrawClipMargin = 0
|
RectDrawClipMargin = 0
|
||||||
});
|
});
|
||||||
@@ -661,14 +653,14 @@ namespace Content.Client.Arcade
|
|||||||
public void UpdateBlocks(BlockGameBlock[] blocks)
|
public void UpdateBlocks(BlockGameBlock[] blocks)
|
||||||
{
|
{
|
||||||
_gameGrid.RemoveAllChildren();
|
_gameGrid.RemoveAllChildren();
|
||||||
for (int y = 0; y < 20; y++)
|
for (var y = 0; y < 20; y++)
|
||||||
{
|
{
|
||||||
for (int x = 0; x < 10; x++)
|
for (var x = 0; x < 10; x++)
|
||||||
{
|
{
|
||||||
var c = GetColorForPosition(blocks, x, y);
|
var c = GetColorForPosition(blocks, x, y);
|
||||||
_gameGrid.AddChild(new PanelContainer
|
_gameGrid.AddChild(new PanelContainer
|
||||||
{
|
{
|
||||||
PanelOverride = new StyleBoxFlat {BackgroundColor = c},
|
PanelOverride = new StyleBoxFlat { BackgroundColor = c },
|
||||||
MinSize = BlockSize,
|
MinSize = BlockSize,
|
||||||
RectDrawClipMargin = 0
|
RectDrawClipMargin = 0
|
||||||
});
|
});
|
||||||
@@ -676,9 +668,9 @@ namespace Content.Client.Arcade
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Color GetColorForPosition(BlockGameBlock[] blocks, int x, int y)
|
private static Color GetColorForPosition(BlockGameBlock[] blocks, int x, int y)
|
||||||
{
|
{
|
||||||
Color c = Color.Transparent;
|
var c = Color.Transparent;
|
||||||
var matchingBlock = blocks.FirstOrNull(b => b.Position.X == x && b.Position.Y == y);
|
var matchingBlock = blocks.FirstOrNull(b => b.Position.X == x && b.Position.Y == y);
|
||||||
if (matchingBlock.HasValue)
|
if (matchingBlock.HasValue)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -24,40 +24,46 @@ namespace Content.Client.Arcade
|
|||||||
Title = Loc.GetString("spacevillain-menu-title");
|
Title = Loc.GetString("spacevillain-menu-title");
|
||||||
Owner = owner;
|
Owner = owner;
|
||||||
|
|
||||||
var grid = new GridContainer {Columns = 1};
|
var grid = new GridContainer { Columns = 1 };
|
||||||
|
|
||||||
var infoGrid = new GridContainer {Columns = 3};
|
var infoGrid = new GridContainer { Columns = 3 };
|
||||||
infoGrid.AddChild(new Label{ Text = Loc.GetString("spacevillain-menu-label-player"), Align = Label.AlignMode.Center });
|
infoGrid.AddChild(new Label { Text = Loc.GetString("spacevillain-menu-label-player"), Align = Label.AlignMode.Center });
|
||||||
infoGrid.AddChild(new Label{ Text = "|", Align = Label.AlignMode.Center });
|
infoGrid.AddChild(new Label { Text = "|", Align = Label.AlignMode.Center });
|
||||||
_enemyNameLabel = new Label{ Align = Label.AlignMode.Center};
|
_enemyNameLabel = new Label { Align = Label.AlignMode.Center };
|
||||||
infoGrid.AddChild(_enemyNameLabel);
|
infoGrid.AddChild(_enemyNameLabel);
|
||||||
|
|
||||||
_playerInfoLabel = new Label {Align = Label.AlignMode.Center};
|
_playerInfoLabel = new Label { Align = Label.AlignMode.Center };
|
||||||
infoGrid.AddChild(_playerInfoLabel);
|
infoGrid.AddChild(_playerInfoLabel);
|
||||||
infoGrid.AddChild(new Label{ Text = "|", Align = Label.AlignMode.Center });
|
infoGrid.AddChild(new Label { Text = "|", Align = Label.AlignMode.Center });
|
||||||
_enemyInfoLabel = new Label {Align = Label.AlignMode.Center};
|
_enemyInfoLabel = new Label { Align = Label.AlignMode.Center };
|
||||||
infoGrid.AddChild(_enemyInfoLabel);
|
infoGrid.AddChild(_enemyInfoLabel);
|
||||||
var centerContainer = new CenterContainer();
|
var centerContainer = new CenterContainer();
|
||||||
centerContainer.AddChild(infoGrid);
|
centerContainer.AddChild(infoGrid);
|
||||||
grid.AddChild(centerContainer);
|
grid.AddChild(centerContainer);
|
||||||
|
|
||||||
_playerActionLabel = new Label {Align = Label.AlignMode.Center};
|
_playerActionLabel = new Label { Align = Label.AlignMode.Center };
|
||||||
grid.AddChild(_playerActionLabel);
|
grid.AddChild(_playerActionLabel);
|
||||||
|
|
||||||
_enemyActionLabel = new Label {Align = Label.AlignMode.Center};
|
_enemyActionLabel = new Label { Align = Label.AlignMode.Center };
|
||||||
grid.AddChild(_enemyActionLabel);
|
grid.AddChild(_enemyActionLabel);
|
||||||
|
|
||||||
var buttonGrid = new GridContainer {Columns = 3};
|
var buttonGrid = new GridContainer { Columns = 3 };
|
||||||
_gameButtons[0] = new ActionButton(Owner, SharedSpaceVillainArcadeComponent.PlayerAction.Attack)
|
_gameButtons[0] = new ActionButton(Owner, SharedSpaceVillainArcadeComponent.PlayerAction.Attack)
|
||||||
{Text = Loc.GetString("spacevillain-menu-button-attack")};
|
{
|
||||||
|
Text = Loc.GetString("spacevillain-menu-button-attack")
|
||||||
|
};
|
||||||
buttonGrid.AddChild(_gameButtons[0]);
|
buttonGrid.AddChild(_gameButtons[0]);
|
||||||
|
|
||||||
_gameButtons[1] = new ActionButton(Owner, SharedSpaceVillainArcadeComponent.PlayerAction.Heal)
|
_gameButtons[1] = new ActionButton(Owner, SharedSpaceVillainArcadeComponent.PlayerAction.Heal)
|
||||||
{Text = Loc.GetString("spacevillain-menu-button-heal")};
|
{
|
||||||
|
Text = Loc.GetString("spacevillain-menu-button-heal")
|
||||||
|
};
|
||||||
buttonGrid.AddChild(_gameButtons[1]);
|
buttonGrid.AddChild(_gameButtons[1]);
|
||||||
|
|
||||||
_gameButtons[2] = new ActionButton(Owner, SharedSpaceVillainArcadeComponent.PlayerAction.Recharge)
|
_gameButtons[2] = new ActionButton(Owner, SharedSpaceVillainArcadeComponent.PlayerAction.Recharge)
|
||||||
{Text = Loc.GetString("spacevillain-menu-button-recharge")};
|
{
|
||||||
|
Text = Loc.GetString("spacevillain-menu-button-recharge")
|
||||||
|
};
|
||||||
buttonGrid.AddChild(_gameButtons[2]);
|
buttonGrid.AddChild(_gameButtons[2]);
|
||||||
|
|
||||||
centerContainer = new CenterContainer();
|
centerContainer = new CenterContainer();
|
||||||
@@ -65,7 +71,9 @@ namespace Content.Client.Arcade
|
|||||||
grid.AddChild(centerContainer);
|
grid.AddChild(centerContainer);
|
||||||
|
|
||||||
var newGame = new ActionButton(Owner, SharedSpaceVillainArcadeComponent.PlayerAction.NewGame)
|
var newGame = new ActionButton(Owner, SharedSpaceVillainArcadeComponent.PlayerAction.NewGame)
|
||||||
{Text = Loc.GetString("spacevillain-menu-button-new-game")};
|
{
|
||||||
|
Text = Loc.GetString("spacevillain-menu-button-new-game")
|
||||||
|
};
|
||||||
grid.AddChild(newGame);
|
grid.AddChild(newGame);
|
||||||
|
|
||||||
Contents.AddChild(grid);
|
Contents.AddChild(grid);
|
||||||
@@ -84,7 +92,8 @@ namespace Content.Client.Arcade
|
|||||||
|
|
||||||
public void UpdateInfo(SharedSpaceVillainArcadeComponent.SpaceVillainArcadeDataUpdateMessage message)
|
public void UpdateInfo(SharedSpaceVillainArcadeComponent.SpaceVillainArcadeDataUpdateMessage message)
|
||||||
{
|
{
|
||||||
if(message is SharedSpaceVillainArcadeComponent.SpaceVillainArcadeMetaDataUpdateMessage metaMessage) UpdateMetadata(metaMessage);
|
if (message is SharedSpaceVillainArcadeComponent.SpaceVillainArcadeMetaDataUpdateMessage metaMessage)
|
||||||
|
UpdateMetadata(metaMessage);
|
||||||
|
|
||||||
_playerInfoLabel.Text = $"HP: {message.PlayerHP} MP: {message.PlayerMP}";
|
_playerInfoLabel.Text = $"HP: {message.PlayerHP} MP: {message.PlayerMP}";
|
||||||
_enemyInfoLabel.Text = $"HP: {message.EnemyHP} MP: {message.EnemyMP}";
|
_enemyInfoLabel.Text = $"HP: {message.EnemyHP} MP: {message.EnemyMP}";
|
||||||
@@ -97,7 +106,7 @@ namespace Content.Client.Arcade
|
|||||||
private readonly SpaceVillainArcadeBoundUserInterface _owner;
|
private readonly SpaceVillainArcadeBoundUserInterface _owner;
|
||||||
private readonly SharedSpaceVillainArcadeComponent.PlayerAction _playerAction;
|
private readonly SharedSpaceVillainArcadeComponent.PlayerAction _playerAction;
|
||||||
|
|
||||||
public ActionButton(SpaceVillainArcadeBoundUserInterface owner,SharedSpaceVillainArcadeComponent.PlayerAction playerAction)
|
public ActionButton(SpaceVillainArcadeBoundUserInterface owner, SharedSpaceVillainArcadeComponent.PlayerAction playerAction)
|
||||||
{
|
{
|
||||||
_owner = owner;
|
_owner = owner;
|
||||||
_playerAction = playerAction;
|
_playerAction = playerAction;
|
||||||
|
|||||||
@@ -1,78 +1,76 @@
|
|||||||
using Content.Shared.Arcade;
|
using Content.Shared.Arcade;
|
||||||
using Robust.Client.GameObjects;
|
using Robust.Client.GameObjects;
|
||||||
using Robust.Shared.GameObjects;
|
|
||||||
|
|
||||||
namespace Content.Client.Arcade.UI
|
namespace Content.Client.Arcade.UI;
|
||||||
|
|
||||||
|
public sealed class BlockGameBoundUserInterface : BoundUserInterface
|
||||||
{
|
{
|
||||||
public sealed class BlockGameBoundUserInterface : BoundUserInterface
|
private BlockGameMenu? _menu;
|
||||||
|
|
||||||
|
public BlockGameBoundUserInterface(ClientUserInterfaceComponent owner, Enum uiKey) : base(owner, uiKey)
|
||||||
{
|
{
|
||||||
private BlockGameMenu? _menu;
|
}
|
||||||
|
|
||||||
public BlockGameBoundUserInterface(ClientUserInterfaceComponent owner, Enum uiKey) : base(owner, uiKey)
|
protected override void Open()
|
||||||
|
{
|
||||||
|
base.Open();
|
||||||
|
|
||||||
|
_menu = new BlockGameMenu(this);
|
||||||
|
_menu.OnClose += Close;
|
||||||
|
_menu.OpenCentered();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void ReceiveMessage(BoundUserInterfaceMessage message)
|
||||||
|
{
|
||||||
|
switch (message)
|
||||||
{
|
{
|
||||||
}
|
case BlockGameMessages.BlockGameVisualUpdateMessage updateMessage:
|
||||||
|
switch (updateMessage.GameVisualType)
|
||||||
protected override void Open()
|
{
|
||||||
{
|
case BlockGameMessages.BlockGameVisualType.GameField:
|
||||||
base.Open();
|
_menu?.UpdateBlocks(updateMessage.Blocks);
|
||||||
|
break;
|
||||||
_menu = new BlockGameMenu(this);
|
case BlockGameMessages.BlockGameVisualType.HoldBlock:
|
||||||
_menu.OnClose += Close;
|
_menu?.UpdateHeldBlock(updateMessage.Blocks);
|
||||||
_menu.OpenCentered();
|
break;
|
||||||
}
|
case BlockGameMessages.BlockGameVisualType.NextBlock:
|
||||||
|
_menu?.UpdateNextBlock(updateMessage.Blocks);
|
||||||
protected override void ReceiveMessage(BoundUserInterfaceMessage message)
|
break;
|
||||||
{
|
}
|
||||||
switch (message)
|
break;
|
||||||
{
|
case BlockGameMessages.BlockGameScoreUpdateMessage scoreUpdate:
|
||||||
case BlockGameMessages.BlockGameVisualUpdateMessage updateMessage:
|
_menu?.UpdatePoints(scoreUpdate.Points);
|
||||||
switch (updateMessage.GameVisualType)
|
break;
|
||||||
{
|
case BlockGameMessages.BlockGameUserStatusMessage userMessage:
|
||||||
case BlockGameMessages.BlockGameVisualType.GameField:
|
_menu?.SetUsability(userMessage.IsPlayer);
|
||||||
_menu?.UpdateBlocks(updateMessage.Blocks);
|
break;
|
||||||
break;
|
case BlockGameMessages.BlockGameSetScreenMessage statusMessage:
|
||||||
case BlockGameMessages.BlockGameVisualType.HoldBlock:
|
if (statusMessage.IsStarted) _menu?.SetStarted();
|
||||||
_menu?.UpdateHeldBlock(updateMessage.Blocks);
|
_menu?.SetScreen(statusMessage.Screen);
|
||||||
break;
|
if (statusMessage is BlockGameMessages.BlockGameGameOverScreenMessage gameOverScreenMessage)
|
||||||
case BlockGameMessages.BlockGameVisualType.NextBlock:
|
_menu?.SetGameoverInfo(gameOverScreenMessage.FinalScore, gameOverScreenMessage.LocalPlacement, gameOverScreenMessage.GlobalPlacement);
|
||||||
_menu?.UpdateNextBlock(updateMessage.Blocks);
|
break;
|
||||||
break;
|
case BlockGameMessages.BlockGameHighScoreUpdateMessage highScoreUpdateMessage:
|
||||||
}
|
_menu?.UpdateHighscores(highScoreUpdateMessage.LocalHighscores,
|
||||||
break;
|
highScoreUpdateMessage.GlobalHighscores);
|
||||||
case BlockGameMessages.BlockGameScoreUpdateMessage scoreUpdate:
|
break;
|
||||||
_menu?.UpdatePoints(scoreUpdate.Points);
|
case BlockGameMessages.BlockGameLevelUpdateMessage levelUpdateMessage:
|
||||||
break;
|
_menu?.UpdateLevel(levelUpdateMessage.Level);
|
||||||
case BlockGameMessages.BlockGameUserStatusMessage userMessage:
|
break;
|
||||||
_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();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void SendAction(BlockGamePlayerAction action)
|
||||||
|
{
|
||||||
|
SendMessage(new BlockGameMessages.BlockGamePlayerActionMessage(action));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
base.Dispose(disposing);
|
||||||
|
if (!disposing)
|
||||||
|
return;
|
||||||
|
|
||||||
|
_menu?.Dispose();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,51 +3,45 @@ using Robust.Shared.GameObjects;
|
|||||||
using Robust.Shared.ViewVariables;
|
using Robust.Shared.ViewVariables;
|
||||||
using static Content.Shared.Arcade.SharedSpaceVillainArcadeComponent;
|
using static Content.Shared.Arcade.SharedSpaceVillainArcadeComponent;
|
||||||
|
|
||||||
namespace Content.Client.Arcade.UI
|
namespace Content.Client.Arcade.UI;
|
||||||
|
|
||||||
|
public sealed class SpaceVillainArcadeBoundUserInterface : BoundUserInterface
|
||||||
{
|
{
|
||||||
public sealed class SpaceVillainArcadeBoundUserInterface : BoundUserInterface
|
[ViewVariables] private SpaceVillainArcadeMenu? _menu;
|
||||||
|
|
||||||
|
//public SharedSpaceVillainArcadeComponent SpaceVillainArcade;
|
||||||
|
|
||||||
|
public SpaceVillainArcadeBoundUserInterface(ClientUserInterfaceComponent owner, Enum uiKey) : base(owner, uiKey)
|
||||||
{
|
{
|
||||||
[ViewVariables] private SpaceVillainArcadeMenu? _menu;
|
SendAction(PlayerAction.RequestData);
|
||||||
|
}
|
||||||
|
|
||||||
//public SharedSpaceVillainArcadeComponent SpaceVillainArcade;
|
public void SendAction(PlayerAction action)
|
||||||
|
{
|
||||||
|
SendMessage(new SpaceVillainArcadePlayerActionMessage(action));
|
||||||
|
}
|
||||||
|
|
||||||
public SpaceVillainArcadeBoundUserInterface(ClientUserInterfaceComponent owner, Enum uiKey) : base(owner, uiKey)
|
protected override void Open()
|
||||||
{
|
{
|
||||||
SendAction(PlayerAction.RequestData);
|
base.Open();
|
||||||
}
|
|
||||||
|
|
||||||
public void SendAction(PlayerAction action)
|
_menu = new SpaceVillainArcadeMenu(this);
|
||||||
{
|
|
||||||
SendMessage(new SpaceVillainArcadePlayerActionMessage(action));
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void Open()
|
_menu.OnClose += Close;
|
||||||
{
|
_menu.OpenCentered();
|
||||||
base.Open();
|
}
|
||||||
|
|
||||||
/*if(!Owner.Owner.TryGetComponent(out SharedSpaceVillainArcadeComponent spaceVillainArcade))
|
protected override void ReceiveMessage(BoundUserInterfaceMessage message)
|
||||||
{
|
{
|
||||||
return;
|
if (message is SpaceVillainArcadeDataUpdateMessage msg)
|
||||||
}
|
_menu?.UpdateInfo(msg);
|
||||||
|
}
|
||||||
|
|
||||||
SpaceVillainArcade = spaceVillainArcade;*/
|
protected override void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
base.Dispose(disposing);
|
||||||
|
|
||||||
_menu = new SpaceVillainArcadeMenu(this);
|
if (disposing)
|
||||||
|
_menu?.Dispose();
|
||||||
_menu.OnClose += Close;
|
|
||||||
_menu.OpenCentered();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void ReceiveMessage(BoundUserInterfaceMessage message)
|
|
||||||
{
|
|
||||||
if (message is SpaceVillainArcadeDataUpdateMessage msg) _menu?.UpdateInfo(msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void Dispose(bool disposing)
|
|
||||||
{
|
|
||||||
base.Dispose(disposing);
|
|
||||||
|
|
||||||
if (disposing) _menu?.Dispose();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,17 +0,0 @@
|
|||||||
using Content.Server.Arcade.Components;
|
|
||||||
using Content.Server.Power.Components;
|
|
||||||
|
|
||||||
namespace Content.Server.Arcade;
|
|
||||||
|
|
||||||
public sealed partial class ArcadeSystem
|
|
||||||
{
|
|
||||||
private void InitializeBlockGame()
|
|
||||||
{
|
|
||||||
SubscribeLocalEvent<BlockGameArcadeComponent, PowerChangedEvent>(OnBlockPowerChanged);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void OnBlockPowerChanged(EntityUid uid, BlockGameArcadeComponent component, ref PowerChangedEvent args)
|
|
||||||
{
|
|
||||||
component.OnPowerStateChanged(args);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
using Content.Server.Arcade.Components;
|
|
||||||
using Content.Server.Power.Components;
|
|
||||||
|
|
||||||
namespace Content.Server.Arcade;
|
|
||||||
|
|
||||||
public sealed partial class ArcadeSystem
|
|
||||||
{
|
|
||||||
private void InitializeSpaceVillain()
|
|
||||||
{
|
|
||||||
SubscribeLocalEvent<SpaceVillainArcadeComponent, PowerChangedEvent>(OnSVillainPower);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnSVillainPower(EntityUid uid, SpaceVillainArcadeComponent component, ref PowerChangedEvent args)
|
|
||||||
{
|
|
||||||
component.OnPowerStateChanged(args);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Content.Server.Arcade.Components;
|
|
||||||
using Content.Server.UserInterface;
|
using Content.Server.UserInterface;
|
||||||
using Content.Shared.Arcade;
|
using Content.Shared.Arcade;
|
||||||
using Robust.Shared.Utility;
|
using Robust.Shared.Utility;
|
||||||
@@ -17,25 +16,6 @@ namespace Content.Server.Arcade
|
|||||||
public override void Initialize()
|
public override void Initialize()
|
||||||
{
|
{
|
||||||
base.Initialize();
|
base.Initialize();
|
||||||
SubscribeLocalEvent<BlockGameArcadeComponent, AfterActivatableUIOpenEvent>(OnAfterUIOpen);
|
|
||||||
SubscribeLocalEvent<SpaceVillainArcadeComponent, AfterActivatableUIOpenEvent>(OnAfterUIOpenSV);
|
|
||||||
SubscribeLocalEvent<BlockGameArcadeComponent, BoundUIClosedEvent>((_,c,args) => c.UnRegisterPlayerSession((IPlayerSession)args.Session));
|
|
||||||
InitializeBlockGame();
|
|
||||||
InitializeSpaceVillain();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnAfterUIOpen(EntityUid uid, BlockGameArcadeComponent component, AfterActivatableUIOpenEvent args)
|
|
||||||
{
|
|
||||||
var actor = Comp<ActorComponent>(args.User);
|
|
||||||
if (component.UserInterface?.SessionHasOpen(actor.PlayerSession) == true)
|
|
||||||
{
|
|
||||||
component.RegisterPlayerSession(actor.PlayerSession);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnAfterUIOpenSV(EntityUid uid, SpaceVillainArcadeComponent component, AfterActivatableUIOpenEvent args)
|
|
||||||
{
|
|
||||||
component.Game ??= new SpaceVillainArcadeComponent.SpaceVillainGame(component);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public HighScorePlacement RegisterHighScore(string name, int score)
|
public HighScorePlacement RegisterHighScore(string name, int score)
|
||||||
@@ -87,14 +67,6 @@ namespace Content.Server.Arcade
|
|||||||
return placement;
|
return placement;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Update(float frameTime)
|
|
||||||
{
|
|
||||||
foreach (var comp in EntityManager.EntityQuery<BlockGameArcadeComponent>())
|
|
||||||
{
|
|
||||||
comp.DoGameTick(frameTime);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public readonly struct HighScorePlacement
|
public readonly struct HighScorePlacement
|
||||||
{
|
{
|
||||||
public readonly int? GlobalPlacement;
|
public readonly int? GlobalPlacement;
|
||||||
|
|||||||
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,867 +0,0 @@
|
|||||||
using System.Linq;
|
|
||||||
using Content.Server.Power.Components;
|
|
||||||
using Content.Server.UserInterface;
|
|
||||||
using Content.Shared.Arcade;
|
|
||||||
using Robust.Server.GameObjects;
|
|
||||||
using Robust.Server.Player;
|
|
||||||
using Robust.Shared.Random;
|
|
||||||
|
|
||||||
namespace Content.Server.Arcade.Components
|
|
||||||
{
|
|
||||||
[RegisterComponent]
|
|
||||||
public sealed class BlockGameArcadeComponent : Component
|
|
||||||
{
|
|
||||||
[Dependency] private readonly IRobustRandom _random = default!;
|
|
||||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
|
||||||
|
|
||||||
public bool Powered => _entityManager.TryGetComponent<ApcPowerReceiverComponent>(Owner, out var powerReceiverComponent) && powerReceiverComponent.Powered;
|
|
||||||
public BoundUserInterface? UserInterface => Owner.GetUIOrNull(BlockGameUiKey.Key);
|
|
||||||
|
|
||||||
private BlockGame? _game;
|
|
||||||
|
|
||||||
private IPlayerSession? _player;
|
|
||||||
private readonly List<IPlayerSession> _spectators = new();
|
|
||||||
|
|
||||||
public 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);
|
|
||||||
}
|
|
||||||
|
|
||||||
public 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);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void Initialize()
|
|
||||||
{
|
|
||||||
base.Initialize();
|
|
||||||
if (UserInterface != null)
|
|
||||||
{
|
|
||||||
UserInterface.OnReceiveMessage += UserInterfaceOnOnReceiveMessage;
|
|
||||||
}
|
|
||||||
_game = new BlockGame(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void OnPowerStateChanged(PowerChangedEvent e)
|
|
||||||
{
|
|
||||||
if (e.Powered) return;
|
|
||||||
|
|
||||||
UserInterface?.CloseAll();
|
|
||||||
_player = null;
|
|
||||||
_spectators.Clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void UserInterfaceOnOnReceiveMessage(ServerBoundUserInterfaceMessage obj)
|
|
||||||
{
|
|
||||||
switch (obj.Message)
|
|
||||||
{
|
|
||||||
case BlockGameMessages.BlockGamePlayerActionMessage playerActionMessage:
|
|
||||||
if (obj.Session != _player) break;
|
|
||||||
|
|
||||||
if (playerActionMessage.PlayerAction == BlockGamePlayerAction.NewGame)
|
|
||||||
{
|
|
||||||
if(_game?.Started == true) _game = new BlockGame(this);
|
|
||||||
_game?.StartGame();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_game?.ProcessInput(playerActionMessage.PlayerAction);
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void DoGameTick(float frameTime)
|
|
||||||
{
|
|
||||||
_game?.GameTick(frameTime);
|
|
||||||
}
|
|
||||||
|
|
||||||
private sealed class BlockGame
|
|
||||||
{
|
|
||||||
//note: field is 10(0 -> 9) wide and 20(0 -> 19) high
|
|
||||||
|
|
||||||
private readonly BlockGameArcadeComponent _component;
|
|
||||||
|
|
||||||
private readonly List<BlockGameBlock> _field = new();
|
|
||||||
|
|
||||||
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 _softDropModifier = 0.1f;
|
|
||||||
|
|
||||||
private float Speed =>
|
|
||||||
-0.03f * Level + 1 * (!_softDropPressed ? 1 : _softDropModifier);
|
|
||||||
|
|
||||||
private const 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 ArcadeSystem.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;
|
|
||||||
_allBlockGamePieces = (BlockGamePieceType[]) Enum.GetValues(typeof(BlockGamePieceType));
|
|
||||||
_internalNextPiece = GetRandomBlockGamePiece(_component._random);
|
|
||||||
InitializeNewBlock();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void SendHighscoreUpdate()
|
|
||||||
{
|
|
||||||
var entitySystem = EntitySystem.Get<ArcadeSystem>();
|
|
||||||
_component.UserInterface?.SendMessage(new BlockGameMessages.BlockGameHighScoreUpdateMessage(entitySystem.GetLocalHighscores(), entitySystem.GetGlobalHighscores()));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void SendHighscoreUpdate(IPlayerSession session)
|
|
||||||
{
|
|
||||||
var entitySystem = EntitySystem.Get<ArcadeSystem>();
|
|
||||||
_component.UserInterface?.SendMessage(new BlockGameMessages.BlockGameHighScoreUpdateMessage(entitySystem.GetLocalHighscores(), entitySystem.GetGlobalHighscores()), session);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void StartGame()
|
|
||||||
{
|
|
||||||
_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;
|
|
||||||
|
|
||||||
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) UpdateAllFieldUI();
|
|
||||||
}
|
|
||||||
|
|
||||||
private float _accumulatedFieldFrameTime;
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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 = GetRandomBlockGamePiece(_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)
|
|
||||||
{
|
|
||||||
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;
|
|
||||||
_component.UserInterface?.SendMessage(new BlockGameMessages.BlockGameSetScreenMessage(BlockGameMessages.BlockGameScreen.Pause, _started));
|
|
||||||
break;
|
|
||||||
case BlockGamePlayerAction.Unpause:
|
|
||||||
if (!_gameOver && _started)
|
|
||||||
{
|
|
||||||
_running = true;
|
|
||||||
_component.UserInterface?.SendMessage(new BlockGameMessages.BlockGameSetScreenMessage(BlockGameMessages.BlockGameScreen.Game));
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case BlockGamePlayerAction.ShowHighscores:
|
|
||||||
_running = false;
|
|
||||||
_component.UserInterface?.SendMessage(new BlockGameMessages.BlockGameSetScreenMessage(BlockGameMessages.BlockGameScreen.Highscores, _started));
|
|
||||||
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 is {Valid: true} playerEntity)
|
|
||||||
{
|
|
||||||
var blockGameSystem = EntitySystem.Get<ArcadeSystem>();
|
|
||||||
|
|
||||||
_highScorePlacement = blockGameSystem.RegisterHighScore(IoCManager.Resolve<IEntityManager>().GetComponent<MetaDataComponent>(playerEntity).EntityName, 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));
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
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 readonly BlockGamePieceType[] _allBlockGamePieces;
|
|
||||||
|
|
||||||
private List<BlockGamePieceType> _blockGamePiecesBuffer = new();
|
|
||||||
|
|
||||||
private BlockGamePiece GetRandomBlockGamePiece(IRobustRandom random)
|
|
||||||
{
|
|
||||||
if (_blockGamePiecesBuffer.Count == 0)
|
|
||||||
{
|
|
||||||
_blockGamePiecesBuffer = _allBlockGamePieces.ToList();
|
|
||||||
}
|
|
||||||
|
|
||||||
var chosenPiece = random.Pick(_blockGamePiecesBuffer);
|
|
||||||
_blockGamePiecesBuffer.Remove(chosenPiece);
|
|
||||||
return BlockGamePiece.GetPiece(chosenPiece);
|
|
||||||
}
|
|
||||||
|
|
||||||
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 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)}}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,389 +0,0 @@
|
|||||||
using Content.Server.Power.Components;
|
|
||||||
using Content.Server.UserInterface;
|
|
||||||
using Content.Shared.Arcade;
|
|
||||||
using Robust.Server.GameObjects;
|
|
||||||
using Robust.Shared.Audio;
|
|
||||||
using Robust.Shared.Player;
|
|
||||||
using Robust.Shared.Prototypes;
|
|
||||||
using Robust.Shared.Random;
|
|
||||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List;
|
|
||||||
|
|
||||||
// TODO: ECS.
|
|
||||||
|
|
||||||
namespace Content.Server.Arcade.Components
|
|
||||||
{
|
|
||||||
[RegisterComponent]
|
|
||||||
public sealed class SpaceVillainArcadeComponent : SharedSpaceVillainArcadeComponent
|
|
||||||
{
|
|
||||||
[Dependency] private readonly IRobustRandom _random = null!;
|
|
||||||
|
|
||||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
|
||||||
private bool Powered => _entityManager.TryGetComponent<ApcPowerReceiverComponent>(Owner, out var powerReceiverComponent) && powerReceiverComponent.Powered;
|
|
||||||
|
|
||||||
[ViewVariables] private BoundUserInterface? UserInterface => Owner.GetUIOrNull(SpaceVillainArcadeUiKey.Key);
|
|
||||||
[ViewVariables] public bool OverflowFlag;
|
|
||||||
[ViewVariables] public bool PlayerInvincibilityFlag;
|
|
||||||
[ViewVariables] public bool EnemyInvincibilityFlag;
|
|
||||||
[ViewVariables] public SpaceVillainGame Game = null!;
|
|
||||||
|
|
||||||
[DataField("newGameSound")] private SoundSpecifier _newGameSound = new SoundPathSpecifier("/Audio/Effects/Arcade/newgame.ogg");
|
|
||||||
[DataField("playerAttackSound")] private SoundSpecifier _playerAttackSound = new SoundPathSpecifier("/Audio/Effects/Arcade/player_attack.ogg");
|
|
||||||
[DataField("playerHealSound")] private SoundSpecifier _playerHealSound = new SoundPathSpecifier("/Audio/Effects/Arcade/player_heal.ogg");
|
|
||||||
[DataField("playerChargeSound")] private SoundSpecifier _playerChargeSound = new SoundPathSpecifier("/Audio/Effects/Arcade/player_charge.ogg");
|
|
||||||
[DataField("winSound")] private SoundSpecifier _winSound = new SoundPathSpecifier("/Audio/Effects/Arcade/win.ogg");
|
|
||||||
[DataField("gameOverSound")] private SoundSpecifier _gameOverSound = new SoundPathSpecifier("/Audio/Effects/Arcade/gameover.ogg");
|
|
||||||
|
|
||||||
[ViewVariables(VVAccess.ReadWrite)]
|
|
||||||
[DataField("possibleFightVerbs")]
|
|
||||||
private List<string> _possibleFightVerbs = new List<string>()
|
|
||||||
{"Defeat", "Annihilate", "Save", "Strike", "Stop", "Destroy", "Robust", "Romance", "Pwn", "Own"};
|
|
||||||
[ViewVariables(VVAccess.ReadWrite)]
|
|
||||||
[DataField("possibleFirstEnemyNames")]
|
|
||||||
private List<string> _possibleFirstEnemyNames = new List<string>(){
|
|
||||||
"the Automatic", "Farmer", "Lord", "Professor", "the Cuban", "the Evil", "the Dread King",
|
|
||||||
"the Space", "Lord", "the Great", "Duke", "General"
|
|
||||||
};
|
|
||||||
[ViewVariables(VVAccess.ReadWrite)]
|
|
||||||
[DataField("possibleLastEnemyNames")]
|
|
||||||
private List<string> _possibleLastEnemyNames = new List<string>()
|
|
||||||
{
|
|
||||||
"Melonoid", "Murdertron", "Sorcerer", "Ruin", "Jeff", "Ectoplasm", "Crushulon", "Uhangoid",
|
|
||||||
"Vhakoid", "Peteoid", "slime", "Griefer", "ERPer", "Lizard Man", "Unicorn"
|
|
||||||
};
|
|
||||||
[ViewVariables(VVAccess.ReadWrite)]
|
|
||||||
[DataField("possibleRewards", customTypeSerializer:typeof(PrototypeIdListSerializer<EntityPrototype>))]
|
|
||||||
private List<string> _possibleRewards = new List<string>()
|
|
||||||
{
|
|
||||||
"ToyMouse", "ToyAi", "ToyNuke", "ToyAssistant", "ToyGriffin", "ToyHonk", "ToyIan",
|
|
||||||
"ToyMarauder", "ToyMauler", "ToyGygax", "ToyOdysseus", "ToyOwlman", "ToyDeathRipley",
|
|
||||||
"ToyPhazon", "ToyFireRipley", "ToyReticence", "ToyRipley", "ToySeraph", "ToyDurand", "ToySkeleton",
|
|
||||||
"FoamCrossbow", "RevolverCapGun", "PlushieHampter", "PlushieLizard", "PlushieAtmosian", "PlushieSpaceLizard",
|
|
||||||
"PlushieNuke", "PlushieCarp", "PlushieRatvar", "PlushieNar", "PlushieSnake", "Basketball", "Football",
|
|
||||||
"PlushieRouny", "PlushieBee", "PlushieSlime", "BalloonCorgi", "ToySword", "CrayonBox", "BoxDonkSoftBox", "BoxCartridgeCap",
|
|
||||||
"HarmonicaInstrument", "OcarinaInstrument", "RecorderInstrument", "GunpetInstrument", "BirdToyInstrument", "PlushieXeno"
|
|
||||||
};
|
|
||||||
|
|
||||||
[DataField("rewardMinAmount")]
|
|
||||||
public int _rewardMinAmount;
|
|
||||||
|
|
||||||
[DataField("rewardMaxAmount")]
|
|
||||||
public int _rewardMaxAmount;
|
|
||||||
|
|
||||||
[ViewVariables(VVAccess.ReadWrite)]
|
|
||||||
public int _rewardAmount = 0;
|
|
||||||
|
|
||||||
protected override void Initialize()
|
|
||||||
{
|
|
||||||
base.Initialize();
|
|
||||||
|
|
||||||
if (UserInterface != null)
|
|
||||||
{
|
|
||||||
UserInterface.OnReceiveMessage += UserInterfaceOnOnReceiveMessage;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Random amount of prizes
|
|
||||||
_rewardAmount = new Random().Next(_rewardMinAmount, _rewardMaxAmount + 1);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public void OnPowerStateChanged(PowerChangedEvent e)
|
|
||||||
{
|
|
||||||
if (e.Powered) return;
|
|
||||||
|
|
||||||
UserInterface?.CloseAll();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void UserInterfaceOnOnReceiveMessage(ServerBoundUserInterfaceMessage serverMsg)
|
|
||||||
{
|
|
||||||
if (!Powered)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (serverMsg.Message is not SpaceVillainArcadePlayerActionMessage msg) return;
|
|
||||||
|
|
||||||
switch (msg.PlayerAction)
|
|
||||||
{
|
|
||||||
case PlayerAction.Attack:
|
|
||||||
Game?.ExecutePlayerAction(msg.PlayerAction);
|
|
||||||
break;
|
|
||||||
case PlayerAction.Heal:
|
|
||||||
Game?.ExecutePlayerAction(msg.PlayerAction);
|
|
||||||
break;
|
|
||||||
case PlayerAction.Recharge:
|
|
||||||
Game?.ExecutePlayerAction(msg.PlayerAction);
|
|
||||||
break;
|
|
||||||
case PlayerAction.NewGame:
|
|
||||||
SoundSystem.Play(_newGameSound.GetSound(), Filter.Pvs(Owner), Owner, AudioParams.Default.WithVolume(-4f));
|
|
||||||
|
|
||||||
Game = new SpaceVillainGame(this);
|
|
||||||
UserInterface?.SendMessage(Game.GenerateMetaDataMessage());
|
|
||||||
break;
|
|
||||||
case PlayerAction.RequestData:
|
|
||||||
UserInterface?.SendMessage(Game.GenerateMetaDataMessage());
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Called when the user wins the game.
|
|
||||||
/// </summary>
|
|
||||||
public void ProcessWin()
|
|
||||||
{
|
|
||||||
if (_rewardAmount > 0)
|
|
||||||
{
|
|
||||||
_entityManager.SpawnEntity(_random.Pick(_possibleRewards), _entityManager.GetComponent<TransformComponent>(Owner).Coordinates);
|
|
||||||
_rewardAmount--;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Picks a fight-verb from the list of possible Verbs.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>A fight-verb.</returns>
|
|
||||||
public string GenerateFightVerb()
|
|
||||||
{
|
|
||||||
return _random.Pick(_possibleFightVerbs);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Generates an enemy-name comprised of a first- and last-name.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>An enemy-name.</returns>
|
|
||||||
public string GenerateEnemyName()
|
|
||||||
{
|
|
||||||
return $"{_random.Pick(_possibleFirstEnemyNames)} {_random.Pick(_possibleLastEnemyNames)}";
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// A Class to handle all the game-logic of the SpaceVillain-game.
|
|
||||||
/// </summary>
|
|
||||||
public sealed class SpaceVillainGame
|
|
||||||
{
|
|
||||||
[Dependency] private readonly IRobustRandom _random = default!;
|
|
||||||
|
|
||||||
[ViewVariables] private readonly SpaceVillainArcadeComponent _owner;
|
|
||||||
|
|
||||||
[ViewVariables] public string Name => $"{_fightVerb} {_enemyName}";
|
|
||||||
[ViewVariables(VVAccess.ReadWrite)] private int _playerHp = 30;
|
|
||||||
[ViewVariables(VVAccess.ReadWrite)] private int _playerHpMax = 30;
|
|
||||||
[ViewVariables(VVAccess.ReadWrite)] private int _playerMp = 10;
|
|
||||||
[ViewVariables(VVAccess.ReadWrite)] private int _playerMpMax = 10;
|
|
||||||
[ViewVariables(VVAccess.ReadWrite)] private int _enemyHp = 45;
|
|
||||||
[ViewVariables(VVAccess.ReadWrite)] private int _enemyHpMax = 45;
|
|
||||||
[ViewVariables(VVAccess.ReadWrite)] private int _enemyMp = 20;
|
|
||||||
[ViewVariables(VVAccess.ReadWrite)] private int _enemyMpMax = 20;
|
|
||||||
[ViewVariables(VVAccess.ReadWrite)] private int _turtleTracker;
|
|
||||||
|
|
||||||
[ViewVariables(VVAccess.ReadWrite)] private readonly string _fightVerb;
|
|
||||||
[ViewVariables(VVAccess.ReadWrite)] private readonly string _enemyName;
|
|
||||||
|
|
||||||
[ViewVariables] private bool _running = true;
|
|
||||||
|
|
||||||
private string _latestPlayerActionMessage = "";
|
|
||||||
private string _latestEnemyActionMessage = "";
|
|
||||||
|
|
||||||
public SpaceVillainGame(SpaceVillainArcadeComponent owner) : this(owner, owner.GenerateFightVerb(), owner.GenerateEnemyName()) { }
|
|
||||||
|
|
||||||
public SpaceVillainGame(SpaceVillainArcadeComponent owner, string fightVerb, string enemyName)
|
|
||||||
{
|
|
||||||
IoCManager.InjectDependencies(this);
|
|
||||||
_owner = owner;
|
|
||||||
//todo defeat the curse secret game mode
|
|
||||||
_fightVerb = fightVerb;
|
|
||||||
_enemyName = enemyName;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Validates all vars incase they overshoot their max-values.
|
|
||||||
/// Does not check if vars surpass 0.
|
|
||||||
/// </summary>
|
|
||||||
private void ValidateVars()
|
|
||||||
{
|
|
||||||
if (_owner.OverflowFlag) return;
|
|
||||||
|
|
||||||
if (_playerHp > _playerHpMax) _playerHp = _playerHpMax;
|
|
||||||
if (_playerMp > _playerMpMax) _playerMp = _playerMpMax;
|
|
||||||
if (_enemyHp > _enemyHpMax) _enemyHp = _enemyHpMax;
|
|
||||||
if (_enemyMp > _enemyMpMax) _enemyMp = _enemyMpMax;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Called by the SpaceVillainArcadeComponent when Userinput is received.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="action">The action the user picked.</param>
|
|
||||||
public void ExecutePlayerAction(PlayerAction action)
|
|
||||||
{
|
|
||||||
if (!_running) return;
|
|
||||||
|
|
||||||
switch (action)
|
|
||||||
{
|
|
||||||
case PlayerAction.Attack:
|
|
||||||
var attackAmount = _random.Next(2, 6);
|
|
||||||
_latestPlayerActionMessage = Loc.GetString("space-villain-game-player-attack-message",
|
|
||||||
("enemyName", _enemyName),
|
|
||||||
("attackAmount", attackAmount));
|
|
||||||
SoundSystem.Play(_owner._playerAttackSound.GetSound(), Filter.Pvs(_owner.Owner), _owner.Owner, AudioParams.Default.WithVolume(-4f));
|
|
||||||
if (!_owner.EnemyInvincibilityFlag)
|
|
||||||
_enemyHp -= attackAmount;
|
|
||||||
_turtleTracker -= _turtleTracker > 0 ? 1 : 0;
|
|
||||||
break;
|
|
||||||
case PlayerAction.Heal:
|
|
||||||
var pointAmount = _random.Next(1, 3);
|
|
||||||
var healAmount = _random.Next(6, 8);
|
|
||||||
_latestPlayerActionMessage = Loc.GetString("space-villain-game-player-heal-message",
|
|
||||||
("magicPointAmount", pointAmount),
|
|
||||||
("healAmount", healAmount));
|
|
||||||
SoundSystem.Play(_owner._playerHealSound.GetSound(), Filter.Pvs(_owner.Owner), _owner.Owner, AudioParams.Default.WithVolume(-4f));
|
|
||||||
if (!_owner.PlayerInvincibilityFlag)
|
|
||||||
_playerMp -= pointAmount;
|
|
||||||
_playerHp += healAmount;
|
|
||||||
_turtleTracker++;
|
|
||||||
break;
|
|
||||||
case PlayerAction.Recharge:
|
|
||||||
var chargeAmount = _random.Next(4, 7);
|
|
||||||
_latestPlayerActionMessage = Loc.GetString("space-villain-game-player-recharge-message", ("regainedPoints", chargeAmount));
|
|
||||||
SoundSystem.Play(_owner._playerChargeSound.GetSound(), Filter.Pvs(_owner.Owner), _owner.Owner, AudioParams.Default.WithVolume(-4f));
|
|
||||||
_playerMp += chargeAmount;
|
|
||||||
_turtleTracker -= _turtleTracker > 0 ? 1 : 0;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!CheckGameConditions())
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
ValidateVars();
|
|
||||||
ExecuteAiAction();
|
|
||||||
|
|
||||||
if (!CheckGameConditions())
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
ValidateVars();
|
|
||||||
UpdateUi();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Checks the Game conditions and Updates the Ui & Plays a sound accordingly.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>A bool indicating if the game should continue.</returns>
|
|
||||||
private bool CheckGameConditions()
|
|
||||||
{
|
|
||||||
if ((_playerHp > 0 && _playerMp > 0) && (_enemyHp <= 0 || _enemyMp <= 0))
|
|
||||||
{
|
|
||||||
_running = false;
|
|
||||||
UpdateUi(Loc.GetString("space-villain-game-player-wins-message"),
|
|
||||||
Loc.GetString("space-villain-game-enemy-dies-message", ("enemyName", _enemyName)),
|
|
||||||
true);
|
|
||||||
SoundSystem.Play(_owner._winSound.GetSound(), Filter.Pvs(_owner.Owner), _owner.Owner, AudioParams.Default.WithVolume(-4f));
|
|
||||||
_owner.ProcessWin();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_playerHp > 0 && _playerMp > 0) return true;
|
|
||||||
|
|
||||||
if ((_enemyHp > 0 && _enemyMp > 0))
|
|
||||||
{
|
|
||||||
_running = false;
|
|
||||||
UpdateUi(Loc.GetString("space-villain-game-player-loses-message"),
|
|
||||||
Loc.GetString("space-villain-game-enemy-cheers-message", ("enemyName", _enemyName)),
|
|
||||||
true);
|
|
||||||
SoundSystem.Play(_owner._gameOverSound.GetSound(), Filter.Pvs(_owner.Owner), _owner.Owner, AudioParams.Default.WithVolume(-4f));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (_enemyHp <= 0 || _enemyMp <= 0)
|
|
||||||
{
|
|
||||||
_running = false;
|
|
||||||
UpdateUi(Loc.GetString("space-villain-game-player-loses-message"),
|
|
||||||
Loc.GetString("space-villain-game-enemy-dies-with-player-message ", ("enemyName", _enemyName)),
|
|
||||||
true);
|
|
||||||
SoundSystem.Play(_owner._gameOverSound.GetSound(), Filter.Pvs(_owner.Owner), _owner.Owner, AudioParams.Default.WithVolume(-4f));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Updates the UI.
|
|
||||||
/// </summary>
|
|
||||||
private void UpdateUi(bool metadata = false)
|
|
||||||
{
|
|
||||||
_owner.UserInterface?.SendMessage(metadata ? GenerateMetaDataMessage() : GenerateUpdateMessage());
|
|
||||||
}
|
|
||||||
|
|
||||||
private void UpdateUi(string message1, string message2, bool metadata = false)
|
|
||||||
{
|
|
||||||
_latestPlayerActionMessage = message1;
|
|
||||||
_latestEnemyActionMessage = message2;
|
|
||||||
UpdateUi(metadata);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Handles the logic of the AI
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>An Enemyaction-message.</returns>
|
|
||||||
private void ExecuteAiAction()
|
|
||||||
{
|
|
||||||
if (_turtleTracker >= 4)
|
|
||||||
{
|
|
||||||
var boomAmount = _random.Next(5, 10);
|
|
||||||
_latestEnemyActionMessage = Loc.GetString("space-villain-game-enemy-throws-bomb-message",
|
|
||||||
("enemyName", _enemyName),
|
|
||||||
("damageReceived", boomAmount));
|
|
||||||
if (_owner.PlayerInvincibilityFlag) return;
|
|
||||||
_playerHp -= boomAmount;
|
|
||||||
_turtleTracker--;
|
|
||||||
}
|
|
||||||
else if (_enemyMp <= 5 && _random.Prob(0.7f))
|
|
||||||
{
|
|
||||||
var stealAmount = _random.Next(2, 3);
|
|
||||||
_latestEnemyActionMessage = Loc.GetString("space-villain-game-enemy-steals-player-power-message",
|
|
||||||
("enemyName", _enemyName),
|
|
||||||
("stolenAmount", stealAmount));
|
|
||||||
if (_owner.PlayerInvincibilityFlag) return;
|
|
||||||
_playerMp -= stealAmount;
|
|
||||||
_enemyMp += stealAmount;
|
|
||||||
}
|
|
||||||
else if (_enemyHp <= 10 && _enemyMp > 4)
|
|
||||||
{
|
|
||||||
_enemyHp += 4;
|
|
||||||
_enemyMp -= 4;
|
|
||||||
_latestEnemyActionMessage = Loc.GetString("space-villain-game-enemy-heals-message",
|
|
||||||
("enemyName", _enemyName),
|
|
||||||
("healedAmount", 4));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var attackAmount = _random.Next(3, 6);
|
|
||||||
_latestEnemyActionMessage =
|
|
||||||
Loc.GetString("space-villain-game-enemy-attacks-message",
|
|
||||||
("enemyName", _enemyName),
|
|
||||||
("damageDealt", attackAmount));
|
|
||||||
if (_owner.PlayerInvincibilityFlag) return;
|
|
||||||
_playerHp -= attackAmount;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Generates a Metadata-message based on the objects values.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>A Metadata-message.</returns>
|
|
||||||
public SpaceVillainArcadeMetaDataUpdateMessage GenerateMetaDataMessage()
|
|
||||||
{
|
|
||||||
return new(_playerHp, _playerMp, _enemyHp, _enemyMp, _latestPlayerActionMessage, _latestEnemyActionMessage, Name, _enemyName, !_running);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Creates an Update-message based on the objects values.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>An Update-Message.</returns>
|
|
||||||
public SpaceVillainArcadeDataUpdateMessage
|
|
||||||
GenerateUpdateMessage()
|
|
||||||
{
|
|
||||||
return new(_playerHp, _playerMp, _enemyHp, _enemyMp, _latestPlayerActionMessage,
|
|
||||||
_latestEnemyActionMessage);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,124 @@
|
|||||||
|
using Content.Shared.Arcade;
|
||||||
|
using Robust.Shared.Audio;
|
||||||
|
using Robust.Shared.Prototypes;
|
||||||
|
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List;
|
||||||
|
|
||||||
|
// TODO: ECS.
|
||||||
|
|
||||||
|
namespace Content.Server.Arcade.SpaceVillain;
|
||||||
|
|
||||||
|
[RegisterComponent]
|
||||||
|
public sealed class SpaceVillainArcadeComponent : SharedSpaceVillainArcadeComponent
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Unused flag that can be hacked via wires.
|
||||||
|
/// Name suggests that it was intended to either make the health/mana values underflow while playing the game or turn the arcade machine into an infinite prize fountain.
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables]
|
||||||
|
public bool OverflowFlag;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The current session of the SpaceVillain game for this arcade machine.
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables]
|
||||||
|
public SpaceVillainGame? Game = null;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The sound played when a new session of the SpaceVillain game is begun.
|
||||||
|
/// </summary>
|
||||||
|
[DataField("newGameSound")]
|
||||||
|
public SoundSpecifier NewGameSound = new SoundPathSpecifier("/Audio/Effects/Arcade/newgame.ogg");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The sound played when the player chooses to attack.
|
||||||
|
/// </summary>
|
||||||
|
[DataField("playerAttackSound")]
|
||||||
|
public SoundSpecifier PlayerAttackSound = new SoundPathSpecifier("/Audio/Effects/Arcade/player_attack.ogg");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The sound played when the player chooses to heal.
|
||||||
|
/// </summary>
|
||||||
|
[DataField("playerHealSound")]
|
||||||
|
public SoundSpecifier PlayerHealSound = new SoundPathSpecifier("/Audio/Effects/Arcade/player_heal.ogg");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The sound played when the player chooses to regain mana.
|
||||||
|
/// </summary>
|
||||||
|
[DataField("playerChargeSound")]
|
||||||
|
public SoundSpecifier PlayerChargeSound = new SoundPathSpecifier("/Audio/Effects/Arcade/player_charge.ogg");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The sound played when the player wins.
|
||||||
|
/// </summary>
|
||||||
|
[DataField("winSound")]
|
||||||
|
public SoundSpecifier WinSound = new SoundPathSpecifier("/Audio/Effects/Arcade/win.ogg");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The sound played when the player loses.
|
||||||
|
/// </summary>
|
||||||
|
[DataField("gameOverSound")]
|
||||||
|
public SoundSpecifier GameOverSound = new SoundPathSpecifier("/Audio/Effects/Arcade/gameover.ogg");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The prefixes that can be used to create the game name.
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables(VVAccess.ReadWrite)]
|
||||||
|
[DataField("possibleFightVerbs")]
|
||||||
|
public List<string> PossibleFightVerbs = new()
|
||||||
|
{"Defeat", "Annihilate", "Save", "Strike", "Stop", "Destroy", "Robust", "Romance", "Pwn", "Own"};
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The first names/titles that can be used to construct the name of the villain.
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables(VVAccess.ReadWrite)]
|
||||||
|
[DataField("possibleFirstEnemyNames")]
|
||||||
|
public List<string> PossibleFirstEnemyNames = new(){
|
||||||
|
"the Automatic", "Farmer", "Lord", "Professor", "the Cuban", "the Evil", "the Dread King",
|
||||||
|
"the Space", "Lord", "the Great", "Duke", "General"
|
||||||
|
};
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The last names that can be used to construct the name of the villain.
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables(VVAccess.ReadWrite)]
|
||||||
|
[DataField("possibleLastEnemyNames")]
|
||||||
|
public List<string> PossibleLastEnemyNames = new()
|
||||||
|
{
|
||||||
|
"Melonoid", "Murdertron", "Sorcerer", "Ruin", "Jeff", "Ectoplasm", "Crushulon", "Uhangoid",
|
||||||
|
"Vhakoid", "Peteoid", "slime", "Griefer", "ERPer", "Lizard Man", "Unicorn"
|
||||||
|
};
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The prototypes that can be dispensed as a reward for winning the game.
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables(VVAccess.ReadWrite)]
|
||||||
|
[DataField("possibleRewards", customTypeSerializer: typeof(PrototypeIdListSerializer<EntityPrototype>))]
|
||||||
|
public List<string> PossibleRewards = new()
|
||||||
|
{
|
||||||
|
"ToyMouse", "ToyAi", "ToyNuke", "ToyAssistant", "ToyGriffin", "ToyHonk", "ToyIan",
|
||||||
|
"ToyMarauder", "ToyMauler", "ToyGygax", "ToyOdysseus", "ToyOwlman", "ToyDeathRipley",
|
||||||
|
"ToyPhazon", "ToyFireRipley", "ToyReticence", "ToyRipley", "ToySeraph", "ToyDurand", "ToySkeleton",
|
||||||
|
"FoamCrossbow", "RevolverCapGun", "PlushieHampter", "PlushieLizard", "PlushieAtmosian", "PlushieSpaceLizard",
|
||||||
|
"PlushieNuke", "PlushieCarp", "PlushieRatvar", "PlushieNar", "PlushieSnake", "Basketball", "Football",
|
||||||
|
"PlushieRouny", "PlushieBee", "PlushieSlime", "BalloonCorgi", "ToySword", "CrayonBox", "BoxDonkSoftBox", "BoxCartridgeCap",
|
||||||
|
"HarmonicaInstrument", "OcarinaInstrument", "RecorderInstrument", "GunpetInstrument", "BirdToyInstrument", "PlushieXeno"
|
||||||
|
};
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The minimum number of prizes the arcade machine can have.
|
||||||
|
/// </summary>
|
||||||
|
[DataField("rewardMinAmount")]
|
||||||
|
public int RewardMinAmount;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The maximum number of prizes the arcade machine can have.
|
||||||
|
/// </summary>
|
||||||
|
[DataField("rewardMaxAmount")]
|
||||||
|
public int RewardMaxAmount;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The remaining number of prizes the arcade machine can dispense.
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables(VVAccess.ReadWrite)]
|
||||||
|
public int RewardAmount = 0;
|
||||||
|
}
|
||||||
@@ -0,0 +1,109 @@
|
|||||||
|
using Content.Server.Power.Components;
|
||||||
|
using Content.Server.UserInterface;
|
||||||
|
using static Content.Shared.Arcade.SharedSpaceVillainArcadeComponent;
|
||||||
|
using Robust.Server.GameObjects;
|
||||||
|
using Robust.Shared.Audio;
|
||||||
|
using Robust.Shared.Random;
|
||||||
|
|
||||||
|
namespace Content.Server.Arcade.SpaceVillain;
|
||||||
|
|
||||||
|
public sealed partial class SpaceVillainArcadeSystem : EntitySystem
|
||||||
|
{
|
||||||
|
[Dependency] private readonly IRobustRandom _random = default!;
|
||||||
|
[Dependency] private readonly SharedAudioSystem _audioSystem = default!;
|
||||||
|
[Dependency] private readonly UserInterfaceSystem _uiSystem = default!;
|
||||||
|
|
||||||
|
public override void Initialize()
|
||||||
|
{
|
||||||
|
base.Initialize();
|
||||||
|
|
||||||
|
SubscribeLocalEvent<SpaceVillainArcadeComponent, ComponentInit>(OnComponentInit);
|
||||||
|
SubscribeLocalEvent<SpaceVillainArcadeComponent, AfterActivatableUIOpenEvent>(OnAfterUIOpenSV);
|
||||||
|
SubscribeLocalEvent<SpaceVillainArcadeComponent, SpaceVillainArcadePlayerActionMessage>(OnSVPlayerAction);
|
||||||
|
SubscribeLocalEvent<SpaceVillainArcadeComponent, PowerChangedEvent>(OnSVillainPower);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Called when the user wins the game.
|
||||||
|
/// Dispenses a prize if the arcade machine has any left.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="uid"></param>
|
||||||
|
/// <param name="arcade"></param>
|
||||||
|
/// <param name="xform"></param>
|
||||||
|
public void ProcessWin(EntityUid uid, SpaceVillainArcadeComponent? arcade = null, TransformComponent? xform = null)
|
||||||
|
{
|
||||||
|
if (!Resolve(uid, ref arcade, ref xform))
|
||||||
|
return;
|
||||||
|
if (arcade.RewardAmount <= 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
EntityManager.SpawnEntity(_random.Pick(arcade.PossibleRewards), xform.Coordinates);
|
||||||
|
arcade.RewardAmount--;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Picks a fight-verb from the list of possible Verbs.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>A fight-verb.</returns>
|
||||||
|
public string GenerateFightVerb(SpaceVillainArcadeComponent arcade)
|
||||||
|
{
|
||||||
|
return _random.Pick(arcade.PossibleFightVerbs);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Generates an enemy-name comprised of a first- and last-name.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>An enemy-name.</returns>
|
||||||
|
public string GenerateEnemyName(SpaceVillainArcadeComponent arcade)
|
||||||
|
{
|
||||||
|
return $"{_random.Pick(arcade.PossibleFirstEnemyNames)} {_random.Pick(arcade.PossibleLastEnemyNames)}";
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnComponentInit(EntityUid uid, SpaceVillainArcadeComponent component, ComponentInit args)
|
||||||
|
{
|
||||||
|
// Random amount of prizes
|
||||||
|
component.RewardAmount = new Random().Next(component.RewardMinAmount, component.RewardMaxAmount + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnSVPlayerAction(EntityUid uid, SpaceVillainArcadeComponent component, SpaceVillainArcadePlayerActionMessage msg)
|
||||||
|
{
|
||||||
|
if (component.Game == null)
|
||||||
|
return;
|
||||||
|
if (!TryComp<ApcPowerReceiverComponent>(uid, out var power) || !power.Powered)
|
||||||
|
return;
|
||||||
|
|
||||||
|
switch (msg.PlayerAction)
|
||||||
|
{
|
||||||
|
case PlayerAction.Attack:
|
||||||
|
case PlayerAction.Heal:
|
||||||
|
case PlayerAction.Recharge:
|
||||||
|
component.Game.ExecutePlayerAction(uid, msg.PlayerAction, component);
|
||||||
|
break;
|
||||||
|
case PlayerAction.NewGame:
|
||||||
|
_audioSystem.PlayPvs(component.NewGameSound, uid, AudioParams.Default.WithVolume(-4f));
|
||||||
|
|
||||||
|
component.Game = new SpaceVillainGame(uid, component, this);
|
||||||
|
if (_uiSystem.TryGetUi(uid, SpaceVillainArcadeUiKey.Key, out var bui))
|
||||||
|
_uiSystem.SendUiMessage(bui, component.Game.GenerateMetaDataMessage());
|
||||||
|
break;
|
||||||
|
case PlayerAction.RequestData:
|
||||||
|
if (_uiSystem.TryGetUi(uid, SpaceVillainArcadeUiKey.Key, out bui))
|
||||||
|
_uiSystem.SendUiMessage(bui, component.Game.GenerateMetaDataMessage());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnAfterUIOpenSV(EntityUid uid, SpaceVillainArcadeComponent component, AfterActivatableUIOpenEvent args)
|
||||||
|
{
|
||||||
|
component.Game ??= new(uid, component, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnSVillainPower(EntityUid uid, SpaceVillainArcadeComponent component, ref PowerChangedEvent args)
|
||||||
|
{
|
||||||
|
if (TryComp<ApcPowerReceiverComponent>(uid, out var power) && power.Powered)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (_uiSystem.TryGetUi(uid, SpaceVillainArcadeUiKey.Key, out var bui))
|
||||||
|
_uiSystem.CloseAll(bui);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
namespace Content.Server.Arcade.SpaceVillain;
|
||||||
|
|
||||||
|
public sealed partial class SpaceVillainGame
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A state holder for the fighters in the SpaceVillain game.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class Fighter
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The current hit point total of the fighter.
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables(VVAccess.ReadWrite)]
|
||||||
|
public int Hp
|
||||||
|
{
|
||||||
|
get => _hp;
|
||||||
|
set => _hp = MathHelper.Clamp(value, 0, HpMax);
|
||||||
|
}
|
||||||
|
private int _hp;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The maximum hit point total of the fighter.
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables(VVAccess.ReadWrite)]
|
||||||
|
public int HpMax
|
||||||
|
{
|
||||||
|
get => _hpMax;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_hpMax = Math.Max(value, 0);
|
||||||
|
Hp = MathHelper.Clamp(Hp, 0, HpMax);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private int _hpMax;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The current mana total of the fighter.
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables(VVAccess.ReadWrite)]
|
||||||
|
public int Mp
|
||||||
|
{
|
||||||
|
get => _mp;
|
||||||
|
set => _mp = MathHelper.Clamp(value, 0, MpMax);
|
||||||
|
}
|
||||||
|
private int _mp;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The maximum mana total of the fighter.
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables(VVAccess.ReadWrite)]
|
||||||
|
public int MpMax
|
||||||
|
{
|
||||||
|
get => _mpMax;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_mpMax = Math.Max(value, 0);
|
||||||
|
Mp = MathHelper.Clamp(Mp, 0, MpMax);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private int _mpMax;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether the given fighter can take damage/lose mana.
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables(VVAccess.ReadWrite)]
|
||||||
|
public bool Invincible = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
using static Content.Shared.Arcade.SharedSpaceVillainArcadeComponent;
|
||||||
|
|
||||||
|
namespace Content.Server.Arcade.SpaceVillain;
|
||||||
|
|
||||||
|
public sealed partial class SpaceVillainGame
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Updates the UI.
|
||||||
|
/// </summary>
|
||||||
|
private void UpdateUi(EntityUid uid, bool metadata = false)
|
||||||
|
{
|
||||||
|
if (_uiSystem.TryGetUi(uid, SpaceVillainArcadeUiKey.Key, out var bui))
|
||||||
|
_uiSystem.SendUiMessage(bui, metadata ? GenerateMetaDataMessage() : GenerateUpdateMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateUi(EntityUid uid, string message1, string message2, bool metadata = false)
|
||||||
|
{
|
||||||
|
_latestPlayerActionMessage = message1;
|
||||||
|
_latestEnemyActionMessage = message2;
|
||||||
|
UpdateUi(uid, metadata);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Generates a Metadata-message based on the objects values.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>A Metadata-message.</returns>
|
||||||
|
public SpaceVillainArcadeMetaDataUpdateMessage GenerateMetaDataMessage()
|
||||||
|
{
|
||||||
|
return new(
|
||||||
|
PlayerChar.Hp, PlayerChar.Mp,
|
||||||
|
VillainChar.Hp, VillainChar.Mp,
|
||||||
|
_latestPlayerActionMessage,
|
||||||
|
_latestEnemyActionMessage,
|
||||||
|
Name,
|
||||||
|
_villainName,
|
||||||
|
!_running
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates an Update-message based on the objects values.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>An Update-Message.</returns>
|
||||||
|
public SpaceVillainArcadeDataUpdateMessage GenerateUpdateMessage()
|
||||||
|
{
|
||||||
|
return new(
|
||||||
|
PlayerChar.Hp, PlayerChar.Mp,
|
||||||
|
VillainChar.Hp, VillainChar.Mp,
|
||||||
|
_latestPlayerActionMessage,
|
||||||
|
_latestEnemyActionMessage
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
252
Content.Server/Arcade/SpaceVillainGame/SpaceVillainGame.cs
Normal file
252
Content.Server/Arcade/SpaceVillainGame/SpaceVillainGame.cs
Normal file
@@ -0,0 +1,252 @@
|
|||||||
|
using static Content.Shared.Arcade.SharedSpaceVillainArcadeComponent;
|
||||||
|
using Robust.Server.GameObjects;
|
||||||
|
using Robust.Shared.Audio;
|
||||||
|
using Robust.Shared.Random;
|
||||||
|
|
||||||
|
namespace Content.Server.Arcade.SpaceVillain;
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A Class to handle all the game-logic of the SpaceVillain-game.
|
||||||
|
/// </summary>
|
||||||
|
public sealed partial class SpaceVillainGame
|
||||||
|
{
|
||||||
|
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||||
|
[Dependency] private readonly IRobustRandom _random = default!;
|
||||||
|
private readonly SharedAudioSystem _audioSystem = default!;
|
||||||
|
private readonly UserInterfaceSystem _uiSystem = default!;
|
||||||
|
private readonly SpaceVillainArcadeSystem _svArcade = default!;
|
||||||
|
|
||||||
|
|
||||||
|
[ViewVariables]
|
||||||
|
private readonly EntityUid _owner = default!;
|
||||||
|
|
||||||
|
[ViewVariables]
|
||||||
|
private bool _running = true;
|
||||||
|
|
||||||
|
[ViewVariables]
|
||||||
|
public string Name => $"{_fightVerb} {_villainName}";
|
||||||
|
|
||||||
|
[ViewVariables]
|
||||||
|
private readonly string _fightVerb;
|
||||||
|
|
||||||
|
[ViewVariables]
|
||||||
|
public readonly Fighter PlayerChar;
|
||||||
|
|
||||||
|
[ViewVariables]
|
||||||
|
private readonly string _villainName;
|
||||||
|
|
||||||
|
[ViewVariables]
|
||||||
|
public readonly Fighter VillainChar;
|
||||||
|
|
||||||
|
[ViewVariables]
|
||||||
|
private int _turtleTracker = 0;
|
||||||
|
|
||||||
|
[ViewVariables]
|
||||||
|
private string _latestPlayerActionMessage = "";
|
||||||
|
|
||||||
|
[ViewVariables]
|
||||||
|
private string _latestEnemyActionMessage = "";
|
||||||
|
|
||||||
|
public SpaceVillainGame(EntityUid owner, SpaceVillainArcadeComponent arcade, SpaceVillainArcadeSystem arcadeSystem)
|
||||||
|
: this(owner, arcade, arcadeSystem, arcadeSystem.GenerateFightVerb(arcade), arcadeSystem.GenerateEnemyName(arcade))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public SpaceVillainGame(EntityUid owner, SpaceVillainArcadeComponent arcade, SpaceVillainArcadeSystem arcadeSystem, string fightVerb, string enemyName)
|
||||||
|
{
|
||||||
|
IoCManager.InjectDependencies(this);
|
||||||
|
_audioSystem = _entityManager.System<SharedAudioSystem>();
|
||||||
|
_uiSystem = _entityManager.System<UserInterfaceSystem>();
|
||||||
|
_svArcade = _entityManager.System<SpaceVillainArcadeSystem>();
|
||||||
|
|
||||||
|
_owner = owner;
|
||||||
|
//todo defeat the curse secret game mode
|
||||||
|
_fightVerb = fightVerb;
|
||||||
|
_villainName = enemyName;
|
||||||
|
|
||||||
|
PlayerChar = new()
|
||||||
|
{
|
||||||
|
HpMax = 30,
|
||||||
|
Hp = 30,
|
||||||
|
MpMax = 10,
|
||||||
|
Mp = 10
|
||||||
|
};
|
||||||
|
|
||||||
|
VillainChar = new()
|
||||||
|
{
|
||||||
|
HpMax = 45,
|
||||||
|
Hp = 45,
|
||||||
|
MpMax = 20,
|
||||||
|
Mp = 20
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Called by the SpaceVillainArcadeComponent when Userinput is received.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="uid">The action the user picked.</param>
|
||||||
|
/// <param name="action">The action the user picked.</param>
|
||||||
|
/// <param name="arcade">The action the user picked.</param>
|
||||||
|
public void ExecutePlayerAction(EntityUid uid, PlayerAction action, SpaceVillainArcadeComponent arcade)
|
||||||
|
{
|
||||||
|
if (!_running)
|
||||||
|
return;
|
||||||
|
|
||||||
|
switch (action)
|
||||||
|
{
|
||||||
|
case PlayerAction.Attack:
|
||||||
|
var attackAmount = _random.Next(2, 6);
|
||||||
|
_latestPlayerActionMessage = Loc.GetString(
|
||||||
|
"space-villain-game-player-attack-message",
|
||||||
|
("enemyName", _villainName),
|
||||||
|
("attackAmount", attackAmount)
|
||||||
|
);
|
||||||
|
_audioSystem.PlayPvs(arcade.PlayerAttackSound, uid, AudioParams.Default.WithVolume(-4f));
|
||||||
|
if (!VillainChar.Invincible)
|
||||||
|
VillainChar.Hp -= attackAmount;
|
||||||
|
_turtleTracker -= _turtleTracker > 0 ? 1 : 0;
|
||||||
|
break;
|
||||||
|
case PlayerAction.Heal:
|
||||||
|
var pointAmount = _random.Next(1, 3);
|
||||||
|
var healAmount = _random.Next(6, 8);
|
||||||
|
_latestPlayerActionMessage = Loc.GetString(
|
||||||
|
"space-villain-game-player-heal-message",
|
||||||
|
("magicPointAmount", pointAmount),
|
||||||
|
("healAmount", healAmount)
|
||||||
|
);
|
||||||
|
_audioSystem.PlayPvs(arcade.PlayerHealSound, uid, AudioParams.Default.WithVolume(-4f));
|
||||||
|
if (!PlayerChar.Invincible)
|
||||||
|
PlayerChar.Mp -= pointAmount;
|
||||||
|
PlayerChar.Hp += healAmount;
|
||||||
|
_turtleTracker++;
|
||||||
|
break;
|
||||||
|
case PlayerAction.Recharge:
|
||||||
|
var chargeAmount = _random.Next(4, 7);
|
||||||
|
_latestPlayerActionMessage = Loc.GetString(
|
||||||
|
"space-villain-game-player-recharge-message",
|
||||||
|
("regainedPoints", chargeAmount)
|
||||||
|
);
|
||||||
|
_audioSystem.PlayPvs(arcade.PlayerChargeSound, uid, AudioParams.Default.WithVolume(-4f));
|
||||||
|
PlayerChar.Mp += chargeAmount;
|
||||||
|
_turtleTracker -= _turtleTracker > 0 ? 1 : 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!CheckGameConditions(uid, arcade))
|
||||||
|
return;
|
||||||
|
|
||||||
|
ExecuteAiAction();
|
||||||
|
|
||||||
|
if (!CheckGameConditions(uid, arcade))
|
||||||
|
return;
|
||||||
|
|
||||||
|
UpdateUi(uid);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Handles the logic of the AI
|
||||||
|
/// </summary>
|
||||||
|
private void ExecuteAiAction()
|
||||||
|
{
|
||||||
|
if (_turtleTracker >= 4)
|
||||||
|
{
|
||||||
|
var boomAmount = _random.Next(5, 10);
|
||||||
|
_latestEnemyActionMessage = Loc.GetString(
|
||||||
|
"space-villain-game-enemy-throws-bomb-message",
|
||||||
|
("enemyName", _villainName),
|
||||||
|
("damageReceived", boomAmount)
|
||||||
|
);
|
||||||
|
if (PlayerChar.Invincible)
|
||||||
|
return;
|
||||||
|
PlayerChar.Hp -= boomAmount;
|
||||||
|
_turtleTracker--;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (VillainChar.Mp <= 5 && _random.Prob(0.7f))
|
||||||
|
{
|
||||||
|
var stealAmount = _random.Next(2, 3);
|
||||||
|
_latestEnemyActionMessage = Loc.GetString(
|
||||||
|
"space-villain-game-enemy-steals-player-power-message",
|
||||||
|
("enemyName", _villainName),
|
||||||
|
("stolenAmount", stealAmount)
|
||||||
|
);
|
||||||
|
if (PlayerChar.Invincible)
|
||||||
|
return;
|
||||||
|
PlayerChar.Mp -= stealAmount;
|
||||||
|
VillainChar.Mp += stealAmount;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (VillainChar.Hp <= 10 && VillainChar.Mp > 4)
|
||||||
|
{
|
||||||
|
VillainChar.Hp += 4;
|
||||||
|
VillainChar.Mp -= 4;
|
||||||
|
_latestEnemyActionMessage = Loc.GetString(
|
||||||
|
"space-villain-game-enemy-heals-message",
|
||||||
|
("enemyName", _villainName),
|
||||||
|
("healedAmount", 4)
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var attackAmount = _random.Next(3, 6);
|
||||||
|
_latestEnemyActionMessage =
|
||||||
|
Loc.GetString(
|
||||||
|
"space-villain-game-enemy-attacks-message",
|
||||||
|
("enemyName", _villainName),
|
||||||
|
("damageDealt", attackAmount)
|
||||||
|
);
|
||||||
|
if (PlayerChar.Invincible)
|
||||||
|
return;
|
||||||
|
PlayerChar.Hp -= attackAmount;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Checks the Game conditions and Updates the Ui & Plays a sound accordingly.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>A bool indicating if the game should continue.</returns>
|
||||||
|
private bool CheckGameConditions(EntityUid uid, SpaceVillainArcadeComponent arcade)
|
||||||
|
{
|
||||||
|
switch (
|
||||||
|
PlayerChar.Hp > 0 && PlayerChar.Mp > 0,
|
||||||
|
VillainChar.Hp > 0 && VillainChar.Mp > 0
|
||||||
|
)
|
||||||
|
{
|
||||||
|
case (true, true):
|
||||||
|
return true;
|
||||||
|
case (true, false):
|
||||||
|
_running = false;
|
||||||
|
UpdateUi(
|
||||||
|
uid,
|
||||||
|
Loc.GetString("space-villain-game-player-wins-message"),
|
||||||
|
Loc.GetString("space-villain-game-enemy-dies-message", ("enemyName", _villainName)),
|
||||||
|
true
|
||||||
|
);
|
||||||
|
_audioSystem.PlayPvs(arcade.WinSound, uid, AudioParams.Default.WithVolume(-4f));
|
||||||
|
_svArcade.ProcessWin(uid, arcade);
|
||||||
|
return false;
|
||||||
|
case (false, true):
|
||||||
|
_running = false;
|
||||||
|
UpdateUi(
|
||||||
|
uid,
|
||||||
|
Loc.GetString("space-villain-game-player-loses-message"),
|
||||||
|
Loc.GetString("space-villain-game-enemy-cheers-message", ("enemyName", _villainName)),
|
||||||
|
true
|
||||||
|
);
|
||||||
|
_audioSystem.PlayPvs(arcade.GameOverSound, uid, AudioParams.Default.WithVolume(-4f));
|
||||||
|
return false;
|
||||||
|
case (false, false):
|
||||||
|
_running = false;
|
||||||
|
UpdateUi(
|
||||||
|
uid,
|
||||||
|
Loc.GetString("space-villain-game-player-loses-message"),
|
||||||
|
Loc.GetString("space-villain-game-enemy-dies-with-player-message ", ("enemyName", _villainName)),
|
||||||
|
true
|
||||||
|
);
|
||||||
|
_audioSystem.PlayPvs(arcade.GameOverSound, uid, AudioParams.Default.WithVolume(-4f));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
using Content.Server.Arcade.Components;
|
using Content.Server.Arcade.SpaceVillain;
|
||||||
using Content.Server.Wires;
|
using Content.Server.Wires;
|
||||||
using Content.Shared.Arcade;
|
using Content.Shared.Arcade;
|
||||||
using Content.Shared.Wires;
|
using Content.Shared.Wires;
|
||||||
@@ -15,23 +15,26 @@ public sealed class ArcadePlayerInvincibleWireAction : BaseToggleWireAction
|
|||||||
|
|
||||||
public override void ToggleValue(EntityUid owner, bool setting)
|
public override void ToggleValue(EntityUid owner, bool setting)
|
||||||
{
|
{
|
||||||
if (EntityManager.TryGetComponent<SpaceVillainArcadeComponent>(owner, out var arcade))
|
if (EntityManager.TryGetComponent<SpaceVillainArcadeComponent>(owner, out var arcade)
|
||||||
|
&& arcade.Game != null)
|
||||||
{
|
{
|
||||||
arcade.PlayerInvincibilityFlag = !setting;
|
arcade.Game.PlayerChar.Invincible = !setting;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public override bool GetValue(EntityUid owner)
|
public override bool GetValue(EntityUid owner)
|
||||||
{
|
{
|
||||||
return EntityManager.TryGetComponent<SpaceVillainArcadeComponent>(owner, out var arcade)
|
return EntityManager.TryGetComponent<SpaceVillainArcadeComponent>(owner, out var arcade)
|
||||||
&& !arcade.PlayerInvincibilityFlag;
|
&& arcade.Game != null
|
||||||
|
&& !arcade.Game.PlayerChar.Invincible;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override StatusLightState? GetLightState(Wire wire)
|
public override StatusLightState? GetLightState(Wire wire)
|
||||||
{
|
{
|
||||||
if (EntityManager.TryGetComponent<SpaceVillainArcadeComponent>(wire.Owner, out var arcade))
|
if (EntityManager.TryGetComponent<SpaceVillainArcadeComponent>(wire.Owner, out var arcade)
|
||||||
|
&& arcade.Game != null)
|
||||||
{
|
{
|
||||||
return arcade.PlayerInvincibilityFlag || arcade.EnemyInvincibilityFlag
|
return arcade.Game.PlayerChar.Invincible || arcade.Game.VillainChar.Invincible
|
||||||
? StatusLightState.BlinkingSlow
|
? StatusLightState.BlinkingSlow
|
||||||
: StatusLightState.On;
|
: StatusLightState.On;
|
||||||
}
|
}
|
||||||
@@ -49,19 +52,24 @@ public sealed class ArcadeEnemyInvincibleWireAction : BaseToggleWireAction
|
|||||||
|
|
||||||
public override void ToggleValue(EntityUid owner, bool setting)
|
public override void ToggleValue(EntityUid owner, bool setting)
|
||||||
{
|
{
|
||||||
if (EntityManager.TryGetComponent<SpaceVillainArcadeComponent>(owner, out var arcade))
|
if (EntityManager.TryGetComponent<SpaceVillainArcadeComponent>(owner, out var arcade)
|
||||||
|
&& arcade.Game != null)
|
||||||
{
|
{
|
||||||
arcade.PlayerInvincibilityFlag = !setting;
|
arcade.Game.VillainChar.Invincible = !setting;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public override bool GetValue(EntityUid owner)
|
public override bool GetValue(EntityUid owner)
|
||||||
{
|
{
|
||||||
return EntityManager.TryGetComponent<SpaceVillainArcadeComponent>(owner, out var arcade)
|
return EntityManager.TryGetComponent<SpaceVillainArcadeComponent>(owner, out var arcade)
|
||||||
&& !arcade.PlayerInvincibilityFlag;
|
&& arcade.Game != null
|
||||||
|
&& !arcade.Game.VillainChar.Invincible;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override StatusLightData? GetStatusLightData(Wire wire) => null;
|
public override StatusLightData? GetStatusLightData(Wire wire)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum ArcadeInvincibilityWireActionKeys : short
|
public enum ArcadeInvincibilityWireActionKeys : short
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using Content.Server.Arcade.Components;
|
using Content.Server.Arcade.SpaceVillain;
|
||||||
using Content.Server.Wires;
|
using Content.Server.Wires;
|
||||||
using Content.Shared.Arcade;
|
using Content.Shared.Arcade;
|
||||||
using Content.Shared.Wires;
|
using Content.Shared.Wires;
|
||||||
@@ -9,7 +9,7 @@ public sealed class ArcadeOverflowWireAction : BaseToggleWireAction
|
|||||||
{
|
{
|
||||||
public override Color Color { get; set; } = Color.Red;
|
public override Color Color { get; set; } = Color.Red;
|
||||||
public override string Name { get; set; } = "wire-name-arcade-overflow";
|
public override string Name { get; set; } = "wire-name-arcade-overflow";
|
||||||
|
|
||||||
public override object? StatusKey { get; } = SharedSpaceVillainArcadeComponent.Indicators.HealthLimiter;
|
public override object? StatusKey { get; } = SharedSpaceVillainArcadeComponent.Indicators.HealthLimiter;
|
||||||
|
|
||||||
public override void ToggleValue(EntityUid owner, bool setting)
|
public override void ToggleValue(EntityUid owner, bool setting)
|
||||||
|
|||||||
@@ -71,7 +71,8 @@ namespace Content.Shared.Arcade
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class BlockGameVector2Extensions{
|
public static class BlockGameVector2Extensions
|
||||||
|
{
|
||||||
public static BlockGameBlock ToBlockGameBlock(this Vector2i vector2, BlockGameBlock.BlockGameBlockColor gameBlockColor)
|
public static BlockGameBlock ToBlockGameBlock(this Vector2i vector2, BlockGameBlock.BlockGameBlockColor gameBlockColor)
|
||||||
{
|
{
|
||||||
return new(vector2, gameBlockColor);
|
return new(vector2, gameBlockColor);
|
||||||
@@ -90,6 +91,5 @@ namespace Content.Shared.Arcade
|
|||||||
{
|
{
|
||||||
return new(-vector.Y, vector.X);
|
return new(-vector.Y, vector.X);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user