Re-organize all projects (#4166)

This commit is contained in:
DrSmugleaf
2021-06-09 22:19:39 +02:00
committed by GitHub
parent 9f50e4061b
commit ff1a2d97ea
1773 changed files with 5258 additions and 5508 deletions

View File

@@ -0,0 +1,898 @@
#nullable enable
using System;
using System.Collections.Generic;
using System.Linq;
using Content.Server.Power.Components;
using Content.Server.UserInterface;
using Content.Shared.ActionBlocker;
using Content.Shared.Arcade;
using Content.Shared.Interaction;
using Content.Shared.NetIDs;
using Robust.Server.GameObjects;
using Robust.Server.Player;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Maths;
using Robust.Shared.Random;
namespace Content.Server.Arcade.Components
{
[RegisterComponent]
[ComponentReference(typeof(IActivate))]
public class BlockGameArcadeComponent : Component, IActivate
{
[Dependency] private readonly IRobustRandom _random = default!;
public override string Name => "BlockGameArcade";
public override uint? NetID => ContentNetIDs.BLOCKGAME_ARCADE;
[ComponentDependency] private readonly PowerReceiverComponent? _powerReceiverComponent = default!;
private bool Powered => _powerReceiverComponent?.Powered ?? false;
private BoundUserInterface? UserInterface => Owner.GetUIOrNull(BlockGameUiKey.Key);
private BlockGame? _game;
private IPlayerSession? _player;
private readonly List<IPlayerSession> _spectators = new();
public override void HandleMessage(ComponentMessage message, IComponent? component)
{
base.HandleMessage(message, component);
switch (message)
{
case PowerChangedMessage powerChanged:
OnPowerStateChanged(powerChanged);
break;
}
}
void IActivate.Activate(ActivateEventArgs eventArgs)
{
if(!eventArgs.User.TryGetComponent(out ActorComponent? actor))
{
return;
}
if (!Powered)
{
return;
}
if(!ActionBlockerSystem.CanInteract(actor.PlayerSession.AttachedEntity)) return;
UserInterface?.Toggle(actor.PlayerSession);
RegisterPlayerSession(actor.PlayerSession);
}
private void RegisterPlayerSession(IPlayerSession session)
{
if (_player == null) _player = session;
else _spectators.Add(session);
UpdatePlayerStatus(session);
_game?.UpdateNewPlayerUI(session);
}
private void DeactivePlayer(IPlayerSession session)
{
if (_player != session) return;
var temp = _player;
_player = null;
if (_spectators.Count != 0)
{
_player = _spectators[0];
_spectators.Remove(_player);
UpdatePlayerStatus(_player);
}
_spectators.Add(temp);
UpdatePlayerStatus(temp);
}
private void UnRegisterPlayerSession(IPlayerSession session)
{
if (_player == session)
{
DeactivePlayer(_player);
}
else
{
_spectators.Remove(session);
UpdatePlayerStatus(session);
}
}
private void UpdatePlayerStatus(IPlayerSession session)
{
UserInterface?.SendMessage(new BlockGameMessages.BlockGameUserStatusMessage(_player == session), session);
}
public override void Initialize()
{
base.Initialize();
if (UserInterface != null)
{
UserInterface.OnReceiveMessage += UserInterfaceOnOnReceiveMessage;
UserInterface.OnClosed += UnRegisterPlayerSession;
}
_game = new BlockGame(this);
}
private void OnPowerStateChanged(PowerChangedMessage 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 (!ActionBlockerSystem.CanInteract(Owner))
{
DeactivePlayer(obj.Session);
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 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 BlockGameSystem.HighScorePlacement? _highScorePlacement = null;
private void SendPointsUpdate()
{
_component.UserInterface?.SendMessage(new BlockGameMessages.BlockGameScoreUpdateMessage(Points));
}
private void SendPointsUpdate(IPlayerSession session)
{
_component.UserInterface?.SendMessage(new BlockGameMessages.BlockGameScoreUpdateMessage(Points));
}
public int Level
{
get => _level;
set
{
_level = value;
SendLevelUpdate();
}
}
private int _level = 0;
private void SendLevelUpdate()
{
_component.UserInterface?.SendMessage(new BlockGameMessages.BlockGameLevelUpdateMessage(Level));
}
private void SendLevelUpdate(IPlayerSession session)
{
_component.UserInterface?.SendMessage(new BlockGameMessages.BlockGameLevelUpdateMessage(Level));
}
private int ClearedLines
{
get => _clearedLines;
set
{
_clearedLines = value;
if (_clearedLines < LevelRequirement) return;
_clearedLines -= LevelRequirement;
Level++;
}
}
private int _clearedLines = 0;
private int LevelRequirement => Math.Min(100, Math.Max(Level * 10 - 50, 10));
public BlockGame(BlockGameArcadeComponent component)
{
_component = component;
_allBlockGamePieces = (BlockGamePieceType[]) Enum.GetValues(typeof(BlockGamePieceType));
_internalNextPiece = GetRandomBlockGamePiece(_component._random);
InitializeNewBlock();
}
private void SendHighscoreUpdate()
{
var entitySystem = EntitySystem.Get<BlockGameSystem>();
_component.UserInterface?.SendMessage(new BlockGameMessages.BlockGameHighScoreUpdateMessage(entitySystem.GetLocalHighscores(), entitySystem.GetGlobalHighscores()));
}
private void SendHighscoreUpdate(IPlayerSession session)
{
var entitySystem = EntitySystem.Get<BlockGameSystem>();
_component.UserInterface?.SendMessage(new BlockGameMessages.BlockGameHighScoreUpdateMessage(entitySystem.GetLocalHighscores(), entitySystem.GetGlobalHighscores()), session);
}
public void StartGame()
{
_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;
var checkTime = 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 != null)
{
var blockGameSystem = EntitySystem.Get<BlockGameSystem>();
_highScorePlacement = blockGameSystem.RegisterHighScore(_component._player.AttachedEntity.Name, Points);
SendHighscoreUpdate();
}
_component.UserInterface?.SendMessage(new BlockGameMessages.BlockGameGameOverScreenMessage(Points, _highScorePlacement?.LocalPlacement, _highScorePlacement?.GlobalPlacement));
}
public void UpdateNewPlayerUI(IPlayerSession session)
{
if (_gameOver)
{
_component.UserInterface?.SendMessage(new BlockGameMessages.BlockGameGameOverScreenMessage(Points, _highScorePlacement?.LocalPlacement, _highScorePlacement?.GlobalPlacement), session);
}
else
{
if (Paused)
{
_component.UserInterface?.SendMessage(new BlockGameMessages.BlockGameSetScreenMessage(BlockGameMessages.BlockGameScreen.Pause, Started), session);
}
else
{
_component.UserInterface?.SendMessage(new BlockGameMessages.BlockGameSetScreenMessage(BlockGameMessages.BlockGameScreen.Game, Started), session);
}
}
FullUpdate(session);
}
public List<BlockGameBlock> ComputeField()
{
var result = new List<BlockGameBlock>();
result.AddRange(_field);
result.AddRange(_currentPiece.Blocks(_currentPiecePosition, _currentRotation));
return result;
}
private enum BlockGamePieceType
{
I,
L,
LInverted,
S,
SInverted,
T,
O
}
private enum BlockGamePieceRotation
{
North,
East,
South,
West
}
private static BlockGamePieceRotation Next(BlockGamePieceRotation rotation, bool inverted)
{
return rotation switch
{
BlockGamePieceRotation.North => inverted ? BlockGamePieceRotation.West : BlockGamePieceRotation.East,
BlockGamePieceRotation.East => inverted ? BlockGamePieceRotation.North : BlockGamePieceRotation.South,
BlockGamePieceRotation.South => inverted ? BlockGamePieceRotation.East : BlockGamePieceRotation.West,
BlockGamePieceRotation.West => inverted ? BlockGamePieceRotation.South : BlockGamePieceRotation.North,
_ => throw new ArgumentOutOfRangeException(nameof(rotation), rotation, null)
};
}
private 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)}}
};
}
}
}
}
}

