Merge pull request #678 from qsb-dev/dev

Version 1.1.0
This commit is contained in:
Will Corby 2024-04-25 14:11:46 -07:00 committed by GitHub
commit d37787b6a0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
51 changed files with 477 additions and 135 deletions

View File

@ -9,8 +9,8 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="OuterWildsGameLibs" Version="1.1.13.457" IncludeAssets="compile" />
<PackageReference Include="OWML" Version="2.9.7" IncludeAssets="compile" />
<PackageReference Include="OuterWildsGameLibs" Version="1.1.14.768" IncludeAssets="compile" />
<PackageReference Include="OWML" Version="2.11.1" IncludeAssets="compile" />
</ItemGroup>
<ItemGroup>

View File

@ -4,6 +4,6 @@
"name": "QSB API Test Mod",
"uniqueName": "_nebula.QSBAPITest",
"version": "1.0.0",
"owmlVersion": "2.9.7",
"owmlVersion": "2.11.1",
"dependencies": [ "Raicuparta.QuantumSpaceBuddies", "_nebula.MenuFramework" ]
}

View File

@ -1,8 +1,8 @@
> :warning: Warning! :warning:
Mod development needs a powerful PC!
Unexpected errors and issues may occur when editing networking code.
Running multiple instances of the game can be very taxing on your computer.
We're not responsible if you push your PC too hard.
> [!WARNING]
> Mod development needs a powerful PC!\
> Unexpected errors and issues may occur when editing networking code.\
> Running multiple instances of the game can be very taxing on your computer.\
> We're not responsible if you push your PC too hard.
## Prerequisites
- Visual Studio 2022.
@ -48,6 +48,9 @@ Use the API by copying [the API definition](https://github.com/misternebula/quan
## Debugging
### Debug Actions :
> [!NOTE]
> this list is slightly outdated. it will be updated when debug settings are updated
Press Q + Numpad Enter to toggle debug mode in game (corresponds with the debug setting "debugMode" in the section below).
Hold Q and press :
@ -65,6 +68,9 @@ Hold Q and press :
### Debug Settings :
> [!NOTE]
> this list is slightly outdated because it will be replaced by mod options at some point
Create a file called `debugsettings.json` in the QSB folder.
The template for this file is this :

View File

@ -17,7 +17,7 @@
</None>
</ItemGroup>
<ItemGroup>
<PackageReference Include="OuterWildsGameLibs" Version="1.1.13.457" IncludeAssets="compile" />
<PackageReference Include="OuterWildsGameLibs" Version="1.1.14.768" IncludeAssets="compile" />
<Reference Include="..\Lib\*.dll" />
</ItemGroup>
</Project>

View File

@ -17,8 +17,8 @@
</None>
</ItemGroup>
<ItemGroup>
<PackageReference Include="OuterWildsGameLibs" Version="1.1.13.457" />
<PackageReference Include="OWML" Version="2.9.7" />
<PackageReference Include="OuterWildsGameLibs" Version="1.1.14.768" />
<PackageReference Include="OWML" Version="2.11.1" />
<Reference Include="..\Lib\*.dll" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,64 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using HarmonyLib;
using Mirror;
using NewHorizons;
using QSB;
using QSB.Patches;
using QSB.Player;
using QSB.SaveSync.Messages;
using QSB.Utility;
namespace QSBNH.Patches;
/// <summary>
/// extremely jank way to inject system and NH addons when joining.
/// this should probably be split into its own separate message, but it doesnt really matter :P
///
/// BUG: completely explodes if one person has NH and the other does not
/// </summary>
internal class GameStateMessagePatches : QSBPatch
{
public override QSBPatchTypes Type => QSBPatchTypes.OnModStart;
private static string _initialSystem;
private static int[] _hostAddonHash;
[HarmonyPostfix]
[HarmonyPatch(typeof(GameStateMessage), nameof(GameStateMessage.Serialize))]
public static void GameStateMessage_Serialize(GameStateMessage __instance, NetworkWriter writer)
{
var currentSystem = QSBNH.Instance.NewHorizonsAPI.GetCurrentStarSystem();
writer.Write(currentSystem);
writer.WriteArray(QSBNH.HashAddonsForSystem(currentSystem));
}
[HarmonyPostfix]
[HarmonyPatch(typeof(GameStateMessage), nameof(GameStateMessage.Deserialize))]
public static void GameStateMessage_Deserialize(GameStateMessage __instance, NetworkReader reader)
{
_initialSystem = reader.Read<string>();
_hostAddonHash = reader.ReadArray<int>();
}
[HarmonyPostfix]
[HarmonyPatch(typeof(GameStateMessage), nameof(GameStateMessage.OnReceiveRemote))]
public static void GameStateMessage_OnReceiveRemote()
{
if (QSBCore.IsHost)
{
DebugLog.DebugWrite($"Why is the host being given the initial state info?");
}
else
{
DebugLog.DebugWrite($"Player#{QSBPlayerManager.LocalPlayerId} is being sent to {_initialSystem}");
WarpManager.RemoteChangeStarSystem(_initialSystem, false, false, _hostAddonHash);
}
}
}

View File

@ -0,0 +1,33 @@
using HarmonyLib;
using NewHorizons.External;
using QSB;
using QSB.Patches;
using QSB.SaveSync;
using QSB.Utility;
namespace QSBNH.Patches;
/// <summary>
/// pretends to be a new profile when in multiplayer so NH saves its data to a new place
/// </summary>
public class NewHorizonsDataPatches : QSBPatch
{
public override QSBPatchTypes Type => QSBPatchTypes.OnModStart;
[HarmonyPrefix]
[HarmonyPatch(typeof(NewHorizonsData), nameof(NewHorizonsData.GetProfileName))]
public static bool NewHorizonsData_GetProfileName(out string __result)
{
if (QSBCore.IsInMultiplayer)
{
__result = QSBStandaloneProfileManager.SharedInstance?.currentProfile?.profileName + "_mult";
DebugLog.DebugWrite($"using fake multiplayer profile {__result} for NH");
}
else
{
__result = QSBStandaloneProfileManager.SharedInstance?.currentProfile?.profileName;
}
return false;
}
}

32
QSB-NH/QSB-NH.csproj Normal file
View File

@ -0,0 +1,32 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net48</TargetFramework>
<RootNamespace>QSBNH</RootNamespace>
<ImplicitUsings>enable</ImplicitUsings>
<OutputPath Condition="Exists('$(OwmlDir)')">$(OwmlDir)\Mods\Raicuparta.QuantumSpaceBuddies</OutputPath>
<NoWarn>CS1998;CS0649</NoWarn>
</PropertyGroup>
<ItemGroup>
<Folder Include="lib\" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\QSB\QSB.csproj" />
</ItemGroup>
<ItemGroup>
<Reference Include="Mirror">
<HintPath>..\Lib\Mirror.dll</HintPath>
</Reference>
<Reference Include="NewHorizons">
<HintPath>lib\NewHorizons.dll</HintPath>
<Private>false</Private>
</Reference>
<Reference Include="UniTask">
<HintPath>..\Lib\UniTask.dll</HintPath>
</Reference>
</ItemGroup>
</Project>

59
QSB-NH/QSBNH.cs Normal file
View File

@ -0,0 +1,59 @@
using Mirror;
using NewHorizons;
using OWML.Common;
using OWML.ModHelper;
using QSB;
using QSB.Utility;
using UnityEngine;
namespace QSBNH
{
public class QSBNH : MonoBehaviour
{
public static QSBNH Instance;
public INewHorizons NewHorizonsAPI;
private void Start()
{
Instance = this;
DebugLog.DebugWrite($"Start of QSB-NH compatibility code.", MessageType.Success);
NewHorizonsAPI = QSBCore.Helper.Interaction.TryGetModApi<INewHorizons>("xen.NewHorizons");
}
public static string HashToMod(int hash)
{
foreach (var mod in NewHorizons.Main.MountedAddons)
{
var name = mod.ModHelper.Manifest.UniqueName;
if (name.GetStableHashCode() == hash)
{
return name;
}
}
return null;
}
public static int[] HashAddonsForSystem(string system)
{
if (NewHorizons.Main.BodyDict.TryGetValue(system, out var bodies))
{
var addonHashes = bodies
.Where(x => x.Mod.ModHelper.Manifest.UniqueName != "xen.NewHorizons")
.Select(x => x.Mod.ModHelper.Manifest.UniqueName.GetStableHashCode())
.Distinct();
var nhPlanetHashes = bodies
.Where(x => x.Mod.ModHelper.Manifest.UniqueName == "xen.NewHorizons")
.Select(x => x.Config.name.GetStableHashCode());
return addonHashes.Concat(nhPlanetHashes).ToArray();
}
else
{
return null;
}
}
}
}

View File

@ -0,0 +1,13 @@
using Cysharp.Threading.Tasks;
using QSB.WorldSync;
using QSBNH.QuantumPlanet.WorldObjects;
namespace QSBNH.QuantumPlanet;
public class QuantumPlanetManager : WorldObjectManager
{
public override WorldObjectScene WorldObjectScene => WorldObjectScene.Both;
public override bool DlcOnly => false;
public override async UniTask BuildWorldObjects(OWScene scene, CancellationToken ct) =>
QSBWorldSync.Init<QSBQuantumPlanet, NewHorizons.Components.Quantum.QuantumPlanet>();
}

View File

@ -0,0 +1,7 @@
using QSB.QuantumSync.WorldObjects;
namespace QSBNH.QuantumPlanet.WorldObjects;
public class QSBQuantumPlanet : QSBQuantumObject<NewHorizons.Components.Quantum.QuantumPlanet>
{
}

167
QSB-NH/WarpManager.cs Normal file
View File

@ -0,0 +1,167 @@
using HarmonyLib;
using NewHorizons;
using QSB.Menus;
using QSB.Messaging;
using QSB.Player;
using QSB;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Mirror;
using QSB.Patches;
using QSB.Utility;
namespace QSBNH;
public static class WarpManager
{
internal static bool RemoteWarp = false;
private static void Kick(string reason)
{
DebugLog.DebugWrite(reason);
MenuManager.Instance.OnKicked(reason);
NetworkClient.Disconnect();
}
public static void RemoteChangeStarSystem(string system, bool ship, bool vessel, int[] hostAddonHash)
{
// Flag to not send a message
RemoteWarp = true;
DebugLog.DebugWrite($"Remote request received to go to {system}");
if (!NewHorizons.Main.SystemDict.ContainsKey(system))
{
// If you can't go to that system then you have to be disconnected
Kick($"You don't have the mod installed for {system}");
}
else
{
var localHash = QSBNH.HashAddonsForSystem(system);
if (localHash != hostAddonHash)
{
var missingAddonHashes = hostAddonHash.Except(localHash);
var extraAddonHashes = localHash.Except(hostAddonHash);
if (missingAddonHashes.Count() > 0)
{
Kick($"You are missing {missingAddonHashes.Count()} addon(s) that effect {system}");
return;
}
if (extraAddonHashes.Count() > 0)
{
var extraMods = extraAddonHashes.Select(x => QSBNH.HashToMod(x));
// TODO: Disable these mods for the client and do not kick them
Kick($"You have {extraAddonHashes.Count()} extra addon(s) that effect {system}. Check the logs.");
DebugLog.DebugWrite($"You have mods affecting {system} that the host does not: {string.Join(", ", extraMods)}");
return;
}
}
NewHorizons.Main.Instance.ChangeCurrentStarSystem(system, ship, vessel);
}
}
public class NHWarpMessage : QSBMessage
{
private string _starSystem;
private bool _shipWarp;
private bool _vesselWarp;
public NHWarpMessage(string starSystem, bool shipWarp, bool vesselWarp) : base()
{
_starSystem = starSystem;
_shipWarp = shipWarp;
_vesselWarp = vesselWarp;
}
public override void Serialize(NetworkWriter writer)
{
base.Serialize(writer);
writer.Write(_starSystem);
writer.Write(_shipWarp);
writer.Write(_vesselWarp);
}
public override void Deserialize(NetworkReader reader)
{
base.Deserialize(reader);
_starSystem = reader.Read<string>();
_shipWarp = reader.Read<bool>();
_vesselWarp = reader.Read<bool>();
}
public override void OnReceiveRemote()
{
DebugLog.DebugWrite($"Player#{From} is telling Player#{To} to warp to {_starSystem}");
if (QSBCore.IsHost && !NewHorizons.Main.SystemDict.ContainsKey(_starSystem))
{
// If the host doesn't have that system then we can't
DebugLog.DebugWrite($"The host doesn't have {_starSystem} installed: aborting");
}
else
{
if (QSBCore.IsHost)
{
new NHWarpMessage(_starSystem, _shipWarp, _vesselWarp).Send();
}
RemoteChangeStarSystem(_starSystem, _shipWarp, _vesselWarp, QSBNH.HashAddonsForSystem(_starSystem));
}
}
}
public class NHWarpPatch : QSBPatch
{
public override QSBPatchTypes Type => QSBPatchTypes.OnModStart;
[HarmonyPrefix]
[HarmonyPatch(typeof(NewHorizons.Main), nameof(NewHorizons.Main.ChangeCurrentStarSystem))]
public static bool NewHorizons_ChangeCurrentStarSystem(string newStarSystem, bool warp, bool vessel)
{
if (RemoteWarp)
{
// We're being told to warp so just do it
RemoteWarp = false;
return true;
}
DebugLog.DebugWrite($"Local request received to go to {newStarSystem}");
if (QSBCore.IsHost)
{
// The host will tell all other users to warp
DebugLog.DebugWrite($"Host: Telling others to go to {newStarSystem}");
new NHWarpMessage(newStarSystem, warp, vessel).Send();
// The host can now warp
return true;
}
else
{
// We're a client that has to tell the host to start warping people
DebugLog.DebugWrite($"Client: Telling host to send us to {newStarSystem}");
new NHWarpMessage(newStarSystem, warp, vessel) { To = 0 }.Send();
// We have to wait for the host to get back to us
return false;
}
}
[HarmonyPostfix]
[HarmonyPatch(typeof(NewHorizons.Main), nameof(NewHorizons.Main.ChangeCurrentStarSystem))]
public static void NewHorizons_ChangeCurrentStarSystem(NewHorizons.Main __instance)
{
if (__instance.IsWarpingFromShip)
{
// If QSB doesn't say we're piloting the ship then dont keep them on as the one warping
__instance.GetType().GetProperty(nameof(NewHorizons.Main.IsWarpingFromShip)).SetValue(__instance, QSBPlayerManager.LocalPlayer.FlyingShip);
}
}
}
}

BIN
QSB-NH/lib/NewHorizons.dll Normal file

Binary file not shown.

View File

@ -34,6 +34,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "APITestMod", "APITestMod\AP
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "QSBPatcher", "QSBPatcher\QSBPatcher.csproj", "{CA4CBA2B-54D5-4C4B-9B51-957BC6D77D6B}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "QSB-NH", "QSB-NH\QSB-NH.csproj", "{74F84A39-1C9D-4EF7-889A-485D33B7B324}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -64,6 +66,10 @@ Global
{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
{74F84A39-1C9D-4EF7-889A-485D33B7B324}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{74F84A39-1C9D-4EF7-889A-485D33B7B324}.Debug|Any CPU.Build.0 = Debug|Any CPU
{74F84A39-1C9D-4EF7-889A-485D33B7B324}.Release|Any CPU.ActiveCfg = Release|Any CPU
{74F84A39-1C9D-4EF7-889A-485D33B7B324}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE

View File

@ -9,6 +9,7 @@ using QSB.WorldSync;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using QSB.Utility.Deterministic;
using UnityEngine;
using UnityEngine.UI;
@ -41,8 +42,9 @@ public class ConversationManager : WorldObjectManager
public override async UniTask BuildWorldObjects(OWScene scene, CancellationToken ct)
{
QSBWorldSync.Init<QSBRemoteDialogueTrigger, RemoteDialogueTrigger>();
QSBWorldSync.Init<QSBCharacterDialogueTree, CharacterDialogueTree>();
// dont create worldobjects for NH warp drive stuff
QSBWorldSync.Init<QSBRemoteDialogueTrigger, RemoteDialogueTrigger>(QSBWorldSync.GetUnityObjects<RemoteDialogueTrigger>().Where(x => x.name != "WarpDriveRemoteTrigger").SortDeterministic());
QSBWorldSync.Init<QSBCharacterDialogueTree, CharacterDialogueTree>(QSBWorldSync.GetUnityObjects<CharacterDialogueTree>().Where(x => x.name != "WarpDriveDialogue").SortDeterministic());
}
public uint GetPlayerTalkingToTree(CharacterDialogueTree tree) =>

View File

@ -1,4 +1,5 @@
using QSB.ConversationSync.WorldObjects;
using OWML.Utils;
using QSB.ConversationSync.WorldObjects;
using QSB.Messaging;
using QSB.Player;
using QSB.Utility;

View File

@ -1,4 +1,5 @@
using Cysharp.Threading.Tasks;
using OWML.Utils;
using QSB.ConversationSync.Messages;
using QSB.Messaging;
using QSB.Player;

View File

@ -33,7 +33,6 @@ public class Translation
public string DLCMismatch;
public string GameProgressLimit;
public string AddonMismatch;
public string IncompatibleMod;
public string PlayerJoinedTheGame;
public string PlayerLeftTheGame;
public string PlayerWasKicked;

View File

@ -1,5 +1,6 @@
using Mirror;
using OWML.Common;
using OWML.Utils;
using QSB.Localization;
using QSB.Messaging;
using QSB.Player.TransformSync;
@ -646,6 +647,7 @@ public class MenuManager : MonoBehaviour, IAddComponentOnStart
LoadGame(PlayerData.GetWarpedToTheEye());
// wait until scene load and then wait until Start has ran
// why is this done? GameStateMessage etc works on title screen since nonhost has to deal with that
Delay.RunWhen(() => TimeLoop._initialized, QSBNetworkManager.singleton.StartHost);
};
@ -657,6 +659,7 @@ public class MenuManager : MonoBehaviour, IAddComponentOnStart
{
LoadGame(PlayerData.GetWarpedToTheEye());
// wait until scene load and then wait until Start has ran
// why is this done? GameStateMessage etc works on title screen since nonhost has to deal with that
Delay.RunWhen(() => TimeLoop._initialized, QSBNetworkManager.singleton.StartHost);
}
}

