mirror of
https://github.com/Impostor/Impostor.git
synced 2026-10-20 01:46:11 +00:00
266 lines
8.7 KiB
C#
266 lines
8.7 KiB
C#
using System;
|
|
using System.Linq;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using Impostor.Api.Games;
|
|
using Impostor.Api.Innersloth;
|
|
using Impostor.Api.Net;
|
|
using Impostor.Hazel;
|
|
using Impostor.Server.Events;
|
|
using Microsoft.Extensions.DependencyInjection;
|
|
using Microsoft.Extensions.Logging;
|
|
|
|
namespace Impostor.Server.Net.State
|
|
{
|
|
internal partial class Game
|
|
{
|
|
private readonly SemaphoreSlim _clientAddLock = new SemaphoreSlim(1, 1);
|
|
|
|
public async ValueTask HandleStartGame(IMessageReader message)
|
|
{
|
|
GameState = GameStates.Starting;
|
|
|
|
using var packet = MessageWriter.Get(MessageType.Reliable);
|
|
message.CopyTo(packet);
|
|
await SendToAllAsync(packet);
|
|
|
|
await _eventManager.CallAsync(new GameStartingEvent(this));
|
|
}
|
|
|
|
public async ValueTask HandleEndGame(IMessageReader message, GameOverReason gameOverReason)
|
|
{
|
|
GameState = GameStates.Ended;
|
|
|
|
// Broadcast end of the game.
|
|
using (var packet = MessageWriter.Get(MessageType.Reliable))
|
|
{
|
|
message.CopyTo(packet);
|
|
await SendToAllAsync(packet);
|
|
}
|
|
|
|
// Put all players in the correct limbo state.
|
|
foreach (var player in _players)
|
|
{
|
|
player.Value.Limbo = LimboStates.PreSpawn;
|
|
}
|
|
|
|
// Delete all PlayerInfo objects
|
|
foreach (var playerInfo in GameNet.GameData.Players.Values.ToArray())
|
|
{
|
|
await DespawnPlayerInfoAsync(playerInfo);
|
|
}
|
|
|
|
await _eventManager.CallAsync(new GameEndedEvent(this, gameOverReason));
|
|
}
|
|
|
|
public async ValueTask HandleAlterGame(IMessageReader message, IClientPlayer sender, bool isPublic)
|
|
{
|
|
IsPublic = isPublic;
|
|
|
|
using var packet = MessageWriter.Get(MessageType.Reliable);
|
|
message.CopyTo(packet);
|
|
await SendToAllExceptAsync(packet, sender.Client.Id);
|
|
|
|
await _eventManager.CallAsync(new GameAlterEvent(this, isPublic));
|
|
}
|
|
|
|
public async ValueTask HandleRemovePlayer(int playerId, DisconnectReason reason)
|
|
{
|
|
await PlayerRemove(playerId);
|
|
|
|
// It's possible that the last player was removed, so check if the game is still around.
|
|
if (GameState == GameStates.Destroyed)
|
|
{
|
|
return;
|
|
}
|
|
|
|
using var packet = MessageWriter.Get(MessageType.Reliable);
|
|
WriteRemovePlayerMessage(packet, false, playerId, reason);
|
|
await SendToAllExceptAsync(packet, playerId);
|
|
}
|
|
|
|
public async ValueTask HandleKickPlayer(int playerId, bool isBan)
|
|
{
|
|
_logger.LogInformation("{0} - Player {1} has left.", Code, playerId);
|
|
|
|
using var message = MessageWriter.Get(MessageType.Reliable);
|
|
|
|
// Send message to everyone that this player was kicked.
|
|
WriteKickPlayerMessage(message, false, playerId, isBan);
|
|
|
|
await SendToAllAsync(message);
|
|
await PlayerRemove(playerId, isBan);
|
|
|
|
// Remove the player from everyone's game.
|
|
WriteRemovePlayerMessage(
|
|
message,
|
|
true,
|
|
playerId,
|
|
isBan ? DisconnectReason.Banned : DisconnectReason.Kicked);
|
|
|
|
await SendToAllExceptAsync(message, playerId);
|
|
}
|
|
|
|
public async ValueTask<GameJoinResult> AddClientAsync(ClientBase client)
|
|
{
|
|
var hasLock = false;
|
|
|
|
try
|
|
{
|
|
hasLock = await _clientAddLock.WaitAsync(TimeSpan.FromMinutes(1));
|
|
|
|
if (hasLock)
|
|
{
|
|
return await AddClientSafeAsync(client);
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
if (hasLock)
|
|
{
|
|
_clientAddLock.Release();
|
|
}
|
|
}
|
|
|
|
return GameJoinResult.FromError(GameJoinError.InvalidClient);
|
|
}
|
|
|
|
private async ValueTask HandleJoinGameNew(ClientPlayer sender, bool isNew)
|
|
{
|
|
_logger.LogInformation("{0} - Player {1} ({2}) is joining.", Code, sender.Client.Name, sender.Client.Id);
|
|
|
|
// Add player to the game.
|
|
if (isNew)
|
|
{
|
|
await PlayerAdd(sender);
|
|
}
|
|
|
|
sender.InitializeSpawnTimeout();
|
|
|
|
using (var message = MessageWriter.Get(MessageType.Reliable))
|
|
{
|
|
WriteJoinedGameMessage(message, false, sender);
|
|
WriteAlterGameMessage(message, false, IsPublic);
|
|
|
|
sender.Limbo = LimboStates.NotLimbo;
|
|
|
|
await SendToAsync(message, sender.Client.Id);
|
|
await BroadcastJoinMessage(message, true, sender);
|
|
}
|
|
}
|
|
|
|
private async ValueTask<GameJoinResult> AddClientSafeAsync(ClientBase client)
|
|
{
|
|
// Check if the IP of the player is banned.
|
|
if (_bannedIps.Contains(client.Connection.EndPoint.Address))
|
|
{
|
|
return GameJoinResult.FromError(GameJoinError.Banned);
|
|
}
|
|
|
|
var player = client.Player;
|
|
|
|
// Check if the player is running the same version as the host
|
|
if (_compatibilityConfig.AllowVersionMixing == false &&
|
|
this.Host != null && client.GameVersion != this.Host.Client.GameVersion)
|
|
{
|
|
var versionCheckResult = _compatibilityManager.CanJoinGame(Host.Client.GameVersion, client.GameVersion);
|
|
if (versionCheckResult != GameJoinError.None)
|
|
{
|
|
return GameJoinResult.FromError(versionCheckResult);
|
|
}
|
|
}
|
|
|
|
if (GameState == GameStates.Starting || GameState == GameStates.Started)
|
|
{
|
|
return GameJoinResult.FromError(GameJoinError.GameStarted);
|
|
}
|
|
|
|
if (GameState == GameStates.Destroyed)
|
|
{
|
|
return GameJoinResult.FromError(GameJoinError.GameDestroyed);
|
|
}
|
|
|
|
// Check if;
|
|
// - The player is already in this game.
|
|
// - The game is full.
|
|
if (player?.Game != this && _players.Count >= Options.MaxPlayers)
|
|
{
|
|
return GameJoinResult.FromError(GameJoinError.GameFull);
|
|
}
|
|
|
|
var isNew = false;
|
|
|
|
if (player == null || player.Game != this)
|
|
{
|
|
var clientPlayer = new ClientPlayer(_serviceProvider.GetRequiredService<ILogger<ClientPlayer>>(), client, this, _timeoutConfig.SpawnTimeout);
|
|
|
|
if (!_clientManager.Validate(client))
|
|
{
|
|
return GameJoinResult.FromError(GameJoinError.InvalidClient);
|
|
}
|
|
|
|
isNew = true;
|
|
player = clientPlayer;
|
|
client.Player = clientPlayer;
|
|
}
|
|
|
|
// Check current player state.
|
|
if (player.Limbo == LimboStates.NotLimbo)
|
|
{
|
|
return GameJoinResult.FromError(GameJoinError.InvalidLimbo);
|
|
}
|
|
|
|
var @event = new GamePlayerJoiningEvent(this, player);
|
|
await _eventManager.CallAsync(@event);
|
|
|
|
if (@event.JoinResult != null && !@event.JoinResult.Value.IsSuccess)
|
|
{
|
|
return @event.JoinResult.Value;
|
|
}
|
|
|
|
if (GameState == GameStates.Ended)
|
|
{
|
|
await HandleJoinGameNext(player, isNew);
|
|
return GameJoinResult.CreateSuccess(player);
|
|
}
|
|
|
|
await HandleJoinGameNew(player, isNew);
|
|
return GameJoinResult.CreateSuccess(player);
|
|
}
|
|
|
|
private async ValueTask HandleJoinGameNext(ClientPlayer sender, bool isNew)
|
|
{
|
|
_logger.LogInformation("{0} - Player {1} ({2}) is rejoining.", Code, sender.Client.Name, sender.Client.Id);
|
|
|
|
// Add player to the game.
|
|
if (isNew)
|
|
{
|
|
await PlayerAdd(sender);
|
|
}
|
|
|
|
// Check if the host joined and let everyone join.
|
|
if (sender.Client.Id == HostId)
|
|
{
|
|
GameState = GameStates.NotStarted;
|
|
|
|
// Spawn the host.
|
|
await HandleJoinGameNew(sender, false);
|
|
|
|
// Pull players out of limbo.
|
|
await CheckLimboPlayers();
|
|
return;
|
|
}
|
|
|
|
sender.Limbo = LimboStates.WaitingForHost;
|
|
|
|
using (var packet = MessageWriter.Get(MessageType.Reliable))
|
|
{
|
|
WriteWaitForHostMessage(packet, false, sender);
|
|
|
|
await SendToAsync(packet, sender.Client.Id);
|
|
await BroadcastJoinMessage(packet, true, sender);
|
|
}
|
|
}
|
|
}
|
|
}
|