init
This commit is contained in:
955
Assets/TcgEngine/Scripts/GameServer/GameServer.cs
Normal file
955
Assets/TcgEngine/Scripts/GameServer/GameServer.cs
Normal file
@@ -0,0 +1,955 @@
|
||||
using System.Collections.Generic;
|
||||
using TcgEngine.AI;
|
||||
using TcgEngine.Gameplay;
|
||||
using Unity.Netcode;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Events;
|
||||
|
||||
namespace TcgEngine.Server
|
||||
{
|
||||
/// <summary>
|
||||
/// Represent one game on the server, when playing solo this will be created locally,
|
||||
/// or if online multiple GameServer, one for each match, will be created by the dedicated server
|
||||
/// Manage receiving actions, sending refresh, and running AI
|
||||
/// </summary>
|
||||
|
||||
public class GameServer
|
||||
{
|
||||
public string game_uid; //Game unique ID
|
||||
public int nb_players = 2;
|
||||
|
||||
public static float game_expire_time = 30f; //How long for the game to be deleted when no one is connected
|
||||
public static float win_expire_time = 60f; //How long for a player to be declared winnner if hes the only one connected
|
||||
|
||||
private Game game_data;
|
||||
private GameLogic gameplay;
|
||||
private float expiration = 0f;
|
||||
private float win_expiration = 0f;
|
||||
private bool is_dedicated_server = false;
|
||||
|
||||
private List<ClientData> players = new List<ClientData>(); //Exclude observers, stays in array when disconnected, only players can send commands
|
||||
private List<ClientData> connected_clients = new List<ClientData>(); //Include obervers, removed from array when disconnected, all clients receive refreshes
|
||||
private List<AIPlayer> ai_list = new List<AIPlayer>(); //List of all AI players
|
||||
private Queue<QueuedGameAction> queued_actions = new Queue<QueuedGameAction>(); //List of action waiting to be processed
|
||||
|
||||
private Dictionary<ushort, CommandEvent> registered_commands = new Dictionary<ushort, CommandEvent>();
|
||||
|
||||
public GameServer(string uid, int players, bool online)
|
||||
{
|
||||
Init(uid, players, online);
|
||||
}
|
||||
|
||||
~GameServer()
|
||||
{
|
||||
Clear();
|
||||
}
|
||||
|
||||
protected virtual void Init(string uid, int players, bool online)
|
||||
{
|
||||
game_uid = uid;
|
||||
nb_players = Mathf.Max(players, 2);
|
||||
is_dedicated_server = online;
|
||||
game_data = new Game(uid, nb_players);
|
||||
gameplay = new GameLogic(game_data);
|
||||
|
||||
//Commands
|
||||
RegisterAction(GameAction.PlayerSettings, ReceivePlayerSettings);
|
||||
RegisterAction(GameAction.PlayerSettingsAI, ReceivePlayerSettingsAI);
|
||||
RegisterAction(GameAction.GameSettings, ReceiveGameplaySettings);
|
||||
RegisterAction(GameAction.PlayCard, ReceivePlayCard);
|
||||
RegisterAction(GameAction.Attack, ReceiveAttackTarget);
|
||||
RegisterAction(GameAction.AttackPlayer, ReceiveAttackPlayer);
|
||||
RegisterAction(GameAction.Move, ReceiveMove);
|
||||
RegisterAction(GameAction.CastAbility, ReceiveCastCardAbility);
|
||||
RegisterAction(GameAction.SelectCard, ReceiveSelectCard);
|
||||
RegisterAction(GameAction.SelectPlayer, ReceiveSelectPlayer);
|
||||
RegisterAction(GameAction.SelectSlot, ReceiveSelectSlot);
|
||||
RegisterAction(GameAction.SelectChoice, ReceiveSelectChoice);
|
||||
RegisterAction(GameAction.SelectCost, ReceiveSelectCost);
|
||||
RegisterAction(GameAction.SelectMulligan, ReceiveSelectMulligan);
|
||||
RegisterAction(GameAction.CancelSelect, ReceiveCancelSelection);
|
||||
RegisterAction(GameAction.EndTurn, ReceiveEndTurn);
|
||||
RegisterAction(GameAction.Resign, ReceiveResign);
|
||||
RegisterAction(GameAction.ChatMessage, ReceiveChat);
|
||||
|
||||
//Events
|
||||
gameplay.onGameStart += OnGameStart;
|
||||
gameplay.onGameEnd += OnGameEnd;
|
||||
gameplay.onTurnStart += OnTurnStart;
|
||||
gameplay.onRefresh += RefreshAll;
|
||||
|
||||
gameplay.onCardPlayed += OnCardPlayed;
|
||||
gameplay.onCardSummoned += OnCardSummoned;
|
||||
gameplay.onCardMoved += OnCardMoved;
|
||||
gameplay.onCardTransformed += OnCardTransformed;
|
||||
gameplay.onCardDiscarded += OnCardDiscarded;
|
||||
gameplay.onCardDrawn += OnCardDraw;
|
||||
gameplay.onRollValue += OnValueRolled;
|
||||
|
||||
gameplay.onAbilityStart += OnAbilityStart;
|
||||
gameplay.onAbilityTargetCard += OnAbilityTargetCard;
|
||||
gameplay.onAbilityTargetPlayer += OnAbilityTargetPlayer;
|
||||
gameplay.onAbilityTargetSlot += OnAbilityTargetSlot;
|
||||
gameplay.onAbilityEnd += OnAbilityEnd;
|
||||
|
||||
gameplay.onAttackStart += OnAttackStart;
|
||||
gameplay.onAttackEnd += OnAttackEnd;
|
||||
gameplay.onAttackPlayerStart += OnAttackPlayerStart;
|
||||
gameplay.onAttackPlayerEnd += OnAttackPlayerEnd;
|
||||
|
||||
gameplay.onCardDamaged += OnCardDamaged;
|
||||
gameplay.onPlayerDamaged += OnPlayerDamaged;
|
||||
gameplay.onCardHealed += OnCardHealed;
|
||||
gameplay.onPlayerHealed += OnPlayerHealed ;
|
||||
|
||||
gameplay.onSecretTrigger += OnSecretTriggered;
|
||||
gameplay.onSecretResolve += OnSecretResolved;
|
||||
}
|
||||
|
||||
protected virtual void Clear()
|
||||
{
|
||||
gameplay.onGameStart -= OnGameStart;
|
||||
gameplay.onGameEnd -= OnGameEnd;
|
||||
gameplay.onTurnStart -= OnTurnStart;
|
||||
gameplay.onRefresh -= RefreshAll;
|
||||
|
||||
gameplay.onCardPlayed -= OnCardPlayed;
|
||||
gameplay.onCardSummoned -= OnCardSummoned;
|
||||
gameplay.onCardMoved -= OnCardMoved;
|
||||
gameplay.onCardTransformed -= OnCardTransformed;
|
||||
gameplay.onCardDiscarded -= OnCardDiscarded;
|
||||
gameplay.onCardDrawn -= OnCardDraw;
|
||||
gameplay.onRollValue -= OnValueRolled;
|
||||
|
||||
gameplay.onAbilityStart -= OnAbilityStart;
|
||||
gameplay.onAbilityTargetCard -= OnAbilityTargetCard;
|
||||
gameplay.onAbilityTargetPlayer -= OnAbilityTargetPlayer;
|
||||
gameplay.onAbilityTargetSlot -= OnAbilityTargetSlot;
|
||||
gameplay.onAbilityEnd -= OnAbilityEnd;
|
||||
|
||||
gameplay.onAttackStart -= OnAttackStart;
|
||||
gameplay.onAttackEnd -= OnAttackEnd;
|
||||
gameplay.onAttackPlayerStart -= OnAttackPlayerStart;
|
||||
gameplay.onAttackPlayerEnd -= OnAttackPlayerEnd;
|
||||
gameplay.onCardDamaged -= OnCardDamaged;
|
||||
gameplay.onPlayerDamaged -= OnPlayerDamaged;
|
||||
|
||||
gameplay.onSecretTrigger -= OnSecretTriggered;
|
||||
gameplay.onSecretResolve -= OnSecretResolved;
|
||||
}
|
||||
|
||||
public virtual void Update()
|
||||
{
|
||||
//Game Expiration if no one is connected or game ended
|
||||
int connected_players = CountConnectedClients();
|
||||
if (HasGameEnded() || connected_players == 0)
|
||||
expiration += Time.deltaTime;
|
||||
|
||||
//Win expiration if all other players left
|
||||
if (connected_players == 1 && HasGameStarted() && !HasGameEnded())
|
||||
win_expiration += Time.deltaTime;
|
||||
|
||||
if (is_dedicated_server && !HasGameEnded() && IsWinExpired())
|
||||
EndExpiredGame();
|
||||
|
||||
//Timer during game
|
||||
if (game_data.state == GameState.Play && !gameplay.IsResolving())
|
||||
{
|
||||
game_data.turn_timer -= Time.deltaTime;
|
||||
if (game_data.turn_timer <= 0f)
|
||||
{
|
||||
//Time expired during turn
|
||||
gameplay.NextStep();
|
||||
}
|
||||
}
|
||||
|
||||
//Start Game when ready
|
||||
if (game_data.state == GameState.Connecting)
|
||||
{
|
||||
bool all_connected = game_data.AreAllPlayersConnected();
|
||||
bool all_ready = game_data.AreAllPlayersReady();
|
||||
if (all_connected && all_ready)
|
||||
{
|
||||
StartGame();
|
||||
}
|
||||
}
|
||||
|
||||
//Process queued actions
|
||||
if (queued_actions.Count > 0 && !gameplay.IsResolving())
|
||||
{
|
||||
QueuedGameAction action = queued_actions.Dequeue();
|
||||
ExecuteAction(action.type, action.client, action.sdata);
|
||||
}
|
||||
|
||||
//Update game logic
|
||||
gameplay.Update(Time.deltaTime);
|
||||
|
||||
//Update AI
|
||||
foreach (AIPlayer ai in ai_list)
|
||||
{
|
||||
ai.Update();
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void StartGame()
|
||||
{
|
||||
//Setup AI
|
||||
bool ai_vs_ai = !is_dedicated_server && GameplayData.Get().ai_vs_ai;
|
||||
foreach (Player player in game_data.players)
|
||||
{
|
||||
if (player.is_ai || ai_vs_ai)
|
||||
{
|
||||
AIPlayer ai_gameplay = AIPlayer.Create(GameplayData.Get().ai_type, gameplay, player.player_id, player.ai_level);
|
||||
ai_list.Add(ai_gameplay);
|
||||
}
|
||||
}
|
||||
|
||||
//Start Game
|
||||
gameplay.StartGame();
|
||||
}
|
||||
|
||||
//End game when it has expired (only one player is still connected)
|
||||
protected virtual void EndExpiredGame()
|
||||
{
|
||||
Game gdata = gameplay.GetGameData();
|
||||
foreach (Player player in gdata.players)
|
||||
{
|
||||
if (player.IsConnected())
|
||||
{
|
||||
gameplay.EndGame(player.player_id);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//------ Receive Actions -------
|
||||
|
||||
private void RegisterAction(ushort tag, UnityAction<ClientData, SerializedData> callback)
|
||||
{
|
||||
CommandEvent cmdevt = new CommandEvent();
|
||||
cmdevt.tag = tag;
|
||||
cmdevt.callback = callback;
|
||||
registered_commands.Add(tag, cmdevt);
|
||||
}
|
||||
|
||||
public void ReceiveAction(ulong client_id, FastBufferReader reader)
|
||||
{
|
||||
ClientData client = GetClient(client_id);
|
||||
if (client != null)
|
||||
{
|
||||
reader.ReadValueSafe(out ushort type);
|
||||
SerializedData sdata = new SerializedData(reader);
|
||||
if (!gameplay.IsResolving())
|
||||
{
|
||||
//Not resolving, execute now
|
||||
ExecuteAction(type, client, sdata);
|
||||
}
|
||||
else
|
||||
{
|
||||
//Resolving, wait before executing
|
||||
QueuedGameAction action = new QueuedGameAction();
|
||||
action.type = type;
|
||||
action.client = client;
|
||||
action.sdata = sdata;
|
||||
sdata.PreRead();
|
||||
queued_actions.Enqueue(action);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void ExecuteAction(ushort type, ClientData client, SerializedData sdata)
|
||||
{
|
||||
bool found = registered_commands.TryGetValue(type, out CommandEvent command);
|
||||
if(found)
|
||||
command.callback.Invoke(client, sdata);
|
||||
}
|
||||
|
||||
//-------
|
||||
|
||||
public void ReceivePlayerSettings(ClientData iclient, SerializedData sdata)
|
||||
{
|
||||
PlayerSettings msg = sdata.Get<PlayerSettings>();
|
||||
Player player = GetPlayer(iclient);
|
||||
if (player != null && msg != null)
|
||||
{
|
||||
SetPlayerSettings(player.player_id, msg);
|
||||
}
|
||||
}
|
||||
|
||||
public void ReceivePlayerSettingsAI(ClientData iclient, SerializedData sdata)
|
||||
{
|
||||
PlayerSettings msg = sdata.Get<PlayerSettings>();
|
||||
Player player = GetPlayer(iclient);
|
||||
if (player != null && msg != null)
|
||||
{
|
||||
SetPlayerSettingsAI(player.player_id, msg);
|
||||
}
|
||||
}
|
||||
|
||||
public void ReceiveGameplaySettings(ClientData iclient, SerializedData sdata)
|
||||
{
|
||||
GameSettings settings = sdata.Get<GameSettings>();
|
||||
if (settings != null)
|
||||
{
|
||||
SetGameSettings(settings);
|
||||
}
|
||||
}
|
||||
|
||||
public void ReceivePlayCard(ClientData iclient, SerializedData sdata)
|
||||
{
|
||||
MsgPlayCard msg = sdata.Get<MsgPlayCard>();
|
||||
Player player = GetPlayer(iclient);
|
||||
if (player != null && msg != null && game_data.IsPlayerActionTurn(player) && !gameplay.IsResolving())
|
||||
{
|
||||
Card card = player.GetCard(msg.card_uid);
|
||||
if (card != null && card.player_id == player.player_id)
|
||||
gameplay.PlayCard(card, msg.slot);
|
||||
}
|
||||
}
|
||||
|
||||
public void ReceiveAttackTarget(ClientData iclient, SerializedData sdata)
|
||||
{
|
||||
MsgAttack msg = sdata.Get<MsgAttack>();
|
||||
Player player = GetPlayer(iclient);
|
||||
if (player != null && msg != null && game_data.IsPlayerActionTurn(player) && !gameplay.IsResolving())
|
||||
{
|
||||
Card attacker = player.GetCard(msg.attacker_uid);
|
||||
Card target = game_data.GetCard(msg.target_uid);
|
||||
if (attacker != null && target != null && attacker.player_id == player.player_id)
|
||||
{
|
||||
gameplay.AttackTarget(attacker, target);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void ReceiveAttackPlayer(ClientData iclient, SerializedData sdata)
|
||||
{
|
||||
MsgAttackPlayer msg = sdata.Get<MsgAttackPlayer>();
|
||||
Player player = GetPlayer(iclient);
|
||||
if (player != null && msg != null && game_data.IsPlayerActionTurn(player) && !gameplay.IsResolving())
|
||||
{
|
||||
Card attacker = player.GetCard(msg.attacker_uid);
|
||||
Player target = game_data.GetPlayer(msg.target_id);
|
||||
if (attacker != null && target != null && attacker.player_id == player.player_id)
|
||||
{
|
||||
gameplay.AttackPlayer(attacker, target);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void ReceiveMove(ClientData iclient, SerializedData sdata)
|
||||
{
|
||||
MsgPlayCard msg = sdata.Get<MsgPlayCard>();
|
||||
Player player = GetPlayer(iclient);
|
||||
if (player != null && msg != null && game_data.IsPlayerActionTurn(player) && !gameplay.IsResolving())
|
||||
{
|
||||
Card card = player.GetCard(msg.card_uid);
|
||||
if (card != null && card.player_id == player.player_id)
|
||||
gameplay.MoveCard(card, msg.slot);
|
||||
}
|
||||
}
|
||||
|
||||
public void ReceiveCastCardAbility(ClientData iclient, SerializedData sdata)
|
||||
{
|
||||
MsgCastAbility msg = sdata.Get<MsgCastAbility>();
|
||||
Player player = GetPlayer(iclient);
|
||||
if (player != null && msg != null && game_data.IsPlayerActionTurn(player) && !gameplay.IsResolving())
|
||||
{
|
||||
Card card = player.GetCard(msg.caster_uid);
|
||||
AbilityData iability = AbilityData.Get(msg.ability_id);
|
||||
if (card != null && card.player_id == player.player_id)
|
||||
gameplay.CastAbility(card, iability);
|
||||
}
|
||||
}
|
||||
|
||||
public void ReceiveSelectCard(ClientData iclient, SerializedData sdata)
|
||||
{
|
||||
MsgCard msg = sdata.Get<MsgCard>();
|
||||
Player player = GetPlayer(iclient);
|
||||
if (player != null && msg != null && game_data.IsPlayerSelectorTurn(player) && !gameplay.IsResolving())
|
||||
{
|
||||
Card target = game_data.GetCard(msg.card_uid);
|
||||
gameplay.SelectCard(target);
|
||||
}
|
||||
}
|
||||
|
||||
public void ReceiveSelectPlayer(ClientData iclient, SerializedData sdata)
|
||||
{
|
||||
MsgPlayer msg = sdata.Get<MsgPlayer>();
|
||||
Player player = GetPlayer(iclient);
|
||||
if (player != null && msg != null && game_data.IsPlayerSelectorTurn(player) && !gameplay.IsResolving())
|
||||
{
|
||||
Player target = game_data.GetPlayer(msg.player_id);
|
||||
gameplay.SelectPlayer(target);
|
||||
}
|
||||
}
|
||||
|
||||
public void ReceiveSelectSlot(ClientData iclient, SerializedData sdata)
|
||||
{
|
||||
Slot slot = sdata.Get<Slot>();
|
||||
Player player = GetPlayer(iclient);
|
||||
if (player != null && game_data.IsPlayerSelectorTurn(player) && !gameplay.IsResolving())
|
||||
{
|
||||
if(slot != null && slot.IsValid())
|
||||
gameplay.SelectSlot(slot);
|
||||
}
|
||||
}
|
||||
|
||||
public void ReceiveSelectChoice(ClientData iclient, SerializedData sdata)
|
||||
{
|
||||
MsgInt msg = sdata.Get<MsgInt>();
|
||||
Player player = GetPlayer(iclient);
|
||||
if (player != null && msg != null && game_data.IsPlayerSelectorTurn(player) && !gameplay.IsResolving())
|
||||
{
|
||||
gameplay.SelectChoice(msg.value);
|
||||
}
|
||||
}
|
||||
|
||||
public void ReceiveSelectCost(ClientData iclient, SerializedData sdata)
|
||||
{
|
||||
MsgInt msg = sdata.Get<MsgInt>();
|
||||
Player player = GetPlayer(iclient);
|
||||
if (player != null && msg != null && game_data.IsPlayerSelectorTurn(player) && !gameplay.IsResolving())
|
||||
{
|
||||
gameplay.SelectCost(msg.value);
|
||||
}
|
||||
}
|
||||
|
||||
public void ReceiveCancelSelection(ClientData iclient, SerializedData sdata)
|
||||
{
|
||||
Player player = GetPlayer(iclient);
|
||||
if (player != null && game_data.IsPlayerSelectorTurn(player) && !gameplay.IsResolving())
|
||||
{
|
||||
gameplay.CancelSelection();
|
||||
}
|
||||
}
|
||||
|
||||
public void ReceiveSelectMulligan(ClientData iclient, SerializedData sdata)
|
||||
{
|
||||
MsgMulligan msg = sdata.Get<MsgMulligan>();
|
||||
Player player = GetPlayer(iclient);
|
||||
if (player != null && msg != null && game_data.IsPlayerMulliganTurn(player) && !gameplay.IsResolving())
|
||||
{
|
||||
gameplay.Mulligan(player, msg.cards);
|
||||
}
|
||||
}
|
||||
|
||||
public void ReceiveEndTurn(ClientData iclient, SerializedData sdata)
|
||||
{
|
||||
Player player = GetPlayer(iclient);
|
||||
if (player != null && game_data.IsPlayerTurn(player))
|
||||
{
|
||||
gameplay.NextStep();
|
||||
}
|
||||
}
|
||||
|
||||
public void ReceiveResign(ClientData iclient, SerializedData sdata)
|
||||
{
|
||||
Player player = GetPlayer(iclient);
|
||||
if (player != null && game_data.state != GameState.Connecting && game_data.state != GameState.GameEnded)
|
||||
{
|
||||
int winner = player.player_id == 0 ? 1 : 0;
|
||||
gameplay.EndGame(winner);
|
||||
}
|
||||
}
|
||||
|
||||
public void ReceiveChat(ClientData iclient, SerializedData sdata)
|
||||
{
|
||||
MsgChat msg = sdata.Get<MsgChat>();
|
||||
Player player = GetPlayer(iclient);
|
||||
if (player != null && msg != null)
|
||||
{
|
||||
msg.player_id = player.player_id; //Force player id to sending client to avoid spoofing
|
||||
SendToAll(GameAction.ChatMessage, msg, NetworkDelivery.Reliable);
|
||||
}
|
||||
}
|
||||
|
||||
//--- Setup Commands ------
|
||||
|
||||
public virtual async void SetPlayerDeck(int player_id, string username, UserDeckData deck)
|
||||
{
|
||||
Player player = game_data.GetPlayer(player_id);
|
||||
if (player != null && game_data.state == GameState.Connecting)
|
||||
{
|
||||
UserData user = Authenticator.Get().UserData; //Offline game, get local user
|
||||
|
||||
if(Authenticator.Get().IsApi())
|
||||
user = await ApiClient.Get().LoadUserData(username); //Online game, validate from api
|
||||
|
||||
//Use user API deck
|
||||
UserDeckData udeck = user?.GetDeck(deck.tid);
|
||||
if (user != null && udeck != null)
|
||||
{
|
||||
if (user.IsDeckValid(udeck))
|
||||
{
|
||||
gameplay.SetPlayerDeck(player, udeck);
|
||||
SendPlayerReady(player);
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.Log(user.username + " deck is invalid: " + udeck.title);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
//Use premade deck
|
||||
DeckData cdeck = DeckData.Get(deck.tid);
|
||||
if (cdeck != null)
|
||||
gameplay.SetPlayerDeck(player, cdeck);
|
||||
|
||||
//Trust client in test mode
|
||||
else if (Authenticator.Get().IsTest())
|
||||
gameplay.SetPlayerDeck(player, deck);
|
||||
|
||||
//Deck not found
|
||||
else
|
||||
Debug.Log("Player " + player_id + " deck not found: " + deck.tid);
|
||||
|
||||
SendPlayerReady(player);
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void SetPlayerSettings(int player_id, PlayerSettings psettings)
|
||||
{
|
||||
if (game_data.state != GameState.Connecting)
|
||||
return; //Cant send setting if game already started
|
||||
|
||||
Player player = game_data.GetPlayer(player_id);
|
||||
if (player != null && !player.ready)
|
||||
{
|
||||
player.avatar = psettings.avatar;
|
||||
player.cardback = psettings.cardback;
|
||||
player.is_ai = false;
|
||||
player.ready = true;
|
||||
SetPlayerDeck(player_id, player.username, psettings.deck);
|
||||
RefreshAll();
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void SetPlayerSettingsAI(int player_id, PlayerSettings psettings)
|
||||
{
|
||||
if (game_data.state != GameState.Connecting)
|
||||
return; //Cant send setting if game already started
|
||||
if (is_dedicated_server)
|
||||
return; //No AI allowed online server
|
||||
|
||||
Player player = game_data.GetOpponentPlayer(player_id);
|
||||
if (player != null && !player.ready)
|
||||
{
|
||||
player.username = psettings.username;
|
||||
player.avatar = psettings.avatar;
|
||||
player.cardback = psettings.cardback;
|
||||
player.is_ai = true;
|
||||
player.ready = true;
|
||||
player.ai_level = psettings.ai_level;
|
||||
|
||||
SetPlayerDeck(player.player_id, player.username, psettings.deck);
|
||||
RefreshAll();
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void SetGameSettings(GameSettings settings)
|
||||
{
|
||||
if (game_data.state == GameState.Connecting)
|
||||
{
|
||||
game_data.settings = settings;
|
||||
RefreshAll();
|
||||
}
|
||||
}
|
||||
|
||||
//-------------
|
||||
|
||||
public void AddClient(ClientData client)
|
||||
{
|
||||
if (!connected_clients.Contains(client))
|
||||
connected_clients.Add(client);
|
||||
}
|
||||
|
||||
public void RemoveClient(ClientData client)
|
||||
{
|
||||
connected_clients.Remove(client);
|
||||
|
||||
Player player = GetPlayer(client);
|
||||
if (player != null && player.connected)
|
||||
{
|
||||
player.connected = false;
|
||||
RefreshAll();
|
||||
}
|
||||
}
|
||||
|
||||
public ClientData GetClient(ulong client_id)
|
||||
{
|
||||
foreach (ClientData client in connected_clients)
|
||||
{
|
||||
if (client.client_id == client_id)
|
||||
return client;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public int AddPlayer(ClientData client)
|
||||
{
|
||||
if (!players.Contains(client))
|
||||
players.Add(client);
|
||||
|
||||
int player_id = FindPlayerID(client.user_id);
|
||||
Player player = game_data.GetPlayer(player_id);
|
||||
if (player != null)
|
||||
{
|
||||
player.username = client.username;
|
||||
player.connected = true;
|
||||
}
|
||||
|
||||
return player_id;
|
||||
}
|
||||
|
||||
public int FindPlayerID(string user_id)
|
||||
{
|
||||
int index = 0;
|
||||
foreach (ClientData player in players)
|
||||
{
|
||||
if (player.user_id == user_id)
|
||||
return index;
|
||||
index++;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
public Player GetPlayer(ClientData client)
|
||||
{
|
||||
return GetPlayer(client.user_id);
|
||||
}
|
||||
|
||||
public Player GetPlayer(string user_id)
|
||||
{
|
||||
int player_id = FindPlayerID(user_id);
|
||||
return game_data?.GetPlayer(player_id);
|
||||
}
|
||||
|
||||
public bool IsPlayer(string user_id)
|
||||
{
|
||||
Player player = GetPlayer(user_id);
|
||||
return player != null;
|
||||
}
|
||||
|
||||
public bool IsConnectedPlayer(string user_id)
|
||||
{
|
||||
Player player = GetPlayer(user_id);
|
||||
return player != null && player.connected;
|
||||
}
|
||||
|
||||
public int CountPlayers()
|
||||
{
|
||||
return players.Count;
|
||||
}
|
||||
|
||||
public int CountConnectedClients()
|
||||
{
|
||||
int nb = 0;
|
||||
Game game = GetGameData();
|
||||
foreach (Player player in game.players)
|
||||
{
|
||||
if (player.IsConnected())
|
||||
{
|
||||
nb++;
|
||||
}
|
||||
}
|
||||
return nb;
|
||||
}
|
||||
|
||||
public Game GetGameData()
|
||||
{
|
||||
return gameplay.GetGameData();
|
||||
}
|
||||
|
||||
public virtual bool HasGameStarted()
|
||||
{
|
||||
return gameplay.IsGameStarted();
|
||||
}
|
||||
|
||||
public virtual bool HasGameEnded()
|
||||
{
|
||||
return gameplay.IsGameEnded();
|
||||
}
|
||||
|
||||
public virtual bool IsGameExpired()
|
||||
{
|
||||
return expiration > game_expire_time; //Means that the game expired (everyone left or game ended)
|
||||
}
|
||||
|
||||
public virtual bool IsWinExpired()
|
||||
{
|
||||
return win_expiration > win_expire_time; //Means that only one player is left, and he should win
|
||||
}
|
||||
|
||||
protected virtual void OnGameStart()
|
||||
{
|
||||
SendToAll(GameAction.GameStart);
|
||||
|
||||
if (is_dedicated_server && Authenticator.Get().IsApi())
|
||||
{
|
||||
//Create Match
|
||||
ApiClient.Get().CreateMatch(game_data);
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void OnGameEnd(Player winner)
|
||||
{
|
||||
MsgPlayer msg = new MsgPlayer();
|
||||
msg.player_id = winner != null ? winner.player_id : -1;
|
||||
SendToAll(GameAction.GameEnd, msg, NetworkDelivery.Reliable);
|
||||
|
||||
if (is_dedicated_server && Authenticator.Get().IsApi())
|
||||
{
|
||||
//End Match and give rewards
|
||||
ApiClient.Get().EndMatch(game_data, winner.player_id);
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void OnTurnStart()
|
||||
{
|
||||
MsgPlayer msg = new MsgPlayer();
|
||||
msg.player_id = game_data.current_player;
|
||||
SendToAll(GameAction.NewTurn, msg, NetworkDelivery.Reliable);
|
||||
}
|
||||
|
||||
protected virtual void OnCardPlayed(Card card, Slot slot)
|
||||
{
|
||||
MsgPlayCard mdata = new MsgPlayCard();
|
||||
mdata.card_uid = card.uid;
|
||||
mdata.slot = slot;
|
||||
SendToAll(GameAction.CardPlayed, mdata, NetworkDelivery.Reliable);
|
||||
}
|
||||
|
||||
protected virtual void OnCardMoved(Card card, Slot slot)
|
||||
{
|
||||
MsgPlayCard mdata = new MsgPlayCard();
|
||||
mdata.card_uid = card.uid;
|
||||
mdata.slot = slot;
|
||||
SendToAll(GameAction.CardMoved, mdata, NetworkDelivery.Reliable);
|
||||
}
|
||||
|
||||
protected virtual void OnCardSummoned(Card card, Slot slot)
|
||||
{
|
||||
MsgPlayCard mdata = new MsgPlayCard();
|
||||
mdata.card_uid = card.uid;
|
||||
mdata.slot = slot;
|
||||
SendToAll(GameAction.CardSummoned, mdata, NetworkDelivery.Reliable);
|
||||
}
|
||||
|
||||
protected virtual void OnCardTransformed(Card card)
|
||||
{
|
||||
MsgCard mdata = new MsgCard();
|
||||
mdata.card_uid = card.uid;
|
||||
SendToAll(GameAction.CardTransformed, mdata, NetworkDelivery.Reliable);
|
||||
}
|
||||
|
||||
protected virtual void OnCardDiscarded(Card card)
|
||||
{
|
||||
MsgCard mdata = new MsgCard();
|
||||
mdata.card_uid = card.uid;
|
||||
SendToAll(GameAction.CardDiscarded, mdata, NetworkDelivery.Reliable);
|
||||
}
|
||||
|
||||
protected virtual void OnCardDraw(int nb)
|
||||
{
|
||||
MsgInt mdata = new MsgInt();
|
||||
mdata.value = nb;
|
||||
SendToAll(GameAction.CardDrawn, mdata, NetworkDelivery.Reliable);
|
||||
}
|
||||
|
||||
protected virtual void OnValueRolled(int nb)
|
||||
{
|
||||
MsgInt mdata = new MsgInt();
|
||||
mdata.value = nb;
|
||||
SendToAll(GameAction.ValueRolled, mdata, NetworkDelivery.Reliable);
|
||||
}
|
||||
|
||||
protected virtual void OnAttackStart(Card attacker, Card target)
|
||||
{
|
||||
MsgAttack mdata = new MsgAttack();
|
||||
mdata.attacker_uid = attacker.uid;
|
||||
mdata.target_uid = target.uid;
|
||||
mdata.damage = 0;
|
||||
SendToAll(GameAction.AttackStart, mdata, NetworkDelivery.Reliable);
|
||||
}
|
||||
|
||||
protected virtual void OnAttackEnd(Card attacker, Card target)
|
||||
{
|
||||
MsgAttack mdata = new MsgAttack();
|
||||
mdata.attacker_uid = attacker.uid;
|
||||
mdata.target_uid = target.uid;
|
||||
mdata.damage = 0;
|
||||
SendToAll(GameAction.AttackEnd, mdata, NetworkDelivery.Reliable);
|
||||
}
|
||||
|
||||
protected virtual void OnAttackPlayerStart(Card attacker, Player target)
|
||||
{
|
||||
MsgAttackPlayer mdata = new MsgAttackPlayer();
|
||||
mdata.attacker_uid = attacker.uid;
|
||||
mdata.target_id = target.player_id;
|
||||
mdata.damage = 0;
|
||||
SendToAll(GameAction.AttackPlayerStart, mdata, NetworkDelivery.Reliable);
|
||||
}
|
||||
|
||||
protected virtual void OnAttackPlayerEnd(Card attacker, Player target)
|
||||
{
|
||||
MsgAttackPlayer mdata = new MsgAttackPlayer();
|
||||
mdata.attacker_uid = attacker.uid;
|
||||
mdata.target_id = target.player_id;
|
||||
mdata.damage = 0;
|
||||
SendToAll(GameAction.AttackPlayerEnd, mdata, NetworkDelivery.Reliable);
|
||||
}
|
||||
|
||||
protected virtual void OnCardDamaged(Card card, int damage)
|
||||
{
|
||||
MsgCardValue mdata = new MsgCardValue();
|
||||
mdata.card_uid = card.uid;
|
||||
mdata.value = damage;
|
||||
SendToAll(GameAction.CardDamaged, mdata, NetworkDelivery.Reliable);
|
||||
}
|
||||
|
||||
protected virtual void OnPlayerDamaged(Player player, int damage)
|
||||
{
|
||||
MsgPlayerValue mdata = new MsgPlayerValue();
|
||||
mdata.player_id = player.player_id;
|
||||
mdata.value = damage;
|
||||
SendToAll(GameAction.PlayerDamaged, mdata, NetworkDelivery.Reliable);
|
||||
}
|
||||
|
||||
protected virtual void OnCardHealed(Card card, int hp)
|
||||
{
|
||||
MsgCardValue mdata = new MsgCardValue();
|
||||
mdata.card_uid = card.uid;
|
||||
mdata.value = hp;
|
||||
SendToAll(GameAction.CardHealed, mdata, NetworkDelivery.Reliable);
|
||||
}
|
||||
|
||||
protected virtual void OnPlayerHealed(Player player, int hp)
|
||||
{
|
||||
MsgPlayerValue mdata = new MsgPlayerValue();
|
||||
mdata.player_id = player.player_id;
|
||||
mdata.value = hp;
|
||||
SendToAll(GameAction.PlayerHealed, mdata, NetworkDelivery.Reliable);
|
||||
}
|
||||
|
||||
protected virtual void OnAbilityStart(AbilityData ability, Card caster)
|
||||
{
|
||||
MsgCastAbility mdata = new MsgCastAbility();
|
||||
mdata.ability_id = ability.id;
|
||||
mdata.caster_uid = caster.uid;
|
||||
mdata.target_uid = "";
|
||||
SendToAll(GameAction.AbilityTrigger, mdata, NetworkDelivery.Reliable);
|
||||
}
|
||||
|
||||
protected virtual void OnAbilityTargetCard(AbilityData ability, Card caster, Card target)
|
||||
{
|
||||
MsgCastAbility mdata = new MsgCastAbility();
|
||||
mdata.ability_id = ability.id;
|
||||
mdata.caster_uid = caster.uid;
|
||||
mdata.target_uid = target != null ? target.uid : "";
|
||||
SendToAll(GameAction.AbilityTargetCard, mdata, NetworkDelivery.Reliable);
|
||||
}
|
||||
|
||||
protected virtual void OnAbilityTargetPlayer(AbilityData ability, Card caster, Player target)
|
||||
{
|
||||
MsgCastAbilityPlayer mdata = new MsgCastAbilityPlayer();
|
||||
mdata.ability_id = ability.id;
|
||||
mdata.caster_uid = caster.uid;
|
||||
mdata.target_id = target != null ? target.player_id : -1;
|
||||
SendToAll(GameAction.AbilityTargetPlayer, mdata, NetworkDelivery.Reliable);
|
||||
}
|
||||
|
||||
protected virtual void OnAbilityTargetSlot(AbilityData ability, Card caster, Slot target)
|
||||
{
|
||||
MsgCastAbilitySlot mdata = new MsgCastAbilitySlot();
|
||||
mdata.ability_id = ability.id;
|
||||
mdata.caster_uid = caster.uid;
|
||||
mdata.slot = target;
|
||||
SendToAll(GameAction.AbilityTargetSlot, mdata, NetworkDelivery.Reliable);
|
||||
}
|
||||
|
||||
protected virtual void OnAbilityEnd(AbilityData ability, Card caster)
|
||||
{
|
||||
MsgCastAbility mdata = new MsgCastAbility();
|
||||
mdata.ability_id = ability.id;
|
||||
mdata.caster_uid = caster.uid;
|
||||
mdata.target_uid = "";
|
||||
SendToAll(GameAction.AbilityEnd, mdata, NetworkDelivery.Reliable);
|
||||
}
|
||||
|
||||
protected virtual void OnSecretTriggered(Card secret, Card trigger)
|
||||
{
|
||||
MsgSecret mdata = new MsgSecret();
|
||||
mdata.secret_uid = secret.uid;
|
||||
mdata.triggerer_uid = trigger != null ? trigger.uid : "";
|
||||
SendToAll(GameAction.SecretTriggered, mdata, NetworkDelivery.Reliable);
|
||||
}
|
||||
|
||||
protected virtual void OnSecretResolved(Card secret, Card trigger)
|
||||
{
|
||||
MsgSecret mdata = new MsgSecret();
|
||||
mdata.secret_uid = secret.uid;
|
||||
mdata.triggerer_uid = trigger != null ? trigger.uid : "";
|
||||
SendToAll(GameAction.SecretResolved, mdata, NetworkDelivery.Reliable);
|
||||
}
|
||||
|
||||
protected virtual void SendPlayerReady(Player player)
|
||||
{
|
||||
if (player != null && player.IsReady())
|
||||
{
|
||||
MsgInt mdata = new MsgInt();
|
||||
mdata.value = player.player_id;
|
||||
SendToAll(GameAction.PlayerReady, mdata, NetworkDelivery.Reliable);
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void RefreshAll()
|
||||
{
|
||||
MsgRefreshAll mdata = new MsgRefreshAll();
|
||||
mdata.game_data = GetGameData();
|
||||
SendToAll(GameAction.RefreshAll, mdata, NetworkDelivery.ReliableFragmentedSequenced);
|
||||
}
|
||||
|
||||
public void SendToAll(ushort tag)
|
||||
{
|
||||
FastBufferWriter writer = new FastBufferWriter(128, Unity.Collections.Allocator.Temp, TcgNetwork.MsgSizeMax);
|
||||
writer.WriteValueSafe(tag);
|
||||
foreach (ClientData iclient in connected_clients)
|
||||
{
|
||||
if (iclient != null)
|
||||
{
|
||||
Messaging.Send("refresh", iclient.client_id, writer, NetworkDelivery.Reliable);
|
||||
}
|
||||
}
|
||||
writer.Dispose();
|
||||
}
|
||||
|
||||
public void SendToAll(ushort tag, INetworkSerializable data, NetworkDelivery delivery)
|
||||
{
|
||||
FastBufferWriter writer = new FastBufferWriter(128, Unity.Collections.Allocator.Temp, TcgNetwork.MsgSizeMax);
|
||||
writer.WriteValueSafe(tag);
|
||||
writer.WriteNetworkSerializable(data);
|
||||
foreach (ClientData iclient in connected_clients)
|
||||
{
|
||||
if (iclient != null)
|
||||
{
|
||||
Messaging.Send("refresh", iclient.client_id, writer, delivery);
|
||||
}
|
||||
}
|
||||
writer.Dispose();
|
||||
}
|
||||
|
||||
public ulong ServerID { get { return TcgNetwork.Get().ServerID; } }
|
||||
public NetworkMessaging Messaging { get { return TcgNetwork.Get().Messaging; } }
|
||||
}
|
||||
|
||||
public struct QueuedGameAction
|
||||
{
|
||||
public ushort type;
|
||||
public ClientData client;
|
||||
public SerializedData sdata;
|
||||
}
|
||||
|
||||
}
|
||||
11
Assets/TcgEngine/Scripts/GameServer/GameServer.cs.meta
Normal file
11
Assets/TcgEngine/Scripts/GameServer/GameServer.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d49bc33cf0fcdd041b6cb7b05d0688de
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
291
Assets/TcgEngine/Scripts/GameServer/ServerManager.cs
Normal file
291
Assets/TcgEngine/Scripts/GameServer/ServerManager.cs
Normal file
@@ -0,0 +1,291 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using System;
|
||||
using UnityEngine.Events;
|
||||
using Unity.Netcode;
|
||||
|
||||
namespace TcgEngine.Server
|
||||
{
|
||||
/// <summary>
|
||||
/// Top-level server script, that manages new connection, and assign players to the right match
|
||||
/// Will also receive game actions and send them to the appropirate game
|
||||
/// Can contain multiple games at once (GameServer)
|
||||
/// </summary>
|
||||
|
||||
public class ServerManager : MonoBehaviour
|
||||
{
|
||||
[Header("API")]
|
||||
public string api_username;
|
||||
public string api_password;
|
||||
|
||||
private Dictionary<ulong, ClientData> client_list = new Dictionary<ulong, ClientData>(); //List of clients
|
||||
private Dictionary<string, GameServer> game_list = new Dictionary<string, GameServer>(); //List of games
|
||||
private List<string> game_remove_list = new List<string>();
|
||||
|
||||
private float login_timer = 0f;
|
||||
|
||||
protected virtual void Awake()
|
||||
{
|
||||
Application.runInBackground = true;
|
||||
Application.targetFrameRate = 200; //Limit server frame rate to prevent using 100% cpu
|
||||
}
|
||||
|
||||
protected virtual void Start()
|
||||
{
|
||||
TcgNetwork network = TcgNetwork.Get();
|
||||
network.onClientJoin += OnClientConnected;
|
||||
network.onClientQuit += OnClientDisconnected;
|
||||
Messaging.ListenMsg("connect", ReceiveConnectPlayer);
|
||||
Messaging.ListenMsg("action", ReceiveGameAction);
|
||||
|
||||
if (!network.IsActive())
|
||||
{
|
||||
network.StartServer(NetworkData.Get().port);
|
||||
}
|
||||
|
||||
Login();
|
||||
}
|
||||
|
||||
protected virtual void Update()
|
||||
{
|
||||
//Update games and Destroy games with no players
|
||||
foreach (KeyValuePair<string, GameServer> pair in game_list)
|
||||
{
|
||||
GameServer gserver = pair.Value;
|
||||
gserver.Update();
|
||||
|
||||
if (gserver.IsGameExpired())
|
||||
game_remove_list.Add(pair.Key);
|
||||
}
|
||||
|
||||
foreach (string key in game_remove_list)
|
||||
{
|
||||
game_list.Remove(key);
|
||||
|
||||
if (ServerMatchmaker.Get())
|
||||
ServerMatchmaker.Get().EndMatch(key);
|
||||
}
|
||||
game_remove_list.Clear();
|
||||
|
||||
//Re login
|
||||
login_timer += Time.deltaTime;
|
||||
if (login_timer > 15f && !Authenticator.Get().IsConnected())
|
||||
{
|
||||
login_timer = 0f;
|
||||
Login();
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual async void Login()
|
||||
{
|
||||
await Authenticator.Get().Login(api_username, api_password);
|
||||
|
||||
bool success = Authenticator.Get().IsConnected();
|
||||
int permission = Authenticator.Get().GetPermission();
|
||||
string api = Authenticator.Get().IsApi() ? "API" : "Local";
|
||||
|
||||
Debug.Log(api + " authentication: " + success + " (" + permission + ")");
|
||||
|
||||
//If login fail, login again
|
||||
if (!success)
|
||||
{
|
||||
TimeTool.WaitFor(5f, () =>
|
||||
{
|
||||
if (!Authenticator.Get().IsConnected())
|
||||
{
|
||||
Login();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void OnClientConnected(ulong client_id)
|
||||
{
|
||||
ClientData iclient = new ClientData(client_id);
|
||||
client_list[client_id] = iclient;
|
||||
}
|
||||
|
||||
protected virtual void OnClientDisconnected(ulong client_id)
|
||||
{
|
||||
ClientData iclient = GetClient(client_id);
|
||||
client_list.Remove(client_id);
|
||||
ReceiveDisconnectPlayer(iclient);
|
||||
}
|
||||
|
||||
protected virtual void ReceiveConnectPlayer(ulong client_id, FastBufferReader reader)
|
||||
{
|
||||
ClientData iclient = GetClient(client_id);
|
||||
reader.ReadNetworkSerializable(out MsgPlayerConnect msg);
|
||||
|
||||
if (iclient != null && msg != null)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(msg.username))
|
||||
return;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(msg.game_uid))
|
||||
return;
|
||||
|
||||
Debug.Log("Client " + client_id + " connecting to game: " + msg.game_uid);
|
||||
|
||||
//Connect to game as player or observer
|
||||
if (msg.observer)
|
||||
ConnectObserverToGame(iclient, msg.user_id, msg.username, msg.game_uid);
|
||||
else
|
||||
ConnectPlayerToGame(iclient, msg.user_id, msg.username, msg.game_uid, msg.nb_players);
|
||||
|
||||
GameServer gserver = GetGame(msg.game_uid);
|
||||
if(gserver != null)
|
||||
gserver.RefreshAll();
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void ReceiveDisconnectPlayer(ClientData iclient)
|
||||
{
|
||||
if (iclient == null)
|
||||
return;
|
||||
|
||||
GameServer gserver = GetGame(iclient.game_uid);
|
||||
if (gserver != null)
|
||||
{
|
||||
gserver.RemoveClient(iclient);
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void ReceiveGameAction(ulong client_id, FastBufferReader reader)
|
||||
{
|
||||
ClientData client = GetClient(client_id);
|
||||
if (client != null)
|
||||
{
|
||||
GameServer gserver = GetGame(client.game_uid);
|
||||
if (gserver != null && gserver.IsConnectedPlayer(client.user_id))
|
||||
gserver.ReceiveAction(client_id, reader);
|
||||
}
|
||||
}
|
||||
|
||||
//Player wants to connect to game_uid
|
||||
protected virtual void ConnectPlayerToGame(ClientData client, string user_id, string username, string game_uid, int nb_players)
|
||||
{
|
||||
//Create game
|
||||
GameServer gserver = GetGame(game_uid);
|
||||
|
||||
if (gserver == null)
|
||||
gserver = CreateGame(game_uid, nb_players);
|
||||
|
||||
bool can_connect = gserver.IsPlayer(user_id) || gserver.CountPlayers() < gserver.nb_players;
|
||||
if (gserver != null && can_connect)
|
||||
{
|
||||
//Add player to game
|
||||
client.game_uid = game_uid;
|
||||
client.user_id = user_id;
|
||||
client.username = username;
|
||||
gserver.AddClient(client);
|
||||
|
||||
int player_id = gserver.AddPlayer(client);
|
||||
|
||||
//Send back result
|
||||
MsgAfterConnected msg_data = new MsgAfterConnected();
|
||||
msg_data.success = true;
|
||||
msg_data.player_id = player_id;
|
||||
msg_data.game_data = gserver.GetGameData();
|
||||
SendToClient(client.client_id, GameAction.Connected, msg_data, NetworkDelivery.ReliableFragmentedSequenced);
|
||||
}
|
||||
}
|
||||
|
||||
//Player wants to connect to game_uid as observer
|
||||
protected virtual void ConnectObserverToGame(ClientData client, string user_id, string username, string game_uid)
|
||||
{
|
||||
GameServer gserver = GetGame(game_uid);
|
||||
if (gserver != null && client != null)
|
||||
{
|
||||
//Set client data
|
||||
client.game_uid = game_uid;
|
||||
client.user_id = user_id;
|
||||
client.username = username;
|
||||
gserver.AddClient(client);
|
||||
|
||||
//Return request
|
||||
MsgAfterConnected msg_data = new MsgAfterConnected();
|
||||
msg_data.success = true;
|
||||
msg_data.player_id = -1;
|
||||
msg_data.game_data = gserver.GetGameData();
|
||||
SendToClient(client.client_id, GameAction.Connected, msg_data, NetworkDelivery.ReliableFragmentedSequenced);
|
||||
}
|
||||
}
|
||||
|
||||
public void SendToClient(ulong client_id, ushort tag, INetworkSerializable data, NetworkDelivery delivery)
|
||||
{
|
||||
FastBufferWriter writer = new FastBufferWriter(128, Unity.Collections.Allocator.Temp, TcgNetwork.MsgSizeMax);
|
||||
writer.WriteValueSafe(tag);
|
||||
writer.WriteNetworkSerializable(data);
|
||||
Messaging.Send("refresh", client_id, writer, delivery);
|
||||
writer.Dispose();
|
||||
}
|
||||
|
||||
public void SendMsgToClient(ushort client_id, string msg)
|
||||
{
|
||||
FastBufferWriter writer = new FastBufferWriter(128, Unity.Collections.Allocator.Temp, TcgNetwork.MsgSizeMax);
|
||||
writer.WriteValueSafe(GameAction.ServerMessage);
|
||||
writer.WriteValueSafe(msg);
|
||||
Messaging.Send("refresh", client_id, writer, NetworkDelivery.Reliable);
|
||||
writer.Dispose();
|
||||
}
|
||||
|
||||
public GameServer CreateGame(string uid, int nb_players)
|
||||
{
|
||||
GameServer game = new GameServer(uid, nb_players, true);
|
||||
game_list[game.game_uid] = game;
|
||||
return game;
|
||||
}
|
||||
|
||||
public void RemoveGame(string game_id)
|
||||
{
|
||||
game_list.Remove(game_id);
|
||||
}
|
||||
|
||||
public GameServer GetGame(string game_uid)
|
||||
{
|
||||
if (string.IsNullOrEmpty(game_uid))
|
||||
return null;
|
||||
if (game_list.ContainsKey(game_uid))
|
||||
return game_list[game_uid];
|
||||
return null;
|
||||
}
|
||||
|
||||
public ClientData GetClient(ulong client_id)
|
||||
{
|
||||
if (client_list.ContainsKey(client_id))
|
||||
return client_list[client_id];
|
||||
return null;
|
||||
}
|
||||
|
||||
public ClientData GetClientByUser(string username)
|
||||
{
|
||||
foreach (KeyValuePair<ulong, ClientData> pair in client_list)
|
||||
{
|
||||
if (pair.Value.username == username)
|
||||
return pair.Value;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public ulong ServerID { get { return TcgNetwork.Get().ServerID; } }
|
||||
public NetworkMessaging Messaging { get { return TcgNetwork.Get().Messaging; } }
|
||||
}
|
||||
|
||||
public class ClientData
|
||||
{
|
||||
public ulong client_id; //index of the connection
|
||||
public string user_id; //Player user_id, in auth system
|
||||
public string username; //Player username
|
||||
public string game_uid; //Unique id for the game
|
||||
|
||||
public ClientData(ulong id) { client_id = id; }
|
||||
}
|
||||
|
||||
public class CommandEvent
|
||||
{
|
||||
public ushort tag;
|
||||
public UnityAction<ClientData, SerializedData> callback;
|
||||
}
|
||||
}
|
||||
11
Assets/TcgEngine/Scripts/GameServer/ServerManager.cs.meta
Normal file
11
Assets/TcgEngine/Scripts/GameServer/ServerManager.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3e451cc077529a6449060b1c4e92825f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
136
Assets/TcgEngine/Scripts/GameServer/ServerManagerLocal.cs
Normal file
136
Assets/TcgEngine/Scripts/GameServer/ServerManagerLocal.cs
Normal file
@@ -0,0 +1,136 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using Unity.Netcode;
|
||||
using UnityEngine;
|
||||
using TcgEngine.Client;
|
||||
|
||||
namespace TcgEngine.Server
|
||||
{
|
||||
/// <summary>
|
||||
/// Local server running on the client to play in solo mode against AI
|
||||
/// Contains only one GameServer
|
||||
/// </summary>
|
||||
|
||||
public class ServerManagerLocal : MonoBehaviour
|
||||
{
|
||||
|
||||
private GameServer server;
|
||||
|
||||
private Dictionary<ulong, ClientData> client_list = new Dictionary<ulong, ClientData>(); //List of clients
|
||||
|
||||
protected virtual void Start()
|
||||
{
|
||||
if (GameClient.game_settings.IsHost())
|
||||
{
|
||||
StartServer(); //Start local server if not playing online
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void StartServer()
|
||||
{
|
||||
TcgNetwork network = TcgNetwork.Get();
|
||||
network.onClientJoin += OnClientJoin;
|
||||
network.onClientQuit += OnClientQuit;
|
||||
network.Messaging.ListenMsg("connect", ReceiveConnectPlayer);
|
||||
network.Messaging.ListenMsg("action", ReceiveGameAction);
|
||||
|
||||
client_list[network.ServerID] = new ClientData(network.ServerID); //Add yourself
|
||||
server = new GameServer(GameClient.game_settings.game_uid, GameClient.game_settings.nb_players, false);
|
||||
}
|
||||
|
||||
protected virtual void OnDestroy()
|
||||
{
|
||||
TcgNetwork network = TcgNetwork.Get();
|
||||
if (network != null)
|
||||
{
|
||||
network.onClientJoin -= OnClientJoin;
|
||||
network.onClientQuit -= OnClientQuit;
|
||||
network.Messaging.UnListenMsg("connect");
|
||||
network.Messaging.UnListenMsg("action");
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void OnClientJoin(ulong client_id)
|
||||
{
|
||||
client_list[client_id] = new ClientData(client_id);
|
||||
}
|
||||
|
||||
protected virtual void OnClientQuit(ulong client_id)
|
||||
{
|
||||
ClientData client = GetClient(client_id);
|
||||
server?.RemoveClient(client);
|
||||
client_list.Remove(client_id);
|
||||
}
|
||||
|
||||
protected virtual void Update()
|
||||
{
|
||||
if (server != null)
|
||||
server.Update();
|
||||
}
|
||||
|
||||
protected virtual void ReceiveConnectPlayer(ulong client_id, FastBufferReader reader)
|
||||
{
|
||||
reader.ReadNetworkSerializable(out MsgPlayerConnect msg);
|
||||
|
||||
if (msg != null)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(msg.username))
|
||||
return;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(msg.game_uid))
|
||||
return;
|
||||
|
||||
ClientData client = GetClient(client_id);
|
||||
if (client == null)
|
||||
return;
|
||||
|
||||
bool can_connect = server.IsPlayer(msg.user_id) || server.CountPlayers() < server.nb_players;
|
||||
if (can_connect)
|
||||
{
|
||||
client.game_uid = msg.game_uid;
|
||||
client.user_id = msg.user_id;
|
||||
client.username = msg.username;
|
||||
server.AddClient(client);
|
||||
|
||||
int player_id = server.AddPlayer(client);
|
||||
|
||||
//Send back result
|
||||
MsgAfterConnected msg_data = new MsgAfterConnected();
|
||||
msg_data.success = true;
|
||||
msg_data.player_id = player_id;
|
||||
msg_data.game_data = server.GetGameData();
|
||||
SendToClient(client_id, GameAction.Connected, msg_data, NetworkDelivery.ReliableFragmentedSequenced);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void ReceiveGameAction(ulong client_id, FastBufferReader reader)
|
||||
{
|
||||
ClientData client = GetClient(client_id);
|
||||
if (client != null)
|
||||
{
|
||||
if (server.IsConnectedPlayer(client.user_id))
|
||||
server.ReceiveAction(client_id, reader);
|
||||
}
|
||||
}
|
||||
|
||||
public void SendToClient(ulong client_id, ushort tag, INetworkSerializable data, NetworkDelivery delivery)
|
||||
{
|
||||
FastBufferWriter writer = new FastBufferWriter(128, Unity.Collections.Allocator.Temp, TcgNetwork.MsgSizeMax);
|
||||
writer.WriteValueSafe(tag);
|
||||
writer.WriteNetworkSerializable(data);
|
||||
Messaging.Send("refresh", client_id, writer, delivery);
|
||||
writer.Dispose();
|
||||
}
|
||||
|
||||
public ClientData GetClient(ulong client_id)
|
||||
{
|
||||
if (client_list.ContainsKey(client_id))
|
||||
return client_list[client_id];
|
||||
return null;
|
||||
}
|
||||
|
||||
public ulong ServerID { get { return TcgNetwork.Get().ServerID; } }
|
||||
public NetworkMessaging Messaging { get { return TcgNetwork.Get().Messaging; } }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d649bee306cfd694f8e16d7bf02f1b97
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
307
Assets/TcgEngine/Scripts/GameServer/ServerMatchmaker.cs
Normal file
307
Assets/TcgEngine/Scripts/GameServer/ServerMatchmaker.cs
Normal file
@@ -0,0 +1,307 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using Unity.Netcode;
|
||||
|
||||
namespace TcgEngine.Server
|
||||
{
|
||||
/// <summary>
|
||||
/// Main server script for the matchmaker
|
||||
/// will receive player request and then match players together and send them the game uid and game url to connect to
|
||||
/// </summary>
|
||||
|
||||
public class ServerMatchmaker : MonoBehaviour
|
||||
{
|
||||
[Header("Matchmaker")]
|
||||
public string[] servers;
|
||||
|
||||
private Dictionary<ulong, ClientData> client_list = new Dictionary<ulong, ClientData>(); //List of clients
|
||||
private Dictionary<string, MatchPlayerData> matchmaking_players = new Dictionary<string, MatchPlayerData>(); //Get deleted every 20 sec
|
||||
private Dictionary<string, MatchData> matched_players = new Dictionary<string, MatchData>(); //user_id -> match
|
||||
private List<MatchPlayerData> valid_users = new List<MatchPlayerData>(); //temporary array
|
||||
private float matchmake_timer = 0f;
|
||||
|
||||
private static ServerMatchmaker _instance;
|
||||
|
||||
protected virtual void Awake()
|
||||
{
|
||||
_instance = this;
|
||||
Application.runInBackground = true;
|
||||
}
|
||||
|
||||
protected virtual void Start()
|
||||
{
|
||||
TcgNetwork network = TcgNetwork.Get();
|
||||
network.onClientJoin += OnClientConnected;
|
||||
network.onClientQuit += OnClientDisconnected;
|
||||
|
||||
Messaging.ListenMsg("matchmaking", ReceiveMatchmaking);
|
||||
Messaging.ListenMsg("matchmaking_list", ReceiveMatchmakingList);
|
||||
Messaging.ListenMsg("match_list", ReceiveMatchList);
|
||||
|
||||
if (!network.IsActive())
|
||||
{
|
||||
network.StartServer(NetworkData.Get().port);
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void Update()
|
||||
{
|
||||
//Matchmaking
|
||||
matchmake_timer += Time.deltaTime;
|
||||
if (matchmake_timer > 20f)
|
||||
{
|
||||
matchmake_timer = 0f;
|
||||
matchmaking_players.Clear(); //Delete and restart, to make sure you only keep recent players
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void OnClientConnected(ulong client_id)
|
||||
{
|
||||
ClientData iclient = new ClientData(client_id);
|
||||
client_list[client_id] = iclient;
|
||||
}
|
||||
|
||||
protected virtual void OnClientDisconnected(ulong client_id)
|
||||
{
|
||||
if (client_list.ContainsKey(client_id))
|
||||
{
|
||||
ClientData iclient = client_list[client_id];
|
||||
if(iclient.username != null)
|
||||
matchmaking_players.Remove(iclient.user_id);
|
||||
client_list.Remove(client_id);
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void ReceiveMatchmaking(ulong client_id, FastBufferReader reader)
|
||||
{
|
||||
ClientData iclient = GetClient(client_id);
|
||||
reader.ReadNetworkSerializable(out MsgMatchmaking msg);
|
||||
|
||||
if (iclient == null || string.IsNullOrWhiteSpace(msg.user_id) || string.IsNullOrWhiteSpace(msg.username))
|
||||
return;
|
||||
|
||||
string user_id = msg.user_id;
|
||||
bool is_refresh = msg.refresh;
|
||||
|
||||
iclient.user_id = msg.user_id;
|
||||
iclient.username = msg.username;
|
||||
|
||||
//Restart matching
|
||||
if (!is_refresh)
|
||||
matched_players.Remove(user_id);
|
||||
|
||||
//Check if already matched
|
||||
if (matched_players.ContainsKey(user_id))
|
||||
{
|
||||
MatchData match = matched_players[user_id];
|
||||
if (!match.ended)
|
||||
{
|
||||
SendMatchmakingResponse(iclient, match, msg.group, match.players.Length); //Was already matched, return saved result!
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
//Data
|
||||
MatchPlayerData pdata = new MatchPlayerData();
|
||||
pdata.user_id = msg.user_id;
|
||||
pdata.username = msg.username;
|
||||
pdata.group = msg.group;
|
||||
pdata.elo_rank = msg.elo;
|
||||
pdata.nb_players = msg.players;
|
||||
|
||||
//Add to matchking players
|
||||
if (!matchmaking_players.ContainsKey(user_id))
|
||||
matchmaking_players.Add(user_id, pdata);
|
||||
|
||||
//Start searching for other valid players
|
||||
float wait_max = 20f;
|
||||
int variance_max = 2000;
|
||||
|
||||
bool friendly = msg.group.StartsWith("u_");
|
||||
float wait_timer = msg.time;
|
||||
float wait_value = Mathf.Clamp01(wait_timer / wait_max);
|
||||
int elo_variance = Mathf.RoundToInt(wait_value * variance_max);
|
||||
|
||||
valid_users.Clear();
|
||||
valid_users.Add(pdata); //Add self
|
||||
|
||||
foreach (KeyValuePair<string, MatchPlayerData> opair in matchmaking_players)
|
||||
{
|
||||
string auser_id = opair.Key;
|
||||
MatchPlayerData adata = opair.Value;
|
||||
int diff = Mathf.Abs(adata.elo_rank - msg.elo);
|
||||
bool same_group = adata.group == msg.group;
|
||||
bool same_players = adata.nb_players == msg.players;
|
||||
bool valid_elo = friendly || diff < elo_variance;
|
||||
if (auser_id != user_id && valid_elo && same_group && same_players)
|
||||
{
|
||||
valid_users.Add(adata);
|
||||
}
|
||||
}
|
||||
|
||||
//Not enough players found, send current count
|
||||
if (valid_users.Count < msg.players)
|
||||
{
|
||||
SendMatchmakingResponse(iclient, null, msg.group, valid_users.Count);
|
||||
return; //Not enough valid users
|
||||
}
|
||||
|
||||
//Match success, send result
|
||||
string prefix = msg.group.Length >= 2 ? msg.group.Substring(0, 2) : "";
|
||||
string game_code = prefix + GameTool.GenerateRandomID(12, 15);
|
||||
string game_url = ""; //Empty url means it will use the default NetworkData url set on the client
|
||||
if (servers.Length > 0)
|
||||
game_url = servers[Random.Range(0, servers.Length)];
|
||||
|
||||
int pindex = 0;
|
||||
MatchData nmatch = new MatchData(msg.group, game_code, game_url, msg.players);
|
||||
foreach (MatchPlayerData vuser in valid_users)
|
||||
{
|
||||
if (pindex < nmatch.players.Length)
|
||||
{
|
||||
matchmaking_players.Remove(vuser.user_id);
|
||||
matched_players[vuser.user_id] = nmatch;
|
||||
nmatch.players[pindex] = vuser.username;
|
||||
pindex++;
|
||||
}
|
||||
}
|
||||
|
||||
//Send response to current request
|
||||
if (matched_players.ContainsKey(user_id))
|
||||
{
|
||||
SendMatchmakingResponse(iclient, nmatch, nmatch.group, nmatch.players.Length); //Just matched to new player!
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void SendMatchmakingResponse(ClientData iclient, MatchData match, string group, int players)
|
||||
{
|
||||
MatchmakingResult msg_match = new MatchmakingResult();
|
||||
msg_match.success = match != null;
|
||||
msg_match.players = players;
|
||||
msg_match.group = group;
|
||||
msg_match.game_uid = match != null ? match.game_uid : "";
|
||||
msg_match.server_url = match != null ? match.server_url : "";
|
||||
|
||||
Messaging.SendObject("matchmaking", iclient.client_id, msg_match, NetworkDelivery.Reliable);
|
||||
}
|
||||
|
||||
protected virtual void ReceiveMatchmakingList(ulong client_id, FastBufferReader reader)
|
||||
{
|
||||
reader.ReadNetworkSerializable(out MsgMatchmakingList msg);
|
||||
|
||||
List<MatchmakingListItem> items = new List<MatchmakingListItem>();
|
||||
|
||||
foreach (KeyValuePair<string, MatchPlayerData> pair in matchmaking_players)
|
||||
{
|
||||
if (string.IsNullOrEmpty(msg.username) || pair.Key == msg.username)
|
||||
{
|
||||
MatchPlayerData pdata = pair.Value;
|
||||
MatchmakingListItem item = new MatchmakingListItem();
|
||||
item.group = pdata.group;
|
||||
item.user_id = pdata.user_id;
|
||||
item.username = pdata.username;
|
||||
items.Add(item);
|
||||
}
|
||||
}
|
||||
|
||||
MatchmakingList msg_list = new MatchmakingList();
|
||||
msg_list.items = items.ToArray();
|
||||
Messaging.SendObject("matchmaking_list", client_id, msg_list, NetworkDelivery.Reliable);
|
||||
}
|
||||
|
||||
protected virtual void ReceiveMatchList(ulong client_id, FastBufferReader reader)
|
||||
{
|
||||
reader.ReadNetworkSerializable(out MsgMatchmakingList msg);
|
||||
|
||||
List<MatchListItem> items = new List<MatchListItem>();
|
||||
|
||||
foreach (KeyValuePair<string, MatchData> pair in matched_players)
|
||||
{
|
||||
if (!pair.Value.ended)
|
||||
{
|
||||
if (string.IsNullOrEmpty(msg.username) || Contains(pair.Value.players, msg.username))
|
||||
{
|
||||
MatchData pdata = pair.Value;
|
||||
MatchListItem item = new MatchListItem();
|
||||
item.group = pair.Value.group;
|
||||
item.username = msg.username;
|
||||
item.game_uid = pdata.game_uid;
|
||||
item.game_url = pdata.server_url;
|
||||
items.Add(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MatchList msg_list = new MatchList();
|
||||
msg_list.items = items.ToArray();
|
||||
|
||||
Messaging.SendObject("match_list", client_id, msg_list, NetworkDelivery.Reliable);
|
||||
}
|
||||
|
||||
private bool Contains(string[] users, string user)
|
||||
{
|
||||
foreach (string auser in users)
|
||||
{
|
||||
if (auser == user)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public void EndMatch(string uid)
|
||||
{
|
||||
foreach (KeyValuePair<string, MatchData> pair in matched_players)
|
||||
{
|
||||
if (pair.Value.game_uid == uid)
|
||||
pair.Value.ended = true;
|
||||
}
|
||||
}
|
||||
|
||||
public ClientData GetClient(ulong client_id)
|
||||
{
|
||||
if (client_list.ContainsKey(client_id))
|
||||
return client_list[client_id];
|
||||
return null;
|
||||
}
|
||||
|
||||
public ClientData GetClientByUser(string username)
|
||||
{
|
||||
foreach (KeyValuePair<ulong, ClientData> pair in client_list)
|
||||
{
|
||||
if (pair.Value.username == username)
|
||||
return pair.Value;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public ulong ServerID { get { return TcgNetwork.Get().ServerID; } }
|
||||
public NetworkMessaging Messaging { get { return TcgNetwork.Get().Messaging; } }
|
||||
|
||||
public static ServerMatchmaker Get()
|
||||
{
|
||||
return _instance;
|
||||
}
|
||||
}
|
||||
|
||||
public class MatchPlayerData
|
||||
{
|
||||
public string user_id;
|
||||
public string username;
|
||||
public string group;
|
||||
public int elo_rank;
|
||||
public int nb_players;
|
||||
}
|
||||
|
||||
public class MatchData
|
||||
{
|
||||
public string group;
|
||||
public string game_uid;
|
||||
public string server_url;
|
||||
public bool ended = false;
|
||||
public string[] players;
|
||||
|
||||
public MatchData(string grp, string uid, string url, int players) { group = grp; game_uid = uid; server_url = url; this.players = new string[players]; }
|
||||
}
|
||||
|
||||
}
|
||||
11
Assets/TcgEngine/Scripts/GameServer/ServerMatchmaker.cs.meta
Normal file
11
Assets/TcgEngine/Scripts/GameServer/ServerMatchmaker.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 189ef7d70f3b50648986d731b64303ce
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user