View File

@ -1,6 +1,7 @@
using Cysharp.Threading.Tasks;
using QSB.MeteorSync.WorldObjects;
using QSB.WorldSync;
using System.Linq;
using System.Threading;
namespace QSB.MeteorSync;
@ -16,7 +17,9 @@ public class MeteorManager : WorldObjectManager
// wait for all late initializers (which includes meteor launchers) to finish
await UniTask.WaitUntil(() => LateInitializerManager.isDoneInitializing, cancellationToken: ct);
WhiteHoleVolume = QSBWorldSync.GetUnityObject<WhiteHoleVolume>();
// NH can make multiple so ensure its the stock whitehole
var whiteHole = QSBWorldSync.GetUnityObjects<AstroObject>().First(x => x.GetAstroObjectName() == AstroObject.Name.WhiteHole);
WhiteHoleVolume = whiteHole?.GetComponentInChildren<WhiteHoleVolume>();
QSBWorldSync.Init<QSBFragment, FragmentIntegrity>();
QSBWorldSync.Init<QSBMeteorLauncher, MeteorLauncher>();
QSBWorldSync.Init<QSBMeteor, MeteorController>();

View File

@ -1,4 +1,5 @@
using QSB.Messaging;
using OWML.Utils;
using QSB.Messaging;
using QSB.OrbSync.Messages;
using QSB.OrbSync.TransformSync;
using QSB.Utility;

