public static bool Prefix(Player send_to_player) { m_snapshot_buffer.m_num_snapshots = 0; foreach (Player player in Overload.NetworkManager.m_Players) { if (!(player == null) && !player.m_spectator && !(player == send_to_player)) { NewPlayerSnapshot playerSnapshot = m_snapshot_buffer.m_snapshots[m_snapshot_buffer.m_num_snapshots++]; playerSnapshot.m_net_id = player.netId; playerSnapshot.m_pos = player.transform.position; playerSnapshot.m_rot = player.transform.rotation; playerSnapshot.m_vel = player.c_player_ship.c_rigidbody.velocity; playerSnapshot.m_vrot = player.c_player_ship.c_rigidbody.angularVelocity; } } if (m_snapshot_buffer.m_num_snapshots > 0) { if (!MPNoPositionCompression.enabled || !MPTweaks.ClientHasTweak(send_to_player.connectionToClient.connectionId, "nocompress_0_3_6")) { send_to_player.connectionToClient.SendByChannel(64, m_snapshot_buffer.ToOldSnapshotMessage(), 1); return(false); } send_to_player.connectionToClient.SendByChannel(MessageTypes.MsgNewPlayerSnapshotToClient, m_snapshot_buffer, 1); } return(false); }
// extrapolate a single NewPlayerSnapshot (including the extra fields besides pos and rot)! // this is used for generating synthetic snapshot messages in case we detected missing packets private static void ExtrapolatePlayerSnapshot(ref NewPlayerSnapshot C, NewPlayerSnapshot B, float t) { C.m_pos = Vector3.LerpUnclamped(B.m_pos, B.m_pos + B.m_vel, t); C.m_rot = Quaternion.SlerpUnclamped(B.m_rot, B.m_rot * Quaternion.Euler(B.m_vrot), t); // assume the rest stays the same C.m_vel = B.m_vel; C.m_vrot = B.m_vrot; C.m_net_id = B.m_net_id; }
public static void interpolatePlayer(Player player, NewPlayerSnapshot A, NewPlayerSnapshot B, float t) { if (HandlePlayerRespawn(player, A)) { return; } player.c_player_ship.c_transform.localPosition = Vector3.LerpUnclamped(A.m_pos, B.m_pos, t); player.c_player_ship.c_transform.rotation = Quaternion.SlerpUnclamped(A.m_rot, B.m_rot, t); player.c_player_ship.c_mesh_collider_trans.localPosition = player.c_player_ship.c_transform.localPosition; }
public static void extrapolatePlayer(Player player, NewPlayerSnapshot snapshot, float t) { if (HandlePlayerRespawn(player, snapshot)) { return; } player.c_player_ship.c_transform.localPosition = Vector3.LerpUnclamped(snapshot.m_pos, snapshot.m_pos + snapshot.m_vel, t); player.c_player_ship.c_transform.rotation = Quaternion.SlerpUnclamped(snapshot.m_rot, snapshot.m_rot * Quaternion.Euler(snapshot.m_vrot), t); player.c_player_ship.c_mesh_collider_trans.localPosition = player.c_player_ship.c_transform.localPosition; }
// interpolate a single NewPlayerSnapshot (including the extra fields besides pos and rot)! // this is used for generating synthetic snapshot messages in case we detected missing packets private static void InterpolatePlayerSnapshot(ref NewPlayerSnapshot C, NewPlayerSnapshot A, NewPlayerSnapshot B, float t) { C.m_pos = Vector3.LerpUnclamped(A.m_pos, B.m_pos, t); C.m_rot = Quaternion.SlerpUnclamped(A.m_rot, B.m_rot, t); C.m_vel = Vector3.LerpUnclamped(A.m_vel, B.m_vel, t); Quaternion A_vrot = Quaternion.Euler(A.m_vrot); Quaternion B_vrot = Quaternion.Euler(C.m_vrot); Quaternion C_vrot = Quaternion.SlerpUnclamped(A_vrot, B_vrot, t); C.m_vrot = C_vrot.eulerAngles; C.m_net_id = A.m_net_id; }
public static NewPlayerSnapshot GetPlayerSnapshot(NewPlayerSnapshotToClientMessage msg, Player p) { for (int i = 0; i < msg.m_num_snapshots; i++) { NewPlayerSnapshot playerSnapshot = msg.m_snapshots[i]; Player candidate = (Player)_Client_GetPlayerFromNetId_Method.Invoke(null, new object[] { playerSnapshot.m_net_id }); if (candidate == p) { return(playerSnapshot); } } return(null); }
public static bool Prefix(NetworkMessage msg) { if (NetworkMatch.GetMatchState() == MatchState.PREGAME || NetworkMatch.InGameplay()) { PlayerSnapshotToClientMessage item = msg.ReadMessage <PlayerSnapshotToClientMessage>(); NewPlayerSnapshotToClientMessage newItem = new NewPlayerSnapshotToClientMessage { m_num_snapshots = item.m_num_snapshots, m_server_timestamp = 0, // Unused. m_snapshots = item.m_snapshots.Select(m => NewPlayerSnapshot.FromOldSnapshot(m)).ToArray() }; MPClientShipReckoning.AddNewPlayerSnapshot(newItem, true); } return(false); }
public static void extrapolatePlayer(Player player, NewPlayerSnapshot snapshot, float t) { if (HandlePlayerRespawn(player, snapshot)) { return; } Vector3 newPos = Vector3.LerpUnclamped(snapshot.m_pos, snapshot.m_pos + snapshot.m_vel, t); // limit ship dive-in if enabled: if (Menus.mms_lag_compensation_collision_limit > 0) { const float radius = 0.98f; /// the ship's collider is radius 1, we use a bit smaller one // how far the ship's enclosing sphere is allowed to dive in float maxDive = (100.0f - (float)Menus.mms_lag_compensation_collision_limit) / 50.0f * radius; Vector3 basePos = snapshot.m_pos; Vector3 deltaPos = newPos - basePos; float dist = deltaPos.magnitude; if (dist > 0.05f && dist > maxDive) // only if ship is moved by a significant amount // NOTE: we only test against LAVA and LEVEL, not other players, because that // would have two drawbacks: // - we would test against the player ship itslef, if speed and ping // is high enough (I tried to disable that collider, but that didn't work) // - if multipe opponents collide, the first one we processed here // would get maximal movement and the others would be cut short, which // is not correct either... { const int layerMask = (1 << (int)UnityObjectLayers.LEVEL) | (1 << (int)UnityObjectLayers.LAVA); RaycastHit hitInfo; Vector3 direction = (1.0f / dist) * deltaPos; if (Physics.SphereCast(basePos, radius, direction, out hitInfo, dist, layerMask, QueryTriggerInteraction.Ignore)) { // how far the ship's enclosing shpere dives into the collider float diveIn = dist - hitInfo.distance; if (diveIn > maxDive) { // limit the ship position diveIn = maxDive; newPos = basePos + (hitInfo.distance + diveIn) * direction; } } } } player.c_player_ship.c_transform.localPosition = newPos; player.c_player_ship.c_transform.rotation = Quaternion.SlerpUnclamped(snapshot.m_rot, snapshot.m_rot * Quaternion.Euler(snapshot.m_vrot), t); player.c_player_ship.c_mesh_collider_trans.localPosition = player.c_player_ship.c_transform.localPosition; }
// Deal with the respawn. Return true if the player should not be moved around private static bool HandlePlayerRespawn(Player player, NewPlayerSnapshot snapshot) { // this logic was in vanilla Player.LerpRemotePlayer() if (player.m_lerp_wait_for_respawn_pos) { float num = Vector3.Distance(snapshot.m_pos, player.m_lerp_respawn_pos); float num2 = Vector3.Distance(snapshot.m_pos, player.m_lerp_death_pos); if (num >= num2) { // still special case for respawning return(true); } player.m_lerp_wait_for_respawn_pos = false; } return(false); }
/// <summary> /// Deserialize the snapshot without compression. /// </summary> /// <param name="reader"></param> public override void Deserialize(NetworkReader reader) { m_server_timestamp = reader.ReadSingle(); m_num_snapshots = (int)reader.ReadByte(); for (int i = 0; i < m_num_snapshots; i++) { NetworkInstanceId net_id = reader.ReadNetworkId(); Vector3 pos = default(Vector3); pos.x = reader.ReadSingle(); pos.y = reader.ReadSingle(); pos.z = reader.ReadSingle(); Quaternion rot = NetworkCompress.DecompressQuaternion(reader.ReadUInt32()); Vector3 vel = default(Vector3); vel.x = HalfHelper.Decompress(reader.ReadUInt16()); vel.y = HalfHelper.Decompress(reader.ReadUInt16()); vel.z = HalfHelper.Decompress(reader.ReadUInt16()); Vector3 vrot = default(Vector3); vrot.x = HalfHelper.Decompress(reader.ReadUInt16()); vrot.y = HalfHelper.Decompress(reader.ReadUInt16()); vrot.z = HalfHelper.Decompress(reader.ReadUInt16()); m_snapshots[i] = new NewPlayerSnapshot(net_id, pos, rot, vel, vrot); } }
// Called per frame, moves ships along their interpolation/extrapolation motions public static void updatePlayerPositions() { float now = Time.time; // needs to be the same time source we use for m_last_update_time NewPlayerSnapshotToClientMessage msgA = null; // interpolation: start NewPlayerSnapshotToClientMessage msgB = null; // interpolation: end, extrapolation start float interpolate_factor = 0.0f; // interpolation: factor in [0,1] float delta_t = 0.0f; int interpolate_ticks = 0; bool do_interpolation = false; // find out which case we have, and get the relevant snapshot message(s) lock (m_last_messages_lock) { /* * for (int xxx=0; xxx<m_last_messages_ring_count; xxx++) { * Debug.LogFormat("having snapshot from {0} represents {1}", m_last_messages_ring[(m_last_messages_ring_pos_last + 4 - xxx)&3].m_timestamp, m_last_update_time - xxx* Time.fixedDeltaTime); * } */ if (m_last_messages_ring_count < 1) { // we do not have any snapshot messages... return; } // make sure m_last_update_time is up-to-date ResyncTime(); // NOTE: now and m_last_update_time indirectly have timeScale already applied, as they are based on Time.time // we need to adjust just the ping and the mms_ship_max_interpolate_frames offset... // Also note that the server still sends the unscaled velocities. delta_t = now + Time.timeScale * MPClientExtrapolation.GetShipExtrapolationTime() - m_last_update_time; // if we want interpolation, add this as a _negative_ offset // we use delta_t=0 as the base for from which we extrapolate into the future delta_t -= (Menus.mms_lag_compensation_ship_added_lag / 1000f) * Time.timeScale; // it might sound absurd, but after this point, the Time.fixedDeltaTime is correct // and MUST NOT be scaled by timeScale. The data packets do contain 16.67ms of // movement each, we already have taken the time dilation into account above... // time difference in physics ticks float delta_ticks = delta_t / Time.fixedDeltaTime; // the number of frames we need to interpolate into // <= 0 means no interpolation at all, // 1 would mean we use the second most recent and the most recent snapshot, and so on... interpolate_ticks = -(int)Mathf.Floor(delta_ticks); // do we need to do interpolation? do_interpolation = (interpolate_ticks > 0); if (do_interpolation) { // we need interpolate_ticks + 1 elements in the ring buffer // NOTE: in the code below, the index [(m_last_messages_ring_pos_last + 4 - i) &3] // effectively acceses the i-ith most recent element (i starting by 0) // since 4-(i-1) == 4-i+ 1 = 5-i, 5-i references the next older one if (interpolate_ticks < m_last_messages_ring_count) { msgA = m_last_messages_ring[(m_last_messages_ring_pos_last + 4 - interpolate_ticks) & 3]; msgB = m_last_messages_ring[(m_last_messages_ring_pos_last + 5 - interpolate_ticks) & 3]; interpolate_factor = delta_ticks - Mathf.Floor(delta_ticks); } else { // not enough packets received so far // "extrapolate" into the past do_interpolation = false; // get the oldest snapshot we have msgB = m_last_messages_ring[(m_last_messages_ring_pos_last + 5 - m_last_messages_ring_count) & 3]; // offset the time for the extrapolation // delta_t is currently relative to the most recent element we have, // but we need it relative to msgA delta_t += Time.fixedDeltaTime * (m_last_messages_ring_count - 1); } } else { // extrapolation case // use the most recently received snapshot msgB = m_last_messages_ring[m_last_messages_ring_pos_last]; } } // lock m_last_frame_time = now; /* * Debug.LogFormat("At: {0} Setting: {1} IntFrames: {2}, dt: {3}, IntFact {4}",now,Menus.mms_ship_max_interpolate_frames, interpolate_ticks, delta_t, interpolate_factor); * if (interpolate_ticks > 0) { * Debug.LogFormat("Using A from {0}", msgA.m_timestamp); * Debug.LogFormat("Using B from {0}", msgB.m_timestamp); * } else { * Debug.LogFormat("Using B from {0}", msgB.m_timestamp); * } */ // keep statistics m_compensation_sum += delta_t; m_compensation_count++; // NOTE: one can't replace(interpolate_ticks > 0) by do_interpolation here, // because even in the (interpolate_ticks > 0) case the code above could // have reset do_interpolation to false because we technically want // the "extrapolation" into the past thing, but we don't want to count that // as extrapolation... m_compensation_interpol_count += (interpolate_ticks > 0)?1:0; if (Time.time >= m_compensation_last + 5.0 && m_compensation_count > 0) { // Debug.LogFormat("ship lag compensation over last {0} frames: {1}ms / {2} physics ticks, {3} interpolation ({4}%) packets: {5} received / {6} missing / {7} old ignored", // m_compensation_count, 1000.0f* (m_compensation_sum/ m_compensation_count), // (m_compensation_sum/m_compensation_count)/Time.fixedDeltaTime, // m_compensation_interpol_count, // 100.0f*((float)m_compensation_interpol_count/(float)m_compensation_count), // m_received_packets_count, m_missing_packets_count, m_ignored_packets_count); m_compensation_sum = 0.0f; m_compensation_count = 0; m_compensation_interpol_count = 0; m_received_packets_count = 0; m_missing_packets_count = 0; m_ignored_packets_count = 0; m_compensation_last = Time.time; } // actually apply the operation to each player foreach (Player player in Overload.NetworkManager.m_Players) { if (player != null && !player.isLocalPlayer && !player.m_spectator) { // do the actual interpolation or extrapolation, as calculated above if (do_interpolation) { NewPlayerSnapshot A = GetPlayerSnapshot(msgA, player); NewPlayerSnapshot B = GetPlayerSnapshot(msgB, player); if (A != null && B != null) { interpolatePlayer(player, A, B, interpolate_factor); } } else { NewPlayerSnapshot snapshot = GetPlayerSnapshot(msgB, player); if (snapshot != null) { extrapolatePlayer(player, snapshot, delta_t); } } } } }