diff --git a/QSB.sln b/QSB.sln
index 2dbaea7e..2461d363 100644
--- a/QSB.sln
+++ b/QSB.sln
@@ -29,7 +29,9 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Mirror", "Mirror", "{851AB4
Mirror\Telepathy.dll = Mirror\Telepathy.dll
EndProjectSection
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "APITestMod", "APITestMod\APITestMod.csproj", "{0A10143E-6C00-409B-B3A5-C54C1B01599D}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "APITestMod", "APITestMod\APITestMod.csproj", "{0A10143E-6C00-409B-B3A5-C54C1B01599D}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "QSBPatcher", "QSBPatcher\QSBPatcher.csproj", "{CA4CBA2B-54D5-4C4B-9B51-957BC6D77D6B}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -57,6 +59,10 @@ Global
{0A10143E-6C00-409B-B3A5-C54C1B01599D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0A10143E-6C00-409B-B3A5-C54C1B01599D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0A10143E-6C00-409B-B3A5-C54C1B01599D}.Release|Any CPU.Build.0 = Release|Any CPU
+ {CA4CBA2B-54D5-4C4B-9B51-957BC6D77D6B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {CA4CBA2B-54D5-4C4B-9B51-957BC6D77D6B}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {CA4CBA2B-54D5-4C4B-9B51-957BC6D77D6B}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {CA4CBA2B-54D5-4C4B-9B51-957BC6D77D6B}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/QSB.sln.DotSettings b/QSB.sln.DotSettings
index 5514b0fa..c7e98ffe 100644
--- a/QSB.sln.DotSettings
+++ b/QSB.sln.DotSettings
@@ -10,6 +10,7 @@
NEXT_LINE_SHIFTED_2
API
ID
+ OW
QSB
UWP
True
diff --git a/QSB/API/IQSBAPI.cs b/QSB/API/IQSBAPI.cs
index eadb98ce..a0e84030 100644
--- a/QSB/API/IQSBAPI.cs
+++ b/QSB/API/IQSBAPI.cs
@@ -37,6 +37,19 @@ public interface IQSBAPI
/// The ID of the player you want the name of.
string GetPlayerName(uint playerID);
+ ///
+ /// Returns the position of a given player.
+ /// This position is in world space, where (0, 0, 0) is roughly where the local player is located.
+ ///
+ /// The ID of the player you want the position of.
+ Vector3 GetPlayerPosition(uint playerID);
+
+ ///
+ /// Returns true if a given player has fully loaded into the game. If the local player is still loading into the game, this will return false.
+ ///
+ /// The ID of the player.
+ bool GetPlayerReady(uint playerID);
+
///
/// Returns the list of IDs of all connected players.
///
diff --git a/QSB/API/QSBAPI.cs b/QSB/API/QSBAPI.cs
index 1430e142..c979c92f 100644
--- a/QSB/API/QSBAPI.cs
+++ b/QSB/API/QSBAPI.cs
@@ -25,6 +25,13 @@ public class QSBAPI : IQSBAPI
public uint GetLocalPlayerID() => QSBPlayerManager.LocalPlayerId;
public string GetPlayerName(uint playerId) => QSBPlayerManager.GetPlayer(playerId).Name;
+ public Vector3 GetPlayerPosition(uint playerId) => QSBPlayerManager.GetPlayer(playerId).Body.transform.position;
+
+ public bool GetPlayerReady(uint playerId)
+ {
+ var player = QSBPlayerManager.GetPlayer(playerId);
+ return player.IsReady && player.Body != null;
+ }
public uint[] GetPlayerIDs() => QSBPlayerManager.PlayerList.Select(x => x.PlayerId).ToArray();
public UnityEvent OnPlayerJoin() => QSBAPIEvents.OnPlayerJoinEvent;
diff --git a/QSB/Animation/Player/HelmetAnimator.cs b/QSB/Animation/Player/HelmetAnimator.cs
new file mode 100644
index 00000000..97664802
--- /dev/null
+++ b/QSB/Animation/Player/HelmetAnimator.cs
@@ -0,0 +1,95 @@
+using OWML.Common;
+using QSB.PlayerBodySetup.Remote;
+using QSB.Utility;
+using UnityEngine;
+
+namespace QSB.Animation.Player;
+
+[UsedInUnityProject]
+public class HelmetAnimator : MonoBehaviour
+{
+ public Transform FakeHelmet;
+ public Transform FakeHead;
+ public GameObject SuitGroup;
+
+ private QSBDitheringAnimator _fakeHelmetDitheringAnimator;
+
+ private const float ANIM_TIME = 0.5f;
+ private bool _isPuttingOnHelmet;
+ private bool _isTakingOffHelmet;
+
+ public void Start()
+ {
+ _fakeHelmetDitheringAnimator = FakeHelmet.GetComponent();
+
+ FakeHead.gameObject.SetActive(false);
+ }
+
+ public void RemoveHelmet()
+ {
+ if (!SuitGroup.activeSelf)
+ {
+ DebugLog.DebugWrite($"Trying to remove helmet when player is not wearing suit!", MessageType.Error);
+ return;
+ }
+
+ _fakeHelmetDitheringAnimator.SetVisible(true);
+ FakeHelmet.gameObject.SetActive(true);
+ FakeHead.gameObject.SetActive(true);
+ _fakeHelmetDitheringAnimator.SetVisible(false, ANIM_TIME);
+ _isTakingOffHelmet = true;
+ }
+
+ public void PutOnHelmet()
+ {
+ if (!SuitGroup.activeSelf)
+ {
+ DebugLog.DebugWrite($"Trying to put on helmet when player is not wearing suit!", MessageType.Error);
+ return;
+ }
+
+ _fakeHelmetDitheringAnimator.SetVisible(false);
+ FakeHead.gameObject.SetActive(true);
+ FakeHelmet.gameObject.SetActive(true);
+ _fakeHelmetDitheringAnimator.SetVisible(true, ANIM_TIME);
+ _isPuttingOnHelmet = true;
+ }
+
+ public void SetHelmetInstant(bool helmetOn)
+ {
+ if (helmetOn)
+ {
+ FakeHelmet.gameObject.SetActive(true);
+ _fakeHelmetDitheringAnimator.SetVisible(true);
+ FakeHead.gameObject.SetActive(false);
+ }
+ else
+ {
+ _fakeHelmetDitheringAnimator.SetVisible(false);
+ FakeHelmet.gameObject.SetActive(false);
+ if (!SuitGroup.activeSelf)
+ {
+ FakeHead.gameObject.SetActive(false);
+ }
+ }
+ }
+
+ private void Update()
+ {
+ if (_isPuttingOnHelmet && _fakeHelmetDitheringAnimator.FullyVisible)
+ {
+ _isPuttingOnHelmet = false;
+ FakeHead.gameObject.SetActive(false);
+ }
+
+ if (_isTakingOffHelmet && _fakeHelmetDitheringAnimator.FullyInvisible)
+ {
+ FakeHelmet.gameObject.SetActive(false);
+
+ if (!SuitGroup.activeSelf)
+ {
+ FakeHead.gameObject.SetActive(false);
+ }
+ }
+ }
+}
diff --git a/QSB/Animation/Player/Messages/PlayerHelmetMessage.cs b/QSB/Animation/Player/Messages/PlayerHelmetMessage.cs
new file mode 100644
index 00000000..d19526b1
--- /dev/null
+++ b/QSB/Animation/Player/Messages/PlayerHelmetMessage.cs
@@ -0,0 +1,43 @@
+using QSB.Messaging;
+using QSB.Player.TransformSync;
+using QSB.WorldSync;
+using QSB.Player;
+
+namespace QSB.Animation.Player.Messages;
+
+public class PlayerHelmetMessage : QSBMessage
+{
+ static PlayerHelmetMessage()
+ {
+ GlobalMessenger.AddListener(OWEvents.PutOnHelmet, () => Handle(true));
+ GlobalMessenger.AddListener(OWEvents.RemoveHelmet, () => Handle(false));
+ }
+
+ private static void Handle(bool on)
+ {
+ if (PlayerTransformSync.LocalInstance)
+ {
+ new PlayerHelmetMessage(on).Send();
+ }
+ }
+
+ public PlayerHelmetMessage(bool on) : base(on) { }
+
+ public override bool ShouldReceive => QSBWorldSync.AllObjectsReady;
+
+ public override void OnReceiveRemote()
+ {
+ var player = QSBPlayerManager.GetPlayer(From);
+ var animator = player.HelmetAnimator;
+ if (Data)
+ {
+ animator.PutOnHelmet();
+ player.AudioController.PlayWearHelmet();
+ }
+ else
+ {
+ animator.RemoveHelmet();
+ player.AudioController.PlayRemoveHelmet();
+ }
+ }
+}
diff --git a/QSB/AssetBundles/qsb_network b/QSB/AssetBundles/qsb_network
index 4da4e11c..701745f1 100644
Binary files a/QSB/AssetBundles/qsb_network and b/QSB/AssetBundles/qsb_network differ
diff --git a/QSB/AssetBundles/qsb_network_big b/QSB/AssetBundles/qsb_network_big
index eb8854ae..ab53bfb6 100644
Binary files a/QSB/AssetBundles/qsb_network_big and b/QSB/AssetBundles/qsb_network_big differ
diff --git a/QSB/AssetBundles/qsb_skins b/QSB/AssetBundles/qsb_skins
new file mode 100644
index 00000000..7d584626
Binary files /dev/null and b/QSB/AssetBundles/qsb_skins differ
diff --git a/QSB/Audio/QSBPlayerAudioController.cs b/QSB/Audio/QSBPlayerAudioController.cs
index 0c50def9..dc3573fc 100644
--- a/QSB/Audio/QSBPlayerAudioController.cs
+++ b/QSB/Audio/QSBPlayerAudioController.cs
@@ -12,6 +12,7 @@ public class QSBPlayerAudioController : MonoBehaviour
public OWAudioSource _damageAudioSource;
private AudioManager _audioManager;
+ private float _playWearHelmetTime;
public void Start()
{
@@ -30,6 +31,15 @@ public class QSBPlayerAudioController : MonoBehaviour
damageAudio.SetActive(true);
}
+ private void Update()
+ {
+ if (Time.time > this._playWearHelmetTime)
+ {
+ enabled = false;
+ PlayOneShot(global::AudioType.PlayerSuitWearHelmet);
+ }
+ }
+
public void PlayEquipTool()
=> _oneShotExternalSource?.PlayOneShot(AudioType.ToolTranslatorEquip);
@@ -48,6 +58,18 @@ public class QSBPlayerAudioController : MonoBehaviour
public void PlayRemoveSuit()
=> PlayOneShot(AudioType.PlayerSuitRemoveSuit);
+ public void PlayRemoveHelmet()
+ {
+ enabled = false;
+ PlayOneShot(AudioType.PlayerSuitRemoveHelmet);
+ }
+
+ public void PlayWearHelmet()
+ {
+ enabled = true;
+ _playWearHelmetTime = Time.time + 0.4f;
+ }
+
public void PlayOneShot(AudioType audioType, float pitch = 1f, float volume = 1f)
{
if (_oneShotExternalSource)
diff --git a/QSB/BodyCustomization/BodyCustomizer.cs b/QSB/BodyCustomization/BodyCustomizer.cs
index b08d01f8..83e191ca 100644
--- a/QSB/BodyCustomization/BodyCustomizer.cs
+++ b/QSB/BodyCustomization/BodyCustomizer.cs
@@ -1,4 +1,5 @@
using System.Collections.Generic;
+using OWML.Common;
using QSB.Utility;
using UnityEngine;
@@ -9,13 +10,26 @@ public class BodyCustomizer : MonoBehaviour, IAddComponentOnStart
private Dictionary skinMap = new();
private Dictionary jetpackMap = new();
+ public AssetBundle SkinsBundle { get; private set; }
+
public static BodyCustomizer Instance { get; private set; }
private void Start()
{
Instance = this;
+ }
- skinMap.Add("Default", (QSBCore.BigBundle.LoadAsset("Assets/GameAssets/Texture2D/Traveller_HEA_Player_Skin_d.png"), QSBCore.BigBundle.LoadAsset("Assets/GameAssets/Texture2D/Traveller_HEA_Player_Skin_n.png")));
+ public void OnBundleLoaded(AssetBundle bundle)
+ {
+ SkinsBundle = bundle;
+ LoadAssets();
+ }
+
+ private void LoadAssets()
+ {
+ DebugLog.DebugWrite($"Loading skin assets...", MessageType.Info);
+
+ skinMap.Add("Default", (SkinsBundle.LoadAsset("Assets/GameAssets/Texture2D/Traveller_HEA_Player_Skin_d.png"), SkinsBundle.LoadAsset("Assets/GameAssets/Texture2D/Traveller_HEA_Player_Skin_n.png")));
skinMap.Add("Type 1", LoadSkin("Type 1"));
skinMap.Add("Type 2", LoadSkin("Type 2"));
skinMap.Add("Type 3", LoadSkin("Type 3"));
@@ -34,7 +48,7 @@ public class BodyCustomizer : MonoBehaviour, IAddComponentOnStart
skinMap.Add("Type 16", LoadSkin("Type 16"));
skinMap.Add("Type 17", LoadSkin("Type 17"));
- jetpackMap.Add("Orange", QSBCore.BigBundle.LoadAsset("Assets/GameAssets/Texture2D/Props_HEA_Jetpack_d.png"));
+ jetpackMap.Add("Orange", SkinsBundle.LoadAsset("Assets/GameAssets/Texture2D/Props_HEA_Jetpack_d.png"));
jetpackMap.Add("Yellow", LoadJetpack("yellow"));
jetpackMap.Add("Red", LoadJetpack("red"));
jetpackMap.Add("Pink", LoadJetpack("pink"));
@@ -48,22 +62,26 @@ public class BodyCustomizer : MonoBehaviour, IAddComponentOnStart
private (Texture2D d, Texture2D n) LoadSkin(string skinName)
{
var number = skinName.Replace($"Type ", "");
- return (QSBCore.BigBundle.LoadAsset($"Assets/GameAssets/Texture2D/Skin Variations/{number}d.png"), QSBCore.BigBundle.LoadAsset($"Assets/GameAssets/Texture2D/Skin Variations/{number}n.png"));
+ return (SkinsBundle.LoadAsset($"Assets/GameAssets/Texture2D/Skin Variations/{number}d.png"), SkinsBundle.LoadAsset($"Assets/GameAssets/Texture2D/Skin Variations/{number}n.png"));
}
private Texture2D LoadJetpack(string jetpackName)
{
- return QSBCore.BigBundle.LoadAsset($"Assets/GameAssets/Texture2D/Jetpack Variations/{jetpackName}.png");
+ return SkinsBundle.LoadAsset($"Assets/GameAssets/Texture2D/Jetpack Variations/{jetpackName}.png");
}
- public void CustomizeRemoteBody(GameObject REMOTE_Traveller_HEA_Player_v2, string skinType, string jetpackType)
+ public void CustomizeRemoteBody(GameObject REMOTE_Traveller_HEA_Player_v2, GameObject fakeHead, string skinType, string jetpackType)
{
var headMesh = REMOTE_Traveller_HEA_Player_v2.transform.Find("player_mesh_noSuit:Traveller_HEA_Player/player_mesh_noSuit:Player_Head");
- var skinMaterial = headMesh.GetComponent().material;
+ var skinMaterial = headMesh.GetComponent().material;
skinMaterial.SetTexture("_MainTex", skinMap[skinType].albedo);
skinMaterial.SetTexture("_BumpMap", skinMap[skinType].normal);
+ var fakeHeadMaterial = fakeHead.GetComponent().material;
+ fakeHeadMaterial.SetTexture("_MainTex", skinMap[skinType].albedo);
+ fakeHeadMaterial.SetTexture("_BumpMap", skinMap[skinType].normal);
+
var jetpackMesh = REMOTE_Traveller_HEA_Player_v2.transform.Find("Traveller_Mesh_v01:Traveller_Geo/Traveller_Mesh_v01:Props_HEA_Jetpack");
var jetpackMaterial = jetpackMesh.GetComponent().material;
diff --git a/QSB/Messaging/OWEvents.cs b/QSB/Messaging/OWEvents.cs
index fa8f0f53..3eadf9d0 100644
--- a/QSB/Messaging/OWEvents.cs
+++ b/QSB/Messaging/OWEvents.cs
@@ -41,4 +41,6 @@ public static class OWEvents
public const string ProbeExitQuantumMoon = nameof(ProbeExitQuantumMoon);
public const string EnterCloak = nameof(EnterCloak);
public const string ExitCloak = nameof(ExitCloak);
+ public const string PutOnHelmet = nameof(PutOnHelmet);
+ public const string RemoveHelmet = nameof(RemoveHelmet);
}
\ No newline at end of file
diff --git a/QSB/Patches/QSBPatchManager.cs b/QSB/Patches/QSBPatchManager.cs
index 969876bd..6a994427 100644
--- a/QSB/Patches/QSBPatchManager.cs
+++ b/QSB/Patches/QSBPatchManager.cs
@@ -87,10 +87,10 @@ public static class QSBPatchManager
}
OnPatchType?.SafeInvoke(type);
- DebugLog.DebugWrite($"Patch block {Enum.GetName(typeof(QSBPatchTypes), type)}", MessageType.Info);
+ //DebugLog.DebugWrite($"Patch block {Enum.GetName(typeof(QSBPatchTypes), type)}", MessageType.Info);
foreach (var patch in _patchList.Where(x => x.Type == type && x.PatchVendor.HasFlag(QSBCore.GameVendor)))
{
- DebugLog.DebugWrite($" - Patching in {patch.GetType().Name}", MessageType.Info);
+ //DebugLog.DebugWrite($" - Patching in {patch.GetType().Name}", MessageType.Info);
try
{
patch.DoPatches(TypeToInstance[type]);
diff --git a/QSB/Player/Messages/PlayerInformationMessage.cs b/QSB/Player/Messages/PlayerInformationMessage.cs
index 8894353d..10f9a078 100644
--- a/QSB/Player/Messages/PlayerInformationMessage.cs
+++ b/QSB/Player/Messages/PlayerInformationMessage.cs
@@ -13,6 +13,7 @@ public class PlayerInformationMessage : QSBMessage
private bool IsReady;
private bool FlashlightActive;
private bool SuitedUp;
+ private bool HelmetOn;
private bool LocalProbeLauncherEquipped;
private bool SignalscopeEquipped;
private bool TranslatorEquipped;
@@ -31,6 +32,7 @@ public class PlayerInformationMessage : QSBMessage
IsReady = player.IsReady;
FlashlightActive = player.FlashlightActive;
SuitedUp = player.SuitedUp;
+ HelmetOn = Locator.GetPlayerSuit() != null && Locator.GetPlayerSuit().IsWearingHelmet();
LocalProbeLauncherEquipped = player.LocalProbeLauncherEquipped;
SignalscopeEquipped = player.SignalscopeEquipped;
TranslatorEquipped = player.TranslatorEquipped;
@@ -50,6 +52,7 @@ public class PlayerInformationMessage : QSBMessage
writer.Write(IsReady);
writer.Write(FlashlightActive);
writer.Write(SuitedUp);
+ writer.Write(HelmetOn);
writer.Write(LocalProbeLauncherEquipped);
writer.Write(SignalscopeEquipped);
writer.Write(TranslatorEquipped);
@@ -69,6 +72,7 @@ public class PlayerInformationMessage : QSBMessage
IsReady = reader.Read();
FlashlightActive = reader.Read();
SuitedUp = reader.Read();
+ HelmetOn = reader.Read();
LocalProbeLauncherEquipped = reader.Read();
SignalscopeEquipped = reader.Read();
TranslatorEquipped = reader.Read();
@@ -91,22 +95,23 @@ public class PlayerInformationMessage : QSBMessage
player.IsReady = IsReady;
player.FlashlightActive = FlashlightActive;
player.SuitedUp = SuitedUp;
+
player.LocalProbeLauncherEquipped = LocalProbeLauncherEquipped;
player.SignalscopeEquipped = SignalscopeEquipped;
player.TranslatorEquipped = TranslatorEquipped;
player.ProbeActive = ProbeActive;
player.IsInShip = IsInShip;
- if (QSBPlayerManager.LocalPlayer.IsReady && player.IsReady)
+
+ Delay.RunWhen(() => player.IsReady && QSBPlayerManager.LocalPlayer.IsReady, () =>
{
player.UpdateObjectsFromStates();
+ player.HelmetAnimator.SetHelmetInstant(HelmetOn);
var REMOTE_Traveller_HEA_Player_v2 = player.Body.transform.Find("REMOTE_Traveller_HEA_Player_v2");
- BodyCustomization.BodyCustomizer.Instance.CustomizeRemoteBody(REMOTE_Traveller_HEA_Player_v2.gameObject, SkinType, JetpackType);
- }
+ BodyCustomization.BodyCustomizer.Instance.CustomizeRemoteBody(REMOTE_Traveller_HEA_Player_v2.gameObject, player.HelmetAnimator.FakeHead.gameObject, SkinType, JetpackType);
- Delay.RunWhen(
- () => player.Camera != null,
- () => player.Camera.fieldOfView = FieldOfView);
+ player.Camera.fieldOfView = FieldOfView;
+ });
player.State = ClientState;
diff --git a/QSB/Player/PlayerInfoParts/Animation.cs b/QSB/Player/PlayerInfoParts/Animation.cs
index 30c43023..e7c34145 100644
--- a/QSB/Player/PlayerInfoParts/Animation.cs
+++ b/QSB/Player/PlayerInfoParts/Animation.cs
@@ -12,4 +12,5 @@ public partial class PlayerInfo
internal QSBDitheringAnimator _ditheringAnimator;
public DreamWorldSpawnAnimator DreamWorldSpawnAnimator { get; set; }
public RemotePlayerFluidDetector FluidDetector { get; set; }
+ public HelmetAnimator HelmetAnimator { get; set; }
}
diff --git a/QSB/Player/TransformSync/PlayerTransformSync.cs b/QSB/Player/TransformSync/PlayerTransformSync.cs
index 2611fef9..813ea05e 100644
--- a/QSB/Player/TransformSync/PlayerTransformSync.cs
+++ b/QSB/Player/TransformSync/PlayerTransformSync.cs
@@ -35,6 +35,12 @@ public class PlayerTransformSync : SectoredTransformSync
{
var player = new PlayerInfo(this);
QSBPlayerManager.PlayerList.SafeAdd(player);
+
+ if (isLocalPlayer)
+ {
+ LocalInstance = this;
+ }
+
base.OnStartClient();
QSBPatch.Remote = !isLocalPlayer;
QSBPlayerManager.OnAddPlayer?.SafeInvoke(Player);
diff --git a/QSB/PlayerBodySetup/Remote/QSBDitheringAnimator.cs b/QSB/PlayerBodySetup/Remote/QSBDitheringAnimator.cs
index 6889e7fa..40933fda 100644
--- a/QSB/PlayerBodySetup/Remote/QSBDitheringAnimator.cs
+++ b/QSB/PlayerBodySetup/Remote/QSBDitheringAnimator.cs
@@ -59,6 +59,10 @@ public class QSBDitheringAnimator : MonoBehaviour
private void UpdateRenderers()
{
+ _renderers ??= GetComponentsInChildren(true)
+ .Select(x => (x.gameObject.GetAddComponent(), x.shadowCastingMode != ShadowCastingMode.Off))
+ .ToArray();
+
foreach (var (renderer, updateShadows) in _renderers)
{
if (renderer == null)
diff --git a/QSB/PlayerBodySetup/Remote/RemotePlayerCreation.cs b/QSB/PlayerBodySetup/Remote/RemotePlayerCreation.cs
index 76233c86..21b894eb 100644
--- a/QSB/PlayerBodySetup/Remote/RemotePlayerCreation.cs
+++ b/QSB/PlayerBodySetup/Remote/RemotePlayerCreation.cs
@@ -1,4 +1,5 @@
-using QSB.Audio;
+using QSB.Animation.Player;
+using QSB.Audio;
using QSB.EchoesOfTheEye.LightSensorSync;
using QSB.Player;
using QSB.RoastingSync;
@@ -54,6 +55,7 @@ public static class RemotePlayerCreation
player.ThrusterLightTracker = player.Body.GetComponentInChildren();
player.FluidDetector = REMOTE_PlayerDetector.GetComponent();
player.RulesetDetector = REMOTE_PlayerDetector.GetComponent();
+ player.HelmetAnimator = REMOTE_Traveller_HEA_Player_v2.GetComponent();
player.AnimationSync.InitRemote(REMOTE_Traveller_HEA_Player_v2.transform);
diff --git a/QSB/QSB.csproj b/QSB/QSB.csproj
index de9fdf4c..af2e6277 100644
--- a/QSB/QSB.csproj
+++ b/QSB/QSB.csproj
@@ -79,6 +79,7 @@
+
diff --git a/QSB/QSBCore.cs b/QSB/QSBCore.cs
index a8bdb40f..ad708b07 100644
--- a/QSB/QSBCore.cs
+++ b/QSB/QSBCore.cs
@@ -14,10 +14,12 @@ using QSB.WorldSync;
using Steamworks;
using System;
using System.Collections.Generic;
+using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using QSB.API;
+using QSB.BodyCustomization;
using QSB.Player.Messages;
using UnityEngine;
using UnityEngine.InputSystem;
@@ -54,7 +56,6 @@ public class QSBCore : ModBehaviour
public static AssetBundle ConversationAssetBundle { get; private set; }
public static AssetBundle DebugAssetBundle { get; private set; }
public static AssetBundle HUDAssetBundle { get; private set; }
- public static AssetBundle BigBundle { get; private set; }
public static bool IsHost => NetworkServer.active;
public static bool IsInMultiplayer;
public static string QSBVersion => Helper.Manifest.Version;
@@ -78,8 +79,6 @@ public class QSBCore : ModBehaviour
public static IMenuAPI MenuApi { get; private set; }
public static DebugSettings DebugSettings { get; private set; } = new();
- public const string NEW_HORIZONS = "xen.NewHorizons";
- public const string NEW_HORIZONS_COMPAT = "xen.NHQSBCompat";
public static readonly string[] IncompatibleMods =
{
@@ -92,6 +91,8 @@ public class QSBCore : ModBehaviour
"PacificEngine.OW_Randomizer",
};
+ public static event Action OnSkinsBundleLoaded;
+
public override object GetApi() => new QSBAPI();
private static void DetermineGameVendor()
@@ -161,7 +162,7 @@ public class QSBCore : ModBehaviour
if (!SteamAPI.Init())
{
- DebugLog.ToConsole($"FATAL - SteamAPI.Init() failed. Refer to Valve's documentation.", MessageType.Fatal);
+ DebugLog.ToConsole($"FATAL - SteamAPI.Init() failed. Do you have Steam open, and are you logged in?", MessageType.Fatal);
return;
}
@@ -258,18 +259,15 @@ public class QSBCore : ModBehaviour
MenuApi = ModHelper.Interaction.TryGetModApi(ModHelper.Manifest.Dependencies[0]);
- DebugLog.DebugWrite("loading qsb_network_big bundle", MessageType.Info);
- var path = Path.Combine(ModHelper.Manifest.ModFolderPath, "AssetBundles/qsb_network_big");
- var request = AssetBundle.LoadFromFileAsync(path);
- request.completed += _ => DebugLog.DebugWrite("qsb_network_big bundle loaded", MessageType.Success);
- BigBundle = request.assetBundle;
+ LoadBundleAsync("qsb_network_big");
+ LoadBundleAsync("qsb_skins", request => BodyCustomizer.Instance.OnBundleLoaded(request.assetBundle));
- NetworkAssetBundle = Helper.Assets.LoadBundle("AssetBundles/qsb_network");
- ConversationAssetBundle = Helper.Assets.LoadBundle("AssetBundles/qsb_conversation");
- DebugAssetBundle = Helper.Assets.LoadBundle("AssetBundles/qsb_debug");
- HUDAssetBundle = Helper.Assets.LoadBundle("AssetBundles/qsb_hud");
+ NetworkAssetBundle = LoadBundle("qsb_network");
+ ConversationAssetBundle = LoadBundle("qsb_conversation");
+ DebugAssetBundle = LoadBundle("qsb_debug");
+ HUDAssetBundle = LoadBundle("qsb_hud");
- if (NetworkAssetBundle == null || ConversationAssetBundle == null || DebugAssetBundle == null)
+ if (NetworkAssetBundle == null || ConversationAssetBundle == null || DebugAssetBundle == null || HUDAssetBundle == null)
{
DebugLog.ToConsole($"FATAL - An assetbundle is missing! Re-install mod or contact devs.", MessageType.Fatal);
return;
@@ -287,6 +285,31 @@ public class QSBCore : ModBehaviour
QSBPatchManager.OnUnpatchType += OnUnpatchType;
}
+ private AssetBundle LoadBundle(string name)
+ {
+ var timer = new Stopwatch();
+ timer.Start();
+ var ret = Helper.Assets.LoadBundle($"AssetBundles/{name}");
+ timer.Stop();
+ DebugLog.ToConsole($"Bundle {name} loaded in {timer.ElapsedMilliseconds} ms!", MessageType.Success);
+ return ret;
+ }
+
+ private void LoadBundleAsync(string bundleName, Action runOnLoaded = null)
+ {
+ DebugLog.DebugWrite($"Loading {bundleName}...", MessageType.Info);
+ var timer = new Stopwatch();
+ timer.Start();
+ var path = Path.Combine(ModHelper.Manifest.ModFolderPath, $"AssetBundles/{bundleName}");
+ var request = AssetBundle.LoadFromFileAsync(path);
+ request.completed += _ =>
+ {
+ timer.Stop();
+ DebugLog.ToConsole($"Bundle {bundleName} loaded in {timer.ElapsedMilliseconds} ms!", MessageType.Success);
+ runOnLoaded?.Invoke(request);
+ };
+ }
+
private static void OnPatchType(QSBPatchTypes type)
{
if (type == QSBPatchTypes.OnClientConnect)
diff --git a/QSB/manifest.json b/QSB/manifest.json
index 69d74e56..8fdeefd5 100644
--- a/QSB/manifest.json
+++ b/QSB/manifest.json
@@ -12,5 +12,6 @@
"owmlVersion": "2.9.8",
"dependencies": [ "_nebula.MenuFramework", "JohnCorby.VanillaFix" ],
"pathsToPreserve": [ "debugsettings.json" ],
- "requireLatestVersion": true
+ "requireLatestVersion": true,
+ "patcher": "QSBPatcher.exe"
}
diff --git a/QSBPatcher/QSBPatcher.cs b/QSBPatcher/QSBPatcher.cs
new file mode 100644
index 00000000..a324deec
--- /dev/null
+++ b/QSBPatcher/QSBPatcher.cs
@@ -0,0 +1,45 @@
+using System;
+using System.IO;
+
+namespace QSBPatcher;
+
+public static class QSBPatcher
+{
+ public static void Main(string[] args)
+ {
+ var basePath = args.Length > 0 ? args[0] : ".";
+ var gamePath = AppDomain.CurrentDomain.BaseDirectory;
+
+ var steamDLLPath = Path.Combine(basePath, "com.rlabrecque.steamworks.net.dll");
+
+ var managedPath = Path.Combine(gamePath, GetDataPath(gamePath), "Managed");
+
+ File.Copy(steamDLLPath, Path.Combine(managedPath, "com.rlabrecque.steamworks.net.dll"), true);
+ }
+
+ private static string GetDataDirectoryName()
+ {
+ var gamePath = AppDomain.CurrentDomain.BaseDirectory;
+ return $"{GetExecutableName(gamePath)}_Data";
+ }
+
+ private static string GetDataPath(string gamePath)
+ {
+ return Path.Combine(gamePath, $"{GetDataDirectoryName()}");
+ }
+
+ private static string GetExecutableName(string gamePath)
+ {
+ var executableNames = new[] { "Outer Wilds.exe", "OuterWilds.exe" };
+ foreach (var executableName in executableNames)
+ {
+ var executablePath = Path.Combine(gamePath, executableName);
+ if (File.Exists(executablePath))
+ {
+ return Path.GetFileNameWithoutExtension(executablePath);
+ }
+ }
+
+ throw new FileNotFoundException($"Outer Wilds exe file not found in {gamePath}");
+ }
+}
diff --git a/QSBPatcher/QSBPatcher.csproj b/QSBPatcher/QSBPatcher.csproj
new file mode 100644
index 00000000..7b6830bd
--- /dev/null
+++ b/QSBPatcher/QSBPatcher.csproj
@@ -0,0 +1,12 @@
+
+
+ Exe
+ QSB Patcher
+ QSB Patcher
+ QSB Patcher
+ Copies steamworks into the game for non-steam vendors
+ William Corby, Henry Pointer
+ William Corby, Henry Pointer
+ Copyright © William Corby, Henry Pointer 2022-2023
+
+
diff --git a/README.md b/README.md
index c3f7b5db..56324613 100644
--- a/README.md
+++ b/README.md
@@ -34,6 +34,7 @@ Spoilers within!
#### Connecting to a server
+- Make sure to have Steam open and logged in.
- On the title screen, click the option `CONNECT TO MULTIPLAYER`.
- Enter the Steam ID of the person you are trying to connect to.
- If "Use KCP Transport" is enabled, enter the public IP address of the person instead.
@@ -41,6 +42,7 @@ Spoilers within!
#### Hosting a server
+- Make sure to have Steam open and logged in.
- On the title screen, click the option `OPEN TO MULTIPLAYER`.
- Share your Steam ID with the people who want to connect.
- If "Use KCP Transport" is enabled, share your public IP address instead. This can be found on websites like https://www.whatismyip.com/.
diff --git a/SteamRerouter/ExeSide/Program.cs b/SteamRerouter/ExeSide/Program.cs
index 4f928714..721d5379 100644
--- a/SteamRerouter/ExeSide/Program.cs
+++ b/SteamRerouter/ExeSide/Program.cs
@@ -53,7 +53,7 @@ public static class Program
if (!SteamAPI.Init())
{
- LogError($"FATAL - SteamAPI.Init() failed. Refer to Valve's documentation.");
+ LogError($"FATAL - SteamAPI.Init() failed. Do you have Steam open, and are you logged in?");
return -1;
}