View File

@ -1,5 +1,6 @@
using HarmonyLib;
using OWML.Common;
using OWML.Utils;
using QSB.Utility;
using System;
using System.Collections.Generic;

View File

@ -16,8 +16,6 @@ public class PlayerJoinMessage : QSBMessage
private string QSBVersion;
private string GameVersion;
private bool DlcInstalled;
// empty if no incompatible mods
private string FirstIncompatibleMod;
private int[] AddonHashes;
@ -28,18 +26,6 @@ public class PlayerJoinMessage : QSBMessage
GameVersion = QSBCore.GameVersion;
DlcInstalled = QSBCore.DLCInstalled;
var allEnabledMods = QSBCore.Helper.Interaction.GetMods();
FirstIncompatibleMod = "";
foreach (var mod in allEnabledMods)
{
if (QSBCore.IncompatibleMods.Contains(mod.ModHelper.Manifest.UniqueName))
{
FirstIncompatibleMod = mod.ModHelper.Manifest.UniqueName;
}
}
AddonHashes = QSBCore.Addons.Keys
.Except(QSBCore.CosmeticAddons)
.Select(x => x.GetStableHashCode())
@ -53,7 +39,6 @@ public class PlayerJoinMessage : QSBMessage
writer.Write(QSBVersion);
writer.Write(GameVersion);
writer.Write(DlcInstalled);
writer.Write(FirstIncompatibleMod);
writer.Write(AddonHashes);
}
@ -65,7 +50,6 @@ public class PlayerJoinMessage : QSBMessage
QSBVersion = reader.ReadString();
GameVersion = reader.ReadString();
DlcInstalled = reader.Read<bool>();
FirstIncompatibleMod = reader.ReadString();
AddonHashes = reader.Read<int[]>();
}
@ -119,12 +103,6 @@ public class PlayerJoinMessage : QSBMessage
new PlayerKickMessage(From, string.Format(QSBLocalization.Current.AddonMismatch, AddonHashes.Length, addonHashes.Length)).Send();
return;
}
if (FirstIncompatibleMod != "" && !QSBCore.IncompatibleModsAllowed)
{
DebugLog.ToConsole($"Error - Client {PlayerName} connecting with incompatible mod. (First mod found was {FirstIncompatibleMod})");
new PlayerKickMessage(From, string.Format(QSBLocalization.Current.IncompatibleMod, FirstIncompatibleMod)).Send();
}
}
var player = QSBPlayerManager.GetPlayer(From);

