using System.Collections;
|
|
using System.Collections.Generic;
|
|
using UnityEngine;
|
|
using Networking.Server;
|
|
using TMPro;
|
|
using UnityEngine.UI;
|
|
using System.Linq;
|
|
|
|
[CreateAssetMenu(menuName = "Major Project/GameModes/Racetrack", order = 201)]
|
|
public class RacetrackGameMode : GameMode
|
|
{
|
|
public MapManager mapManager;
|
|
public blockSpawn spawn;
|
|
|
|
public int MaxRound = 999;
|
|
public string nextScene = "ServerTestScene";
|
|
List<ClientData> ConnectedClients;
|
|
public Material OverlayMaterial;
|
|
public float scrollSpeed = 1.0f; //The rate at which the level will scroll past
|
|
|
|
[SerializeField]
|
|
[Tooltip("Layers to ignore when checking for blocks")]
|
|
public LayerMask Ignore;
|
|
|
|
|
|
public int RoundCount { get; private set; }
|
|
private Dictionary<ClientData, List<Block>> BlocksOwned;
|
|
int currentBoulderCount;
|
|
|
|
/// <summary>
|
|
/// Called once before any players have spawned
|
|
/// </summary>
|
|
protected override void OnPreGameStart()
|
|
{
|
|
mapManager.init();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Called once all players have finished their moves but before the Objective is checked
|
|
/// </summary>
|
|
protected override void OnRoundEnd(PlayerData[] allPlayers)
|
|
{
|
|
cameraCheck(allPlayers);
|
|
|
|
//At the end of each round, any stuck players are freed to resume moving next round
|
|
respawnPlayers(allPlayers);
|
|
|
|
/*foreach (PlayerData player in allPlayers)
|
|
{
|
|
player.character.stuck = false;
|
|
|
|
if (player.character.respawnNeeded && player.client.Lives > 0)
|
|
{
|
|
player.character.respawnCharacter();
|
|
}
|
|
}*/
|
|
|
|
RoundCount++;
|
|
}
|
|
|
|
void respawnPlayers(PlayerData[] allPlayers)
|
|
{
|
|
//First step: determine if anyone is waiting to respawn
|
|
List<PlayerData> respawningPlayers = new List<PlayerData>();
|
|
List<PlayerData> survivingPlayers = new List<PlayerData>();
|
|
foreach (PlayerData player in allPlayers)
|
|
{
|
|
if (player.client.Lives > 0)
|
|
{
|
|
if (player.character.respawnNeeded)
|
|
{
|
|
respawningPlayers.Add(player);
|
|
player.client.Lives--;
|
|
}
|
|
else
|
|
{
|
|
survivingPlayers.Add(player);
|
|
}
|
|
}
|
|
}
|
|
|
|
//If nobody needs a respawn, we stop here. Otherwise, we carry on
|
|
if (respawningPlayers.Count > 0)
|
|
{
|
|
//Next step: figure out the appropriate respawn point
|
|
//If there are any players left, it will be the average of their positions
|
|
//Otherwise, it will be as close to the centre of the screen as possible
|
|
Vector3 respawnLocation = new Vector3(0.0f, -0.5f, 0.0f);
|
|
//Debug.Log("Initial respawn location: " + respawnLocation);
|
|
|
|
if (survivingPlayers.Count > 0) //If any players are not currently waiting to respawn, we get the average of their locations
|
|
{
|
|
//Debug.Log("Averaging survivor locations");
|
|
//We list the positions of all the surviving players
|
|
List<Vector3> survivingPlayerLocations = new List<Vector3>();
|
|
foreach (PlayerData player in survivingPlayers)
|
|
{
|
|
survivingPlayerLocations.Add(player.character.transform.position);
|
|
}
|
|
|
|
foreach (Vector3 vec in survivingPlayerLocations)
|
|
{
|
|
respawnLocation += vec;
|
|
}
|
|
|
|
respawnLocation = respawnLocation / survivingPlayers.Count;
|
|
//Debug.Log("Averaged survivor locations, new respawn location: " + respawnLocation);
|
|
}
|
|
else //Otherwise, we go for the middle of the screen
|
|
{
|
|
//Debug.Log("Seeking the middle of the screen");
|
|
//We start in the middle of the track, at the back, and scroll forward until we're past the centre of the camera's view
|
|
respawnLocation.x = mapManager.startX;
|
|
|
|
Vector3 respawnLocationVP = Camera.main.WorldToViewportPoint(respawnLocation);
|
|
|
|
while (respawnLocationVP.x < 0.5f || respawnLocationVP.y < 0.5f)
|
|
{
|
|
respawnLocation.x += 1.0f;
|
|
respawnLocationVP = Camera.main.WorldToViewportPoint(respawnLocation);
|
|
//Debug.Log("respawnLocation = " + respawnLocation + ", viewport = " + respawnLocationVP);
|
|
}
|
|
//Debug.Log("Found middle of camera view, new respawn location: " + respawnLocation);
|
|
}
|
|
|
|
//Now we randomly pick blocks in the vicinity of this point to respawn players on
|
|
//Valid blocks are those within x & +/-1 from the chosen location
|
|
//If we don't find enough open, unoccupied blocks within that radius, then we widen it
|
|
|
|
int radius = 0;
|
|
List<Block> respawnBlocks;
|
|
do
|
|
{
|
|
respawnBlocks = new List<Block>();
|
|
radius++;
|
|
|
|
//Debug.Log("Finding respawn blocks at radius = " + radius);
|
|
|
|
for (int i = -1 * radius; i <= radius; i++)
|
|
{
|
|
for (int j = -1 * radius; j <= radius; j++)
|
|
{
|
|
Block currentBlock;
|
|
|
|
Vector3 currentPos = new Vector3(respawnLocation.x + i, respawnLocation.y, respawnLocation.z + j);
|
|
|
|
//Debug.Log("Checking position " + currentPos);
|
|
|
|
if (Block.isBlockAtPosition(currentPos, 1, ~Ignore, out currentBlock))
|
|
{
|
|
/*Debug.Log("Found block at " + currentPos
|
|
+ ". is_Walkable = " + currentBlock.is_Walkable
|
|
+ ", currentBlock is Water = " + (currentBlock is Water)
|
|
+ ", isPit = " + currentBlock.isPit
|
|
+ ", no currentPlayer = " + (currentBlock.CurrentPlayer == null));*/
|
|
|
|
if (currentBlock.is_Walkable //Are we allowed on this block?
|
|
&& !(currentBlock is Water) && !(currentBlock.isPit) //Don't respawn on top of instant traps
|
|
&& currentBlock.CurrentPlayer == null) //Block must be unoccupied
|
|
{
|
|
respawnBlocks.Add(currentBlock);
|
|
//Debug.Log("Added block at " + currentPos);
|
|
}
|
|
else
|
|
{
|
|
//Debug.Log("Block at " + currentPos + " invalid");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//Debug.Log("No block at " + currentPos);
|
|
}
|
|
}
|
|
}
|
|
} while (respawnBlocks.Count < respawningPlayers.Count);
|
|
|
|
//Debug.Log("Possible respawn spots: ");
|
|
|
|
/*foreach(Block block in respawnBlocks)
|
|
{
|
|
Debug.Log(block.transform.position);
|
|
}*/
|
|
|
|
foreach (PlayerData player in respawningPlayers)
|
|
{
|
|
//We randomly pick a block for them to respawn on
|
|
int respawnIndex = (int)Random.Range(0.0f, (float)respawnBlocks.Count);
|
|
//Debug.Log("Respawn index = " + respawnIndex);
|
|
Block respawnBlock = respawnBlocks[respawnIndex];
|
|
//Debug.Log("Respawn location = " + respawnBlock.transform.position);
|
|
player.character.respawnCharacter(respawnBlock);
|
|
|
|
respawnBlocks.Remove(respawnBlock); //Then we remove it from the list for the next player
|
|
}
|
|
}
|
|
}
|
|
|
|
void cameraCheck(PlayerData[] allPlayers)
|
|
{
|
|
//Safety check: if anyone has somehow ended up behind the camera before it moved, mark them as needing respawning
|
|
checkInView(allPlayers);
|
|
|
|
//Get the average x-position of all players
|
|
float xAvg = 0.0f;
|
|
int livePlayerCount = 0;
|
|
|
|
foreach (PlayerData player in allPlayers)
|
|
{
|
|
if (!(player.character.respawnNeeded) && player.client.Lives > 0)
|
|
{
|
|
xAvg += player.character.transform.position.x;
|
|
livePlayerCount++;
|
|
}
|
|
}
|
|
|
|
if (livePlayerCount > 0)
|
|
{
|
|
xAvg = xAvg / livePlayerCount;
|
|
}
|
|
|
|
//Turn that position into a vector in viewport space
|
|
Vector3 xAvgPos = new Vector3(xAvg, 0.0f, 0.0f);
|
|
Vector3 xAvgVP = Camera.main.WorldToViewportPoint(xAvgPos);
|
|
|
|
//Debug.Log("xAvgPos = " + xAvgPos + ", xAvgVP = " + xAvgVP);
|
|
|
|
//We move the camera forward by at least one increment
|
|
//Keep doing it until the average x-position is roughly centred
|
|
do
|
|
{
|
|
Camera.main.transform.Translate(scrollSpeed, 0, 0, Space.World);
|
|
xAvgVP = Camera.main.WorldToViewportPoint(xAvgPos);
|
|
|
|
//Debug.Log("Camera = " + Camera.main.transform.position + ", xAvgVP = " + xAvgVP);
|
|
} while (xAvgVP.x > 0.5f || xAvgVP.y > 0.5f);
|
|
|
|
//If the foremost player is off the screen, scroll forward to catch up with them
|
|
|
|
//Find who's furthest ahead
|
|
PlayerData playerAhead = allPlayers[0];
|
|
foreach (PlayerData player in allPlayers)
|
|
{
|
|
if (player.client.Lives > 0 && player.character.transform.position.x > playerAhead.character.transform.position.x)
|
|
{
|
|
playerAhead = player;
|
|
}
|
|
}
|
|
|
|
//Turn their position to a viewport vector
|
|
Vector3 playerVP = Camera.main.WorldToViewportPoint(playerAhead.character.transform.position);
|
|
|
|
//If that position is not in view, advance until it is
|
|
while (playerVP.x > 1 || playerVP.y > 1)
|
|
{
|
|
Camera.main.transform.Translate(scrollSpeed, 0, 0, Space.World);
|
|
playerVP = Camera.main.WorldToViewportPoint(playerAhead.character.transform.position);
|
|
}/**/
|
|
|
|
//If anyone is out of view at this point, they are dead and need to respawn
|
|
checkInView(allPlayers);
|
|
/*foreach (PlayerData player in allPlayers)
|
|
{
|
|
if (player.client.Lives > 0 && !(player.character.respawnNeeded))
|
|
{
|
|
playerVP = Camera.main.WorldToViewportPoint(player.character.transform.position);
|
|
if (playerVP.x < 0.0f || playerVP.y < 0.0f)
|
|
{
|
|
player.character.respawnNeeded = true;
|
|
}
|
|
}
|
|
}*/
|
|
|
|
//We check for track sections we need to add/remove
|
|
mapManager.checkTrack();
|
|
//spawn.wakeup();
|
|
|
|
//Move the camera forward at a steady rate each round
|
|
/*if (scrollSpeed > 0.0f)
|
|
{
|
|
Camera.main.transform.Translate(scrollSpeed, 0, 0, Space.World);
|
|
mapManager.checkTrack();
|
|
//Debug.Log("New camera position at x = " + Camera.main.transform.position.x);
|
|
|
|
//Plan: get average position of surviving players, centre camera view on it
|
|
//Also, will need to check every time someone moves: are they still on camera?
|
|
//If ahead of camera, advance it until they're visible
|
|
//If behind camera, they lose a life & respawn
|
|
}
|
|
else
|
|
{
|
|
//Debug.Log("Not scrolling");
|
|
}*/
|
|
|
|
}
|
|
|
|
//Marks any player out of the camera's view as in need of respawning
|
|
void checkInView(PlayerData[] allPlayers)
|
|
{
|
|
//If anyone is out of view at this point, they are dead and need to respawn
|
|
foreach (PlayerData player in allPlayers)
|
|
{
|
|
if (player.client.Lives > 0 && !(player.character.respawnNeeded))
|
|
{
|
|
Vector3 playerVP = Camera.main.WorldToViewportPoint(player.character.transform.position);
|
|
if (playerVP.x < 0.0f || playerVP.y < 0.0f)
|
|
{
|
|
player.character.respawnNeeded = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Checks if the Game is finished
|
|
/// </summary>
|
|
/// <returns>returns if game is finished</returns>
|
|
public override bool isGameOver(PlayerData[] allPlayers)
|
|
{
|
|
return (RoundCount >= MaxRound -1);
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Called once per player after they have moved onto a block
|
|
/// </summary>
|
|
/// <param name="character">Character which moved</param>
|
|
/// <param name="client">Client of the character</param>
|
|
/// <param name="currentBlock">Block moved onto</param>
|
|
protected override void OnPlayerMoved(Character character, ClientData client, Block currentBlock)
|
|
{
|
|
handleFalling(character, client, currentBlock, character.justMoved);
|
|
|
|
/*Debug.Log("Moved to square at " + currentBlock.transform.position.x + ", "
|
|
+ currentBlock.transform.position.y + ", "
|
|
+ currentBlock.transform.position.z);
|
|
|
|
//If a character has fallen in the water or into a pit, we mark that fact, and they lose the rest of their turn
|
|
character.inWater = currentBlock.isWater;
|
|
character.respawnNeeded = currentBlock.isPit;
|
|
|
|
if (character.inWater == true || character.respawnNeeded == true)
|
|
{
|
|
character.stuck = true;
|
|
}
|
|
|
|
Debug.Log("inWater = " + character.inWater + ", respawnNeeded = " + character.respawnNeeded + ", stuck = " + character.stuck);*/
|
|
|
|
//Commented out because we don't do this in the racetrack mode
|
|
|
|
/*ClientData OwnedClient;
|
|
Material overlay = null;
|
|
if (isOwned(currentBlock, out OwnedClient))
|
|
{
|
|
if (OwnedClient == client)
|
|
return;
|
|
|
|
BlocksOwned[OwnedClient].Remove(currentBlock);
|
|
|
|
foreach (Material mat in currentBlock.GetComponent<Renderer>().materials)
|
|
{
|
|
if (mat.name == OverlayMaterial.name + " (Instance)")
|
|
overlay = mat;
|
|
}
|
|
}
|
|
|
|
if (overlay == null)
|
|
{
|
|
overlay = new Material(OverlayMaterial);
|
|
List<Material> mats = new List<Material>(currentBlock.GetComponent<Renderer>().materials);
|
|
mats.Add(overlay);
|
|
currentBlock.GetComponent<Renderer>().materials = mats.ToArray();
|
|
}
|
|
|
|
overlay.SetColor("_NewColor", client.Color);
|
|
|
|
if (!BlocksOwned.ContainsKey(client))
|
|
BlocksOwned.Add(client, new List<Block>());
|
|
|
|
BlocksOwned[client].Add(currentBlock);
|
|
|
|
if (overlay != null)
|
|
currentBlock.StartCoroutine(AnimateBlock(overlay, 0.25f));*/
|
|
}
|
|
|
|
protected override void OnRoundStart(PlayerData[] allPlayers)
|
|
{
|
|
|
|
}
|
|
|
|
protected override void OnAllPlayersMoved(PlayerData[] allPlayers)
|
|
{
|
|
foreach (PlayerData player in allPlayers)
|
|
{
|
|
/* The justMoved variable is used to determine whether a player taking their turn in the water should become stuck
|
|
* (because they moved into/in the water), or not (because they're turning while remaining in the same square)
|
|
* It's not needed from move to move, so we clear it
|
|
*/
|
|
player.character.justMoved = false;
|
|
|
|
/* if (BlocksOwned.ContainsKey(player.client))
|
|
player.client.SceneScore = BlocksOwned[player.client].Count;
|
|
else
|
|
player.client.SceneScore = 0;*/
|
|
}
|
|
}
|
|
|
|
protected override void OnGameOver(PlayerData[] allPlayers)
|
|
{
|
|
throw new System.NotImplementedException();
|
|
}
|
|
|
|
private bool isOwned(Block block, out ClientData client)
|
|
{
|
|
client = null;
|
|
|
|
foreach (KeyValuePair<ClientData, List<Block>> ownedList in BlocksOwned)
|
|
{
|
|
if (ownedList.Value.Contains(block))
|
|
{
|
|
client = ownedList.Key;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
private void handleFalling(Character character, ClientData client, Block currentBlock, bool didMove)
|
|
{
|
|
//If a character has fallen in the water or into a pit, we mark that fact, and they lose the rest of their turn
|
|
character.inWater = currentBlock.isWater;
|
|
character.respawnNeeded = currentBlock.isPit;
|
|
character.onCrystal = currentBlock.isCrystals;
|
|
character.underRock = currentBlock.isRock;
|
|
|
|
if (didMove && (character.inWater || character.respawnNeeded))
|
|
{
|
|
character.stuck = true;
|
|
}
|
|
//Debug.Log("inWater = " + character.inWater + ", respawnNeeded = " + character.respawnNeeded + ", stuck = " + character.stuck);
|
|
}
|
|
|
|
protected override void OnPlayerKilled(Character character, ClientData client)
|
|
{
|
|
if (character.respawnNeeded || character.onCrystal)
|
|
{
|
|
character.lives -= 1;
|
|
character.ClientLink.Lives = character.lives;
|
|
}
|
|
if(character.underRock && currentBoulderCount < 1)
|
|
{
|
|
character.lives -= 1;
|
|
character.ClientLink.Lives = character.lives;
|
|
}
|
|
}
|
|
|
|
private IEnumerator AnimateBlock(Material mat, float time)
|
|
{
|
|
float timeElasped = 0;
|
|
|
|
while (timeElasped < time)
|
|
{
|
|
mat.SetFloat("_Multiplier", (timeElasped / time));
|
|
yield return new WaitForEndOfFrame();
|
|
timeElasped += Time.deltaTime;
|
|
}
|
|
mat.SetFloat("_Multiplier", 1);
|
|
}
|
|
|
|
protected override void OnGameStart(PlayerData[] AllPlayers)
|
|
{
|
|
BlocksOwned = new Dictionary<ClientData, List<Block>>();
|
|
RoundCount = 0;
|
|
AllPlayers = getPlayerOrder(AllPlayers);
|
|
for (int i = 0; i < AllPlayers.Length; i++)
|
|
{
|
|
OnPlayerMoved(AllPlayers[i].character, AllPlayers[i].client, AllPlayers[i].character.CurrentBlock);
|
|
}
|
|
}
|
|
|
|
public override PlayerData[] getPlayerOrder(PlayerData[] AllPlayers)
|
|
{
|
|
AllPlayers = AllPlayers.ToArray().OrderBy(unit => unit.character.CurrentBlock.transform.position.x).ToArray();
|
|
int order = 1;
|
|
foreach(PlayerData data in AllPlayers)
|
|
{
|
|
data.character.runOrder = order;
|
|
order++;
|
|
}
|
|
return AllPlayers;
|
|
}
|
|
}
|