using System.Collections.Generic; using UnityEngine; using Mirror; using System.Linq; using UnityEngine.UI; using System.Collections; using CustomExtensions; using Debug = CustomLogger.Debug; public class SpaceshipController : NetworkBehaviour { public LayerMask pickupsLayer; [SyncVar] public bool ready; public SpriteRenderer playerImg; public SkinsData skins; [SyncVar(hook = nameof(OnSkinChanged))] public string skinName = null; [SyncVar(hook = nameof(OnPnameChanged))] public string pname; [SyncVar(hook = nameof(OnScoresChanged))] public int Scores; [SyncVar(hook = nameof(OnKillsChanged))] public int Kills; [SyncVar(hook = nameof(OnTrailTimeChanged))] public float trailTime; public float minTrailTime; public float maxTrailTime; [SyncVar] public int moonsCollected; public float trailIncrementRate = 0.5f; [SyncVar] public bool dead; [SyncVar] public float speed; [SyncVar] public float speedMultiplier; [SyncVar] public float boostConsumption; [SyncVar] public float energyGain; public float _scaleMultiplier = 1; public Text pnameTxt; public Transform body; public TrailMgr trailMgr; public float movingSpeed = 0.1f; public float turningSmoothFactor = 0.1f; public Joystick joystick; [SyncVar] public bool boosting; public bool boosting_local; [Header("Client Prediction")] private Vector2 input; [SyncVar] public Vector2 m_Input; [SerializeField]private float rubberbandSmoothness = 0.3f; private float timer; private int currentTick; private float minTimeBetweenTicks; private const float SERVER_TICK_RATE = 30f; private const int BUFFER_SIZE = 2048; private const bool CUSTOM_NET_TRANSFORM = true; private float ERROR_THRESHOLD =0.25f; private int MIN_ERROR_COUNT = 10; public bool showDebugHUD = false; private double clientNetworkRTT; public InputState[] clientInputBuffer; public Queue inputQueue; public PlayerState[] stateBuffer; public GameObject DeathEffect; public GameObject debrisEffect; // public GameObject boostStartEffect; public ParticleSystem[] boostStartEffect; public ParticleSystem[] boostEffects; // public GameObject PickupEffect; public float distanceFromCenter = 0; RocketLevel rocketStats; [Header("AudioFX")] public AudioSource engineAudio; public float normalPitch,boostedPitch; public void SetReady(bool value) { if (isServer) { ready = value; } else { CmdSetReady(value); } } [Command] void CmdSetReady(bool value) { ready = value; } [Command] void CmdSetPname(string value) { pname = value; } void OnPnameChanged(string oldName, string newName) { pnameTxt.text = newName; Debug.Log(pname + " Joined the game"); } [Command] void CmdSetSkin(string newSkin) { skinName = newSkin; } [Command] void CmdSetStats(float _speed, float _energy, float _boost) { speedMultiplier = _speed; energyGain = _energy; boostConsumption = _boost; } void OnSkinChanged(string oldSkin, string newSkin) { ChangeSkin(newSkin); } void ChangeSkin(string name) { Sprite newSprite = SkinManagerHelper.getSkinSprite(name, skins); playerImg.sprite = newSprite; } void OnScoresChanged(int oldScores, int newScores) { Debug.Log($"Add scores {newScores - oldScores}, (total: {newScores})"); } bool isBoostAvailable {get{ return trailTime > minTrailTime;}} void OnTrailTimeChanged(float oldValue, float newValue) { trailMgr.trail.time = newValue; if (isLocalPlayer) { // SceneData.holder.boostBtn.gameObject.SetActive(isBoostAvailable); SceneData.holder.boostBtn.transform.GetChild(0).gameObject.SetActive(isBoostAvailable); SceneData.holder.boostBtn.transform.GetChild(0).GetChild(0).gameObject.SetActive(trailTime > maxTrailTime/2f); SceneData.holder.boostBtn.transform.GetChild(0).GetChild(1).gameObject.SetActive(trailTime > maxTrailTime* 0.8f); SceneData.holder.boostBtn.transform.GetChild(0).GetChild(2).gameObject.SetActive(trailTime >= maxTrailTime); SceneData.holder.boostBtn.transform.GetChild(0).Find("Slider").GetComponent().fillAmount = Mathf.Clamp(0.73f - ((trailTime /maxTrailTime)*0.73f),0f,0.73f); if (!isBoostAvailable && boosting) { CmdSetBoosting(false); } } } void OnBoostDown() { if(!isBoostAvailable){return; } if (isLocalPlayer) { boosting_local = true; AudioManager.instnace.Boost(); if (isServer) { boosting = true; } else { CmdSetBoosting(true); } CameraFollower.instance.ShakeBoost(); Vibrate(); // GameObject fx = EffectPool.Spawn(boostStartEffect, transform.position); foreach(ParticleSystem particle in boostStartEffect){ particle.Play(); } } } void Vibrate(){ #if UNITY_ANDROID if(ControlSettings.HapticEnabled){ Handheld.Vibrate(); } #endif } void OnBoostUp() { if (isLocalPlayer) { boosting_local = false; if (isServer) { boosting = false; } else { CmdSetBoosting(false); } } } [Command] void CmdSetBoosting(bool value) { boosting = value; } [SyncVar] public double startedTime = 0; void ResetStats() { startedXp = DBmanager.Xp; startedMetal = DBmanager.Metal; } void Start() { clientInputBuffer = new InputState[BUFFER_SIZE]; inputQueue = new Queue(); stateBuffer = new PlayerState[BUFFER_SIZE]; minTimeBetweenTicks = 1f / SERVER_TICK_RATE; string[] args = System.Environment.GetCommandLineArgs(); for (int i = 0; i < args.Length; i++) { // Debug.Log("ARG " + i + ": " + args[i]); if (args[i] == "-et") { ERROR_THRESHOLD =float.Parse(args[i+1]); } if(args[i] == "-grace"){ MIN_ERROR_COUNT = int.Parse(args[i+1]); } } if (isLocalPlayer) { ResetStats(); if (MinigameManager.instance.isRanked) { DBmanager.SetMetal(DBmanager.Metal + DBmanager.CurrentRank.energyReward); DBmanager.SetMetal(DBmanager.Metal - DBmanager.CurrentRank.entryFee); } if (joystick == null) { joystick = FindObjectOfType(); } FindObjectOfType().SetTarget(transform); string myName = DBmanager.DisplayName; SceneData.localPlayer = gameObject; SceneData.OnBoostDown.AddListener(OnBoostDown); SceneData.OnBoostUp.AddListener(OnBoostUp); //Set username if (isServer) { pname = myName; } else { CmdSetPname(myName); } //Set Skin if (DBmanager.GetSkinIdByName(SkinShopManager.GetEquipedSkin()) < 0) { //False skin purchase } else { string skin = SkinShopManager.GetEquipedSkin(); SkinType rarity = SkinShopManager.GetRarityFromName(skin); PurchasedSkin purchasedSkin = DBmanager.GetSkinByName(skin); float m_speed = SkinShopManager.GetStatsForRarity(rarity)[purchasedSkin.speedLevel].speedMultiplier; float m_energy = SkinShopManager.GetStatsForRarity(rarity)[purchasedSkin.energyLevel].moonMultiplier; float m_boost = SkinShopManager.GetStatsForRarity(rarity)[purchasedSkin.boostLevel].boostConsumption; if (isServer) { skinName = skin; speedMultiplier = m_speed; energyGain = m_energy; boostConsumption = m_boost; } else { CmdSetSkin(skin); CmdSetStats(m_speed, m_energy, m_boost); } } pnameTxt.text = myName; pnameTxt.gameObject.SetActive(false); } if (isServer) { startedTime = NetworkTime.time; trailTime = 0; DecreaseTrail(0); }else{ if(AudioManager.SFX_enabled){ engineAudio.volume = 1; }else{ engineAudio.volume= 0; } } } float scale => Mathf.Clamp(1 + (trailTime * _scaleMultiplier), 1, 10); void Update() { #if UNITY_EDITOR // Debug.Log(FindObjectsOfType().Length); #endif if (MinigameManager.instance.isRanked && !MinigameManager.instance.RankedGameStarted) { return; } distanceFromCenter = Vector3.Distance(transform.position, Vector3.zero); pnameTxt.rectTransform.rotation = Quaternion.Euler(Vector3.zero); //Update size of trail and spaceship transform.localScale = Vector3.Lerp(transform.localScale, new Vector3(scale, scale, scale), 0.1f); trailMgr.trail.startWidth = Mathf.Lerp(trailMgr.trail.startWidth, scale, 0.1f); if (dead) { return; } if(isLocalPlayer){ if (joystick == null) { joystick = FindObjectOfType(); if(joystick==null){return;} } input = joystick.input; if (Input.GetKeyDown(KeyCode.F)) { CmdCheatKills(); } if(Input.GetKeyDown(KeyCode.Space)){ OnBoostDown(); } if(Input.GetKeyUp(KeyCode.Space)){ OnBoostUp(); } // Debug.Log(startedTime-NetworkTime.time); double survivalTime = (startedTime == 0) ? 0 : NetworkTime.time - ((MinigameManager.instance.isRanked) ? MinigameManager.instance.startedTime : startedTime); if (DBmanager.MostTime < (int)survivalTime) { DBmanager.SetMostTime((int)survivalTime); } SceneData.SetTimerTxt(survivalTime); // Debug.Log(lastTime - currentTick); } timer += Time.deltaTime; while(timer >= minTimeBetweenTicks){ timer -= minTimeBetweenTicks; if(isServer){ HandleTick(); }else if(isLocalPlayer){ ClientHandleTick(); } currentTick++; } // return; } void FixedUpdate(){ if(!isLocalPlayer && !isServer){ transform.position = latestServerState.Position; transform.rotation = latestServerState.Rotation; } } #region Movement IEnumerator SendToClient(PlayerState statePayload) { yield return new WaitForSeconds(0.02f); OnServerMovementState(statePayload); } void HandleTick(){ #region obsoleteCommented /* curBufferIndex = bufferIndex; if(isLocalPlayer){ // HandleInput(input); InputState curInputState = new InputState(){Tick=currentTick, Input = input}; PlayerState curState = new PlayerState(){Tick=currentTick, Position= transform.position, Rotation = transform.rotation}; clientInputBuffer[bufferIndex] = curInputState; clientStateBuffer[bufferIndex] = curState; // transform.Translate(transform.forward * input.y); // transform.Rotate(transform.up * input.x); UpdateStates(currentTick, NetworkTime.rtt, input, curState.Position, curState.Rotation); // body.position = targetState.Position; // body.rotation = targetState.Rotation; // body.position = Vector3.Lerp(body.position,targetState.Position,MovementSmoothnessFactor * movingSpeed); // body.rotation = Quaternion.Lerp(body.rotation, targetState.Rotation,MovementSmoothnessFactor*movingSpeed); }else if (isServer){ HandleInput(m_Input); if(CUSTOM_NET_TRANSFORM){ RpcUpdateOnClient(body.position, body.rotation, m_Input); } CheckForPickups(); }else{ //client but not localPlayer body.position = targetState.Position; body.rotation = targetState.Rotation; // for(int i = 0; i < ticksGap; i++){ // HandleInput(m_Input); // } } // if(isLocalPlayer){CameraFollower.UpdateFrame();} float stateError = clientStateBuffer[bufferIndex].Difference(serverStateBuffer[latencyBufferIndex]); foreach(ParticleSystem boostEffect in boostEffects){ if(boostEffect.isEmitting && !boosting){ boostEffect.Stop(); }else if(!boostEffect.isEmitting && boosting){ boostEffect.Play(); } } if(isServer){ InputState curServerInputState = new InputState(){Tick=currentTick, Input = m_Input}; PlayerState curServerState = new PlayerState(){Tick=currentTick, Position = transform.position, Rotation = transform.rotation}; serverInputBuffer[bufferIndex] = curServerInputState; serverStateBuffer[bufferIndex] = curServerState; if(!clientInputBuffer[bufferIndex].CloseEnough(serverInputBuffer[latencyBufferIndex],ERROR_THRESHOLD)){ ErrorInputCount++; } // if(!clientStateBuffer[bufferIndex].CloseEnough(serverStateBuffer[latencyBufferIndex],ERROR_THRESHOLD)){ if(stateError > ERROR_THRESHOLD){ ErrorStateCount++; errorCounter++; if(errorCounter> MIN_ERROR_COUNT){RpcRubberband(transform.position, transform.rotation); errorCounter=0;} }else{ errorCounter = 0; } //boost check if (boosting && scale > 1) { speed = movingSpeed * 2 * speedMultiplier; DecreaseTrail(Time.deltaTime * boostConsumption * 0.5f); // scaleMultiplier -= Time.deltaTime; // if (scaleMultiplier < 1) { scaleMultiplier = 1; } //Clamp in case gets lower } else { speed = movingSpeed * speedMultiplier; } }*/ #endregion CheckForPickups(); UpdateSpeed(); int bufferIndex = -1; while(inputQueue.Count > 0){ InputState inputState = inputQueue.Dequeue(); bufferIndex = inputState.Tick % BUFFER_SIZE; PlayerState statePayload = ProcessMovement(inputState); stateBuffer[bufferIndex] = statePayload; } if(bufferIndex != -1){ StartCoroutine(SendToClient(stateBuffer[bufferIndex])); } } void UpdateSpeed(){ if (boosting && trailTime >= minTrailTime) { speed = movingSpeed * 2 * speedMultiplier; DecreaseTrail(Time.deltaTime * boostConsumption * 0.5f); } else { boosting = false; speed = movingSpeed * speedMultiplier; } } public float speedLocal = 0; void UpdateSpeedLocal(){ if (boosting_local && trailTime >= minTrailTime) { speedLocal = movingSpeed * 2 * speedMultiplier; DecreaseTrail(Time.deltaTime * boostConsumption * 0.5f); } else { boosting_local = false; OnBoostUp(); speedLocal = movingSpeed * speedMultiplier; } } void ClientHandleTick(){ if(isLocalPlayer){ UpdateSpeedLocal(); } engineAudio.pitch = Mathf.Lerp(engineAudio.pitch, (boosting) ? boostedPitch : normalPitch, 0.1f); if (!latestServerState.Equals(default(PlayerState)) && (lastProcessedState.Equals(default(PlayerState)) || !latestServerState.Equals(lastProcessedState))) { // Debug.Log("Consider Reconciliation"); HandleServerReconciliation(); } int bufferIndex = currentTick % BUFFER_SIZE; // Add payload to inputBuffer InputState inputPayload = new InputState(); inputPayload.Tick = currentTick; inputPayload.Input = joystick.input; clientInputBuffer[bufferIndex] = inputPayload; // Add payload to stateBuffer stateBuffer[bufferIndex] = ProcessMovement(inputPayload); if(isLocalPlayer){ CameraFollower.UpdateFrame(); } // Send input to server StartCoroutine(SendToServer(inputPayload)); } public void OnClientInput(InputState inputPayload){ if(isServer){ m_OnClientInput(inputPayload); }else{ CmdOnClientInput(inputPayload); } } [Command] public void CmdOnClientInput(InputState inputPayload){ m_OnClientInput(inputPayload); } public void m_OnClientInput(InputState inputPayload) { inputQueue.Enqueue(inputPayload); } private PlayerState latestServerState; private PlayerState lastProcessedState; void HandleServerReconciliation() { lastProcessedState = latestServerState; int serverStateBufferIndex = latestServerState.Tick % BUFFER_SIZE; float positionError = Vector3.Distance(latestServerState.Position, stateBuffer[serverStateBufferIndex].Position); float rotationError = CustomExtensions.QuaternionExtensions.Difference(latestServerState.Rotation, stateBuffer[serverStateBufferIndex].Rotation); if (positionError > 0.001f || !latestServerState.Rotation.Approximately(stateBuffer[serverStateBufferIndex].Rotation, 0.0001f)) { Debug.Log($"We have to reconcile bro, Errors\npos:{positionError}, rot:{rotationError}"); // Rewind & Replay transform.position = latestServerState.Position; transform.rotation = latestServerState.Rotation; // Update buffer at index of latest server state stateBuffer[serverStateBufferIndex] = latestServerState; // Now re-simulate the rest of the ticks up to the current tick on the client int tickToProcess = latestServerState.Tick + 1; while (tickToProcess < currentTick) { int bufferIndex = tickToProcess % BUFFER_SIZE; // Process new movement with reconciled state PlayerState statePayload = ProcessMovement(clientInputBuffer[bufferIndex]); // Update buffer with recalculated state stateBuffer[bufferIndex] = statePayload; tickToProcess++; } }else{ // Debug.Log($"Reconciliation cancelled\n, Errors\npos:{positionError}, rot:{rotationError}"); } } public void m_OnServerMovementState(PlayerState serverState) { latestServerState = serverState; } public void OnServerMovementState(PlayerState serverState){ if(isServer){ RpcOnServerMovementState(serverState); }else{ m_OnServerMovementState(serverState); // CmdOnServerMovementState(serverState); } } [ClientRpc] public void RpcOnServerMovementState(PlayerState serverState){ m_OnServerMovementState(serverState); // Debug.Log(serverState.Position + ":" + transform.position); } IEnumerator SendToServer(InputState inputPayload) { yield return new WaitForSeconds(0.02f); OnClientInput(inputPayload); } PlayerState ProcessMovement(InputState input) { // Should always be in sync with same function on Client body.Translate(new Vector3(0, (isLocalPlayer) ? speedLocal: speed), body); Turn(input.Input); return new PlayerState() { Tick = input.Tick, Position = transform.position, Rotation = transform.rotation }; } #endregion void CheckForPickups(){ Collider2D[] hits = Physics2D.OverlapCircleAll(transform.position, 3, pickupsLayer); foreach(Collider2D hit in hits){ if(hit!=null ){ // Debug.Log(hit.name); PickupItem pickupItem = hit.GetComponent(); if(pickupItem !=null && pickupItem.active){ pickupItem.active=false; pickupItem.Deactivate(transform); CollectPickup(pickupItem.type); } } } } // void HandleInput(Vector2 _input){ // // transform.Translate(transform.forward * _input.y); // // transform.Rotate(transform.up * _input.x); // body.Translate(new Vector3(0, speed), body); // Turn(_input); // } void Turn(Vector2 input) { body.rotation = Quaternion.Lerp(body.rotation, getTurnAngle(input), turningSmoothFactor * input.magnitude); } Quaternion getTurnAngle(Vector2 input) { var angle1 = Mathf.Atan2(-input.y, -input.x) * Mathf.Rad2Deg; return Quaternion.AngleAxis(angle1 + 90, Vector3.forward); } [ClientRpc] void RpcUpdateTrail(Vector3[] positions) { //trailMgr.trail.SetPositions(positions); } public float Angle(Vector2 vector2) { return 360 - (Mathf.Atan2(vector2.x, vector2.y) * Mathf.Rad2Deg * Mathf.Sign(vector2.x)); } //Auto assign default variables [Editor only] void OnValidate() { if (body == null) { body = transform; } if (trailMgr == null) { trailMgr = GetComponent(); } } public void TrailCollided(Collider2D hit) { if (!isServer) { Debug.Log("Got hit : " + hit.name); // Debug.Log("This cannot run on client, That's illegal!"); // <-- What this log says return; } SpaceshipController deadPlayer = hit.GetComponent(); // Debug.Log("got hit by player? : " + (deadPlayer != null)); if (deadPlayer != null && !deadPlayer.dead && (NetworkTime.time - deadPlayer.startedTime) > 5) { // <-- okay we killed someone | KILLCODE // deadPlayer.RpcShowDeathEffect(deadPlayer.transform.position); deadPlayer.Die(pname); Debug.Log($"{pname} killed {deadPlayer.pname}"); OnKill(); } SpaceshipNetworkBot deadBot = hit.GetComponent(); // Debug.Log("got hit by bot? : " + (deadPlayer != deadBot)); if(deadBot != null){ deadBot.Die(); Kills++; return; } } public void ShowDeathEffect(Vector2 position){ if(isServer){ // MinigameManager.instance.ShowDeathEffect(position); EffectPool.Spawn(DeathEffect, position); RpcShowDeathEffect(position); }else{ CmdShowDeathEffect(position); } } [Command] void CmdShowDeathEffect(Vector2 position){ EffectPool.Spawn(DeathEffect, position); RpcShowDeathEffect(position); } [ClientRpc] void RpcShowDeathEffect(Vector2 position){ EffectPool.Spawn(DeathEffect, position); // EffectPool.Spawn(debrisEffect, position).GetComponent().Play(); } void OnKill() { Kills++; // Scores += 10; //TODO: Need to change Scores on kills? // scaleMultiplier += 0.05f; // OnScaleChanged(scaleMultiplier, scaleMultiplier); IncreaseTrail(trailIncrementRate); RpcOnKill(); } [ClientRpc] void RpcOnKill() { Vibrate(); } void OnKillsChanged(int oldVal, int newVal) { if (isLocalPlayer) { Debug.Log("Show kills for " + newVal); KillText.Show(newVal); } } void IncreaseTrail(float rate) { trailTime = Mathf.Clamp(trailMgr.trail.time + rate, minTrailTime, maxTrailTime); trailMgr.trail.time = trailTime; // Debug.Log("Increasing trail of" + pname); } public void DecreaseTrail(float rate) { trailTime = Mathf.Clamp(trailMgr.trail.time - rate, minTrailTime, maxTrailTime); trailMgr.trail.time = trailTime; // Debug.Log("Decreasing trail of" + pname); } [Command] void CmdCheatKills() { OnKill(); } public void CollectPickup(PickupItem.PickupType type) { if (!isServer) { Debug.Log("Server function ran on client. That's illegal!"); } // <-- What this log says if(type == PickupItem.PickupType.Moon1 || type == PickupItem.PickupType.Moon2|| type == PickupItem.PickupType.Moon3|| type == PickupItem.PickupType.Moon4|| type == PickupItem.PickupType.Moon5){ //Moon! IncreaseTrail(trailIncrementRate); // scaleMultiplier += 0.05f; moonsCollected++; Scores += 2; }else { IncreaseTrail(trailIncrementRate / 2f); Scores += 1; } RpcCollectPickup(type); } [ClientRpc] void RpcCollectPickup(PickupItem.PickupType type) { if (isLocalPlayer) { if (type == PickupItem.PickupType.Twep) { DBmanager.SetTweps(DBmanager.Tweps + 1); } else { int gainedEnergy = (int)(energyGain * type.GetEnergy()); MinigameManager.instance.GainMetals(gainedEnergy); } AudioManager.instnace.CollectMoon(trailTime,maxTrailTime); } } public void Die(string killer) { MinigameManager.instance.SpawnLeftoverPickups(transform.position, (int)((float)Scores / 4f)); RpcShowDeathEffect(transform.position); Debug.Log($"Sending death signal to {pname} by {killer}"); //Handle Respawning // OnScaleChanged(scaleMultiplier, 1); // scaleMultiplier = 1; dead = true; Scores = 0; Kills = 0; startedTime = NetworkTime.time; DecreaseTrail(trailTime); trailMgr.trail.time = trailTime; RpcDie(killer); gameObject.SetActive(false); //MinigameManager.instance.SetRespawn(gameObject); } private int startedXp; private int startedMetal; bool RewardedRankedMetal = false; [ClientRpc] public void RpcDie(string killer) { Debug.Log($"{killer} killed {pname} : isItMe? -> {isLocalPlayer}"); KillfeedMgr.instance.AddNewEntry($"{pname} was killed by {killer}"); gameObject.SetActive(false); if (isLocalPlayer) { //TODO: Death message goes here SceneData.holder.ShowDeadscreen(DBmanager.Xp - startedXp, DBmanager.Metal - startedMetal, NetworkTime.time - startedTime); if (MinigameManager.instance.isRanked && RankedSplash.instance.gameObject.activeSelf) { // MinigameManager.instance.rankedSummary.ShowLoss(); // StartCoroutine(ShowRankedResults()); if (MinigameManager.instance.winnerId <= 0) { Debug.LogError("Winner ID not synced yet"); checkWinner(1000); } if (MinigameManager.instance.winnerId != netId) { //LOSER! MinigameManager.instance.rankedSummary.ShowLoss(); AudioManager.instnace.MinigameLost(); } else { MinigameManager.instance.rankedSummary.ShowWin(); AudioManager.instnace.MinigameWon(); } } } } async void checkWinner(int delay){ await System.Threading.Tasks.Task.Delay(delay); if (MinigameManager.instance.winnerId != netId) { //LOSER! MinigameManager.instance.rankedSummary.ShowLoss(); AudioManager.instnace.MinigameLost(); } else { MinigameManager.instance.rankedSummary.ShowWin(); AudioManager.instnace.MinigameWon(); } } IEnumerator ShowRankedResults() { while (MinigameManager.instance.winnerId <= 0) { yield return new WaitForFixedUpdate(); } } public void Respawn(Vector3 respawnPoint) { Debug.Log("Respawning " + pname); if (isServer) { respawn(respawnPoint); } else { CmdRespawn(respawnPoint); } } [Command] void CmdRespawn(Vector3 respawnPoint) { respawn(respawnPoint); } public void respawn(Vector3 respawnPoint) { dead = false; trailMgr.trail.emitting = false; trailMgr.trail.Clear(); RpcRespawn(respawnPoint); transform.position = respawnPoint; trailMgr.trail.emitting = true; gameObject.SetActive(true); } [ClientRpc] public void RpcRespawn(Vector3 respawnPoint) { GetComponent().enableValidation = false; trailMgr.trail.Clear(); trailMgr.positions = new Vector3[0]; trailMgr.trail.emitting = false; transform.position = respawnPoint; trailMgr.trail.emitting = true; GetComponent().enableValidation = true; gameObject.SetActive(true); if (isLocalPlayer) { SceneData.holder.deadScreen.SetActive(false); ResetStats(); } } [Command] public void CmdWonRanked() { WonRanked(); } [Command] public void CmdLostRanked() { LostRanked(); } public void WonRanked() { if (!isServer) { return; } RpcWonRanked(); Die("server"); } [ClientRpc] void RpcWonRanked() { if (isLocalPlayer) { DBmanager.SetMetal(DBmanager.Metal + 500); MinigameManager.instance.rankedSummary.ShowWin(); //Add trophies DBmanager.SetTrophies(DBmanager.Trophies + 30); } } public void LostRanked() { if (!isServer) { return; } RpcLostRanked(); Die("server"); } [ClientRpc] void RpcLostRanked() { if (isLocalPlayer) { DBmanager.SetMetal(DBmanager.Metal + 250); MinigameManager.instance.rankedSummary.ShowLoss(); // DBmanager.SetTrophies(Mathf.Clamp(DBmanager.Trophies - 15, 0, int.MaxValue)); DBmanager.SetTrophies(Mathf.Clamp(DBmanager.Trophies - DBmanager.CurrentRank.trophieLoss, 0, int.MaxValue)); } } //Emojis public void ShowEmoji(int index, Vector3 position){ if(isServer){ RpcShowEmoji(index, position); }else{ CmdShowEmoji(index, position); EmojiButton.instance.showEmoji(index,position); } } [Command] void CmdShowEmoji(int index, Vector3 position){ RpcShowEmoji(index,position); } [ClientRpc] void RpcShowEmoji(int index, Vector3 position){ if(isLocalPlayer){ Debug.Log("ClientRPC for emoji received. I already showed it");return;} EmojiButton.instance.showEmoji(index, position); } }