View File

@ -1,4 +1,5 @@
using OWML.Common;
using OWML.Utils;
using QSB.Messaging;
using QSB.Patches;
using QSB.Player.Messages;

View File

@ -75,7 +75,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="OuterWildsGameLibs" Version="1.1.14.768" IncludeAssets="compile" />
<PackageReference Include="OWML" Version="2.9.8" IncludeAssets="compile" />
<PackageReference Include="OWML" Version="2.11.1" IncludeAssets="compile" />
<Reference Include="..\Lib\*.dll" />
<ProjectReference Include="..\FizzySteamworks\FizzySteamworks.csproj" />
<ProjectReference Include="..\SteamRerouter\SteamRerouter.csproj" />

View File

@ -66,7 +66,6 @@ public class QSBCore : ModBehaviour
Application.version.Split('.').Take(3).Join(delimiter: ".");
public static bool DLCInstalled => EntitlementsManager.IsDlcOwned() == EntitlementsManager.AsyncOwnershipStatus.Owned;
public static bool UseKcpTransport { get; private set; }
public static bool IncompatibleModsAllowed { get; private set; }
public static bool ShowPlayerNames { get; private set; }
public static bool ShipDamage { get; private set; }
public static bool ShowExtraHUDElements { get; private set; }
@ -84,17 +83,7 @@ public class QSBCore : ModBehaviour
private static string randomSkinType;
private static string randomJetpackType;
public static readonly string[] IncompatibleMods =
{
// incompatible mods
"Raicuparta.NomaiVR",
"xen.NewHorizons",
"Vesper.AutoResume",
"Vesper.OuterWildsMMO",
"_nebula.StopTime",
"PacificEngine.OW_Randomizer",
};
public static Assembly QSBNHAssembly = null;
public static event Action OnSkinsBundleLoaded;
@ -227,7 +216,7 @@ public class QSBCore : ModBehaviour
Helper = ModHelper;
DebugLog.ToConsole($"* Start of QSB version {QSBVersion} - authored by {Helper.Manifest.Author}", MessageType.Info);
CheckCompatibilityMods();
CheckNewHorizons();
DebugSettings = Helper.Storage.Load<DebugSettings>("debugsettings.json") ?? new DebugSettings();
@ -415,7 +404,6 @@ public class QSBCore : ModBehaviour
QSBNetworkManager.UpdateTransport();
DefaultServerIP = config.GetSettingsValue<string>("defaultServerIP");
IncompatibleModsAllowed = config.GetSettingsValue<bool>("incompatibleModsAllowed");
ShowPlayerNames = config.GetSettingsValue<bool>("showPlayerNames");
ShipDamage = config.GetSettingsValue<bool>("shipDamage");
ShowExtraHUDElements = config.GetSettingsValue<bool>("showExtraHud");
@ -463,23 +451,13 @@ public class QSBCore : ModBehaviour
}
}
private void CheckCompatibilityMods()
private void CheckNewHorizons()
{
var mainMod = "";
var compatMod = "";
var missingCompat = false;
/*if (Helper.Interaction.ModExists(NEW_HORIZONS) && !Helper.Interaction.ModExists(NEW_HORIZONS_COMPAT))
if (ModHelper.Interaction.ModExists("xen.NewHorizons"))
{
mainMod = NEW_HORIZONS;
compatMod = NEW_HORIZONS_COMPAT;
missingCompat = true;
}*/
if (missingCompat)
{
DebugLog.ToConsole($"FATAL - You have mod \"{mainMod}\" installed, which is not compatible with QSB without the compatibility mod \"{compatMod}\". " +
$"Either disable the mod, or install/enable the compatibility mod.", MessageType.Fatal);
// NH compat has to be in a different DLL since it uses IAddComponentOnStart, and depends on the NH DLL.
QSBNHAssembly = Assembly.LoadFrom(Path.Combine(ModHelper.Manifest.ModFolderPath, "QSB-NH.dll"));
gameObject.AddComponent(QSBNHAssembly.GetType("QSBNH.QSBNH", true));
}
}
}

