using System; using NitroxClient.Communication.Abstract; using NitroxClient.GameLogic.PlayerLogic.PlayerModel; using NitroxClient.GameLogic.PlayerLogic.PlayerModel.Abstract; using NitroxClient.MonoBehaviours; using NitroxClient.Unity.Helper; using NitroxModel.DataStructures; using NitroxModel.DataStructures.GameLogic; using NitroxModel.DataStructures.Util; using NitroxModel.MultiplayerSession; using NitroxModel.Packets; using NitroxModel_Subnautica.DataStructures; using UnityEngine; using UnityEngine.Rendering; using Object = UnityEngine.Object; namespace NitroxClient.GameLogic; public class LocalPlayer : ILocalNitroxPlayer { private readonly IMultiplayerSession multiplayerSession; private readonly IPacketSender packetSender; private readonly ThrottledPacketSender throttledPacketSender; private readonly Lazy body; private readonly Lazy playerModel; private readonly Lazy bodyPrototype; public GameObject Body => body.Value; public GameObject PlayerModel => playerModel.Value; public GameObject BodyPrototype => bodyPrototype.Value; public string PlayerName => multiplayerSession.AuthenticationContext.Username; /// /// Gets the player id. The session is lost on disconnect so this can return null. /// public ushort? PlayerId => multiplayerSession?.Reservation?.PlayerId; public PlayerSettings PlayerSettings => multiplayerSession.PlayerSettings; public Perms Permissions { get; set; } public IntroCinematicMode IntroCinematicMode { get; set; } public bool KeepInventoryOnDeath { get; set; } public LocalPlayer(IMultiplayerSession multiplayerSession, IPacketSender packetSender, ThrottledPacketSender throttledPacketSender) { this.multiplayerSession = multiplayerSession; this.packetSender = packetSender; this.throttledPacketSender = throttledPacketSender; body = new Lazy(() => Player.main.RequireGameObject("body")); playerModel = new Lazy(() => Body.RequireGameObject("player_view")); bodyPrototype = new Lazy(CreateBodyPrototype); Permissions = Perms.PLAYER; IntroCinematicMode = IntroCinematicMode.NONE; KeepInventoryOnDeath = false; } public void BroadcastLocation(Vector3 location, Vector3 velocity, Quaternion bodyRotation, Quaternion aimingRotation) { if (!PlayerId.HasValue) { return; } PlayerMovement playerMovement = new(PlayerId.Value, location.ToDto(), velocity.ToDto(), bodyRotation.ToDto(), aimingRotation.ToDto()); packetSender.Send(playerMovement); } public void AnimationChange(AnimChangeType type, AnimChangeState state) { if (PlayerId.HasValue) { packetSender.Send(new AnimationChangeEvent(PlayerId.Value, (int)type, (int)state)); } } public void BroadcastStats(float oxygen, float maxOxygen, float health, float food, float water, float infectionAmount) { if (PlayerId.HasValue) { packetSender.Send(new PlayerStats(PlayerId.Value, oxygen, maxOxygen, health, food, water, infectionAmount)); } } public void BroadcastDeath(Vector3 deathPosition) { if (PlayerId.HasValue) { packetSender.Send(new PlayerDeathEvent(PlayerId.Value, deathPosition.ToDto())); } } public void BroadcastSubrootChange(Optional subrootId) { if (PlayerId.HasValue) { packetSender.Send(new SubRootChanged(PlayerId.Value, subrootId)); } } public void BroadcastEscapePodChange(Optional escapePodId) { if (PlayerId.HasValue) { packetSender.Send(new EscapePodChanged(PlayerId.Value, escapePodId)); } } public void BroadcastWeld(NitroxId id, float healthAdded) => packetSender.Send(new WeldAction(id, healthAdded)); public void BroadcastHeldItemChanged(NitroxId itemId, PlayerHeldItemChanged.ChangeType techType, NitroxTechType isFirstTime) { if (PlayerId.HasValue) { packetSender.Send(new PlayerHeldItemChanged(PlayerId.Value, itemId, techType, isFirstTime)); } } public void BroadcastQuickSlotsBindingChanged(Optional[] slotItemIds) => throttledPacketSender.SendThrottled(new PlayerQuickSlotsBindingChanged(slotItemIds), (packet) => 1); private GameObject CreateBodyPrototype() { GameObject prototype = Body; // Cheap fix for showing head, much easier since male_geo contains many different heads prototype.GetComponentInParent().head.shadowCastingMode = ShadowCastingMode.On; GameObject clone = Object.Instantiate(prototype, Multiplayer.Main.transform, false); prototype.GetComponentInParent().head.shadowCastingMode = ShadowCastingMode.ShadowsOnly; clone.SetActive(false); clone.name = "RemotePlayerPrototype"; // Removing items that are held in hand foreach (Transform child in clone.transform.Find($"player_view/{PlayerEquipmentConstants.ITEM_ATTACH_POINT_GAME_OBJECT_NAME}")) { if (!child.gameObject.name.Contains("attach1_")) { Object.DestroyImmediate(child.gameObject); } } return clone; } }