partial void EndReadyCheck() { if (IsFinished) { return; } IsFinished = true; foreach (Client client in ActivePlayers) { if (client != null && !client.Spectating) { IWriteMessage msg = new WriteOnlyMessage(); msg.Write((byte)ServerPacketHeader.READY_CHECK); msg.Write((byte)ReadyCheckState.End); msg.Write((ushort)Clients.Count); foreach (var(id, state) in Clients) { msg.Write(id); msg.Write((byte)state); } GameMain.Server.ServerPeer.Send(msg, client.Connection, DeliveryMethod.Reliable); } } }
/// <summary> /// Notify the server of crew changes /// </summary> /// <param name="updatePending">When set to true will tell the server to update the pending hires</param> /// <param name="firedCharacter">When not null tell the server to fire this character</param> /// <param name="validateHires">When set to true will tell the server to validate pending hires</param> public void SendCrewState(bool updatePending, CharacterInfo firedCharacter = null, bool validateHires = false) { if (campaign is MultiPlayerCampaign) { IWriteMessage msg = new WriteOnlyMessage(); msg.Write((byte)ClientPacketHeader.CREW); msg.Write(updatePending); if (updatePending) { msg.Write((ushort)PendingHires.Count); foreach (CharacterInfo pendingHire in PendingHires) { msg.Write(pendingHire.GetIdentifier()); } } msg.Write(validateHires); msg.Write(firedCharacter != null); if (firedCharacter != null) { msg.Write(firedCharacter.GetIdentifier()); } GameMain.Client.ClientPeer?.Send(msg, DeliveryMethod.Reliable); } }
public void InitializeReadyCheck(string author, Client?sender = null) { foreach (Client client in ActivePlayers) { if (client != null && !client.Spectating) { IWriteMessage msg = new WriteOnlyMessage(); msg.Write((byte)ServerPacketHeader.READY_CHECK); msg.Write((byte)ReadyCheckState.Start); msg.Write(endTime); msg.Write(author); if (sender != null) { msg.Write(true); msg.Write(sender.ID); } else { msg.Write(false); } msg.Write((ushort)ActivePlayers.Count); foreach (byte clientId in Clients.Keys) { msg.Write(clientId); } GameMain.Server.ServerPeer.Send(msg, client.Connection, DeliveryMethod.Reliable); } } }
private static void SendState(ReadyStatus status) { IWriteMessage msg = new WriteOnlyMessage(); msg.Write((byte)ClientPacketHeader.READY_CHECK); msg.Write((byte)ReadyCheckState.Update); msg.Write((byte)status); GameMain.Client?.ClientPeer?.Send(msg, DeliveryMethod.Reliable); }
public void ServerWrite(IWriteMessage msg, Client c, object[] extraData = null) { msg.Write(ID); IWriteMessage tempBuffer = new WriteOnlyMessage(); subBody.Body.ServerWrite(tempBuffer, c, extraData); msg.Write((byte)tempBuffer.LengthBytes); msg.Write(tempBuffer.Buffer, 0, tempBuffer.LengthBytes); msg.WritePadBits(); }
public void ServerWritePosition(IWriteMessage msg, Client c, object[] extraData = null) { msg.Write(ID); IWriteMessage tempBuffer = new WriteOnlyMessage(); body.ServerWrite(tempBuffer, c, extraData); msg.WriteVariableUInt32((uint)tempBuffer.LengthBytes); msg.Write(tempBuffer.Buffer, 0, tempBuffer.LengthBytes); msg.WritePadBits(); }
private void NotifyUnlock(LocationConnection connection) { foreach (Client client in GameMain.Server.ConnectedClients) { IWriteMessage outmsg = new WriteOnlyMessage(); outmsg.Write((byte)ServerPacketHeader.EVENTACTION); outmsg.Write((byte)EventManager.NetworkEventType.UNLOCKPATH); outmsg.Write((UInt16)GameMain.GameSession.Map.Connections.IndexOf(connection)); GameMain.Server.ServerPeer.Send(outmsg, client.Connection, DeliveryMethod.Reliable); } }
private void NotifyMissionUnlock(MissionPrefab prefab) { foreach (Client client in GameMain.Server.ConnectedClients) { IWriteMessage outmsg = new WriteOnlyMessage(); outmsg.Write((byte)ServerPacketHeader.EVENTACTION); outmsg.Write((byte)EventManager.NetworkEventType.MISSION); outmsg.Write(prefab.Identifier); GameMain.Server.ServerPeer.Send(outmsg, client.Connection, DeliveryMethod.Reliable); } }
private void SendUpgradeResetMessage(Dictionary <string, int> newUpgrades) { foreach (Client c in GameMain.Server.ConnectedClients) { IWriteMessage outmsg = new WriteOnlyMessage(); outmsg.Write((byte)ServerPacketHeader.RESET_UPGRADES); outmsg.Write(true); outmsg.Write(Campaign.Money); // outmsg.Write((uint)newUpgrades.Count); // foreach (var (key, value) in newUpgrades) // { // outmsg.Write(key); // outmsg.Write((byte)value); // } GameMain.Server?.ServerPeer?.Send(outmsg, c.Connection, DeliveryMethod.Reliable); } }
private void ServerWrite(IEnumerable <Entity> targets) { IWriteMessage outmsg = new WriteOnlyMessage(); outmsg.Write((byte)ServerPacketHeader.EVENTACTION); outmsg.Write((byte)EventManager.NetworkEventType.STATUSEFFECT); outmsg.Write(ParentEvent.Prefab.Identifier); outmsg.Write((UInt16)actionIndex); outmsg.Write((UInt16)targets.Count()); foreach (Entity target in targets) { outmsg.Write(target.ID); } foreach (Client c in GameMain.Server.ConnectedClients) { GameMain.Server.ServerPeer?.Send(outmsg, c.Connection, DeliveryMethod.Reliable); } }
private void UpdateReadyCheck(byte otherClient, ReadyStatus state) { if (Clients.All(pair => pair.Value != ReadyStatus.Unanswered)) { EndReadyCheck(); return; } foreach (Client client in ActivePlayers) { IWriteMessage msg = new WriteOnlyMessage(); msg.Write((byte)ServerPacketHeader.READY_CHECK); msg.Write((byte)ReadyCheckState.Update); msg.Write(time); // sync time msg.Write((byte)state); msg.Write(otherClient); GameMain.Server.ServerPeer.Send(msg, client.Connection, DeliveryMethod.Reliable); } }
public static void CreateReadyCheck() { if (lastReadyCheck < DateTime.Now) { #if !DEBUG lastReadyCheck = DateTime.Now.AddMinutes(1); #endif IWriteMessage msg = new WriteOnlyMessage(); msg.Write((byte)ClientPacketHeader.READY_CHECK); msg.Write((byte)ReadyCheckState.Start); GameMain.Client?.ClientPeer?.Send(msg, DeliveryMethod.Reliable); return; } GUIMessageBox msgBox = new GUIMessageBox(readyCheckHeader, readyCheckPleaseWait((lastReadyCheck - DateTime.Now).Seconds), new[] { closeButton }); msgBox.Buttons[0].OnClicked = delegate { msgBox.Close(); return(true); }; }
/// <summary> /// Notifies the clients of the current bot situation like syncing pending and available hires /// available hires are also synced /// </summary> /// <param name="validateHires">When set to true notifies the clients that the hires have been validated.</param> /// <param name="firedCharacter">When not null will inform the clients that his character has been fired.</param> /// <remarks> /// It might be obsolete to sync available hires. I found that the available hires are always the same between /// the client and the server when there's only one person on the server but when a second person joins both of /// their available hires are different from the server. /// </remarks> public void SendCrewState(bool validateHires, CharacterInfo firedCharacter) { List <CharacterInfo> availableHires = new List <CharacterInfo>(); List <CharacterInfo> pendingHires = new List <CharacterInfo>(); if (map.CurrentLocation != null && map.CurrentLocation.Type.HasHireableCharacters) { availableHires = map.CurrentLocation.GetHireableCharacters().ToList(); pendingHires = map.CurrentLocation?.HireManager.PendingHires; } foreach (Client client in GameMain.Server.ConnectedClients) { IWriteMessage msg = new WriteOnlyMessage(); msg.Write((byte)ServerPacketHeader.CREW); msg.Write((ushort)availableHires.Count); foreach (CharacterInfo hire in availableHires) { hire.ServerWrite(msg); msg.Write(hire.Salary); } msg.Write((ushort)pendingHires.Count); foreach (CharacterInfo pendingHire in pendingHires) { msg.Write(pendingHire.GetIdentifier()); } msg.Write(validateHires); msg.Write(firedCharacter != null); if (firedCharacter != null) { msg.Write(firedCharacter.GetIdentifier()); } GameMain.Server.ServerPeer.Send(msg, client.Connection, DeliveryMethod.Reliable); } }
private void ServerWrite(Character speaker, Client client) { IWriteMessage outmsg = new WriteOnlyMessage(); outmsg.Write((byte)ServerPacketHeader.EVENTACTION); outmsg.Write((byte)EventManager.NetworkEventType.CONVERSATION); outmsg.Write(Identifier); outmsg.Write(EventSprite); outmsg.Write((byte)DialogType); outmsg.Write(ContinueConversation); if (interrupt) { outmsg.Write(speaker?.ID ?? Entity.NullEntityID); outmsg.Write(string.Empty); outmsg.Write(false); outmsg.Write((byte)0); outmsg.Write((byte)0); } else { outmsg.Write(speaker?.ID ?? Entity.NullEntityID); outmsg.Write(Text ?? string.Empty); outmsg.Write(FadeToBlack); outmsg.Write((byte)Options.Count); for (int i = 0; i < Options.Count; i++) { outmsg.Write(Options[i].Text); } int[] endings = GetEndingOptions(); outmsg.Write((byte)endings.Length); foreach (var end in endings) { outmsg.Write((byte)end); } } GameMain.Server?.ServerPeer?.Send(outmsg, client.Connection, DeliveryMethod.Reliable); }
public virtual void ServerWrite(IWriteMessage msg, Client c, object[] extraData = null) { if (GameMain.Server == null) { return; } if (extraData != null) { switch ((NetEntityEvent.Type)extraData[0]) { case NetEntityEvent.Type.InventoryState: msg.WriteRangedInteger(0, 0, 5); msg.Write(GameMain.Server.EntityEventManager.Events.Last()?.ID ?? (ushort)0); Inventory.ServerWrite(msg, c); break; case NetEntityEvent.Type.Control: msg.WriteRangedInteger(1, 0, 5); Client owner = (Client)extraData[1]; msg.Write(owner != null && owner.Character == this && GameMain.Server.ConnectedClients.Contains(owner) ? owner.ID : (byte)0); break; case NetEntityEvent.Type.Status: msg.WriteRangedInteger(2, 0, 5); WriteStatus(msg); break; case NetEntityEvent.Type.UpdateSkills: msg.WriteRangedInteger(3, 0, 5); if (Info?.Job == null) { msg.Write((byte)0); } else { msg.Write((byte)Info.Job.Skills.Count); foreach (Skill skill in Info.Job.Skills) { msg.Write(skill.Identifier); msg.Write(skill.Level); } } break; case NetEntityEvent.Type.ExecuteAttack: Limb attackLimb = extraData[1] as Limb; UInt16 targetEntityID = (UInt16)extraData[2]; int targetLimbIndex = extraData.Length > 3 ? (int)extraData[3] : 0; msg.WriteRangedInteger(4, 0, 5); msg.Write((byte)(Removed ? 255 : Array.IndexOf(AnimController.Limbs, attackLimb))); msg.Write(targetEntityID); msg.Write((byte)targetLimbIndex); break; case NetEntityEvent.Type.AssignCampaignInteraction: msg.WriteRangedInteger(5, 0, 5); msg.Write((byte)CampaignInteractionType); break; default: DebugConsole.ThrowError("Invalid NetworkEvent type for entity " + ToString() + " (" + (NetEntityEvent.Type)extraData[0] + ")"); break; } msg.WritePadBits(); } else { msg.Write(ID); IWriteMessage tempBuffer = new WriteOnlyMessage(); if (this == c.Character) { tempBuffer.Write(true); if (LastNetworkUpdateID < memInput.Count + 1) { tempBuffer.Write((UInt16)0); } else { tempBuffer.Write((UInt16)(LastNetworkUpdateID - memInput.Count - 1)); } } else { tempBuffer.Write(false); bool aiming = false; bool use = false; bool attack = false; bool shoot = false; if (IsRemotePlayer) { aiming = dequeuedInput.HasFlag(InputNetFlags.Aim); use = dequeuedInput.HasFlag(InputNetFlags.Use); attack = dequeuedInput.HasFlag(InputNetFlags.Attack); shoot = dequeuedInput.HasFlag(InputNetFlags.Shoot); } else if (keys != null) { aiming = keys[(int)InputType.Aim].GetHeldQueue; use = keys[(int)InputType.Use].GetHeldQueue; attack = keys[(int)InputType.Attack].GetHeldQueue; shoot = keys[(int)InputType.Shoot].GetHeldQueue; networkUpdateSent = true; } tempBuffer.Write(aiming); tempBuffer.Write(shoot); tempBuffer.Write(use); if (AnimController is HumanoidAnimController) { tempBuffer.Write(((HumanoidAnimController)AnimController).Crouching); } tempBuffer.Write(attack); Vector2 relativeCursorPos = cursorPosition - AimRefPosition; tempBuffer.Write((UInt16)(65535.0 * Math.Atan2(relativeCursorPos.Y, relativeCursorPos.X) / (2.0 * Math.PI))); tempBuffer.Write(IsRagdolled || Stun > 0.0f || IsDead || IsIncapacitated); tempBuffer.Write(AnimController.Dir > 0.0f); } if (SelectedCharacter != null || SelectedConstruction != null) { tempBuffer.Write(true); tempBuffer.Write(SelectedCharacter != null ? SelectedCharacter.ID : NullEntityID); tempBuffer.Write(SelectedConstruction != null ? SelectedConstruction.ID : NullEntityID); if (SelectedCharacter != null) { tempBuffer.Write(AnimController.Anim == AnimController.Animation.CPR); } } else { tempBuffer.Write(false); } tempBuffer.Write(SimPosition.X); tempBuffer.Write(SimPosition.Y); float MaxVel = NetConfig.MaxPhysicsBodyVelocity; AnimController.Collider.LinearVelocity = new Vector2( MathHelper.Clamp(AnimController.Collider.LinearVelocity.X, -MaxVel, MaxVel), MathHelper.Clamp(AnimController.Collider.LinearVelocity.Y, -MaxVel, MaxVel)); tempBuffer.WriteRangedSingle(AnimController.Collider.LinearVelocity.X, -MaxVel, MaxVel, 12); tempBuffer.WriteRangedSingle(AnimController.Collider.LinearVelocity.Y, -MaxVel, MaxVel, 12); bool fixedRotation = AnimController.Collider.FarseerBody.FixedRotation || !AnimController.Collider.PhysEnabled; tempBuffer.Write(fixedRotation); if (!fixedRotation) { tempBuffer.Write(AnimController.Collider.Rotation); float MaxAngularVel = NetConfig.MaxPhysicsBodyAngularVelocity; AnimController.Collider.AngularVelocity = NetConfig.Quantize(AnimController.Collider.AngularVelocity, -MaxAngularVel, MaxAngularVel, 8); tempBuffer.WriteRangedSingle(MathHelper.Clamp(AnimController.Collider.AngularVelocity, -MaxAngularVel, MaxAngularVel), -MaxAngularVel, MaxAngularVel, 8); } bool writeStatus = healthUpdateTimer <= 0.0f; tempBuffer.Write(writeStatus); if (writeStatus) { WriteStatus(tempBuffer); HealthUpdatePending = false; } tempBuffer.WritePadBits(); msg.WriteVariableUInt32((uint)tempBuffer.LengthBytes); msg.Write(tempBuffer.Buffer, 0, tempBuffer.LengthBytes); } }
public virtual void ServerWrite(IWriteMessage msg, Client c, object[] extraData = null) { if (GameMain.Server == null) { return; } if (extraData != null) { const int min = 0, max = 13; switch ((NetEntityEvent.Type)extraData[0]) { case NetEntityEvent.Type.InventoryState: msg.WriteRangedInteger(0, min, max); msg.Write(GameMain.Server.EntityEventManager.Events.Last()?.ID ?? (ushort)0); Inventory.ServerWrite(msg, c); break; case NetEntityEvent.Type.Control: msg.WriteRangedInteger(1, min, max); Client owner = (Client)extraData[1]; msg.Write(owner != null && owner.Character == this && GameMain.Server.ConnectedClients.Contains(owner) ? owner.ID : (byte)0); break; case NetEntityEvent.Type.Status: msg.WriteRangedInteger(2, min, max); WriteStatus(msg); break; case NetEntityEvent.Type.UpdateSkills: msg.WriteRangedInteger(3, min, max); if (Info?.Job == null) { msg.Write((byte)0); } else { msg.Write((byte)Info.Job.Skills.Count); foreach (Skill skill in Info.Job.Skills) { msg.Write(skill.Identifier); msg.Write(skill.Level); } } break; case NetEntityEvent.Type.SetAttackTarget: case NetEntityEvent.Type.ExecuteAttack: Limb attackLimb = extraData[1] as Limb; UInt16 targetEntityID = (UInt16)extraData[2]; int targetLimbIndex = extraData.Length > 3 ? (int)extraData[3] : 0; msg.WriteRangedInteger(extraData[0] is NetEntityEvent.Type.SetAttackTarget ? 4 : 5, min, max); msg.Write((byte)(Removed ? 255 : Array.IndexOf(AnimController.Limbs, attackLimb))); msg.Write(targetEntityID); msg.Write((byte)targetLimbIndex); msg.Write(extraData.Length > 4 ? (float)extraData[4] : 0); msg.Write(extraData.Length > 5 ? (float)extraData[5] : 0); break; case NetEntityEvent.Type.AssignCampaignInteraction: msg.WriteRangedInteger(6, min, max); msg.Write((byte)CampaignInteractionType); msg.Write(RequireConsciousnessForCustomInteract); break; case NetEntityEvent.Type.ObjectiveManagerState: msg.WriteRangedInteger(7, min, max); int type = (extraData[1] as string) switch { "order" => 1, "objective" => 2, _ => 0 }; msg.WriteRangedInteger(type, 0, 2); if (!(AIController is HumanAIController controller)) { msg.Write(false); break; } if (type == 1) { var currentOrderInfo = controller.ObjectiveManager.GetCurrentOrderInfo(); bool validOrder = currentOrderInfo.HasValue; msg.Write(validOrder); if (!validOrder) { break; } var orderPrefab = currentOrderInfo.Value.Order.Prefab; int orderIndex = Order.PrefabList.IndexOf(orderPrefab); msg.WriteRangedInteger(orderIndex, 0, Order.PrefabList.Count); if (!orderPrefab.HasOptions) { break; } int optionIndex = orderPrefab.AllOptions.IndexOf(currentOrderInfo.Value.OrderOption); if (optionIndex == -1) { DebugConsole.AddWarning($"Error while writing order data. Order option \"{(currentOrderInfo.Value.OrderOption ?? null)}\" not found in the order prefab \"{orderPrefab.Name}\"."); } msg.WriteRangedInteger(optionIndex, -1, orderPrefab.AllOptions.Length); } else if (type == 2) { var objective = controller.ObjectiveManager.CurrentObjective; bool validObjective = !string.IsNullOrEmpty(objective?.Identifier); msg.Write(validObjective); if (!validObjective) { break; } msg.Write(objective.Identifier); msg.Write(objective.Option ?? ""); UInt16 targetEntityId = 0; if (objective is AIObjectiveOperateItem operateObjective && operateObjective.OperateTarget != null) { targetEntityId = operateObjective.OperateTarget.ID; } msg.Write(targetEntityId); } break; case NetEntityEvent.Type.TeamChange: msg.WriteRangedInteger(8, min, max); msg.Write((byte)TeamID); break; case NetEntityEvent.Type.AddToCrew: msg.WriteRangedInteger(9, min, max); msg.Write((byte)(CharacterTeamType)extraData[1]); // team id ushort[] inventoryItemIDs = (ushort[])extraData[2]; msg.Write((ushort)inventoryItemIDs.Length); for (int i = 0; i < inventoryItemIDs.Length; i++) { msg.Write(inventoryItemIDs[i]); } break; case NetEntityEvent.Type.UpdateExperience: msg.WriteRangedInteger(10, min, max); msg.Write(Info.ExperiencePoints); break; case NetEntityEvent.Type.UpdateTalents: msg.WriteRangedInteger(11, min, max); msg.Write((ushort)characterTalents.Count); foreach (var unlockedTalent in characterTalents) { msg.Write(unlockedTalent.AddedThisRound); msg.Write(unlockedTalent.Prefab.UIntIdentifier); } break; case NetEntityEvent.Type.UpdateMoney: msg.WriteRangedInteger(12, min, max); msg.Write(GameMain.GameSession.Campaign.Money); break; case NetEntityEvent.Type.UpdatePermanentStats: msg.WriteRangedInteger(13, min, max); if (Info == null || extraData.Length < 2 || !(extraData[1] is StatTypes statType)) { msg.Write((byte)0); msg.Write((byte)0); } else if (!Info.SavedStatValues.ContainsKey(statType)) { msg.Write((byte)0); msg.Write((byte)statType); } else { msg.Write((byte)Info.SavedStatValues[statType].Count); msg.Write((byte)statType); foreach (var savedStatValue in Info.SavedStatValues[statType]) { msg.Write(savedStatValue.StatIdentifier); msg.Write(savedStatValue.StatValue); msg.Write(savedStatValue.RemoveOnDeath); } } break; default: DebugConsole.ThrowError("Invalid NetworkEvent type for entity " + ToString() + " (" + (NetEntityEvent.Type)extraData[0] + ")"); break; } msg.WritePadBits(); } else { msg.Write(ID); IWriteMessage tempBuffer = new WriteOnlyMessage(); if (this == c.Character) { tempBuffer.Write(true); if (LastNetworkUpdateID < memInput.Count + 1) { tempBuffer.Write((UInt16)0); } else { tempBuffer.Write((UInt16)(LastNetworkUpdateID - memInput.Count - 1)); } } else { tempBuffer.Write(false); bool aiming = false; bool use = false; bool attack = false; bool shoot = false; if (IsRemotePlayer) { aiming = dequeuedInput.HasFlag(InputNetFlags.Aim); use = dequeuedInput.HasFlag(InputNetFlags.Use); attack = dequeuedInput.HasFlag(InputNetFlags.Attack); shoot = dequeuedInput.HasFlag(InputNetFlags.Shoot); } else if (keys != null) { aiming = keys[(int)InputType.Aim].GetHeldQueue; use = keys[(int)InputType.Use].GetHeldQueue; attack = keys[(int)InputType.Attack].GetHeldQueue; shoot = keys[(int)InputType.Shoot].GetHeldQueue; networkUpdateSent = true; } tempBuffer.Write(aiming); tempBuffer.Write(shoot); tempBuffer.Write(use); if (AnimController is HumanoidAnimController) { tempBuffer.Write(((HumanoidAnimController)AnimController).Crouching); } tempBuffer.Write(attack); Vector2 relativeCursorPos = cursorPosition - AimRefPosition; tempBuffer.Write((UInt16)(65535.0 * Math.Atan2(relativeCursorPos.Y, relativeCursorPos.X) / (2.0 * Math.PI))); tempBuffer.Write(IsRagdolled || Stun > 0.0f || IsDead || IsIncapacitated); tempBuffer.Write(AnimController.Dir > 0.0f); } if (SelectedCharacter != null || SelectedConstruction != null) { tempBuffer.Write(true); tempBuffer.Write(SelectedCharacter != null ? SelectedCharacter.ID : NullEntityID); tempBuffer.Write(SelectedConstruction != null ? SelectedConstruction.ID : NullEntityID); if (SelectedCharacter != null) { tempBuffer.Write(AnimController.Anim == AnimController.Animation.CPR); } } else { tempBuffer.Write(false); } tempBuffer.Write(SimPosition.X); tempBuffer.Write(SimPosition.Y); float MaxVel = NetConfig.MaxPhysicsBodyVelocity; AnimController.Collider.LinearVelocity = new Vector2( MathHelper.Clamp(AnimController.Collider.LinearVelocity.X, -MaxVel, MaxVel), MathHelper.Clamp(AnimController.Collider.LinearVelocity.Y, -MaxVel, MaxVel)); tempBuffer.WriteRangedSingle(AnimController.Collider.LinearVelocity.X, -MaxVel, MaxVel, 12); tempBuffer.WriteRangedSingle(AnimController.Collider.LinearVelocity.Y, -MaxVel, MaxVel, 12); bool fixedRotation = AnimController.Collider.FarseerBody.FixedRotation || !AnimController.Collider.PhysEnabled; tempBuffer.Write(fixedRotation); if (!fixedRotation) { tempBuffer.Write(AnimController.Collider.Rotation); float MaxAngularVel = NetConfig.MaxPhysicsBodyAngularVelocity; AnimController.Collider.AngularVelocity = NetConfig.Quantize(AnimController.Collider.AngularVelocity, -MaxAngularVel, MaxAngularVel, 8); tempBuffer.WriteRangedSingle(MathHelper.Clamp(AnimController.Collider.AngularVelocity, -MaxAngularVel, MaxAngularVel), -MaxAngularVel, MaxAngularVel, 8); } bool writeStatus = healthUpdateTimer <= 0.0f; tempBuffer.Write(writeStatus); if (writeStatus) { WriteStatus(tempBuffer); AIController?.ServerWrite(tempBuffer); HealthUpdatePending = false; } tempBuffer.WritePadBits(); msg.WriteVariableUInt32((uint)tempBuffer.LengthBytes); msg.Write(tempBuffer.Buffer, 0, tempBuffer.LengthBytes); } }