|
|
|
|
// ----------------------------------------------------------------------------
|
|
|
|
|
// <copyright file="PunTurnManager.cs" company="Exit Games GmbH">
|
|
|
|
|
// PhotonNetwork Framework for Unity - Copyright (C) 2018 Exit Games GmbH
|
|
|
|
|
// </copyright>
|
|
|
|
|
// <summary>
|
|
|
|
|
// Manager for Turn Based games, using PUN
|
|
|
|
|
// </summary>
|
|
|
|
|
// <author>developer@exitgames.com</author>
|
|
|
|
|
// ----------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
using System;
|
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
|
|
|
|
|
using UnityEngine;
|
|
|
|
|
|
|
|
|
|
using Photon.Realtime;
|
|
|
|
|
|
|
|
|
|
using ExitGames.Client.Photon;
|
|
|
|
|
using Hashtable = ExitGames.Client.Photon.Hashtable;
|
|
|
|
|
|
|
|
|
|
namespace Photon.Pun.UtilityScripts
|
|
|
|
|
{
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Pun turnBased Game manager.
|
|
|
|
|
/// Provides an Interface (IPunTurnManagerCallbacks) for the typical turn flow and logic, between players
|
|
|
|
|
/// Provides Extensions for Player, Room and RoomInfo to feature dedicated api for TurnBased Needs
|
|
|
|
|
/// </summary>
|
|
|
|
|
public class PunTurnManager : MonoBehaviourPunCallbacks, IOnEventCallback
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// External definition for better garbage collection management, used in ProcessEvent.
|
|
|
|
|
/// </summary>
|
|
|
|
|
Player sender;
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Wraps accessing the "turn" custom properties of a room.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <value>The turn index</value>
|
|
|
|
|
public int Turn
|
|
|
|
|
{
|
|
|
|
|
get { return PhotonNetwork.CurrentRoom.GetTurn(); }
|
|
|
|
|
private set
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
_isOverCallProcessed = false;
|
|
|
|
|
|
|
|
|
|
PhotonNetwork.CurrentRoom.SetTurn(value, true);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// The duration of the turn in seconds.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public float TurnDuration = 20f;
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Gets the elapsed time in the current turn in seconds
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <value>The elapsed time in the turn.</value>
|
|
|
|
|
public float ElapsedTimeInTurn
|
|
|
|
|
{
|
|
|
|
|
get { return ((float) (PhotonNetwork.ServerTimestamp - PhotonNetwork.CurrentRoom.GetTurnStart())) / 1000.0f; }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Gets the remaining seconds for the current turn. Ranges from 0 to TurnDuration
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <value>The remaining seconds fo the current turn</value>
|
|
|
|
|
public float RemainingSecondsInTurn
|
|
|
|
|
{
|
|
|
|
|
get { return Mathf.Max(0f, this.TurnDuration - this.ElapsedTimeInTurn); }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Gets a value indicating whether the turn is completed by all.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <value><c>true</c> if this turn is completed by all; otherwise, <c>false</c>.</value>
|
|
|
|
|
public bool IsCompletedByAll
|
|
|
|
|
{
|
|
|
|
|
get { return PhotonNetwork.CurrentRoom != null && Turn > 0 && this.finishedPlayers.Count == PhotonNetwork.CurrentRoom.PlayerCount; }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Gets a value indicating whether the current turn is finished by me.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <value><c>true</c> if the current turn is finished by me; otherwise, <c>false</c>.</value>
|
|
|
|
|
public bool IsFinishedByMe
|
|
|
|
|
{
|
|
|
|
|
get { return this.finishedPlayers.Contains(PhotonNetwork.LocalPlayer); }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Gets a value indicating whether the current turn is over. That is the ElapsedTimeinTurn is greater or equal to the TurnDuration
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <value><c>true</c> if the current turn is over; otherwise, <c>false</c>.</value>
|
|
|
|
|
public bool IsOver
|
|
|
|
|
{
|
|
|
|
|
get { return this.RemainingSecondsInTurn <= 0f; }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// The turn manager listener. Set this to your own script instance to catch Callbacks
|
|
|
|
|
/// </summary>
|
|
|
|
|
public IPunTurnManagerCallbacks TurnManagerListener;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// The finished players.
|
|
|
|
|
/// </summary>
|
|
|
|
|
private readonly HashSet<Player> finishedPlayers = new HashSet<Player>();
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// The turn manager event offset event message byte. Used internaly for defining data in Room Custom Properties
|
|
|
|
|
/// </summary>
|
|
|
|
|
public const byte TurnManagerEventOffset = 0;
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// The Move event message byte. Used internaly for saving data in Room Custom Properties
|
|
|
|
|
/// </summary>
|
|
|
|
|
public const byte EvMove = 1 + TurnManagerEventOffset;
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// The Final Move event message byte. Used internaly for saving data in Room Custom Properties
|
|
|
|
|
/// </summary>
|
|
|
|
|
public const byte EvFinalMove = 2 + TurnManagerEventOffset;
|
|
|
|
|
|
|
|
|
|
// keep track of message calls
|
|
|
|
|
private bool _isOverCallProcessed = false;
|
|
|
|
|
|
|
|
|
|
#region MonoBehaviour CallBack
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void Start(){}
|
|
|
|
|
|
|
|
|
|
void Update()
|
|
|
|
|
{
|
|
|
|
|
if (Turn > 0 && this.IsOver && !_isOverCallProcessed)
|
|
|
|
|
{
|
|
|
|
|
_isOverCallProcessed = true;
|
|
|
|
|
this.TurnManagerListener.OnTurnTimeEnds(this.Turn);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Tells the TurnManager to begins a new turn.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public void BeginTurn()
|
|
|
|
|
{
|
|
|
|
|
Turn = this.Turn + 1; // note: this will set a property in the room, which is available to the other players.
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Call to send an action. Optionally finish the turn, too.
|
|
|
|
|
/// The move object can be anything. Try to optimize though and only send the strict minimum set of information to define the turn move.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="move"></param>
|
|
|
|
|
/// <param name="finished"></param>
|
|
|
|
|
public void SendMove(object move, bool finished)
|
|
|
|
|
{
|
|
|
|
|
if (IsFinishedByMe)
|
|
|
|
|
{
|
|
|
|
|
UnityEngine.Debug.LogWarning("Can't SendMove. Turn is finished by this player.");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// along with the actual move, we have to send which turn this move belongs to
|
|
|
|
|
Hashtable moveHt = new Hashtable();
|
|
|
|
|
moveHt.Add("turn", Turn);
|
|
|
|
|
moveHt.Add("move", move);
|
|
|
|
|
|
|
|
|
|
byte evCode = (finished) ? EvFinalMove : EvMove;
|
|
|
|
|
PhotonNetwork.RaiseEvent(evCode, moveHt, new RaiseEventOptions() {CachingOption = EventCaching.AddToRoomCache}, SendOptions.SendReliable);
|
|
|
|
|
if (finished)
|
|
|
|
|
{
|
|
|
|
|
PhotonNetwork.LocalPlayer.SetFinishedTurn(Turn);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// the server won't send the event back to the origin (by default). to get the event, call it locally
|
|
|
|
|
// (note: the order of events might be mixed up as we do this locally)
|
|
|
|
|
ProcessOnEvent(evCode, moveHt, PhotonNetwork.LocalPlayer.ActorNumber);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Gets if the player finished the current turn.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <returns><c>true</c>, if player finished the current turn, <c>false</c> otherwise.</returns>
|
|
|
|
|
/// <param name="player">The Player to check for</param>
|
|
|
|
|
public bool GetPlayerFinishedTurn(Player player)
|
|
|
|
|
{
|
|
|
|
|
if (player != null && this.finishedPlayers != null && this.finishedPlayers.Contains(player))
|
|
|
|
|
{
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#region Callbacks
|
|
|
|
|
|
|
|
|
|
// called internally
|
|
|
|
|
void ProcessOnEvent(byte eventCode, object content, int senderId)
|
|
|
|
|
{
|
|
|
|
|
if (senderId == -1)
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
sender = PhotonNetwork.CurrentRoom.GetPlayer(senderId);
|
|
|
|
|
|
|
|
|
|
switch (eventCode)
|
|
|
|
|
{
|
|
|
|
|
case EvMove:
|
|
|
|
|
{
|
|
|
|
|
Hashtable evTable = content as Hashtable;
|
|
|
|
|
int turn = (int)evTable["turn"];
|
|
|
|
|
object move = evTable["move"];
|
|
|
|
|
this.TurnManagerListener.OnPlayerMove(sender, turn, move);
|
|
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case EvFinalMove:
|
|
|
|
|
{
|
|
|
|
|
Hashtable evTable = content as Hashtable;
|
|
|
|
|
int turn = (int)evTable["turn"];
|
|
|
|
|
object move = evTable["move"];
|
|
|
|
|
|
|
|
|
|
if (turn == this.Turn)
|
|
|
|
|
{
|
|
|
|
|
this.finishedPlayers.Add(sender);
|
|
|
|
|
|
|
|
|
|
this.TurnManagerListener.OnPlayerFinished(sender, turn, move);
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (IsCompletedByAll)
|
|
|
|
|
{
|
|
|
|
|
this.TurnManagerListener.OnTurnCompleted(this.Turn);
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Called by PhotonNetwork.OnEventCall registration
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="photonEvent">Photon event.</param>
|
|
|
|
|
public void OnEvent(EventData photonEvent)
|
|
|
|
|
{
|
|
|
|
|
this.ProcessOnEvent(photonEvent.Code, photonEvent.CustomData, photonEvent.Sender);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Called by PhotonNetwork
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="propertiesThatChanged">Properties that changed.</param>
|
|
|
|
|
public override void OnRoomPropertiesUpdate(Hashtable propertiesThatChanged)
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
// Debug.Log("OnRoomPropertiesUpdate: "+propertiesThatChanged.ToStringFull());
|
|
|
|
|
|
|
|
|
|
if (propertiesThatChanged.ContainsKey("Turn"))
|
|
|
|
|
{
|
|
|
|
|
_isOverCallProcessed = false;
|
|
|
|
|
this.finishedPlayers.Clear();
|
|
|
|
|
this.TurnManagerListener.OnTurnBegins(this.Turn);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public interface IPunTurnManagerCallbacks
|
|
|
|
|
{
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Called the turn begins event.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="turn">Turn Index</param>
|
|
|
|
|
void OnTurnBegins(int turn);
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Called when a turn is completed (finished by all players)
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="turn">Turn Index</param>
|
|
|
|
|
void OnTurnCompleted(int turn);
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Called when a player moved (but did not finish the turn)
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="player">Player reference</param>
|
|
|
|
|
/// <param name="turn">Turn Index</param>
|
|
|
|
|
/// <param name="move">Move Object data</param>
|
|
|
|
|
void OnPlayerMove(Player player, int turn, object move);
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// When a player finishes a turn (includes the action/move of that player)
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="player">Player reference</param>
|
|
|
|
|
/// <param name="turn">Turn index</param>
|
|
|
|
|
/// <param name="move">Move Object data</param>
|
|
|
|
|
void OnPlayerFinished(Player player, int turn, object move);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Called when a turn completes due to a time constraint (timeout for a turn)
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="turn">Turn index</param>
|
|
|
|
|
void OnTurnTimeEnds(int turn);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public static class TurnExtensions
|
|
|
|
|
{
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// currently ongoing turn number
|
|
|
|
|
/// </summary>
|
|
|
|
|
public static readonly string TurnPropKey = "Turn";
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// start (server) time for currently ongoing turn (used to calculate end)
|
|
|
|
|
/// </summary>
|
|
|
|
|
public static readonly string TurnStartPropKey = "TStart";
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Finished Turn of Actor (followed by number)
|
|
|
|
|
/// </summary>
|
|
|
|
|
public static readonly string FinishedTurnPropKey = "FToA";
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Sets the turn.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="room">Room reference</param>
|
|
|
|
|
/// <param name="turn">Turn index</param>
|
|
|
|
|
/// <param name="setStartTime">If set to <c>true</c> set start time.</param>
|
|
|
|
|
public static void SetTurn(this Room room, int turn, bool setStartTime = false)
|
|
|
|
|
{
|
|
|
|
|
if (room == null || room.CustomProperties == null)
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Hashtable turnProps = new Hashtable();
|
|
|
|
|
turnProps[TurnPropKey] = turn;
|
|
|
|
|
if (setStartTime)
|
|
|
|
|
{
|
|
|
|
|
turnProps[TurnStartPropKey] = PhotonNetwork.ServerTimestamp;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
room.SetCustomProperties(turnProps);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Gets the current turn from a RoomInfo
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <returns>The turn index </returns>
|
|
|
|
|
/// <param name="room">RoomInfo reference</param>
|
|
|
|
|
public static int GetTurn(this RoomInfo room)
|
|
|
|
|
{
|
|
|
|
|
if (room == null || room.CustomProperties == null || !room.CustomProperties.ContainsKey(TurnPropKey))
|
|
|
|
|
{
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return (int) room.CustomProperties[TurnPropKey];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Returns the start time when the turn began. This can be used to calculate how long it's going on.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <returns>The turn start.</returns>
|
|
|
|
|
/// <param name="room">Room.</param>
|
|
|
|
|
public static int GetTurnStart(this RoomInfo room)
|
|
|
|
|
{
|
|
|
|
|
if (room == null || room.CustomProperties == null || !room.CustomProperties.ContainsKey(TurnStartPropKey))
|
|
|
|
|
{
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return (int) room.CustomProperties[TurnStartPropKey];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// gets the player's finished turn (from the ROOM properties)
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <returns>The finished turn index</returns>
|
|
|
|
|
/// <param name="player">Player reference</param>
|
|
|
|
|
public static int GetFinishedTurn(this Player player)
|
|
|
|
|
{
|
|
|
|
|
Room room = PhotonNetwork.CurrentRoom;
|
|
|
|
|
if (room == null || room.CustomProperties == null || !room.CustomProperties.ContainsKey(TurnPropKey))
|
|
|
|
|
{
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
string propKey = FinishedTurnPropKey + player.ActorNumber;
|
|
|
|
|
return (int) room.CustomProperties[propKey];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Sets the player's finished turn (in the ROOM properties)
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="player">Player Reference</param>
|
|
|
|
|
/// <param name="turn">Turn Index</param>
|
|
|
|
|
public static void SetFinishedTurn(this Player player, int turn)
|
|
|
|
|
{
|
|
|
|
|
Room room = PhotonNetwork.CurrentRoom;
|
|
|
|
|
if (room == null || room.CustomProperties == null)
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
string propKey = FinishedTurnPropKey + player.ActorNumber;
|
|
|
|
|
Hashtable finishedTurnProp = new Hashtable();
|
|
|
|
|
finishedTurnProp[propKey] = turn;
|
|
|
|
|
|
|
|
|
|
room.SetCustomProperties(finishedTurnProp);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|