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; }