UPF/Assets/Game/Scripts/Minigame/SpaceshipController.cs
2022-10-06 23:45:04 +05:30

785 lines
24 KiB
C#

using System.Collections.Generic;
using UnityEngine;
using Mirror;
using System.Linq;
using UnityEngine.UI;
using System.Collections;
public class SpaceshipController : NetworkBehaviour
{
[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;
[SyncVar]
public int moonsCollected;
public float trailIncrementRate = 0.5f;
[SyncVar]
public bool dead;
[SyncVar]
public float speed;
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;
public Vector2 joyInput;
[SyncVar]
public bool boosting;
[Header("Client Prediction")]
public SortedDictionary<int, StatePayload> serverStateBuffer = new SortedDictionary<int, StatePayload>();
public Vector3 DetourError = new Vector3(0, 0.2f, 0);
public Vector3 Detour = Vector3.zero;
public Quaternion RotationDetour = Quaternion.identity;
public float DetourCorrectionFactor = 0.5f;
public float timeDelayErrorFix = 1f;
public bool showDebugHUD = false;
public float distanceFromCenter = 0;
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;
}
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})");
}
void OnTrailTimeChanged(float oldValue, float newValue)
{
trailMgr.trail.time = newValue;
if (isLocalPlayer)
{
SceneData.holder.boostBtn.gameObject.SetActive(scale > 1);
if(scale <=1 && boosting){
CmdSetBoosting(false);
}
}
}
void OnBoostDown()
{
if (isLocalPlayer)
{
if (isServer)
{
boosting = true;
}
else
{
CmdSetBoosting(true);
}
}
}
void OnBoostUp()
{
if (isLocalPlayer)
{
if (isServer)
{
boosting = false;
}
else
{
CmdSetBoosting(false);
}
}
}
[Command]
void CmdSetBoosting(bool value)
{
boosting = value;
}
[SyncVar]
double startedTime = 0;
void ResetStats()
{
startedXp = DBmanager.Xp;
startedMetal = DBmanager.Metal;
}
void Start()
{
if (isLocalPlayer)
{
ResetStats();
if (joystick == null) { joystick = FindObjectOfType<Joystick>(); }
FindObjectOfType<CameraFollower>().SetTarget(transform);
string myName = PlayerPrefs.GetString("username");
SceneData.localPlayer = gameObject;
SceneData.OnBoostDown.AddListener(OnBoostDown);
SceneData.OnBoostUp.AddListener(OnBoostUp);
//Set username
if (isServer) { pname = myName; }
else
{
CmdSetPname(myName);
}
//Set Skin
if (!DBmanager.SkinsPurchased.Contains(SkinShopManager.GetEquipedSkin()))
{
//False skin purchase
}
else
{
if (isServer)
{
skinName = SkinShopManager.GetEquipedSkin();
}
else
{
CmdSetSkin(SkinShopManager.GetEquipedSkin());
}
}
pnameTxt.text = myName;
pnameTxt.gameObject.SetActive(false);
}
if (isServer)
{
startedTime = NetworkTime.time;
}
}
// Update is called once per frame
int timeInMillis => (int)(NetworkTime.time * 1000);
int roundedTime => Mathf.FloorToInt((float)timeInMillis / 100f) * 100;
int lastClientUpdateTime = 0;
float scale => Mathf.Clamp(1+(trailTime * _scaleMultiplier),1,10);
void FixedUpdate()
{
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)
{
joyInput = joystick.input;
//Cheats => TODO: Remove this
if (Input.GetKeyDown(KeyCode.F))
{
CmdCheatKills();
}
// 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);
}
//Simulate on both client and server
if (isLocalPlayer || isServer)
{
body.Translate(new Vector3(0, speed), body);
if (joyInput != Vector2.zero)
{
Turn(joyInput);
}
}
if (isServer)
{
//boost check
if (boosting && scale > 1)
{
speed = movingSpeed * 2;
DecreaseTrail(Time.deltaTime * 3);
// scaleMultiplier -= Time.deltaTime;
// if (scaleMultiplier < 1) { scaleMultiplier = 1; } //Clamp in case gets lower
}
else
{
speed = movingSpeed;
}
}
///Diff = rot1 . rot2
///1 = rot1 . rot2 / Diff
///1 * Diff * Diff = rot1.rot2. Diff
if (isLocalPlayer)
{
CmdUpdateJoyInput(joyInput);
//Fix Detours
if (Mathf.Abs(Detour.magnitude) > 0.1f || true)
{
Vector3 newPosition = body.position + Detour;
Quaternion newRotation = body.rotation * RotationDetour;
if (Detour.magnitude > 0.5f)
{
trailMgr.trail.emitting = false;
}
body.position = Vector3.Lerp(body.position, newPosition, (Mathf.Abs(Detour.magnitude) > 0.2f) ? DetourCorrectionFactor * 2 * Detour.magnitude : DetourCorrectionFactor);
Detour = newPosition - body.position;
body.rotation = Quaternion.Lerp(body.rotation, newRotation, DetourCorrectionFactor * ((joystick.touchDown) ? 0.1f : 1));
RotationDetour = Quaternion.Inverse(transform.rotation) * newRotation;
trailMgr.trail.emitting = true;
}
}
//Fill input and position buffer for client predictions
StatePayload currentState = new StatePayload(timeInMillis, transform.position, transform.rotation, joyInput);
if (isLocalPlayer)
{
if (lastClientUpdateTime < roundedTime)
{
CmdValidateMovement(roundedTime, currentState.Position, currentState.Rotation, currentState.Input);
lastClientUpdateTime = roundedTime;
}
}
else if (isServer)
{
int lastUpdatedTime = serverStateBuffer.Count > 0 ? serverStateBuffer.Keys.Last() : 0;
if (timeInMillis >= lastUpdatedTime + 100)
{
serverStateBuffer.Add(roundedTime, currentState);
if (serverStateBuffer.Count > 1024)
{
serverStateBuffer.Remove(serverStateBuffer.Keys.First());
}
}
RpcUpdatePosition(joyInput, transform.position, transform.rotation, timeInMillis);
RpcUpdateTrail(trailMgr.positions);
}
else
{
transform.position = Vector3.Lerp(transform.position, targetPosition, 0.1f);
transform.rotation = Quaternion.Lerp(transform.rotation, targetRotation, 0.1f);
}
}
Vector3 targetPosition;
Quaternion targetRotation;
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);
}
[Command]
void CmdUpdateJoyInput(Vector2 input)
{
joyInput = input;
}
[Command]
void CmdValidateMovement(int time, Vector3 position, Quaternion rotation, Vector2 input)
{
StatePayload payload = new StatePayload(time, position, rotation, input);
StatePayload lastServerState = serverStateBuffer.Values.Last();
StatePayload payloadToCompare;
if (serverStateBuffer.Count < 1) { Debug.Log("still Initiating server buffer"); return; }
joyInput = payload.Input;
if (!serverStateBuffer.ContainsKey(time))
{
payloadToCompare = lastServerState;
if (lastServerState.Time < time)
{
// Debug.Log("Server doesn't have that data yet\nYou asked for " + time + " best I can do is " + lastServerState.Time);
}
else
{
for (int i = 0; i < 50; i++)
{
if (serverStateBuffer.ContainsKey(time - i))
{
payloadToCompare = serverStateBuffer[time - i];
Debug.Log($"Found closest state at {payloadToCompare.Time}, requested {time}");
break;
}
}
}
}
else
{
payloadToCompare = serverStateBuffer[time];
}
Vector3 positionDiff = payloadToCompare.Position - payload.Position;
int timeDiff = lastServerState.Time - time;
if (Mathf.Abs(positionDiff.magnitude) < 0.2f && lastServerState.Time - time < 500)
{
//Validated, move on
}
else
{
// Debug.Log($"RB : {positionDiff.magnitude} [{timeDiff}ms]");
RpcRubberBand(joyInput, serverStateBuffer.Values.Last().Position, serverStateBuffer.Values.Last().Rotation, trailMgr.positions, timeInMillis);
}
}
[ClientRpc]
void RpcUpdatePosition(Vector2 input, Vector3 position, Quaternion rotation, double sentTime)
{
if (isLocalPlayer || isServer) { return; }
double delay = (timeInMillis - sentTime) * timeDelayErrorFix;
int numberOfFrames = (int)((float)(delay / 1000f) * 50f);
Quaternion newRotation = rotation;
Vector3 direction = (rotation * Vector3.forward).normalized;
Vector3 newPosition = position + ((direction * speed) * numberOfFrames);
for (int i = 0; i < numberOfFrames; i++)
{
newRotation = Quaternion.Lerp(newRotation, getTurnAngle(input), turningSmoothFactor * input.magnitude);
}
targetPosition = newPosition - DetourError;
targetRotation = newRotation;
}
double lastRubberBandTime = 0;
[ClientRpc]
void RpcRubberBand(Vector2 input, Vector3 position, Quaternion rotation, Vector3[] trailPositions, double sentTime)
{
if (true)
{
if (sentTime < lastRubberBandTime) { Debug.Log("Old rubber band rpc, ignoree..."); return; }
//Lag comprehension
double delay = (timeInMillis - sentTime) * timeDelayErrorFix;
int numberOfFrames = (int)((float)(delay / 1000f) * 50f);
Quaternion newRotation = rotation;
Vector3 direction = (rotation * Vector3.forward).normalized;
Vector3 newPosition = position + ((direction * speed) * numberOfFrames);
for (int i = 0; i < numberOfFrames; i++)
{
newRotation = Quaternion.Lerp(newRotation, getTurnAngle(input), turningSmoothFactor * input.magnitude);
}
int distanceSinceSent = (int)Vector3.Distance(newPosition, position);
#region trailSyncOld
// Vector3[] newTrailPositions = new Vector3[trailPositions.Length];
// for(int i=0; i < trailPositions.Length; i++){
// if(i > trailPositions.Length - distanceSinceSent-1){
// //fill with current data
// newTrailPositions[i] = trailMgr.positions[i];
// }else{
// Vector3 newTrailPoint = trailPositions[i-distanceSinceSent];
// if(trailMgr.positions.Length > i){
// if(Vector3.Distance(newTrailPoint, trailMgr.positions[i]) < 0.4f){
// newTrailPoint = trailMgr.positions[i];
// }
// }
// newTrailPositions[i] = newTrailPoint;
// }
// }
// trailMgr.trail.SetPositions(newTrailPositions);
#endregion
// Vector3 newPosition = position + new Vector3(0, movingSpeed * );
// Vector3 newPosition = position;
Detour = newPosition - transform.position - DetourError;
// RotationDetour = rotation;
RotationDetour = Quaternion.Inverse(transform.rotation) * newRotation;
lastRubberBandTime = sentTime;
// Debug.Log($"Rubber banded (Detour of pos:{Detour}, rotation: {RotationDetour}) you to {transform.position}, @ {sentTime} (delay: {delay}");
}
}
[ClientRpc]
void RpcUpdateTrail(Vector3[] positions)
{
//trailMgr.trail.SetPositions(positions);
}
void OnGUI()
{
if (!isLocalPlayer) { return; }
if (showDebugHUD)
{
GUI.Label(new Rect(Screen.width - 120, 10, 100, 20), transform.position.ToString());
GUI.Label(new Rect(Screen.width - 120, 30, 100, 20), timeInMillis.ToString());
GUI.Label(new Rect(Screen.width - 100, Screen.height - 30, 50, 20), (NetworkTime.rtt * 1000).ToString() + " ms");
}
}
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<TrailMgr>();
}
}
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<SpaceshipController>();
Debug.Log("got hit by player? : " + deadPlayer != null);
if (deadPlayer != null && !deadPlayer.dead && (NetworkTime.time- deadPlayer.startedTime) > 5)
{ // <-- okay we killed someone | KILLCODE
deadPlayer.Die(pname);
Debug.Log($"{pname} killed {deadPlayer.pname}");
OnKill();
}
}
void OnKill()
{
Kills++;
Scores += 10; //TODO: Need to change Scores on kills?
// scaleMultiplier += 0.05f;
// OnScaleChanged(scaleMultiplier, scaleMultiplier);
IncreaseTrail(trailIncrementRate);
RpcOnKill();
}
[ClientRpc]
void RpcOnKill()
{
}
void OnKillsChanged(int oldVal, int newVal)
{
if (isLocalPlayer)
{
Debug.Log("Show kills for " + newVal);
KillText.Show(newVal);
}
}
void IncreaseTrail(float rate)
{
trailTime = trailMgr.trail.time + rate;
trailMgr.trail.time = trailTime;
// Debug.Log("Increasing trail of" + pname);
}
public void DecreaseTrail(float rate)
{
trailTime = Mathf.Clamp(trailMgr.trail.time - rate,0,float.MaxValue);
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
switch (type)
{
case PickupItem.PickupType.Moon:
IncreaseTrail(trailIncrementRate);
// scaleMultiplier += 0.05f;
moonsCollected++;
break;
case PickupItem.PickupType.Star:
IncreaseTrail(trailIncrementRate / 2f);
// scaleMultiplier += 0.025f;
break;
}
RpcCollectPickup(type);
}
[ClientRpc]
void RpcCollectPickup(PickupItem.PickupType type)
{
if (isLocalPlayer)
{
MinigameManager.instance.GainMetals((type == PickupItem.PickupType.Star) ? 2 : 5);
}
}
public void Die(string killer)
{
MinigameManager.instance.SpawnLeftoverPickups(transform.position, Scores);
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;
trailTime = 1;
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($"<b>{pname}</b> was killed by <b>{killer}</b>");
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)
{
// MinigameManager.instance.rankedSummary.ShowLoss();
// StartCoroutine(ShowRankedResults());
if(MinigameManager.instance.winnerId<=0){
Debug.LogError("Winner ID not synced yet");
}
if (MinigameManager.instance.winnerId != netId)
{
//LOSER!
MinigameManager.instance.rankedSummary.ShowLoss();
}
else
{
MinigameManager.instance.rankedSummary.ShowWin();
}
}
}
}
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<NetworkTrail>().enableValidation = false;
trailMgr.trail.Clear();
trailMgr.positions = new Vector3[0];
trailMgr.trail.emitting = false;
transform.position = respawnPoint;
trailMgr.trail.emitting = true;
GetComponent<NetworkTrail>().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");
}
void RpcLostRanked()
{
if (isLocalPlayer)
{
DBmanager.SetMetal(DBmanager.Metal+250);
MinigameManager.instance.rankedSummary.ShowLoss();
DBmanager.SetTrophies(Mathf.Clamp(DBmanager.Trophies - 15, 0, int.MaxValue));
}
}
}
public class StatePayload
{
public int Time;
public Vector3 Position;
public Quaternion Rotation;
public Vector2 Input;
public StatePayload(int time, Vector3 position, Quaternion rotation, Vector2 input)
{
Time = time;
Position = position;
Rotation = rotation;
Input = input;
}
}