View File

@ -1,4 +1,5 @@
using OWML.Common;
using OWML.Utils;
using QSB.Utility;
using System;

View File

@ -1,4 +1,5 @@
using HarmonyLib;
using OWML.Utils;
using QSB.Patches;
using QSB.Utility;

View File

@ -1,4 +1,5 @@
using HarmonyLib;
using OWML.Utils;
using QSB.Messaging;
using QSB.Patches;
using QSB.ShipSync.Messages;

View File

@ -1,4 +1,5 @@
using QSB.Messaging;
using OWML.Utils;
using QSB.Messaging;
using QSB.ShipSync.Messages.Component;
using QSB.Utility;
using QSB.WorldSync;

View File

@ -1,4 +1,5 @@
using QSB.Messaging;
using OWML.Utils;
using QSB.Messaging;
using QSB.ShipSync.Messages.Hull;
using QSB.Utility;
using QSB.WorldSync;

View File

@ -1,4 +1,5 @@
using HarmonyLib;
using OWML.Utils;
using QSB.Messaging;
using QSB.Patches;
using QSB.Tools.ProbeTool.Messages;

View File

@ -28,7 +28,6 @@
"DLCMismatch": "DLC Installationsstatus stimmt nicht überein. (Client:{0}, Server:{1})",
"GameProgressLimit": "Spiel ist zu weit fortgeschritten.",
"AddonMismatch": "Addons stimmen nicht überein. (Client:{0} addons, Server:{1} addons)",
"IncompatibleMod": "Es wird eine inkompatible/unerlaubte Modifikation genutzt. Die erste gefundene Modifikation war {0}",
"PlayerJoinedTheGame": "{0} trat bei!",
"PlayerLeftTheGame": "{0} verließ!",
"PlayerWasKicked": "{0} wurde gekickt.",

