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 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> BlocksOwned; int currentBoulderCount; /// /// Called once before any players have spawned /// protected override void OnPreGameStart() { mapManager.init(); } /// /// Called once all players have finished their moves but before the Objective is checked /// 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 respawningPlayers = new List(); List survivingPlayers = new List(); foreach (PlayerData player in allPlayers) { if (player.client.Lives > 0) { if (player.character.respawnNeeded) { respawningPlayers.Add(player); } 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 survivingPlayerLocations = new List(); 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 respawnBlocks; do { respawnBlocks = new List(); 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) { //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 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"); }*/ } /// /// Checks if the Game is finished /// /// returns if game is finished public override bool isGameOver(PlayerData[] allPlayers) { return (RoundCount >= MaxRound -1); } /// /// Called once per player after they have moved onto a block /// /// Character which moved /// Client of the character /// Block moved onto 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().materials) { if (mat.name == OverlayMaterial.name + " (Instance)") overlay = mat; } } if (overlay == null) { overlay = new Material(OverlayMaterial); List mats = new List(currentBlock.GetComponent().materials); mats.Add(overlay); currentBlock.GetComponent().materials = mats.ToArray(); } overlay.SetColor("_NewColor", client.Color); if (!BlocksOwned.ContainsKey(client)) BlocksOwned.Add(client, new List()); 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> 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>(); 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; } }