View File

@@ -0,0 +1,29 @@
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Random;
namespace Content.Server.Arcade.Components
{
[RegisterComponent]
public class RandomArcadeGameComponent : Component, IMapInit
{
public override string Name => "RandomArcade";
public void MapInit()
{
var arcades = new[]
{
"BlockGameArcade",
"SpaceVillainArcade"
};
var entityManager = IoCManager.Resolve<IEntityManager>();
entityManager.SpawnEntity(
IoCManager.Resolve<IRobustRandom>().Pick(arcades),
Owner.Transform.Coordinates);
Owner.Delete();
}
}
}

View File

@@ -0,0 +1,441 @@
#nullable enable
using System.Collections.Generic;
using Content.Server.Power.Components;
using Content.Server.UserInterface;
using Content.Server.VendingMachines;
using Content.Server.Wires.Components;
using Content.Shared.ActionBlocker;
using Content.Shared.Arcade;
using Content.Shared.Interaction;
using Content.Shared.Wires;
using Robust.Server.GameObjects;
using Robust.Shared.Audio;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
using Robust.Shared.Maths;
using Robust.Shared.Player;
using Robust.Shared.Random;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.ViewVariables;
namespace Content.Server.Arcade.Components
{
[RegisterComponent]
[ComponentReference(typeof(IActivate))]
public class SpaceVillainArcadeComponent : SharedSpaceVillainArcadeComponent, IActivate, IWires
{
[Dependency] private readonly IRobustRandom _random = null!;
[ComponentDependency] private readonly PowerReceiverComponent? _powerReceiverComponent = default!;
[ComponentDependency] private readonly WiresComponent? _wiresComponent = default!;
private bool Powered => _powerReceiverComponent != null && _powerReceiverComponent.Powered;
[ViewVariables] private BoundUserInterface? UserInterface => Owner.GetUIOrNull(SpaceVillainArcadeUiKey.Key);
[ViewVariables] private bool _overflowFlag;
[ViewVariables] private bool _playerInvincibilityFlag;
[ViewVariables] private bool _enemyInvincibilityFlag;
[ViewVariables] private SpaceVillainGame _game = null!;
[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")] 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"
};
void IActivate.Activate(ActivateEventArgs eventArgs)
{
if(!eventArgs.User.TryGetComponent(out ActorComponent? actor))
{
return;
}
if (!Powered)
{
return;
}
if(!ActionBlockerSystem.CanInteract(actor.PlayerSession.AttachedEntity)) return;
_game ??= new SpaceVillainGame(this);
if (_wiresComponent?.IsPanelOpen == true)
{
_wiresComponent.OpenInterface(actor.PlayerSession);
} else
{
UserInterface?.Toggle(actor.PlayerSession);
}
}
public override void Initialize()
{
base.Initialize();
if (UserInterface != null)
{
UserInterface.OnReceiveMessage += UserInterfaceOnOnReceiveMessage;
}
}
public override void HandleMessage(ComponentMessage message, IComponent? component)
{
base.HandleMessage(message, component);
switch (message)
{
case PowerChangedMessage powerChanged:
OnOnPowerStateChanged(powerChanged);
break;
}
}
private void OnOnPowerStateChanged(PowerChangedMessage 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(Filter.Pvs(Owner), "/Audio/Effects/Arcade/newgame.ogg", Owner, AudioParams.Default.WithVolume(-4f));
_game = new SpaceVillainGame(this);
UserInterface?.SendMessage(_game.GenerateMetaDataMessage());
break;
case PlayerAction.RequestData:
UserInterface?.SendMessage(_game.GenerateMetaDataMessage());
break;
}
}
public enum Wires
{
/// <summary>
/// Disables Max Health&Mana for both Enemy and Player.
/// </summary>
Overflow,
/// <summary>
/// Makes Player Invincible.
/// </summary>
PlayerInvincible,
/// <summary>
/// Makes Enemy Invincible.
/// </summary>
EnemyInvincible
}
public void RegisterWires(WiresComponent.WiresBuilder builder)
{
builder.CreateWire(Wires.Overflow);
builder.CreateWire(Wires.PlayerInvincible);
builder.CreateWire(Wires.EnemyInvincible);
builder.CreateWire(4);
builder.CreateWire(5);
builder.CreateWire(6);
IndicatorUpdate();
}
public void WiresUpdate(WiresUpdateEventArgs args)
{
var wire = (Wires) args.Identifier;
var value = args.Action != SharedWiresComponent.WiresAction.Mend;
switch (wire)
{
case Wires.Overflow:
_overflowFlag = value;
break;
case Wires.PlayerInvincible:
_playerInvincibilityFlag = value;
break;
case Wires.EnemyInvincible:
_enemyInvincibilityFlag = value;
break;
}
IndicatorUpdate();
}
public void IndicatorUpdate()
{
_wiresComponent?.SetStatus(Indicators.HealthManager,
new SharedWiresComponent.StatusLightData(Color.Purple,
_playerInvincibilityFlag || _enemyInvincibilityFlag
? SharedWiresComponent.StatusLightState.BlinkingSlow
: SharedWiresComponent.StatusLightState.On,
"MNGR"));
_wiresComponent?.SetStatus(Indicators.HealthLimiter,
new SharedWiresComponent.StatusLightData(Color.Red,
_overflowFlag
? SharedWiresComponent.StatusLightState.BlinkingSlow
: SharedWiresComponent.StatusLightState.On,
"LIMT"));
}
/// <summary>
/// Called when the user wins the game.
/// </summary>
public void ProcessWin()
{
var entityManager = IoCManager.Resolve<IEntityManager>();
entityManager.SpawnEntity(_random.Pick(_possibleRewards), Owner.Transform.MapPosition);
}
/// <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 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("You attack {0} for {1}!", _enemyName, attackAmount);
SoundSystem.Play(Filter.Pvs(_owner.Owner), "/Audio/Effects/Arcade/player_attack.ogg", _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("You use {0} magic to heal for {1} damage!", pointAmount, healAmount);
SoundSystem.Play(Filter.Pvs(_owner.Owner), "/Audio/Effects/Arcade/player_heal.ogg", _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("You regain {0} points", chargeAmount);
SoundSystem.Play(Filter.Pvs(_owner.Owner), "/Audio/Effects/Arcade/player_charge.ogg", _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("You won!"), Loc.GetString("{0} dies.", _enemyName), true);
SoundSystem.Play(Filter.Pvs(_owner.Owner), "/Audio/Effects/Arcade/win.ogg", _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("You lost!"), Loc.GetString("{0} cheers.", _enemyName), true);
SoundSystem.Play(Filter.Pvs(_owner.Owner), "/Audio/Effects/Arcade/gameover.ogg", _owner.Owner, AudioParams.Default.WithVolume(-4f));
return false;
}
if (_enemyHp <= 0 || _enemyMp <= 0)
{
_running = false;
UpdateUi(Loc.GetString("You lost!"), Loc.GetString("{0} dies, but takes you with him.", _enemyName), true);
SoundSystem.Play(Filter.Pvs(_owner.Owner), "/Audio/Effects/Arcade/gameover.ogg", _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("{0} throws a bomb, exploding you for {1} damage!", _enemyName, 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("{0} steals {1} of your power!", _enemyName, stealAmount);
if (_owner._playerInvincibilityFlag) return;
_playerMp -= stealAmount;
_enemyMp += stealAmount;
}else if (_enemyHp <= 10 && _enemyMp > 4)
{
_enemyHp += 4;
_enemyMp -= 4;
_latestEnemyActionMessage = Loc.GetString("{0} heals for 4 health!", _enemyName);
}
else
{
var attackAmount = _random.Next(3, 6);
_latestEnemyActionMessage =
Loc.GetString("{0} attacks you for {1} damage!", _enemyName, 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);
}
}
}
}