UPF/Assets/Game/Scripts/Minigame/SpaceshipController.cs
2022-08-30 02:14:03 +05:30

529 lines
18 KiB
C#

using System.Collections.Generic;
using UnityEngine;
using Mirror;
using System.Linq;
using UnityEngine.UI;
public class SpaceshipController : NetworkBehaviour
{
[SyncVar(hook=nameof(OnPnameChanged))]
public string pname;
[SyncVar(hook=nameof(OnScoresChanged))]
public int Scores;
[SyncVar(hook=nameof(OnTrailTimeChanged))]
public float trailTime;
public float trailIncrementRate = 0.5f;
[SyncVar]
public bool dead;
[SyncVar]
public float speed;
[SyncVar(hook=nameof(OnScaleChanged))]
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;
[Command]
void CmdSetPname(string value){
pname = value;
}
void OnPnameChanged(string oldName, string newName){
pnameTxt.text = newName;
}
void OnScoresChanged(int oldScores, int newScores){
Debug.Log($"Add scores { newScores - oldScores}, (total: {newScores})");
}
void OnTrailTimeChanged(float oldValue, float newValue){
trailMgr.trail.time = newValue;
}
void OnBoostDown(){
if(isLocalPlayer){
if(isServer){
boosting=true;
}else{
CmdSetBoosting(true);
}
}
}
void OnBoostUp(){
if(isLocalPlayer){
if(isServer){
boosting=false;
}else{
CmdSetBoosting(false);
}
}
}
void OnScaleChanged(float oldScale, float newScale){
if(isLocalPlayer){
SceneData.holder.boostBtn.gameObject.SetActive(newScale>1);
}
}
[Command]
void CmdSetBoosting(bool value){
boosting=value;
}
[SyncVar]
double startedTime = 0;
void Start()
{
scaleMultiplier=1;
if (isLocalPlayer)
{
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);
if(isServer){pname=myName;}else{
CmdSetPname(myName);
}
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;
void FixedUpdate()
{
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(scaleMultiplier,scaleMultiplier,scaleMultiplier),0.1f);
trailMgr.trail.startWidth = Mathf.Lerp(trailMgr.trail.startWidth,scaleMultiplier,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);
SceneData.SetTimerTxt((startedTime ==0) ? 0 :NetworkTime.time - startedTime);
}
//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 && scaleMultiplier > 1){
speed= movingSpeed*2;
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("This cannot run on client, That's illegal!"); // <-- What this log says
return;
}
SpaceshipController deadPlayer = hit.GetComponent<SpaceshipController>();
if(deadPlayer!=null && !deadPlayer.dead){ // <-- okay we killed someone | KILLCODE
deadPlayer.Die(pname);
Debug.Log($"{pname} killed {deadPlayer.pname}");
OnKill();
}
}
void OnKill(){
Scores+= 10; //TODO: Need to change Scores on kills?
scaleMultiplier+=0.05f;
OnScaleChanged(scaleMultiplier,scaleMultiplier);
IncreaseTrail(trailIncrementRate);
}
void IncreaseTrail(float rate){
trailTime = trailMgr.trail.time+ rate;
trailMgr.trail.time = trailTime;
Debug.Log("Increasing trail of" + pname);
}
[Command]
void CmdCheatKills(){
OnKill();
}
public void CollectPickup(PickupItem.PickupType type){
if(isClient){Debug.Log("Server function ran on client. That's illegal!");} // <-- What this log says
switch(type){
case PickupItem.PickupType.Moon:
IncreaseTrail(trailIncrementRate);
break;
case PickupItem.PickupType.Star:
IncreaseTrail(trailIncrementRate/2f);
break;
}
RpcCollectPickup(type);
}
[ClientRpc]
void RpcCollectPickup(PickupItem.PickupType type){
if(isLocalPlayer){
MinigameManager.instance.GainMetals((type==PickupItem.PickupType.Star) ? 10 : 30);
}
}
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;
startedTime=NetworkTime.time;
trailTime = 1;
trailMgr.trail.time = trailTime;
RpcDie(killer);
MinigameManager.instance.SetRespawn(gameObject);
}
[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.deadScreen.SetActive(true);
}
}
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);
}
}
}
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;
}
}