View File

@ -28,7 +28,6 @@
"DLCMismatch": "DLC installation state does not match. (Client:{0}, Server:{1})",
"GameProgressLimit": "Game has progressed too far.",
"AddonMismatch": "Addon mismatch. (Client:{0} addons, Server:{1} addons)",
"IncompatibleMod": "Using an incompatible/disallowed mod. First mod found was {0}",
"PlayerJoinedTheGame": "{0} joined!",
"PlayerLeftTheGame": "{0} left!",
"PlayerWasKicked": "{0} was kicked.",

View File

@ -28,7 +28,6 @@
"DLCMismatch": "Les états d'installation du DLC ne correspondent pas. (Client:{0}, Serveur:{1})",
"GameProgressLimit": "Le jeu a trop progressé.",
"AddonMismatch": "Non-concordance des addons. (Client:{0} addons, Serveur:{1} addons)",
"IncompatibleMod": "Tu utilises un mod incompatible/non autorisé. Le premier mod trouvé était {0}",
"PlayerJoinedTheGame": "{0} a rejoint!",
"PlayerLeftTheGame": "{0} est parti!",
"PlayerWasKicked": "{0} a été expulsé.",

View File

@ -28,7 +28,6 @@
"DLCMismatch": "O estado da instalação da DLC não correspondem. (Cliente:{0}, Servidor:{1})",
"GameProgressLimit": "O jogo progrediu além do limite.",
"AddonMismatch": "Incompatibilidade de Addons. (Cliente:{0} addons, Servidor:{1} addons)",
"IncompatibleMod": "Usando um mod incompativel ou não permitido. Primeiro mod encontrado foi {0}",
"PlayerJoinedTheGame": "{0} entrou!",
"PlayerLeftTheGame": "{0} saiu!",
"PlayerWasKicked": "{0} foi expulso.",

View File

@ -28,7 +28,6 @@
"DLCMismatch": "Состояние присутствия DLC отличается. (Клиент:{0}, Сервер:{1})",
"GameProgressLimit": "Игра продолжалась слишком долго.",
"AddonMismatch": "Аддоны различаются. (Клиент:{0} аддонов, Сервер:{1} аддонов)",
"IncompatibleMod": "Используется несовместимый(ые)/неразрешенный(ые) мод(ы). Первый из них - {0}",
"PlayerJoinedTheGame": "{0} подключился!",
"PlayerWasKicked": "{0} был отключён.",
"KickedFromServer": "Отключён от сервера. Причина : {0}",

