470 lines
19 KiB
C#
470 lines
19 KiB
C#
using Lidgren.Network;
|
|
using MonomiPark.SlimeRancher.DataModel;
|
|
using MonomiPark.SlimeRancher.Regions;
|
|
using Newtonsoft.Json;
|
|
using SRMultiplayer;
|
|
using SRMultiplayer.Packets;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Text;
|
|
using System.Threading.Tasks;
|
|
using UnityEngine;
|
|
|
|
namespace SRMultiplayer.Networking
|
|
{
|
|
public partial class NetworkPlayer : MonoBehaviour
|
|
{
|
|
public NetConnection Connection { get; internal set; }
|
|
public Guid UUID { get; internal set; }
|
|
|
|
public byte ID { get; internal set; }
|
|
public string Username { get; internal set; }
|
|
public bool IsLocal { get { return ID == Globals.LocalID; } }
|
|
public List<string> Mods { get; set; }
|
|
public List<DLCPackage.Id> DLCs { get; set; }
|
|
|
|
public bool HasLoaded;
|
|
public RegionRegistry.RegionSetId CurrentRegionSet
|
|
{
|
|
get
|
|
{
|
|
return m_RegionSet;
|
|
}
|
|
set
|
|
{
|
|
m_RegionMember.actorModel.currRegionSetId = value;
|
|
m_RegionSet = value;
|
|
}
|
|
}
|
|
public QuicksilverEnergyGenerator CurrentGenerator
|
|
{
|
|
get
|
|
{
|
|
QuicksilverEnergyGenerator generator = null;
|
|
foreach (var region in Globals.LocalPlayer.Regions)
|
|
{
|
|
generator = region.GetComponent<QuicksilverEnergyGenerator>();
|
|
if (generator != null)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
return generator;
|
|
}
|
|
}
|
|
|
|
private RegionRegistry.RegionSetId m_RegionSet;
|
|
private RegionMember m_RegionMember;
|
|
private Transform m_Label;
|
|
private float m_MovementUpdateTimer;
|
|
private Vector3 m_ActualPosition; // where we render
|
|
private Vector3 m_LatestPosition; // last position reported by server
|
|
private float m_LatestPositionTime; // the time we want to arrive at the last position reported by server
|
|
private Vector3 m_PreviousPosition; // the previous point we're interpolating from
|
|
private float m_InterpolationPeriod = 0.1f;
|
|
private float m_PreviousRotation;
|
|
private float m_LatestRotation;
|
|
private float m_ActualRotation;
|
|
private float m_ActualWeaponY;
|
|
private float m_LatestWeaponY;
|
|
private float m_PreviousWeaponY;
|
|
private Animator m_Animator;
|
|
private GameObject m_Camera;
|
|
private SECTR_PointSource m_VacAudio;
|
|
private SECTR_PointSource m_JetpackAudio;
|
|
private SECTR_AudioCue m_VacStartCue;
|
|
private SECTR_AudioCue m_VacRunCue;
|
|
private SECTR_AudioCue m_VacEndCue;
|
|
private SECTR_AudioCue m_JetpackStartCue;
|
|
private SECTR_AudioCue m_JetpackRunCue;
|
|
private SECTR_AudioCue m_JetpackEndCue;
|
|
private bool m_VacAudioActive;
|
|
private bool m_JetpackAudioActive;
|
|
private Transform m_LeftHandTarget;
|
|
private WeaponVacuum m_WeaponVacuum;
|
|
|
|
public GameObject PlayerObj;
|
|
public GameObject VacFX;
|
|
public GameObject AirBurstFX;
|
|
public GameObject ShootFX;
|
|
public GameObject CaptureFX;
|
|
public GameObject CaptureFailedFX;
|
|
public GameObject DestroyOnVacFX;
|
|
public Transform VacTransform;
|
|
public List<NetworkRegion> Regions = new List<NetworkRegion>();
|
|
public float airBurstRadius;
|
|
public float airBurstPower;
|
|
|
|
private void Update()
|
|
{
|
|
if (!HasLoaded) return;
|
|
|
|
if(IsLocal)
|
|
{
|
|
m_MovementUpdateTimer -= Time.deltaTime;
|
|
if (m_MovementUpdateTimer <= 0 && m_WeaponVacuum != null)
|
|
{
|
|
if (Vector3.Distance(m_ActualPosition, SRSingleton<SceneContext>.Instance.Player.transform.position) > 0.1f ||
|
|
!Utils.CloseEnoughForMe(m_ActualRotation, SRSingleton<SceneContext>.Instance.Player.transform.eulerAngles.y, 0.1f) ||
|
|
!Utils.CloseEnoughForMe(m_ActualWeaponY, m_WeaponVacuum.transform.eulerAngles.x, 0.1f))
|
|
{
|
|
m_ActualPosition = SRSingleton<SceneContext>.Instance.Player.transform.position;
|
|
m_ActualRotation = SRSingleton<SceneContext>.Instance.Player.transform.eulerAngles.y;
|
|
m_ActualWeaponY = m_WeaponVacuum.transform.eulerAngles.x;
|
|
|
|
m_MovementUpdateTimer = 0.1f;
|
|
new PacketPlayerPosition()
|
|
{
|
|
ID = Globals.LocalID,
|
|
Position = SRSingleton<SceneContext>.Instance.Player.transform.position,
|
|
Rotation = SRSingleton<SceneContext>.Instance.Player.transform.eulerAngles.y,
|
|
RegionSet = (byte)SRSingleton<SceneContext>.Instance.PlayerState.model.currRegionSetId,
|
|
WeaponY = m_WeaponVacuum.transform.eulerAngles.x
|
|
}.Send();
|
|
}
|
|
m_Animator.SetFloat("MoveX", SRInput.Actions.horizontal);
|
|
m_Animator.SetFloat("MoveY", SRInput.Actions.vertical);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (m_Label != null)
|
|
{
|
|
m_Label.rotation = Quaternion.LookRotation(m_Label.position - Camera.main.transform.position);
|
|
}
|
|
|
|
float t = 1.0f - ((m_LatestPositionTime - Time.time) / m_InterpolationPeriod);
|
|
m_ActualPosition = Vector3.Lerp(m_PreviousPosition, m_LatestPosition, t);
|
|
transform.position = m_ActualPosition;
|
|
|
|
m_ActualRotation = Mathf.LerpAngle(m_PreviousRotation, m_LatestRotation, t);
|
|
transform.eulerAngles = new Vector3(0, m_ActualRotation, 0);
|
|
|
|
if (m_Animator != null)
|
|
{
|
|
m_ActualWeaponY = Mathf.LerpAngle(m_PreviousWeaponY, m_LatestWeaponY, t);
|
|
m_Animator.SetFloat("WeaponY", m_ActualWeaponY);
|
|
}
|
|
}
|
|
}
|
|
|
|
public float GetWeaponLocation()
|
|
{
|
|
return m_ActualWeaponY;
|
|
}
|
|
|
|
private void OnAnimatorIK()
|
|
{
|
|
if(m_Animator != null && m_LeftHandTarget != null)
|
|
{
|
|
m_Animator.SetIKPositionWeight(AvatarIKGoal.LeftFoot, 1);
|
|
m_Animator.SetIKRotationWeight(AvatarIKGoal.LeftFoot, 1);
|
|
m_Animator.SetIKPosition(AvatarIKGoal.LeftFoot, m_LeftHandTarget.position);
|
|
m_Animator.SetIKRotation(AvatarIKGoal.LeftFoot, m_LeftHandTarget.rotation);
|
|
}
|
|
}
|
|
|
|
public void UpdateWeaponRotation(float angle)
|
|
{
|
|
if (angle > 180)
|
|
angle -= 360;
|
|
|
|
var val = (1 - (Mathf.InverseLerp(-90, 90, angle) * 2)) + 0.1f;
|
|
m_LatestWeaponY = val;
|
|
m_PreviousWeaponY = m_ActualWeaponY;
|
|
}
|
|
|
|
public void PositionRotationUpdate(Vector3 pos, float rot, bool immediate)
|
|
{
|
|
if(Vector3.Distance(pos, m_ActualPosition) > 10f) // Teleport detection
|
|
{
|
|
immediate = true;
|
|
}
|
|
if (!immediate)
|
|
{
|
|
m_LatestPosition = pos;
|
|
m_LatestPositionTime = Time.time + m_InterpolationPeriod;
|
|
m_PreviousPosition = m_ActualPosition;
|
|
|
|
m_LatestRotation = rot;
|
|
m_PreviousRotation = m_ActualRotation;
|
|
}
|
|
else
|
|
{
|
|
m_LatestPosition = pos;
|
|
m_LatestPositionTime = Time.time + m_InterpolationPeriod;
|
|
m_PreviousPosition = pos;
|
|
m_ActualPosition = pos;
|
|
transform.position = m_ActualPosition;
|
|
|
|
m_PreviousRotation = rot;
|
|
m_LatestRotation = rot;
|
|
m_ActualRotation = rot;
|
|
transform.eulerAngles = new Vector3(0, rot, 0);
|
|
}
|
|
}
|
|
|
|
internal void Spawn()
|
|
{
|
|
SRMP.Log("Spawning " + Username);
|
|
|
|
PlayerObj = GameObject.Instantiate(Globals.BeatrixModel);
|
|
PlayerObj.transform.SetParent(transform);
|
|
PlayerObj.transform.localPosition = Vector3.zero;
|
|
|
|
m_Animator = PlayerObj.GetComponentInChildren<Animator>();
|
|
m_Animator.runtimeAnimatorController = Globals.BeatrixController;
|
|
m_Animator.applyRootMotion = false;
|
|
|
|
StartCoroutine(SetupAnimator());
|
|
|
|
m_WeaponVacuum = SRSingleton<SceneContext>.Instance.player.GetComponentInChildren<WeaponVacuum>();
|
|
|
|
if (!IsLocal)
|
|
{
|
|
var barrel = PlayerObj.transform.FindDisabled("bone_barrel");
|
|
var handle = PlayerObj.transform.FindDisabled("bone_handle");
|
|
VacTransform = new GameObject("VacTransform").transform;
|
|
VacTransform.SetParent(barrel, false);
|
|
VacTransform.localEulerAngles = new Vector3(90, 0, 0);
|
|
|
|
handle.localPosition = new Vector3(0.0092f, -0.00916f, 0.01449f);
|
|
m_LeftHandTarget = new GameObject("LeftHandTarget").transform;
|
|
m_LeftHandTarget.SetParent(handle, false);
|
|
m_LeftHandTarget.localPosition = new Vector3(0.0174f, -0.0024f, -0.0045f);
|
|
m_LeftHandTarget.localEulerAngles = new Vector3(27.636f, 72.67101f, -15.03f);
|
|
|
|
var energyJetpack = SRSingleton<SceneContext>.Instance.player.GetComponentInChildren<EnergyJetpack>();
|
|
m_JetpackAudio = energyJetpack.jetpackAudio.CopyComponent(PlayerObj);
|
|
m_JetpackStartCue = energyJetpack.jetpackStartCue;
|
|
m_JetpackRunCue = energyJetpack.jetpackRunCue;
|
|
m_JetpackEndCue = energyJetpack.jetpackEndCue;
|
|
|
|
m_VacAudio = m_WeaponVacuum.vacAudio.CopyComponent(PlayerObj);
|
|
m_VacStartCue = m_WeaponVacuum.vacStartCue;
|
|
m_VacRunCue = m_WeaponVacuum.vacRunCue;
|
|
m_VacEndCue = m_WeaponVacuum.vacEndCue;
|
|
|
|
airBurstPower = m_WeaponVacuum.airBurstPower;
|
|
airBurstRadius = m_WeaponVacuum.airBurstRadius;
|
|
|
|
AirBurstFX = m_WeaponVacuum.airBurstFX;
|
|
CaptureFailedFX = m_WeaponVacuum.captureFailedFX;
|
|
CaptureFX = m_WeaponVacuum.captureFX;
|
|
DestroyOnVacFX = m_WeaponVacuum.destroyOnVacFX;
|
|
ShootFX = m_WeaponVacuum.shootFX;
|
|
VacFX = GameObject.Instantiate(m_WeaponVacuum.vacFX, VacTransform, false);
|
|
VacFX.transform.localScale = new Vector3(.05f, .05f, .05f);
|
|
VacFX.transform.localPosition = new Vector3(0, 0, 0);
|
|
|
|
var realMarker = SRSingleton<SceneContext>.Instance.player.GetComponent<PlayerDisplayOnMap>();
|
|
m_RegionMember = PlayerObj.AddComponent<RegionMember>();
|
|
var mapDisplay = PlayerObj.AddComponent<NetworkPlayerDisplayOnMap>();
|
|
m_RegionMember.canHibernate = false;
|
|
m_RegionMember.actorModel = new PlayerModel();
|
|
|
|
mapDisplay.Player = this;
|
|
mapDisplay.Member = m_RegionMember;
|
|
mapDisplay.markerPrefab = realMarker.markerPrefab;
|
|
mapDisplay.HideInFog = realMarker.HideInFog;
|
|
|
|
var label = new GameObject("PlayerLabel");
|
|
var textMesh = label.AddComponent<TextMesh>();
|
|
textMesh.text = $"{this}";
|
|
textMesh.characterSize = 0.2f;
|
|
textMesh.anchor = TextAnchor.MiddleCenter;
|
|
textMesh.fontSize = 24;
|
|
|
|
label.transform.SetParent(PlayerObj.transform);
|
|
label.transform.localPosition = new Vector3(0, 3, 0);
|
|
label.transform.localScale *= 0.5f;
|
|
|
|
m_Label = label.transform;
|
|
foreach (var collider in PlayerObj.GetComponentsInChildren<Collider>())
|
|
{
|
|
collider.enabled = false;
|
|
}
|
|
var col = PlayerObj.AddComponent<CapsuleCollider>();
|
|
col.height = 2;
|
|
col.center = new Vector3(0, 1.2f, 0);
|
|
}
|
|
else
|
|
{
|
|
PlayerObj.transform.localEulerAngles = new Vector3(0, 0, 0);
|
|
foreach (var renderer in PlayerObj.GetComponentsInChildren<Renderer>())
|
|
{
|
|
renderer.enabled = false;
|
|
}
|
|
foreach (var collider in PlayerObj.GetComponentsInChildren<Collider>())
|
|
{
|
|
collider.enabled = false;
|
|
}
|
|
|
|
var cameraObj = new GameObject("Camera");
|
|
cameraObj.transform.SetParent(PlayerObj.transform);
|
|
cameraObj.transform.localPosition = new Vector3(0, 3, -4);
|
|
cameraObj.transform.localEulerAngles = new Vector3(20, 0, 0);
|
|
cameraObj.AddComponent<Camera>();
|
|
cameraObj.SetActive(false);
|
|
|
|
m_Camera = cameraObj;
|
|
}
|
|
|
|
PlayerObj.SetActive(true);
|
|
}
|
|
|
|
internal void Save()
|
|
{
|
|
var path = Path.Combine(SRMP.ModDataPath, SRSingleton<GameContext>.Instance.AutoSaveDirector.SavedGame.GetName(), UUID.ToString() + ".json");
|
|
File.WriteAllText(path, JsonConvert.SerializeObject(new NetworkPlayerSave()
|
|
{
|
|
LastUsername = Username,
|
|
RegionSet = (byte)CurrentRegionSet,
|
|
WeaponY = m_LatestWeaponY,
|
|
PositionX = transform.position.x,
|
|
PositionY = transform.position.y,
|
|
PositionZ = transform.position.z,
|
|
Rotation = transform.eulerAngles.y
|
|
}));
|
|
}
|
|
|
|
internal void Load()
|
|
{
|
|
var path = Path.Combine(SRMP.ModDataPath, SRSingleton<GameContext>.Instance.AutoSaveDirector.SavedGame.GetName(), UUID.ToString() + ".json");
|
|
if (File.Exists(path))
|
|
{
|
|
var playerSave = JsonConvert.DeserializeObject<NetworkPlayerSave>(File.ReadAllText(path));
|
|
transform.position = new Vector3(playerSave.PositionX, playerSave.PositionY, playerSave.PositionZ);
|
|
transform.eulerAngles = new Vector3(0, playerSave.Rotation, 0);
|
|
UpdateWeaponRotation(playerSave.WeaponY);
|
|
CurrentRegionSet = (RegionRegistry.RegionSetId)playerSave.RegionSet;
|
|
|
|
new PacketPlayerPosition()
|
|
{
|
|
ID = ID,
|
|
Position = transform.position,
|
|
Rotation = transform.eulerAngles.y,
|
|
WeaponY = m_LatestWeaponY,
|
|
RegionSet = (byte)CurrentRegionSet
|
|
}.Send(this);
|
|
}
|
|
}
|
|
|
|
internal void OnLeft()
|
|
{
|
|
foreach (var netRegion in Globals.Regions.Values)
|
|
{
|
|
if (netRegion.Owner == ID)
|
|
{
|
|
netRegion.SetOwnership(0);
|
|
}
|
|
netRegion.RemovePlayer(this);
|
|
}
|
|
foreach (var netActor in Globals.Actors.Values)
|
|
{
|
|
if (netActor.Owner == ID)
|
|
{
|
|
netActor.SetOwnership(0);
|
|
}
|
|
netActor.KnownPlayers.Remove(this);
|
|
}
|
|
}
|
|
|
|
public void Airburst()
|
|
{
|
|
Vector3 position = VacTransform.position;
|
|
foreach (Collider collider in Physics.OverlapSphere(position, airBurstRadius))
|
|
{
|
|
if (collider && collider.GetComponent<Rigidbody>() != null && collider.gameObject != gameObject)
|
|
{
|
|
Identifiable component = collider.gameObject.GetComponent<Identifiable>();
|
|
if (component != null && (Identifiable.IsSlime(component.id) || component.id == Identifiable.Id.PLAYER) && Vector3.Dot(VacTransform.transform.up, collider.gameObject.transform.position - VacTransform.transform.position) > 0f)
|
|
{
|
|
Rigidbody component2 = collider.GetComponent<Rigidbody>();
|
|
if (component.id == Identifiable.Id.PLAYER)
|
|
{
|
|
vp_FPController playerComponent = collider.gameObject.GetComponent<vp_FPController>();
|
|
Vector3 a = component2.position - position;
|
|
float magnitude = a.magnitude;
|
|
a.Normalize();
|
|
float num = 1f - Mathf.Max(2f, magnitude) / airBurstRadius;
|
|
var force = a * ((airBurstPower * component2.mass) * num * num);
|
|
playerComponent.AddForce(force * 0.001f);
|
|
}
|
|
else
|
|
{
|
|
PhysicsUtil.SoftExplosionForce(airBurstPower * component2.mass, position, airBurstRadius, component2);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
GameObject.Instantiate(AirBurstFX, Vector3.zero, Quaternion.identity).transform.SetParent(VacTransform, false);
|
|
}
|
|
|
|
internal void SetVacAudio(bool active)
|
|
{
|
|
if (active && !m_VacAudioActive)
|
|
{
|
|
m_VacAudio.Cue = m_VacStartCue;
|
|
m_VacAudio.Play();
|
|
m_VacAudio.Cue = m_VacRunCue;
|
|
m_VacAudio.Play();
|
|
}
|
|
else if (!active && m_VacAudioActive)
|
|
{
|
|
m_VacAudio.Cue = m_VacEndCue;
|
|
m_VacAudio.Play();
|
|
}
|
|
m_VacAudioActive = active;
|
|
}
|
|
|
|
internal void SetJetpackAudio(bool active)
|
|
{
|
|
if (active && !m_JetpackAudioActive)
|
|
{
|
|
m_JetpackAudio.Cue = m_JetpackStartCue;
|
|
m_JetpackAudio.Play();
|
|
m_JetpackAudio.Cue = m_JetpackRunCue;
|
|
m_JetpackAudio.Play();
|
|
}
|
|
else if (!active && m_JetpackAudioActive)
|
|
{
|
|
m_JetpackAudio.Cue = m_JetpackEndCue;
|
|
m_JetpackAudio.Play();
|
|
}
|
|
m_JetpackAudioActive = active;
|
|
}
|
|
|
|
internal void ToggleThirdPerson(bool enable)
|
|
{
|
|
foreach (var renderer in SRSingleton<SceneContext>.Instance.Player.GetComponentsInChildren<Renderer>())
|
|
{
|
|
renderer.enabled = !enable;
|
|
}
|
|
foreach (var renderer in PlayerObj.GetComponentsInChildren<Renderer>())
|
|
{
|
|
renderer.enabled = enable;
|
|
}
|
|
m_Camera.SetActive(enable);
|
|
}
|
|
|
|
public override bool Equals(object other)
|
|
{
|
|
if (other == null || !(other is NetworkPlayer)) return false;
|
|
|
|
return ((NetworkPlayer)other).ID == ID;
|
|
}
|
|
|
|
public override int GetHashCode()
|
|
{
|
|
return ID.GetHashCode();
|
|
}
|
|
|
|
public override string ToString()
|
|
{
|
|
return $"{Username} ({ID})";
|
|
}
|
|
}
|
|
}
|