//The movement part public void BaseMovement(ref Results inpRes, ref Inputs inp, ref float deltaMultiplier, ref Vector3 maxSpeed, ref Vector3 localSpeed) { //Gets the target maximum speed if (inp.keys & Keys.CROUCH) { maxSpeed = data.maxSpeedCrouch; inpRes.flags |= Flags.CROUCHED; controller.height = Mathf.Clamp(controller.height - crouchSwitchMul, data.controllerHeightCrouch, data.controllerHeightNormal); controller.center = new Vector3(0, controller.height * data.controllerCentreMultiplier, 0); } else { if (inpRes.flags & Flags.CROUCHED) { inpRes.flags &= ~Flags.CROUCHED; Collider[] hits; hits = Physics.OverlapCapsule(inpRes.position + new Vector3(0f, data.controllerHeightCrouch, 0f), inpRes.position + new Vector3(0f, data.controllerHeightNormal, 0f), controller.radius); for (int i = 0; i < hits.Length; i++) { if (hits[i].transform.root != myTransform.root) { inpRes.flags |= Flags.CROUCHED; inp.keys |= Keys.CROUCH; maxSpeed = data.maxSpeedCrouch; break; } } } if (!(inpRes.flags & Flags.CROUCHED)) { controller.height = Mathf.Clamp(controller.height + crouchSwitchMul, data.controllerHeightCrouch, data.controllerHeightNormal); controller.center = new Vector3(0, controller.height * data.controllerCentreMultiplier, 0); } else { controller.height = data.controllerHeightCrouch; controller.center = new Vector3(0, controller.height * data.controllerCentreMultiplier, 0); } } if (!(inp.keys & Keys.JUMP)) { inpRes.flags &= ~Flags.JUMPED; } if (inpRes.flags & Flags.IS_GROUNDED && inp.keys & Keys.JUMP && !(inpRes.flags & (Flags.CROUCHED | Flags.JUMPED))) { localSpeed.y = data.speedJump; if (!data.allowBunnyhopping) { inpRes.flags |= Flags.JUMPED; } } else if (!(inpRes.flags & Flags.IS_GROUNDED)) { localSpeed.y += Physics.gravity.y * deltaMultiplier; } else { localSpeed.y = -1f; } if (inpRes.flags & Flags.IS_GROUNDED) { if (Mathf.Sign(localSpeed.z * inp.inputs.y) == 1 && inp.inputs.y != 0 && Mathf.Abs(localSpeed.z) <= maxSpeed.z * Mathf.Abs(inp.inputs.y)) { localSpeed.z = Mathf.Clamp(localSpeed.z + (inp.inputs.y > 0 ? data.accelerationForward : -data.accelerationBack) * deltaMultiplier, -maxSpeed.z * Mathf.Abs(inp.inputs.y), maxSpeed.z * Mathf.Abs(inp.inputs.y)); } else if (inp.inputs.y == 0 || Mathf.Abs(localSpeed.z) > maxSpeed.z * Mathf.Abs(inp.inputs.y)) { localSpeed.z = Mathf.Clamp(localSpeed.z + (data.decceleration * -Mathf.Sign(localSpeed.z)) * deltaMultiplier, localSpeed.z >= 0 ? 0 : -maxSpeed.z, localSpeed.z <= 0 ? 0 : maxSpeed.z); } else { localSpeed.z = Mathf.Clamp(localSpeed.z + data.accelerationStop * inp.inputs.y * deltaMultiplier, localSpeed.z >= 0 ? -data.accelerationBack * deltaMultiplier : -maxSpeed.z, localSpeed.z <= 0 ? data.accelerationForward * deltaMultiplier : maxSpeed.z); } if (Mathf.Sign(localSpeed.x * inp.inputs.x) == 1 && inp.inputs.x != 0 && Mathf.Abs(localSpeed.x) <= maxSpeed.x * Mathf.Abs(inp.inputs.x)) { localSpeed.x = Mathf.Clamp(localSpeed.x + Mathf.Sign(inp.inputs.x) * data.accelerationSides * deltaMultiplier, -maxSpeed.x * ((Mathf.Sign(localSpeed.x * inp.inputs.x) == 1 && inp.inputs.x != 0) ? Mathf.Abs(inp.inputs.x) : 1f - Mathf.Abs(inp.inputs.x)), maxSpeed.x * ((Mathf.Sign(localSpeed.x * inp.inputs.x) == 1 && inp.inputs.x != 0) ? Mathf.Abs(inp.inputs.x) : 1f - Mathf.Abs(inp.inputs.x))); } else if (inp.inputs.x == 0 || Mathf.Abs(localSpeed.x) > maxSpeed.x * Mathf.Abs(inp.inputs.x)) { localSpeed.x = Mathf.Clamp(localSpeed.x + (data.decceleration * -Mathf.Sign(localSpeed.x)) * deltaMultiplier, localSpeed.x >= 0 ? 0 : -maxSpeed.x, localSpeed.x <= 0 ? 0 : maxSpeed.x); } else { localSpeed.x = Mathf.Clamp(localSpeed.x + data.accelerationStop * inp.inputs.x * deltaMultiplier, localSpeed.x >= 0 ? -data.accelerationBack * deltaMultiplier : -maxSpeed.x, localSpeed.x <= 0 ? data.accelerationForward * deltaMultiplier : maxSpeed.x); } } }
//Actual movement code. Mostly isolated, except transform Results MoveCharacter(Results inpRes, Inputs inp, float deltaMultiplier, Vector3 maxSpeed) { //If controlled outside, return results with the current transform position. if (inpRes.flags & Flags.CONTROLLED_OUTSIDE) { inpRes.speed = myTransform.position - inpRes.position; return(new Results(myTransform.position, myTransform.rotation, hitNormal, inp.y, inpRes.speed, inpRes.flags, 0, 0, inpRes.ragdollTime, inp.timestamp)); } //Calculates if ragdoll should be disabled if (inpRes.flags & Flags.RAGDOLL) { inpRes.speed = myTransform.position - inpRes.position; if (inpRes.speed.magnitude >= data.ragdollStopVelocity) { inpRes.ragdollTime = inp.timestamp; } if (inp.timestamp - inpRes.ragdollTime >= ragdollTime) { inpRes.flags &= ~Flags.RAGDOLL; } return(new Results(myTransform.position, myTransform.rotation, hitNormal, inp.y, inpRes.speed, inpRes.flags, 0, 0, inpRes.ragdollTime, inp.timestamp)); } //Clamp camera angles inp.y = Mathf.Clamp(inp.y, dataInp.camMinY, dataInp.camMaxY); if (inp.x > 360f) { inp.x -= 360f; } else if (inp.x < 0f) { inp.x += 360f; } //Save current position and rotation to restore after the move Vector3 pos = myTransform.position; Quaternion rot = myTransform.rotation; //Set the position and rotation to the last results ones myTransform.position = inpRes.position; myTransform.rotation = inpRes.rotation; Vector3 tempSpeed = myTransform.InverseTransformDirection(inpRes.speed); myTransform.rotation = Quaternion.Euler(new Vector3(0, inp.x, 0)); //Character sliding of surfaces if (!(inpRes.flags & Flags.IS_GROUNDED)) { //inpRes.speed.x += (1f - inpRes.groundNormal.y) * inpRes.groundNormal.x * (inpRes.speed.y > 0 ? 0 : -inpRes.speed.y) * (1f - data.slideFriction); inpRes.speed.x += (1f - inpRes.groundNormal.y) * inpRes.groundNormal.x * (1f - data.slideFriction); //inpRes.speed.z += (1f - inpRes.groundNormal.y) * inpRes.groundNormal.z * (inpRes.speed.y > 0 ? 0 : -inpRes.speed.y) * (1f - data.slideFriction); inpRes.speed.z += (1f - inpRes.groundNormal.y) * inpRes.groundNormal.z * (1f - data.slideFriction); } Vector3 localSpeed = myTransform.InverseTransformDirection(inpRes.speed); Vector3 localSpeed2 = Vector3.Lerp(myTransform.InverseTransformDirection(inpRes.speed), tempSpeed, data.velocityTransferCurve.Evaluate(Mathf.Abs(inpRes.rotation.eulerAngles.y - inp.x) / (deltaMultiplier * data.velocityTransferDivisor))); if (!(inpRes.flags & Flags.IS_GROUNDED) && data.strafing) { AirStrafe(ref inpRes, ref inp, ref deltaMultiplier, ref maxSpeed, ref localSpeed, ref localSpeed2); } else { localSpeed = localSpeed2; } BaseMovement(ref inpRes, ref inp, ref deltaMultiplier, ref maxSpeed, ref localSpeed); float tY = myTransform.position.y; //Convert the local coordinates to the world ones inpRes.speed = transform.TransformDirection(localSpeed); hitNormal = new Vector3(0, 0, 0); //Set the speed to the curve values. Allowing to limit the speed inpRes.speed.x = data.finalSpeedCurve.Evaluate(inpRes.speed.x); inpRes.speed.y = data.finalSpeedCurve.Evaluate(inpRes.speed.y); inpRes.speed.z = data.finalSpeedCurve.Evaluate(inpRes.speed.z); //Move the controller controller.Move(inpRes.speed * deltaMultiplier); //This code continues after OnControllerColliderHit gets called (if it does) if (hitNormal == Vector3.zero) { inpRes.flags &= ~Flags.IS_GROUNDED; } else if (Vector3.Angle(Vector3.up, hitNormal) <= data.slopeLimit) { inpRes.flags |= Flags.IS_GROUNDED; } else { inpRes.flags &= ~Flags.IS_GROUNDED; } //float speed = inpRes.speed.y; inpRes.speed = (transform.position - inpRes.position) / deltaMultiplier; //if (inpRes.speed.y > 0) // inpRes.speed.y = Mathf.Min (inpRes.speed.y, Mathf.Max(0, speed)); //else // inpRes.speed.y = Mathf.Max (inpRes.speed.y, Mathf.Min(0, speed)); //inpRes.speed.y = speed; float gpt = 1f; float gp = myTransform.position.y; //WIP, broken, Handles hitting ground while spacebar is pressed. It determines how much time was left to move based on at which height the player hit the ground. Some math involved. if (data.handleMidTickJump && !(inpRes.flags & Flags.IS_GROUNDED) && tY - gp >= 0 && inp.keys & Keys.JUMP && (controller.isGrounded || Physics.Raycast(myTransform.position + controller.center, Vector3.down, (controller.height / 2) + (controller.skinWidth * 1.5f)))) { float oSpeed = inpRes.speed.y; gpt = (tY - gp) / (-oSpeed); inpRes.speed.y = data.speedJump + ((Physics.gravity.y / 2) * Mathf.Abs((1f - gpt) * deltaMultiplier)); Debug.Log(inpRes.speed.y + " " + gpt); controller.Move(myTransform.TransformDirection(0, inpRes.speed.y * deltaMultiplier, 0)); inpRes.flags |= Flags.IS_GROUNDED; Debug.DrawLine(new Vector3(myTransform.position.x, gp, myTransform.position.z), myTransform.position, Color.blue, deltaMultiplier); inpRes.flags |= Flags.JUMPED; } //If snapping is enabled, then do it if (data.snapSize > 0f) { myTransform.position = new Vector3(Mathf.Round(myTransform.position.x * snapInvert) * data.snapSize, Mathf.Round(myTransform.position.y * snapInvert) * data.snapSize, Mathf.Round(myTransform.position.z * snapInvert) * data.snapSize); } //If grounded set the speed to the gravity if (inpRes.flags & Flags.IS_GROUNDED) { localSpeed.y = Physics.gravity.y * Mathf.Clamp(deltaMultiplier, 1f, 1f); } if (inpRes.speed.magnitude > data.ragdollStartVelocity) { inpRes.flags |= Flags.RAGDOLL; inpRes.ragdollTime = inp.timestamp; } //Generate the return value inpRes = new Results(myTransform.position, myTransform.rotation, hitNormal, inp.y, inpRes.speed, inpRes.flags, gp, gpt, inpRes.ragdollTime, inp.timestamp); //Set back the position and rotation myTransform.position = pos; myTransform.rotation = rot; return(inpRes); }
protected override void InputUpdate(ref Inputs inputs) { base.InputUpdate(ref inputs); inputs.keys.Set(keyAttack, Input.GetMouseButtonDown(0)); }
//Handles strafing in air public void AirStrafe(ref Results inpRes, ref Inputs inp, ref float deltaMultiplier, ref Vector3 maxSpeed, ref Vector3 localSpeed, ref Vector3 localSpeed2) { if (inpRes.flags & Flags.IS_GROUNDED) { return; } float tAccel = data.strafeAngleCurve.Evaluate(Mathf.Abs(inpRes.rotation.eulerAngles.y.ClampAngle() - inp.x.ClampAngle()) / deltaMultiplier); bool rDir = (inpRes.rotation.eulerAngles.y.ClampAngle() - inp.x.ClampAngle()) > 0; if (((inp.inputs.x > 0f && !rDir) || (inp.inputs.x < 0f && rDir)) && inp.inputs.y == 0) { if (localSpeed.z >= 0) { localSpeed.z = localSpeed2.z + tAccel * data.strafeToSpeedCurve.Evaluate(Mathf.Abs(localSpeed.z) * strafeToSpeedCurveScaleMul); localSpeed.x = localSpeed2.x; localSpeed.y = localSpeed2.y; } else { localSpeed.z = localSpeed.z + tAccel * data.strafeToSpeedCurve.Evaluate(Mathf.Abs(localSpeed.z) * strafeToSpeedCurveScaleMul); } } else if (((inp.inputs.x < 0f && !rDir) || inp.inputs.x > 0f && rDir) && inp.inputs.y == 0) { if (localSpeed.z <= 0) { localSpeed.z = localSpeed2.z - tAccel * data.strafeToSpeedCurve.Evaluate(Mathf.Abs(localSpeed.z) * strafeToSpeedCurveScaleMul); localSpeed.x = localSpeed2.x; localSpeed.y = localSpeed2.y; } else { localSpeed.z = localSpeed.z - tAccel * data.strafeToSpeedCurve.Evaluate(Mathf.Abs(localSpeed.z) * strafeToSpeedCurveScaleMul); } } else if (((inp.inputs.y > 0f && !rDir) || (inp.inputs.y < 0f && rDir)) && inp.inputs.x == 0) { if (localSpeed.x <= 0) { localSpeed.x = localSpeed2.x - tAccel * data.strafeToSpeedCurve.Evaluate(Mathf.Abs(localSpeed.x) * strafeToSpeedCurveScaleMul); localSpeed.z = localSpeed2.z; localSpeed.y = localSpeed2.y; } else { localSpeed.x = localSpeed.x - tAccel * data.strafeToSpeedCurve.Evaluate(Mathf.Abs(localSpeed.x) * strafeToSpeedCurveScaleMul); } } else if (((inp.inputs.y > 0f && rDir) || (inp.inputs.y < 0f && !rDir)) && inp.inputs.x == 0) { if (localSpeed.x >= 0) { localSpeed.x = localSpeed2.x + tAccel * data.strafeToSpeedCurve.Evaluate(Mathf.Abs(localSpeed.x) * strafeToSpeedCurveScaleMul); localSpeed.z = localSpeed2.z; localSpeed.y = localSpeed2.y; } else { localSpeed.x = localSpeed.x + tAccel * data.strafeToSpeedCurve.Evaluate(Mathf.Abs(localSpeed.x) * strafeToSpeedCurveScaleMul); } } }
protected abstract void RunPostMove(ref Results results, ref Inputs inp);
public Inputs ReadInputs(NetworkReader reader, Inputs inp) { uint mask = reader.ReadPackedUInt32(); return(ReadInputs(reader, inp, mask)); }
protected abstract void InputUpdate(ref Inputs inputs);
protected abstract void RunCommand(ref Results results, Inputs inp);
void ProcessCommandsServer(ref Inputs[] commands, uint size) { controller.enabled = true; if (startTick == 0u) { startTick.value = GameManager.tick; } for (int i = 0; i < size; i++) { int valid = IsCommandValid(ref commands[i]); //If valid is -1, you are free to ban the player if (valid != 1) { continue; } curInput = commands[i]; LagCompensation.StartLagCompensation(GameManager.players[gmIndex], ref curInput); RunCommand(ref lastResults, curInput); LagCompensation.EndLagCompensation(GameManager.players[gmIndex]); if (data.debug && lastTick + 1 != curInput.timestamp && lastTick != 0) { Debug.Log("Missing tick " + lastTick + 1); } lastTick = curInput.timestamp; simulationTime = GameManager.curtime; commandTime = GameManager.curtime; SetDirtyBit(1); } if (size > 0) { updateCount++; histResults[updateCount % 3] = lastResults; sendResults = lastResults; startTime = Mathf.Min(Time.time, Time.fixedTime); } if (tickUpdate != null) { tickUpdate(lastResults, false); } if (tickUpdateNotify != null) { tickUpdateNotify(false); } if (data.debug && tickUpdateDebug != null) { tickUpdateDebug(curInput, lastResults, false); } controller.enabled = false; }
public abstract int IsCommandValid(ref Inputs inp);
protected override void RunCommand(ref Results results, Inputs inp) { }
protected override void RunPostMove(ref Results results, ref Inputs inp) { }
public NetworkDataTick(Inputs inp, Results res) { input = new Extensions.SInputs(inp); result = new Extensions.SResults(res); }
public void BaseMovement(ref Results inpRes, ref Inputs inp, ref float deltaMultiplier, ref Vector3 maxSpeed, ref Vector3 localSpeed) { if (inp.sprint) maxSpeed = data.maxSpeedSprint; if (inp.crouch) { maxSpeed = data.maxSpeedCrouch; if (!inpRes.crouch) { inpRes.crouch = true; } controller.height = Mathf.Clamp (controller.height - crouchSwitchMul, data.controllerHeightCrouch, data.controllerHeightNormal); controller.center = new Vector3 (0, controller.height * data.controllerCentreMultiplier, 0); } else { if (inpRes.crouch) { inpRes.crouch = false; Collider[] hits; hits = Physics.OverlapCapsule (inpRes.position + new Vector3(0f, data.controllerHeightCrouch, 0f), inpRes.position + new Vector3(0f, data.controllerHeightNormal, 0f), controller.radius); for (int i = 0; i < hits.Length; i++) if (hits [i].transform.root != myTransform.root) { inpRes.crouch = true; inp.crouch = true; maxSpeed = data.maxSpeedCrouch; break; } } if (!inpRes.crouch) { controller.height = Mathf.Clamp (controller.height + crouchSwitchMul, data.controllerHeightCrouch, data.controllerHeightNormal); controller.center = new Vector3 (0, controller.height * data.controllerCentreMultiplier, 0); } else { controller.height = data.controllerHeightCrouch; controller.center = new Vector3(0, controller.height * data.controllerCentreMultiplier, 0); } } inpRes.jumped = false; if (inpRes.isGrounded && inp.jump && !inpRes.crouch) { localSpeed.y = data.speedJump; inpRes.jumped = true; } else if (!inpRes.isGrounded) localSpeed.y += Physics.gravity.y * deltaMultiplier; else localSpeed.y = -1f; if (inpRes.isGrounded) { if (localSpeed.x >= 0f && inp.inputs.x > 0f) { localSpeed.x += data.accelerationSides * deltaMultiplier; if (localSpeed.x > maxSpeed.x) localSpeed.x = maxSpeed.x; } else if (localSpeed.x > 0f && (inp.inputs.x < 0f || localSpeed.x > maxSpeed.x)) { localSpeed.x -= data.accelerationStop * deltaMultiplier; if (localSpeed.x < 0) localSpeed.x = 0f; } else if (localSpeed.x <= 0f && inp.inputs.x < 0f) { localSpeed.x -= data.accelerationSides * deltaMultiplier; if (localSpeed.x < -maxSpeed.x) localSpeed.x = -maxSpeed.x; } else if (localSpeed.x < 0f && (inp.inputs.x > 0f || localSpeed.x < -maxSpeed.x)) { localSpeed.x += data.accelerationStop * deltaMultiplier; if (localSpeed.x > 0) localSpeed.x = 0f; } else if (localSpeed.x > 0f) { localSpeed.x -= data.decceleration * deltaMultiplier; if (localSpeed.x < 0f) localSpeed.x = 0f; } else if (localSpeed.x < 0f) { localSpeed.x += data.decceleration * deltaMultiplier; if (localSpeed.x > 0f) localSpeed.x = 0f; } else localSpeed.x = 0; if (localSpeed.z >= 0f && inp.inputs.y > 0f) { localSpeed.z += data.accelerationSides * deltaMultiplier; if (localSpeed.z > maxSpeed.z) localSpeed.z = maxSpeed.z; } else if (localSpeed.z > 0f && (inp.inputs.y < 0f || localSpeed.z > maxSpeed.z)) { localSpeed.z -= data.accelerationStop * deltaMultiplier; if (localSpeed.z < 0) localSpeed.z = 0f; } else if (localSpeed.z <= 0f && inp.inputs.y < 0f) { localSpeed.z -= data.accelerationSides * deltaMultiplier; if (localSpeed.z < -maxSpeed.z) localSpeed.z = -maxSpeed.z; } else if (localSpeed.z <= 0f && (inp.inputs.y > 0f || localSpeed.z < -maxSpeed.z)) { localSpeed.z += data.accelerationStop * deltaMultiplier; if (localSpeed.z > 0) localSpeed.z = 0f; } else if (localSpeed.z > 0f) { localSpeed.z -= data.decceleration * deltaMultiplier; if (localSpeed.z < 0f) localSpeed.z = 0f; } else if (localSpeed.z < 0f) { localSpeed.z += data.decceleration * deltaMultiplier; if (localSpeed.z > 0f) localSpeed.z = 0f; } else localSpeed.z = 0; } }
void CmdSendInputs (Inputs inp, Results res) { #else void CmdSendInputs (Inputs inp) { #endif #if (SIMULATE) #if (CLIENT_TRUST) StartCoroutine (SendInputs (inp, res)); #else StartCoroutine (SendInputs (inp)); #endif } #if (CLIENT_TRUST) IEnumerator SendInputs (Inputs inp, Results res) { #else IEnumerator SendInputs (Inputs inp) { #endif yield return new WaitForSeconds (UnityEngine.Random.Range (0.21f, 0.28f)); #endif if (!isLocalPlayer) { if (clientInputs.Count > data.clientInputsBuffer) clientInputs.RemoveAt (0); if (!ClientInputsContainTimestamp (inp.timestamp)) clientInputs.Add (inp); #if (CLIENT_TRUST) tempResults = res; #endif currentTFixedUpdates += sendUpdates; if (data.debug && lastTick + 1 != inp.timestamp && lastTick != -1) { Debug.Log ("Missing tick " + lastTick + 1); } lastTick = inp.timestamp; } } [ClientRpc] void RpcSendResults (Results res) { if (isServer) return; #if (SIMULATE) StartCoroutine (SendResults (res)); } IEnumerator SendResults (Results res) { yield return new WaitForSeconds (UnityEngine.Random.Range (0.21f, 0.38f)); #endif if (isLocalPlayer) { foreach (Results t in clientResults) { if (t.timestamp == res.timestamp) Debug_UI.UpdateUI (posEnd, res.position, t.position, currentTick, res.timestamp); } if (serverResultList.Count > data.serverResultsBuffer) serverResultList.RemoveAt (0); if (!ServerResultsContainTimestamp (res.timestamp)) serverResultList.Add (res); serverResults = SortServerResultsAndReturnFirst (); if (serverResultList.Count >= data.serverResultsBuffer) reconciliate = true; } else { currentTick++; if (!isServer) { serverResults = res; onTickUpdate.Invoke (res); } if (currentTick > 2) { serverResults = res; posStart = posEnd; rotStart = rotEnd; headStartRot = headEndRot; headEndRot = res.camX; //if (Time.fixedTime - 2f > startTime) startTime = Time.fixedTime; //else // startTime = Time.fixedTime - ((Time.fixedTime - startTime) / (Time.fixedDeltaTime * _sendUpdates) - 1) * (Time.fixedDeltaTime * _sendUpdates); posEnd = posEndO; rotEnd = rotEndO; groundPointTime = serverResults.groundPointTime; posEndG = serverResults.groundPoint; posEndO = serverResults.position; rotEndO = serverResults.rotation; } else { startTime = Time.fixedTime; serverResults = res; posStart = serverResults.position; rotStart = serverResults.rotation; headStartRot = headEndRot; headEndRot = res.camX; posEnd = posStart; rotEnd = rotStart; groundPointTime = serverResults.groundPointTime; posEndG = posEndO.y; posEndO = posStart; rotEndO = rotStart; } } } Results SortServerResultsAndReturnFirst () { Results tempRes; for (int x = 0; x < serverResultList.Count; x++) { for (int y = 0; y < serverResultList.Count - 1; y++) { if (serverResultList [y].timestamp > serverResultList [y + 1].timestamp) { tempRes = serverResultList [y + 1]; serverResultList [y + 1] = serverResultList [y]; serverResultList [y] = tempRes; } } } if (serverResultList.Count > data.serverResultsBuffer) serverResultList.RemoveAt (0); return serverResultList [0]; } bool ServerResultsContainTimestamp (int timeStamp) { for (int i = 0; i < serverResultList.Count; i++) { if (serverResultList [i].timestamp == timeStamp) return true; } return false; } Inputs SortClientInputsAndReturnFirst () { Inputs tempInp; for (int x = 0; x < clientInputs.Count; x++) { for (int y = 0; y < clientInputs.Count - 1; y++) { if (clientInputs [y].timestamp > clientInputs [y + 1].timestamp) { tempInp = clientInputs [y + 1]; clientInputs [y + 1] = clientInputs [y]; clientInputs [y] = tempInp; } } } if (clientInputs.Count > data.clientInputsBuffer) clientInputs.RemoveAt (0); return clientInputs [0]; } bool ClientInputsContainTimestamp (int timeStamp) { for (int i = 0; i < clientInputs.Count; i++) { if (clientInputs [i].timestamp == timeStamp) return true; } return false; } //Function which replays the old inputs if prediction errors occur void Reconciliate () { for (int i = 0; i < clientResults.Count; i++) { if (clientResults [i].timestamp == serverResults.timestamp) { clientResults.RemoveRange (0, i); for (int o = 0; o < clientInputs.Count; o++) { if (clientInputs [o].timestamp == serverResults.timestamp) { clientInputs.RemoveRange (0, o); break; } } break; } } tempResults = serverResults; controller.enabled = true; for (int i = 1; i < clientInputs.Count - 1; i++) { tempResults = MoveCharacter (tempResults, clientInputs [i], Time.fixedDeltaTime * sendUpdates, data.maxSpeedNormal); } groundPointTime = tempResults.groundPointTime; posEnd = tempResults.position; rotEnd = tempResults.rotation; posEndG = tempResults.groundPoint; } //Input gathering void Update () { if (isLocalPlayer) { if (inputsInterface == null) throw (new UnityException ("inputsInterface is not set!")); curInput.inputs.x = inputsInterface.GetMoveX (); curInput.inputs.y = inputsInterface.GetMoveY (); curInput.x = inputsInterface.GetMouseX (); curInput.y = inputsInterface.GetMouseY (); curInput.jump = inputsInterface.GetJump (); curInput.sprint = inputsInterface.GetSprint (); curInput.crouch = inputsInterface.GetCrouch (); } } //This is where the ticks happen void FixedUpdate () { if (data.strafeToSpeedCurveScale != _strafeToSpeedCurveScale) { _strafeToSpeedCurveScale = data.strafeToSpeedCurveScale; strafeToSpeedCurveScaleMul = 1f / data.strafeToSpeedCurveScale; } if (isLocalPlayer || isServer) { currentFixedUpdates++; } if (isLocalPlayer && currentFixedUpdates >= sendUpdates) { currentTick++; if (!isServer) { onTickUpdate.Invoke (lastResults); clientResults.Add (lastResults); } if (clientInputs.Count >= data.inputsToStore) clientInputs.RemoveAt (0); clientInputs.Add (curInput); curInput.timestamp = currentTick; posStart = myTransform.position; rotStart = myTransform.rotation; startTime = Time.fixedTime; if (reconciliate) { Reconciliate (); lastResults = tempResults; reconciliate = false; } controller.enabled = true; lastResults = MoveCharacter (lastResults, clientInputs [clientInputs.Count - 1], Time.fixedDeltaTime * _sendUpdates, data.maxSpeedNormal); #if (CLIENT_TRUST) CmdSendInputs (clientInputs [clientInputs.Count - 1], lastResults); #else CmdSendInputs (clientInputs [clientInputs.Count - 1]); #endif if (data.debug) onTickUpdateDebug.Invoke(clientInputs [clientInputs.Count - 1], lastResults); controller.enabled = false; posEnd = lastResults.position; groundPointTime = lastResults.groundPointTime; posEndG = lastResults.groundPoint; rotEnd = lastResults.rotation; } if (isServer && currentFixedUpdates >= sendUpdates && (currentTFixedUpdates >= sendUpdates || isLocalPlayer)) { if (isLocalPlayer) { onTickUpdate.Invoke (lastResults); RpcSendResults (lastResults); } if (!isLocalPlayer && clientInputs.Count > 0) { currentFixedUpdates -= sendUpdates; currentTFixedUpdates -= sendUpdates; //if (clientInputs.Count == 0) // clientInputs.Add (curInputServer); //clientInputs[clientInputs.Count - 1] = curInputServer; curInput = SortClientInputsAndReturnFirst (); posStart = myTransform.position; rotStart = myTransform.rotation; startTime = Time.fixedTime; controller.enabled = true; serverResults = MoveCharacter (serverResults, curInput, Time.fixedDeltaTime * _sendUpdates, data.maxSpeedNormal); #if (CLIENT_TRUST) if (serverResults.timestamp == tempResults.timestamp && Vector3.SqrMagnitude(serverResults.position-tempResults.position) <= data.clientPositionToleration * data.clientPositionToleration && Vector3.SqrMagnitude(serverResults.speed-tempResults.speed) <= data.clientSpeedToleration * data.clientSpeedToleration && ((serverResults.isGrounded == tempResults.isGrounded) || !data.clientGroundedMatch) && ((serverResults.crouch == tempResults.crouch) || !data.clientCrouchMatch)) serverResults = tempResults; #endif groundPointTime = serverResults.groundPointTime; posEnd = serverResults.position; rotEnd = serverResults.rotation; posEndG = serverResults.groundPoint; controller.enabled = false; onTickUpdate.Invoke (serverResults); if (data.debug) onTickUpdateDebug.Invoke(curInput, serverResults); RpcSendResults (serverResults); } } if (isLocalPlayer && currentFixedUpdates >= sendUpdates) currentFixedUpdates = 0; } //This is where all the interpolation happens void LateUpdate () { if (data.movementType == MoveType.UpdateOnceAndLerp) { if (isLocalPlayer || isServer || (Time.time - startTime) / (Time.fixedDeltaTime * _sendUpdates) <= 1f) { interpPos = Vector3.Lerp (posStart, posEnd, (Time.time - startTime) / (Time.fixedDeltaTime * _sendUpdates)); //if ((Time.time - startTime) / (Time.fixedDeltaTime * _sendUpdates) <= groundPointTime) // interpPos.y = Mathf.Lerp (posStart.y, posEndG, (Time.time - startTime) / (Time.fixedDeltaTime * _sendUpdates * groundPointTime)); //else // interpPos.y = Mathf.Lerp (posStart.y, posEndG, (Time.time - startTime + (groundPointTime * Time.fixedDeltaTime * _sendUpdates)) / (Time.fixedDeltaTime * _sendUpdates * (1f - groundPointTime))); myTransform.rotation = Quaternion.Lerp (rotStart, rotEnd, (Time.time - startTime) / (Time.fixedDeltaTime * _sendUpdates)); if (isLocalPlayer) myTransform.rotation = Quaternion.Euler (myTransform.rotation.eulerAngles.x, curInput.x, myTransform.rotation.eulerAngles.z); myTransform.position = interpPos; } else { myTransform.position = Vector3.Lerp (posEnd, posEndO, (Time.time - startTime) / (Time.fixedDeltaTime * _sendUpdates) - 1f); myTransform.rotation = Quaternion.Lerp (rotEnd, rotEndO, (Time.time - startTime) / (Time.fixedDeltaTime * _sendUpdates) - 1f); } } else { myTransform.position = posEnd; myTransform.rotation = rotEnd; } } //Data not to be messed with. Needs to be outside the function due to OnControllerColliderHit Vector3 hitNormal; //Actual movement code. Mostly isolated, except transform Results MoveCharacter (Results inpRes, Inputs inp, float deltaMultiplier, Vector3 maxSpeed) { inp.y = Mathf.Clamp (curInput.y, dataInp.camMinY, dataInp.camMaxY); if (inp.x > 360f) inp.x -= 360f; else if (inp.x < 0f) inp.x += 360f; Vector3 pos = myTransform.position; Quaternion rot = myTransform.rotation; myTransform.position = inpRes.position; myTransform.rotation = inpRes.rotation; Vector3 tempSpeed = myTransform.InverseTransformDirection (inpRes.speed); myTransform.rotation = Quaternion.Euler (new Vector3 (0, inp.x, 0)); //Character sliding of surfaces if (!inpRes.isGrounded) { inpRes.speed.x += (1f - inpRes.groundNormal.y) * inpRes.groundNormal.x * (inpRes.speed.y > 0 ? 0 : -inpRes.speed.y) * (1f - data.slideFriction); inpRes.speed.x += (1f - inpRes.groundNormal.y) * inpRes.groundNormal.x * (inpRes.speed.y < 0 ? 0 : inpRes.speed.y) * (1f - data.slideFriction); inpRes.speed.z += (1f - inpRes.groundNormal.y) * inpRes.groundNormal.z * (inpRes.speed.y > 0 ? 0 : -inpRes.speed.y) * (1f - data.slideFriction); inpRes.speed.z += (1f - inpRes.groundNormal.y) * inpRes.groundNormal.z * (inpRes.speed.y < 0 ? 0 : -inpRes.speed.y) * (1f - data.slideFriction); } Vector3 localSpeed = myTransform.InverseTransformDirection (inpRes.speed); Vector3 localSpeed2 = Vector3.Lerp (myTransform.InverseTransformDirection (inpRes.speed), tempSpeed, data.velocityTransferCurve.Evaluate (Mathf.Abs (inpRes.rotation.eulerAngles.y - inp.x) / (deltaMultiplier * data.velocityTransferDivisor))); if (!inpRes.isGrounded && data.strafing) AirStrafe (ref inpRes, ref inp, ref deltaMultiplier, ref maxSpeed, ref localSpeed, ref localSpeed2); else localSpeed = localSpeed2; BaseMovement (ref inpRes, ref inp, ref deltaMultiplier, ref maxSpeed, ref localSpeed); float tY = myTransform.position.y; inpRes.speed = transform.TransformDirection (localSpeed); hitNormal = new Vector3 (0, 0, 0); inpRes.speed.x = data.finalSpeedCurve.Evaluate (inpRes.speed.x); inpRes.speed.y = data.finalSpeedCurve.Evaluate (inpRes.speed.y); inpRes.speed.z = data.finalSpeedCurve.Evaluate (inpRes.speed.z); controller.Move (inpRes.speed * deltaMultiplier); //This code continues after OnControllerColliderHit gets called (if it does) if (Vector3.Angle (Vector3.up, hitNormal) <= data.slopeLimit) inpRes.isGrounded = true; else inpRes.isGrounded = false; //float speed = inpRes.speed.y; inpRes.speed = (transform.position - inpRes.position) / deltaMultiplier; //inpRes.speed.y = speed; float gpt = 1f; float gp = myTransform.position.y; //WIP, broken, Handles hitting ground while spacebar is pressed. It determines how much time was left to move based on at which height the player hit the ground. Some math involved. if (data.handleMidTickJump && !inpRes.isGrounded && tY - gp >= 0 && inp.jump && (controller.isGrounded || Physics.Raycast (myTransform.position + controller.center, Vector3.down, (controller.height / 2) + (controller.skinWidth * 1.5f)))) { float oSpeed = inpRes.speed.y; gpt = (tY - gp) / (-oSpeed); inpRes.speed.y = data.speedJump + ((Physics.gravity.y / 2) * Mathf.Abs((1f - gpt) * deltaMultiplier)); Debug.Log (inpRes.speed.y + " " + gpt); controller.Move (myTransform.TransformDirection (0, inpRes.speed.y * deltaMultiplier, 0)); inpRes.isGrounded = true; Debug.DrawLine (new Vector3( myTransform.position.x, gp, myTransform.position.z), myTransform.position, Color.blue, deltaMultiplier); inpRes.jumped = true; } if (data.snapSize > 0f) myTransform.position = new Vector3 (Mathf.Round (myTransform.position.x * snapInvert) * data.snapSize, Mathf.Round (myTransform.position.y * snapInvert) * data.snapSize, Mathf.Round (myTransform.position.z * snapInvert) * data.snapSize); if (inpRes.isGrounded) localSpeed.y = Physics.gravity.y * Mathf.Clamp(deltaMultiplier, 1f, 1f); inpRes = new Results (myTransform.position, myTransform.rotation, hitNormal, inp.y, inpRes.speed, inpRes.isGrounded, inpRes.jumped, inpRes.crouch, gp, gpt, inp.timestamp); myTransform.position = pos; myTransform.rotation = rot; return inpRes; } //The part which determines if the controller was hit or not void OnControllerColliderHit (ControllerColliderHit hit) { hitNormal = hit.normal; }