View File

@ -28,7 +28,6 @@
"DLCMismatch": "El estado de instalación del DLC no coincide. (Cliente:{0}, Servidor:{1})",
"GameProgressLimit": "El juego ha progresado mas del límite permitido.",
"AddonMismatch": "Incompatibilidad de Addons. (Cliente:{0} addons, Servidor:{1} addons)",
"IncompatibleMod": "Se está usando un mod incompatible/no permitido. El primer mod encontrado ha sido {0}",
"PlayerJoinedTheGame": "¡{0} se ha unido!",
"PlayerWasKicked": "{0} ha sido expulsado.",
"KickedFromServer": "Expulsado del servidor. Razón : {0}",

View File

@ -28,7 +28,6 @@
"DLCMismatch": "DLC yüklenme durumu uyumlu değil. (İstemci:{0}, Sunucu:{1})",
"GameProgressLimit": "Oyun çok fazla ilerlemiş.",
"AddonMismatch": "Yüklü eklentiler uyumsuz. (İstemci:{0} eklentileri, Sunucu:{1} eklentileri)",
"IncompatibleMod": "İzin verilmeyen veya uyumsuz mod kullanılıyor. İlk bulunan mod {0}",
"PlayerJoinedTheGame": "{0} katıldı!",
"PlayerLeftTheGame": "{0} ayrıldı.",
"PlayerWasKicked": "{0} atıldı.",

View File

@ -28,7 +28,6 @@
"DLCMismatch": "DLC安装情况不匹配。客户端{0},服务端:{1}",
"GameProgressLimit": "游戏中时间太久了。",
"AddonMismatch": "插件不匹配(客户端:{0}插件,服务端:{1}插件)",
"IncompatibleMod": "使用了不兼容/不允许的模组,检测到的第一个模组是{0}",
"PlayerJoinedTheGame": "{0}加入了游戏!",
"PlayerWasKicked": "{0}被踢出了游戏。",
"KickedFromServer": "被踢出了游戏,理由是:{0}",

View File

@ -1,4 +1,5 @@
using OWML.Common;
using OWML.Utils;
using QSB.EchoesOfTheEye.DreamLantern;
using QSB.EchoesOfTheEye.DreamLantern.WorldObjects;
using QSB.ItemSync.WorldObjects.Items;

View File

@ -205,6 +205,8 @@ public class DebugGUI : MonoBehaviour, IAddComponentOnStart
WriteLine(2, $" - Ref. Sector : {(referenceSector == null ? "NULL" : referenceSector.Name)}", referenceSector == null ? Color.red : Color.white);
WriteLine(2, $" - Ref. Transform : {(referenceTransform == null ? "NULL" : referenceTransform.name)}", referenceTransform == null ? Color.red : Color.white);
WriteLine(2, $" - Local Position : {player.Body.transform.localPosition}");
WriteLine(2, $" - Position : {player.Body.transform.position}");
}
}

View File

@ -1,4 +1,5 @@
using HarmonyLib;
using OWML.Utils;
using QSB.Patches;
using System.Collections.Generic;
using UnityEngine;

View File

@ -71,21 +71,6 @@ public static class Extensions
#region C#
public static void SafeInvoke(this MulticastDelegate multicast, params object[] args)
{
foreach (var del in multicast.GetInvocationList())
{
try
{
del.DynamicInvoke(args);
}
catch (TargetInvocationException ex)
{
DebugLog.ToConsole($"Error invoking delegate! {ex.InnerException}", MessageType.Error);
}
}
}
public static float Map(this float value, float inputFrom, float inputTo, float outputFrom, float outputTo, bool clamp)
{
var mappedValue = (value - inputFrom) / (inputTo - inputFrom) * (outputTo - outputFrom) + outputFrom;
@ -165,30 +150,21 @@ public static class Extensions
public static bool IsInRange<T>(this IList<T> list, int index) => index >= 0 && index < list.Count;
public static void RaiseEvent<T>(this T instance, string eventName, params object[] args)
public static IEnumerable<Type> GetDerivedTypes(this Type type)
{
const BindingFlags flags = BindingFlags.Instance
| BindingFlags.Static
| BindingFlags.Public
| BindingFlags.NonPublic
| BindingFlags.DeclaredOnly;
if (typeof(T)
.GetField(eventName, flags)?
.GetValue(instance) is not MulticastDelegate multiDelegate)
var assemblies = QSBCore.Addons.Values
.Select(x => x.GetType().Assembly)
.Append(type.Assembly);
if (QSBCore.QSBNHAssembly != null)
{
return;
assemblies = assemblies.Append(QSBCore.QSBNHAssembly);
}
multiDelegate.SafeInvoke(args);
}
public static IEnumerable<Type> GetDerivedTypes(this Type type) =>
QSBCore.Addons.Values
.Select(x => x.GetType().Assembly)
.Append(type.Assembly)
.SelectMany(x => x.GetTypes())
return assemblies.SelectMany(x => x.GetTypes())
.Where(x => !x.IsInterface && !x.IsAbstract && type.IsAssignableFrom(x))
.OrderBy(x => x.FullName);
}
public static Guid ToGuid(this int value)
{

View File

@ -1,4 +1,5 @@
using QSB.Messaging;
using OWML.Utils;
using QSB.Messaging;
using QSB.Utility;
using System;

View File

@ -1,4 +1,5 @@
using HarmonyLib;
using OWML.Utils;
using QSB.Messaging;
using QSB.Patches;
using QSB.Utility;

View File

@ -1,4 +1,5 @@
using QSB.Messaging;
using OWML.Utils;
using QSB.Messaging;
using QSB.Utility;
using QSB.WorldSync;
using QSB.ZeroGCaveSync.Messages;

View File

@ -28,12 +28,6 @@
"value": "localhost",
"tooltip": "Used if you leave the connect prompt blank."
},
"incompatibleModsAllowed": {
"title": "Incompatible Mods Allowed",
"type": "toggle",
"value": false,
"tooltip": "Kicks players if they have certain mods."
},
"showPlayerNames": {
"title": "Show Player Names",
"type": "toggle",

View File

@ -3,15 +3,17 @@
"filename": "QSB.dll",
"author": "Nebula, John, Alek, & Rai",
"name": "Quantum Space Buddies",
"warning": {
"title": "Follow these steps before playing multiplayer :",
"body": "- Disable *all* other mods. (Can heavily affect performance)\n- Make sure you are not running any other network-intensive applications."
},
"uniqueName": "Raicuparta.QuantumSpaceBuddies",
"version": "1.0.1",
"owmlVersion": "2.9.8",
"version": "1.1.0",
"owmlVersion": "2.11.1",
"dependencies": [ "_nebula.MenuFramework", "JohnCorby.VanillaFix" ],
"pathsToPreserve": [ "debugsettings.json" ],
"conflicts": [
"Vesper.AutoResume",
"Vesper.OuterWildsMMO",
"_nebula.StopTime",
"PacificEngine.OW_CommonResources"
],
"requireLatestVersion": true,
"patcher": "QSBPatcher.exe",
"donateLinks": [ "https://www.paypal.me/nebula2056", "https://www.paypal.me/johncorby" ]

View File

@ -61,17 +61,27 @@ There still might be one or two small mechanics that aren't synced - let us know
Also, you might encounter bugs that mean you can't progress in multiplayer. Again, let us know if you find one!
### Compatibility with other mods
TL;DR - Don't use any mods with QSB that aren't marked as QSB compatible.
QSB relies on object hierarchy to sync objects, so any mod that changes that risks breaking QSB. Also, QSB relies on certain game events being called when things happen in-game. Any mod that makes these things happen without calling the correct events will break QSB. Some mods will work fine and have been tested, like CrouchMod. Others may only work partly, like EnableDebugMode and TAICheat.
QSB relies on object hierarchy to sync objects, so any mod that changes that risks breaking QSB.
QSB also relies on certain game events being called when things happen in-game, so any mod that makes these things happen without calling the correct events will break QSB.
### Is this mod compatible with NomaiVR?
Most small mods will work fine. The more complex and far reaching the mod, the less likely it will work completely.
Try as many mods as you like, but don't be surprised if things break.
Short answer - Kind of.
#### NomaiVr
Long answer - We've done our best to try to keep them compatible, but no work has been done to explicitly make them play nice. Some things may work, others may not.
Getting both mods to work together is a big undertaking, and would require rewrites to a lot of code in both mods.
If you want to play with VR, make sure the server host has "Incompatible Mods Allowed" enabled.
[Here](https://github.com/qsb-dev/quantum-space-buddies/issues?q=is%3Aissue+is%3Aopen+label%3ANomaiVR) are the known issues. You are welcome to add to this list by creating issues.
Most things seem to work _enough_. There are some visual bugs, and I believe a few softlocks, but the experience shouldn't be too bad.
We haven't done too much work to make them compatible, so the things that are broken are unlikely to be fixed.
#### New Horizons
[Here](https://github.com/qsb-dev/quantum-space-buddies/issues?q=is%3Aissue+is%3Aopen+label%3A%22New+Horizons%22) are the known issues. You are welcome to add to this list by creating issues.
We do our best to stay mostly compatible with base New Horizons, but the compatibility of each addon is mixed.
Most of them at least partially work. Most custom mechanics will not work until the addon developer explicitly adds QSB support.
### Why do I keep getting thrown around the ship?

View File

@ -17,7 +17,7 @@
</None>
</ItemGroup>
<ItemGroup>
<PackageReference Include="OuterWildsGameLibs" Version="1.1.13.457" IncludeAssets="compile" />
<PackageReference Include="OWML" Version="2.9.7" IncludeAssets="compile" />
<PackageReference Include="OuterWildsGameLibs" Version="1.1.14.768" IncludeAssets="compile" />
<PackageReference Include="OWML" Version="2.11.1" IncludeAssets="compile" />
</ItemGroup>
</Project>