Merge branch 'dev' into steamworks-v3

This commit is contained in:
_nebula 2023-07-27 13:03:50 +01:00
commit 1cdfa957ef
40 changed files with 457 additions and 211 deletions

98
DEVELOPMENT.md Normal file
View File

@ -0,0 +1,98 @@
> :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.
## Prerequisites
- Visual Studio 2022.
- Epic or Steam version of Outer Wilds.
- Keyboard with numpad for in-game debug actions.
We recommend using the Outer Wilds Mod Manager, but you can use OWML on its own if you want.
## Cloning and configuration
- Clone QSB's source code.
- Copy the file `DevEnv.template.targets` and rename it to `DevEnv.targets`.
- In `DevEnv.targets`, edit the entry for `<OwmlDir>` to point to your installation of OWML. This should be the folder named `OWML`. If using the manager, you can find this directory by :
- Legacy Manager : Press the "Mods Directory" button and go up a folder.
- New Manager : Press the "..." button at the top, and select "Show OWML Folder".
- `QSB.sln` should now be ready to open. ***This solution needs to be opened with Visual Studio 2022 or higher!***
## Steam
If using the Steam version of Outer Wilds, you will need to create a file to allow you to run multiple instances of the game.
- Navigate to your game install folder. You can find this by right-clicking on the game in Steam, and going `Manage > Browse local files`.
- Create a file named `steam_appid.txt`.
- In this file, write `753640` and save.
This file will override some Steam DRM features and allow the game to be ran multiple times at once.
## Building
Simply build the solution normally. (`Build > Build Solution` or CTRL-SHIFT-B)
The files will automatically be copied over to your OWML installation and be ready to play - no DLL copying needed.
For documentation reasons, here is the build flow :
- MirrorWeaver is built.
- EpicOnlineTransport is built.
- EpicRerouter is built.
- QSB is built.
- Any `.exe.config` files are removed from the build.
- QSB.dll is processed ("weaved") by MirrorWeaver. This injects all the boilerplate code that Mirror needs to function.
- If needed/possible, any `.dll` or `.exe` files are copied to the Unity project.
## Debugging
### Debug Actions :
Hold Q and press :
- Numpad 1 - Teleport to nearest player.
- Numpad 2 - If holding LeftShift, warp to the dreamworld Vault fire. If not, warp to the Endless Canyon.
- Numpad 3 - Unlock the Sealed Vault.
- Numpad 4 - Damage the ship's electrical system.
- Numpad 5 - Trigger the supernova.
- Numpad 6 - Set the flags for having met Solanum and the Prisoner.
- Numpad 7 - Warp to the Vessel.
- Numpad 8 - Insert the Advanced Warp Core into the Vessel.
- Numpad 9 - If holding LeftShift, load the SolarSystem scene. If not, load the EyeOfTheUniverse scene.
- Numpad 0 - Revive a random dead player.
### Debug Settings :
Create a file called `debugsettings.json` in the mod folder.
The template for this file is this :
```json
{
"dumpWorldObjects": false,
"instanceIdInLogs": false,
"hookDebugLogs": false,
"avoidTimeSync": false,
"autoStart": false,
"kickEveryone": false,
"disableLoopDeath": false,
"debugMode": false,
"drawGui": false,
"drawLines": false,
"drawLabels": false,
"drawQuantumVisibilityObjects": false,
"drawGhostAI": false,
"greySkybox": false
}
```
- dumpWorldObjects - Creates a file with information about the WorldObjects that were created.
- instanceIdInLogs - Appends the game instance id to every log message sent.
- hookDebugLogs - Print Unity logs and warnings.
- avoidTimeSync - Disables the syncing of time.
- autoStart - Host/connect automatically for faster testing.
- kickEveryone - Kick anyone who joins a game.
- disableLoopDeath - Make it so the loop doesn't end when everyone is dead.
- debugMode - Enables debug mode. If this is set to `false`, none of the following settings do anything.
- drawGui - Draws a GUI at the top of the screen that gives information on many things.
- drawLines - Draws gizmo-esque lines around things. Indicates reference sectors/transforms, triggers, etc. LAGGY.
- drawLabels - Draws GUI labels attached to some objects. LAGGY.
- drawQuantumVisibilityObjects - Indicates visibility objects with an orange shape.
- drawGhostAI - Draws debug lines and labels just for the ghosts.
- greySkybox - Turns the skybox grey. Useful in the Eye, where it's pretty dark.

View File

@ -11,7 +11,12 @@
<PackageLicenseFile>LICENSE</PackageLicenseFile> <PackageLicenseFile>LICENSE</PackageLicenseFile>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<None Include="LICENSE"> <PackageReference Include="OuterWildsGameLibs" Version="1.1.13.457" />
<PackageReference Include="OWML" Version="2.9.3" />
<Reference Include="../Mirror/*.dll" />
</ItemGroup>
<ItemGroup>
<None Update="License.md">
<Pack>True</Pack> <Pack>True</Pack>
<PackagePath>\</PackagePath> <PackagePath>\</PackagePath>
</None> </None>

View File

@ -8,6 +8,7 @@ EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{2569F98D-F671-42AA-82DE-505B05CDCEF2}" Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{2569F98D-F671-42AA-82DE-505B05CDCEF2}"
ProjectSection(SolutionItems) = preProject ProjectSection(SolutionItems) = preProject
.gitignore = .gitignore .gitignore = .gitignore
DEVELOPMENT.md = DEVELOPMENT.md
LICENSE = LICENSE LICENSE = LICENSE
README.md = README.md README.md = README.md
TRANSLATING.md = TRANSLATING.md TRANSLATING.md = TRANSLATING.md

View File

@ -8,6 +8,7 @@
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpCodeStyle/BRACES_FOR_USING/@EntryValue">Required</s:String> <s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpCodeStyle/BRACES_FOR_USING/@EntryValue">Required</s:String>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpCodeStyle/BRACES_FOR_WHILE/@EntryValue">Required</s:String> <s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpCodeStyle/BRACES_FOR_WHILE/@EntryValue">Required</s:String>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/CASE_BLOCK_BRACES/@EntryValue">NEXT_LINE_SHIFTED_2</s:String> <s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/CASE_BLOCK_BRACES/@EntryValue">NEXT_LINE_SHIFTED_2</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=QSB/@EntryIndexedValue">QSB</s:String>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpKeepExistingMigration/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpKeepExistingMigration/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpPlaceEmbeddedOnSameLineMigration/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpPlaceEmbeddedOnSameLineMigration/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpUseContinuousIndentInsideBracesMigration/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpUseContinuousIndentInsideBracesMigration/@EntryIndexedValue">True</s:Boolean>

View File

@ -13,7 +13,13 @@ public class ShipThrusterAudioOneShotMessage : QSBMessage<(AudioType audioType,
public override void OnReceiveRemote() public override void OnReceiveRemote()
{ {
var source = ShipManager.Instance.ShipThrusterAudio._rotationalSource; var source = ShipManager.Instance?.ShipThrusterAudio?._rotationalSource;
if (source == null)
{
return;
}
source.pitch = Data.pitch; source.pitch = Data.pitch;
source.PlayOneShot(Data.audioType, Data.volume); source.PlayOneShot(Data.audioType, Data.volume);
} }

View File

@ -5,6 +5,7 @@ using QSB.Messaging;
using QSB.Player; using QSB.Player;
using QSB.RespawnSync; using QSB.RespawnSync;
using QSB.Utility; using QSB.Utility;
using UnityEngine;
namespace QSB.DeathSync.Messages; namespace QSB.DeathSync.Messages;
@ -41,7 +42,7 @@ public class PlayerDeathMessage : QSBMessage<DeathType>
var deathMessage = Necronomicon.GetPhrase(Data, NecronomiconIndex); var deathMessage = Necronomicon.GetPhrase(Data, NecronomiconIndex);
if (deathMessage != null) if (deathMessage != null)
{ {
MultiplayerHUDManager.Instance.WriteMessage($"<color=brown>{string.Format(deathMessage, playerName)}</color>"); MultiplayerHUDManager.Instance.WriteMessage(string.Format(deathMessage, playerName), Color.grey);
} }
RespawnManager.Instance.OnPlayerDeath(player); RespawnManager.Instance.OnPlayerDeath(player);

View File

@ -239,7 +239,7 @@ public class RespawnOnDeath : MonoBehaviour
} }
var cloak = Locator.GetCloakFieldController(); var cloak = Locator.GetCloakFieldController();
// visible stranger and maybe NH disables cloak // visible stranger disables cloak
if (cloak) if (cloak)
{ {
cloak._playerInsideCloak = false; cloak._playerInsideCloak = false;

View File

@ -4,17 +4,18 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using UnityEngine;
namespace QSB.HUD.Messages; namespace QSB.HUD.Messages;
internal class ChatMessage : QSBMessage<string> internal class ChatMessage : QSBMessage<(string message, Color color)>
{ {
public ChatMessage(string msg) : base(msg) { } public ChatMessage(string msg, Color color) : base((msg, color)) { }
public override void OnReceiveLocal() => OnReceiveRemote(); public override void OnReceiveLocal() => OnReceiveRemote();
public override void OnReceiveRemote() public override void OnReceiveRemote()
{ {
MultiplayerHUDManager.Instance.WriteMessage(Data); MultiplayerHUDManager.Instance.WriteMessage(Data.message, Data.color);
} }
} }

View File

@ -9,6 +9,7 @@ using QSB.WorldSync;
using System; using System;
using System.Linq; using System.Linq;
using UnityEngine; using UnityEngine;
using UnityEngine.InputSystem;
using UnityEngine.SceneManagement; using UnityEngine.SceneManagement;
using UnityEngine.UI; using UnityEngine.UI;
@ -71,12 +72,12 @@ internal class MultiplayerHUDManager : MonoBehaviour, IAddComponentOnStart
private const float FADE_TIME = 2f; private const float FADE_TIME = 2f;
private bool _writingMessage; private bool _writingMessage;
private readonly string[] _lines = new string[LINE_COUNT]; private readonly (string msg, Color color)[] _lines = new (string msg, Color color)[LINE_COUNT];
// this should really be a deque, but eh // this should really be a deque, but eh
private readonly ListStack<string> _messages = new(false); private readonly ListStack<(string msg, Color color)> _messages = new(false);
private float _lastMessageTime; private float _lastMessageTime;
public void WriteMessage(string message) public void WriteMessage(string message, Color color)
{ {
if (!QSBWorldSync.AllObjectsReady) if (!QSBWorldSync.AllObjectsReady)
{ {
@ -86,7 +87,7 @@ internal class MultiplayerHUDManager : MonoBehaviour, IAddComponentOnStart
/* Tricky problem to solve. /* Tricky problem to solve.
* - 11 available lines for text to fit onto * - 11 available lines for text to fit onto
* - Each line can be max 41 characters * - Each line can be max 41 characters
* - Newest messages apepear at the bottom, and get pushed up by newer messages. * - Newest messages appear at the bottom, and get pushed up by newer messages.
* - Messages can use several lines. * - Messages can use several lines.
* *
* From newest to oldest message, work out how many lines it needs * From newest to oldest message, work out how many lines it needs
@ -95,18 +96,18 @@ internal class MultiplayerHUDManager : MonoBehaviour, IAddComponentOnStart
_lastMessageTime = Time.time; _lastMessageTime = Time.time;
_messages.Push(message); _messages.Push((message, color));
if (_messages.Count > LINE_COUNT) if (_messages.Count > LINE_COUNT)
{ {
_messages.RemoveFirstElementAndShift(); _messages.PopFromBack();
} }
var currentLineIndex = 10; var currentLineIndex = 10;
foreach (var msg in _messages.Reverse()) foreach (var msg in _messages.Reverse())
{ {
var characterCount = msg.Length; var characterCount = msg.msg.Length;
var linesNeeded = Mathf.CeilToInt((float)characterCount / CHAR_COUNT); var linesNeeded = Mathf.CeilToInt((float)characterCount / CHAR_COUNT);
var chunk = 0; var chunk = 0;
for (var i = linesNeeded - 1; i >= 0; i--) for (var i = linesNeeded - 1; i >= 0; i--)
@ -117,8 +118,8 @@ internal class MultiplayerHUDManager : MonoBehaviour, IAddComponentOnStart
continue; continue;
} }
var chunkString = string.Concat(msg.Skip(CHAR_COUNT * chunk).Take(CHAR_COUNT)); var chunkString = string.Concat(msg.msg.Skip(CHAR_COUNT * chunk).Take(CHAR_COUNT));
_lines[currentLineIndex - i] = chunkString; _lines[currentLineIndex - i] = (chunkString, msg.color);
chunk++; chunk++;
} }
@ -133,17 +134,20 @@ internal class MultiplayerHUDManager : MonoBehaviour, IAddComponentOnStart
var finalText = ""; var finalText = "";
foreach (var line in _lines) foreach (var line in _lines)
{ {
var msgColor = ColorUtility.ToHtmlStringRGBA(line.color);
var msg = $"<color=#{msgColor}>{line.msg}</color>";
if (line == default) if (line == default)
{ {
finalText += Environment.NewLine; finalText += Environment.NewLine;
} }
else if (line.Length == 42) else if (line.msg.Length == CHAR_COUNT + 1)
{ {
finalText += line; finalText += msg;
} }
else else
{ {
finalText += $"{line}{Environment.NewLine}"; finalText += $"{msg}{Environment.NewLine}";
} }
} }
@ -159,6 +163,8 @@ internal class MultiplayerHUDManager : MonoBehaviour, IAddComponentOnStart
_textChat.GetComponent<CanvasGroup>().alpha = 1; _textChat.GetComponent<CanvasGroup>().alpha = 1;
} }
ListStack<string> previousMessages = new(true);
private void Update() private void Update()
{ {
if (!QSBWorldSync.AllObjectsReady || _playerList == null) if (!QSBWorldSync.AllObjectsReady || _playerList == null)
@ -170,12 +176,39 @@ internal class MultiplayerHUDManager : MonoBehaviour, IAddComponentOnStart
var inSuit = Locator.GetPlayerSuit().IsWearingHelmet(); var inSuit = Locator.GetPlayerSuit().IsWearingHelmet();
if (OWInput.IsNewlyPressed(InputLibrary.enter, InputMode.Character) && !_writingMessage && inSuit) if ((OWInput.IsNewlyPressed(InputLibrary.enter, InputMode.Character) || (Keyboard.current[Key.Slash].wasPressedThisFrame && OWInput.IsInputMode(InputMode.Character)))
&& !_writingMessage && inSuit && QSBCore.TextChatInput)
{ {
OWInput.ChangeInputMode(InputMode.KeyboardInput); OWInput.ChangeInputMode(InputMode.KeyboardInput);
_writingMessage = true; _writingMessage = true;
_inputField.ActivateInputField(); _inputField.ActivateInputField();
_textChat.GetComponent<CanvasGroup>().alpha = 1; _textChat.GetComponent<CanvasGroup>().alpha = 1;
if (Keyboard.current[Key.Slash].wasPressedThisFrame)
{
Delay.RunNextFrame(() => _inputField.text = "/");
}
}
if (Keyboard.current[Key.UpArrow].wasPressedThisFrame && _writingMessage)
{
var currentText = _inputField.text;
if (previousMessages.Contains(currentText))
{
var index = previousMessages.IndexOf(currentText);
if (index == 0)
{
return;
}
_inputField.text = previousMessages[index - 1];
}
else
{
_inputField.text = previousMessages.Last();
}
} }
if (OWInput.IsNewlyPressed(InputLibrary.enter, InputMode.KeyboardInput) && _writingMessage) if (OWInput.IsNewlyPressed(InputLibrary.enter, InputMode.KeyboardInput) && _writingMessage)
@ -187,8 +220,16 @@ internal class MultiplayerHUDManager : MonoBehaviour, IAddComponentOnStart
var message = _inputField.text; var message = _inputField.text;
_inputField.text = ""; _inputField.text = "";
message = message.Replace("\n", "").Replace("\r", ""); message = message.Replace("\n", "").Replace("\r", "");
previousMessages.Push(message);
if (QSBCore.DebugSettings.DebugMode && CommandInterpreter.InterpretCommand(message))
{
return;
}
message = $"{QSBPlayerManager.LocalPlayer.Name}: {message}"; message = $"{QSBPlayerManager.LocalPlayer.Name}: {message}";
new ChatMessage(message).Send(); new ChatMessage(message, Color.white).Send();
} }
if (OWInput.IsNewlyPressed(InputLibrary.escape, InputMode.KeyboardInput) && _writingMessage) if (OWInput.IsNewlyPressed(InputLibrary.escape, InputMode.KeyboardInput) && _writingMessage)
@ -403,7 +444,7 @@ internal class MultiplayerHUDManager : MonoBehaviour, IAddComponentOnStart
Destroy(player.HUDBox?.gameObject); Destroy(player.HUDBox?.gameObject);
Destroy(player.MinimapPlayerMarker); Destroy(player.MinimapPlayerMarker);
WriteMessage($"<color=yellow>{string.Format(QSBLocalization.Current.PlayerLeftTheGame, player.Name)}</color>"); WriteMessage(string.Format(QSBLocalization.Current.PlayerLeftTheGame, player.Name), Color.yellow);
} }
private PlanetTrigger CreateTrigger(string parentPath, HUDIcon icon) private PlanetTrigger CreateTrigger(string parentPath, HUDIcon icon)

View File

@ -16,7 +16,7 @@ public class PlanetTrigger : SectoredMonoBehaviour
} }
MultiplayerHUDManager.HUDIconStack.Push(Icon); MultiplayerHUDManager.HUDIconStack.Push(Icon);
var top = MultiplayerHUDManager.HUDIconStack.Peek(); var top = MultiplayerHUDManager.HUDIconStack.PeekFront();
new PlanetMessage(top).Send(); new PlanetMessage(top).Send();
} }
@ -28,7 +28,7 @@ public class PlanetTrigger : SectoredMonoBehaviour
} }
MultiplayerHUDManager.HUDIconStack.Remove(Icon); MultiplayerHUDManager.HUDIconStack.Remove(Icon);
var top = MultiplayerHUDManager.HUDIconStack.Peek(); var top = MultiplayerHUDManager.HUDIconStack.PeekFront();
new PlanetMessage(top).Send(); new PlanetMessage(top).Send();
} }
} }

View File

@ -1,5 +1,4 @@
using Mirror; using System.Collections.Generic;
using System.Collections.Generic;
namespace QSB.Localization; namespace QSB.Localization;
@ -29,7 +28,6 @@ public class Translation
public string OK; public string OK;
public string ServerRefusedConnection; public string ServerRefusedConnection;
public string ClientDisconnectWithError; public string ClientDisconnectWithError;
public Dictionary<TransportError, string> TransportErrors;
public string QSBVersionMismatch; public string QSBVersionMismatch;
public string OWVersionMismatch; public string OWVersionMismatch;
public string DLCMismatch; public string DLCMismatch;

View File

@ -736,7 +736,7 @@ internal class MenuManager : MonoBehaviour, IAddComponentOnStart
} }
}; };
OpenInfoPopup(string.Format(QSBLocalization.Current.ClientDisconnectWithError, QSBLocalization.Current.TransportErrors[error], reason), QSBLocalization.Current.OK); OpenInfoPopup(string.Format(QSBLocalization.Current.ClientDisconnectWithError, reason), QSBLocalization.Current.OK);
} }
SetButtonActive(DisconnectButton, false); SetButtonActive(DisconnectButton, false);

View File

@ -7,7 +7,7 @@ public abstract class QSBMessage
/// <summary> /// <summary>
/// set automatically by Send /// set automatically by Send
/// </summary> /// </summary>
internal uint From; protected internal uint From;
/// <summary> /// <summary>
/// (default) uint.MaxValue = send to everyone <br/> /// (default) uint.MaxValue = send to everyone <br/>
/// 0 = send to host /// 0 = send to host

View File

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

View File

@ -37,13 +37,6 @@ internal class ModelShipManager : WorldObjectManager
public override async UniTask BuildWorldObjects(OWScene scene, CancellationToken ct) public override async UniTask BuildWorldObjects(OWScene scene, CancellationToken ct)
{ {
// NH can remove this
var modelShip = QSBWorldSync.GetUnityObject<RemoteFlightConsole>()._modelShipBody;
if (!modelShip)
{
return;
}
if (QSBCore.IsHost) if (QSBCore.IsHost)
{ {
Instantiate(QSBNetworkManager.singleton.ModelShipPrefab).SpawnWithServerOwnership(); Instantiate(QSBNetworkManager.singleton.ModelShipPrefab).SpawnWithServerOwnership();

View File

@ -23,16 +23,44 @@ public static class QSBPatchManager
{ {
if (_inited) if (_inited)
{ {
var count = _patchList.Count; var newPatches = new List<QSBPatch>();
foreach (var type in typeof(QSBPatch).GetDerivedTypes()) foreach (var type in typeof(QSBPatch).GetDerivedTypes())
{ {
if (!_patchList.Any(x => x.GetType() == type)) if (!newPatches.Any(x => x.GetType() == type)
&& !_patchList.Any(x => x.GetType() == type))
{ {
_patchList.Add((QSBPatch)Activator.CreateInstance(type)); newPatches.Add((QSBPatch)Activator.CreateInstance(type));
} }
} }
DebugLog.DebugWrite($"Registered {_patchList.Count - count} addon patches.", MessageType.Success); _patchList.AddRange(newPatches);
// could do lots of code to make sure all addon patches are done here,
// but the only patch type that will have been used by this point in the
// mod execution is OnModStart
DebugLog.DebugWrite($"Re-patching block OnModStart for addons", MessageType.Info);
var harmonyInstance = TypeToInstance[QSBPatchTypes.OnModStart];
foreach (var patch in newPatches)
{
if (patch.Type != QSBPatchTypes.OnModStart)
{
continue;
}
DebugLog.DebugWrite($" - Patching in {patch.GetType().Name}", MessageType.Info);
try
{
patch.DoPatches(harmonyInstance);
}
catch (Exception ex)
{
DebugLog.ToConsole($"Error while patching {patch.GetType().Name} :\r\n{ex}", MessageType.Error);
}
}
DebugLog.DebugWrite($"Registered {newPatches.Count()} addon patches.", MessageType.Success);
return; return;
} }
@ -59,10 +87,10 @@ public static class QSBPatchManager
} }
OnPatchType?.SafeInvoke(type); 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))) 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 try
{ {
patch.DoPatches(TypeToInstance[type]); patch.DoPatches(TypeToInstance[type]);

View File

@ -6,6 +6,7 @@ using QSB.Localization;
using QSB.Messaging; using QSB.Messaging;
using QSB.Utility; using QSB.Utility;
using System.Linq; using System.Linq;
using UnityEngine;
namespace QSB.Player.Messages; namespace QSB.Player.Messages;
@ -126,7 +127,7 @@ public class PlayerJoinMessage : QSBMessage
var player = QSBPlayerManager.GetPlayer(From); var player = QSBPlayerManager.GetPlayer(From);
player.Name = PlayerName; player.Name = PlayerName;
MultiplayerHUDManager.Instance.WriteMessage($"<color=green>{string.Format(QSBLocalization.Current.PlayerJoinedTheGame, player.Name)}</color>"); MultiplayerHUDManager.Instance.WriteMessage(string.Format(QSBLocalization.Current.PlayerJoinedTheGame, player.Name), Color.green);
DebugLog.DebugWrite($"{player} joined. qsbVersion:{QSBVersion}, gameVersion:{GameVersion}, dlcInstalled:{DlcInstalled}", MessageType.Info); DebugLog.DebugWrite($"{player} joined. qsbVersion:{QSBVersion}, gameVersion:{GameVersion}, dlcInstalled:{DlcInstalled}", MessageType.Info);
} }

View File

@ -4,6 +4,7 @@ using QSB.Localization;
using QSB.Menus; using QSB.Menus;
using QSB.Messaging; using QSB.Messaging;
using QSB.Utility; using QSB.Utility;
using UnityEngine;
namespace QSB.Player.Messages; namespace QSB.Player.Messages;
@ -35,15 +36,15 @@ internal class PlayerKickMessage : QSBMessage<string>
{ {
if (QSBPlayerManager.PlayerExists(PlayerId)) if (QSBPlayerManager.PlayerExists(PlayerId))
{ {
MultiplayerHUDManager.Instance.WriteMessage($"<color=red>{string.Format(QSBLocalization.Current.PlayerWasKicked, QSBPlayerManager.GetPlayer(PlayerId).Name)}</color>"); MultiplayerHUDManager.Instance.WriteMessage(string.Format(QSBLocalization.Current.PlayerWasKicked, QSBPlayerManager.GetPlayer(PlayerId).Name), Color.red);
return; return;
} }
MultiplayerHUDManager.Instance.WriteMessage($"<color=red>{string.Format(QSBLocalization.Current.PlayerWasKicked, PlayerId)}</color>"); MultiplayerHUDManager.Instance.WriteMessage(string.Format(QSBLocalization.Current.PlayerWasKicked, PlayerId), Color.red);
return; return;
} }
MultiplayerHUDManager.Instance.WriteMessage($"<color=red>{string.Format(QSBLocalization.Current.KickedFromServer, Data)}</color>"); MultiplayerHUDManager.Instance.WriteMessage(string.Format(QSBLocalization.Current.KickedFromServer, Data), Color.red);
MenuManager.Instance.OnKicked(Data); MenuManager.Instance.OnKicked(Data);
NetworkClient.Disconnect(); NetworkClient.Disconnect();

View File

@ -11,6 +11,7 @@ using QSB.QuantumSync.WorldObjects;
using QSB.ShipSync; using QSB.ShipSync;
using QSB.Tools; using QSB.Tools;
using QSB.Utility; using QSB.Utility;
using System.Collections.Generic;
using System.Linq; using System.Linq;
using UnityEngine; using UnityEngine;
@ -178,5 +179,20 @@ public partial class PlayerInfo
HUDBox.OnRespawn(); HUDBox.OnRespawn();
} }
private Dictionary<string, object> _customData = new();
public void SetCustomData<T>(string key, T data)
=> _customData[key] = data;
public T GetCustomData<T>(string key)
{
if (!_customData.ContainsKey(key))
{
return default;
}
return (T)_customData[key];
}
public override string ToString() => $"{PlayerId}:{GetType().Name} ({Name})"; public override string ToString() => $"{PlayerId}:{GetType().Name} ({Name})";
} }

View File

@ -4,7 +4,7 @@ namespace QSB.PoolSync;
internal class CustomNomaiRemoteCamera : MonoBehaviour internal class CustomNomaiRemoteCamera : MonoBehaviour
{ {
private OWCamera _camera; public OWCamera _camera;
private AudioListener _audioListener; private AudioListener _audioListener;
private NomaiViewerImageEffect _viewerImageEffect; private NomaiViewerImageEffect _viewerImageEffect;
private CustomNomaiRemoteCameraPlatform _owningPlatform; private CustomNomaiRemoteCameraPlatform _owningPlatform;

View File

@ -6,6 +6,7 @@ using QSB.Player.Messages;
using QSB.Utility; using QSB.Utility;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Reflection;
using UnityEngine; using UnityEngine;
namespace QSB.PoolSync; namespace QSB.PoolSync;
@ -516,8 +517,16 @@ internal class CustomNomaiRemoteCameraPlatform : NomaiShared
private void SwitchToPlayerCamera() private void SwitchToPlayerCamera()
{ {
// does nothing except run CCU's prefix if (QSBCore.Helper.Interaction.ModExists("xen.CommonCameraUtility"))
_oldPlatform.SwitchToPlayerCamera(); {
// this is a really fucking dumb fix, but i cannot be
// bothered to rewrite this class to make this work better
var ccuAssembly = QSBCore.Helper.Interaction.TryGetMod("xen.CommonCameraUtility").GetType().Assembly;
var utilClass = ccuAssembly.GetType("CommonCameraUtil.CommonCameraUtil");
var instance = utilClass.GetField("Instance", BindingFlags.Public | BindingFlags.Static).GetValue(null);
var removeCameraMethod = utilClass.GetMethod("RemoveCamera", BindingFlags.Public | BindingFlags.Instance);
removeCameraMethod.Invoke(instance, new object[] { _slavePlatform._ownedCamera._camera });
}
if (_slavePlatform._visualSector != null) if (_slavePlatform._visualSector != null)
{ {

View File

@ -71,8 +71,8 @@
</None> </None>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="OuterWildsGameLibs" Version="1.1.13.456" IncludeAssets="compile" /> <PackageReference Include="OuterWildsGameLibs" Version="1.1.13.457" IncludeAssets="compile" />
<PackageReference Include="OWML" Version="2.9.0" IncludeAssets="compile" /> <PackageReference Include="OWML" Version="2.9.3" IncludeAssets="compile" />
<Reference Include="..\Mirror\*.dll" /> <Reference Include="..\Mirror\*.dll" />
<Reference Include="..\UniTask\*.dll" /> <Reference Include="..\UniTask\*.dll" />
<ProjectReference Include="..\FizzySteamworks\FizzySteamworks.csproj" /> <ProjectReference Include="..\FizzySteamworks\FizzySteamworks.csproj" />

View File

@ -64,6 +64,7 @@ public class QSBCore : ModBehaviour
public static bool ShowPlayerNames { get; private set; } public static bool ShowPlayerNames { get; private set; }
public static bool ShipDamage { get; private set; } public static bool ShipDamage { get; private set; }
public static bool ShowExtraHUDElements { get; private set; } public static bool ShowExtraHUDElements { get; private set; }
public static bool TextChatInput { get; private set; }
public static GameVendor GameVendor { get; private set; } = GameVendor.None; public static GameVendor GameVendor { get; private set; } = GameVendor.None;
public static bool IsStandalone => GameVendor is GameVendor.Epic or GameVendor.Steam; public static bool IsStandalone => GameVendor is GameVendor.Epic or GameVendor.Steam;
public static IProfileManager ProfileManager => IsStandalone public static IProfileManager ProfileManager => IsStandalone
@ -322,6 +323,7 @@ public class QSBCore : ModBehaviour
ShowPlayerNames = config.GetSettingsValue<bool>("showPlayerNames"); ShowPlayerNames = config.GetSettingsValue<bool>("showPlayerNames");
ShipDamage = config.GetSettingsValue<bool>("shipDamage"); ShipDamage = config.GetSettingsValue<bool>("shipDamage");
ShowExtraHUDElements = config.GetSettingsValue<bool>("showExtraHud"); ShowExtraHUDElements = config.GetSettingsValue<bool>("showExtraHud");
TextChatInput = config.GetSettingsValue<bool>("textChatInput");
if (IsHost) if (IsHost)
{ {

View File

@ -94,52 +94,52 @@ public class QSBNetworkManager : NetworkManager, IAddComponentOnStart
playerPrefab = QSBCore.NetworkAssetBundle.LoadAsset<GameObject>("Assets/Prefabs/NETWORK_Player_Body.prefab"); playerPrefab = QSBCore.NetworkAssetBundle.LoadAsset<GameObject>("Assets/Prefabs/NETWORK_Player_Body.prefab");
playerPrefab.GetRequiredComponent<NetworkIdentity>().SetValue("_assetId", (uint)1); playerPrefab.GetRequiredComponent<NetworkIdentity>().SetValue("_assetId", (uint)1);
ShipPrefab = MakeNewNetworkObject(2, "NetworkShip", typeof(ShipTransformSync)); ShipPrefab = MakeNewNetworkObject("NetworkShip", typeof(ShipTransformSync));
var shipVector3Sync = ShipPrefab.AddComponent<Vector3VariableSyncer>(); var shipVector3Sync = ShipPrefab.AddComponent<Vector3VariableSyncer>();
var shipThrustSync = ShipPrefab.AddComponent<ShipThrusterVariableSyncer>(); var shipThrustSync = ShipPrefab.AddComponent<ShipThrusterVariableSyncer>();
shipThrustSync.AccelerationSyncer = shipVector3Sync; shipThrustSync.AccelerationSyncer = shipVector3Sync;
spawnPrefabs.Add(ShipPrefab); spawnPrefabs.Add(ShipPrefab);
_probePrefab = MakeNewNetworkObject(3, "NetworkProbe", typeof(PlayerProbeSync)); _probePrefab = MakeNewNetworkObject("NetworkProbe", typeof(PlayerProbeSync));
spawnPrefabs.Add(_probePrefab); spawnPrefabs.Add(_probePrefab);
OrbPrefab = MakeNewNetworkObject(4, "NetworkOrb", typeof(NomaiOrbTransformSync)); OrbPrefab = MakeNewNetworkObject("NetworkOrb", typeof(NomaiOrbTransformSync));
spawnPrefabs.Add(OrbPrefab); spawnPrefabs.Add(OrbPrefab);
AnglerPrefab = MakeNewNetworkObject(5, "NetworkAngler", typeof(AnglerTransformSync)); AnglerPrefab = MakeNewNetworkObject("NetworkAngler", typeof(AnglerTransformSync));
spawnPrefabs.Add(AnglerPrefab); spawnPrefabs.Add(AnglerPrefab);
JellyfishPrefab = MakeNewNetworkObject(6, "NetworkJellyfish", typeof(JellyfishTransformSync)); JellyfishPrefab = MakeNewNetworkObject("NetworkJellyfish", typeof(JellyfishTransformSync));
spawnPrefabs.Add(JellyfishPrefab); spawnPrefabs.Add(JellyfishPrefab);
OccasionalPrefab = MakeNewNetworkObject(7, "NetworkOccasional", typeof(OccasionalTransformSync)); OccasionalPrefab = MakeNewNetworkObject("NetworkOccasional", typeof(OccasionalTransformSync));
spawnPrefabs.Add(OccasionalPrefab); spawnPrefabs.Add(OccasionalPrefab);
RaftPrefab = MakeNewNetworkObject(8, "NetworkRaft", typeof(RaftTransformSync)); RaftPrefab = MakeNewNetworkObject("NetworkRaft", typeof(RaftTransformSync));
spawnPrefabs.Add(RaftPrefab); spawnPrefabs.Add(RaftPrefab);
DoorPrefab = MakeNewNetworkObject(9, "NetworkEclipseDoor", typeof(EclipseDoorVariableSyncer)); DoorPrefab = MakeNewNetworkObject("NetworkEclipseDoor", typeof(EclipseDoorVariableSyncer));
spawnPrefabs.Add(DoorPrefab); spawnPrefabs.Add(DoorPrefab);
ElevatorPrefab = MakeNewNetworkObject(10, "NetworkEclipseElevator", typeof(EclipseElevatorVariableSyncer)); ElevatorPrefab = MakeNewNetworkObject("NetworkEclipseElevator", typeof(EclipseElevatorVariableSyncer));
spawnPrefabs.Add(ElevatorPrefab); spawnPrefabs.Add(ElevatorPrefab);
AirlockPrefab = MakeNewNetworkObject(11, "NetworkGhostAirlock", typeof(AirlockVariableSyncer)); AirlockPrefab = MakeNewNetworkObject("NetworkGhostAirlock", typeof(AirlockVariableSyncer));
spawnPrefabs.Add(AirlockPrefab); spawnPrefabs.Add(AirlockPrefab);
ShipModulePrefab = MakeNewNetworkObject(12, "NetworkShipModule", typeof(ShipModuleTransformSync)); ShipModulePrefab = MakeNewNetworkObject("NetworkShipModule", typeof(ShipModuleTransformSync));
spawnPrefabs.Add(ShipModulePrefab); spawnPrefabs.Add(ShipModulePrefab);
ShipLegPrefab = MakeNewNetworkObject(13, "NetworkShipLeg", typeof(ShipLegTransformSync)); ShipLegPrefab = MakeNewNetworkObject("NetworkShipLeg", typeof(ShipLegTransformSync));
spawnPrefabs.Add(ShipLegPrefab); spawnPrefabs.Add(ShipLegPrefab);
ModelShipPrefab = MakeNewNetworkObject(14, "NetworkModelShip", typeof(ModelShipTransformSync)); ModelShipPrefab = MakeNewNetworkObject("NetworkModelShip", typeof(ModelShipTransformSync));
var modelShipVector3Syncer = ModelShipPrefab.AddComponent<Vector3VariableSyncer>(); var modelShipVector3Syncer = ModelShipPrefab.AddComponent<Vector3VariableSyncer>();
var modelShipThrusterVariableSyncer = ModelShipPrefab.AddComponent<ModelShipThrusterVariableSyncer>(); var modelShipThrusterVariableSyncer = ModelShipPrefab.AddComponent<ModelShipThrusterVariableSyncer>();
modelShipThrusterVariableSyncer.AccelerationSyncer = modelShipVector3Syncer; modelShipThrusterVariableSyncer.AccelerationSyncer = modelShipVector3Syncer;
spawnPrefabs.Add(ModelShipPrefab); spawnPrefabs.Add(ModelShipPrefab);
StationaryProbeLauncherPrefab = MakeNewNetworkObject(15, "NetworkStationaryProbeLauncher", typeof(StationaryProbeLauncherVariableSyncer)); StationaryProbeLauncherPrefab = MakeNewNetworkObject("NetworkStationaryProbeLauncher", typeof(StationaryProbeLauncherVariableSyncer));
spawnPrefabs.Add(StationaryProbeLauncherPrefab); spawnPrefabs.Add(StationaryProbeLauncherPrefab);
ConfigureNetworkManager(); ConfigureNetworkManager();
@ -191,11 +191,13 @@ public class QSBNetworkManager : NetworkManager, IAddComponentOnStart
} }
}); });
private static uint _assetId = 2; // 1 is the player
/// create a new network prefab from the network object prefab template. /// create a new network prefab from the network object prefab template.
/// this works by calling Unload(false) and then reloading the AssetBundle, /// this works by calling Unload(false) and then reloading the AssetBundle,
/// which makes LoadAsset give you a new resource. /// which makes LoadAsset give you a new resource.
/// see https://docs.unity3d.com/Manual/AssetBundles-Native.html. /// see https://docs.unity3d.com/Manual/AssetBundles-Native.html.
private static GameObject MakeNewNetworkObject(uint assetId, string name, Type networkBehaviourType) public static GameObject MakeNewNetworkObject(string name, Type networkBehaviourType)
{ {
var bundle = QSBCore.Helper.Assets.LoadBundle("AssetBundles/qsb_empty"); var bundle = QSBCore.Helper.Assets.LoadBundle("AssetBundles/qsb_empty");
@ -209,8 +211,11 @@ public class QSBNetworkManager : NetworkManager, IAddComponentOnStart
bundle.Unload(false); bundle.Unload(false);
template.name = name; template.name = name;
template.AddComponent<NetworkIdentity>().SetValue("_assetId", assetId); template.AddComponent<NetworkIdentity>().SetValue("_assetId", _assetId);
template.AddComponent(networkBehaviourType); template.AddComponent(networkBehaviourType);
_assetId++;
return template; return template;
} }

View File

@ -91,15 +91,11 @@ public class QSBSectorManager : WorldObjectManager
// time loop spinning ring // time loop spinning ring
{ {
// NH can remove this
var TimeLoopRing_Body = GameObject.Find("TimeLoopRing_Body"); var TimeLoopRing_Body = GameObject.Find("TimeLoopRing_Body");
if (TimeLoopRing_Body) var Sector_TimeLoopInterior = GameObject.Find("Sector_TimeLoopInterior").GetComponent<Sector>();
{ // use the same trigger as the parent sector
var Sector_TimeLoopInterior = GameObject.Find("Sector_TimeLoopInterior").GetComponent<Sector>(); FakeSector.Create(TimeLoopRing_Body, Sector_TimeLoopInterior,
// use the same trigger as the parent sector x => x._triggerRoot = Sector_TimeLoopInterior._triggerRoot);
FakeSector.Create(TimeLoopRing_Body, Sector_TimeLoopInterior,
x => x._triggerRoot = Sector_TimeLoopInterior._triggerRoot);
}
} }
// TH elevators // TH elevators

View File

@ -37,15 +37,21 @@ internal class FlyShipMessage : QSBMessage<bool>
{ {
SetCurrentFlyer(From, Data); SetCurrentFlyer(From, Data);
var shipCockpitController = ShipManager.Instance.CockpitController; var shipCockpitController = ShipManager.Instance.CockpitController;
if (shipCockpitController == null)
{
return;
}
if (Data) if (Data)
{ {
QSBPlayerManager.GetPlayer(From)?.AudioController?.PlayOneShot(AudioType.ShipCockpitBuckleUp); QSBPlayerManager.GetPlayer(From)?.AudioController?.PlayOneShot(AudioType.ShipCockpitBuckleUp);
shipCockpitController._interactVolume.DisableInteraction(); shipCockpitController._interactVolume?.DisableInteraction();
} }
else else
{ {
QSBPlayerManager.GetPlayer(From)?.AudioController?.PlayOneShot(AudioType.ShipCockpitUnbuckle); QSBPlayerManager.GetPlayer(From)?.AudioController?.PlayOneShot(AudioType.ShipCockpitUnbuckle);
shipCockpitController._interactVolume.EnableInteraction(); shipCockpitController._interactVolume?.EnableInteraction();
} }
} }
@ -57,7 +63,7 @@ internal class FlyShipMessage : QSBMessage<bool>
if (QSBCore.IsHost) if (QSBCore.IsHost)
{ {
ShipTransformSync.LocalInstance.netIdentity.SetOwner(isFlying ShipTransformSync.LocalInstance?.netIdentity.SetOwner(isFlying
? id ? id
: QSBPlayerManager.LocalPlayerId); : QSBPlayerManager.LocalPlayerId);
} }

View File

@ -6,7 +6,7 @@ using UnityEngine;
namespace QSB.ShipSync.Patches; namespace QSB.ShipSync.Patches;
internal class ShipAudioPatches : QSBPatch public class ShipAudioPatches : QSBPatch
{ {
public override QSBPatchTypes Type => QSBPatchTypes.OnClientConnect; public override QSBPatchTypes Type => QSBPatchTypes.OnClientConnect;

View File

@ -8,7 +8,7 @@ using QSB.WorldSync;
namespace QSB.ShipSync.Patches; namespace QSB.ShipSync.Patches;
[HarmonyPatch(typeof(ShipDetachableModule))] [HarmonyPatch(typeof(ShipDetachableModule))]
internal class ShipDetachableModulePatches : QSBPatch public class ShipDetachableModulePatches : QSBPatch
{ {
public override QSBPatchTypes Type => QSBPatchTypes.OnClientConnect; public override QSBPatchTypes Type => QSBPatchTypes.OnClientConnect;

View File

@ -8,7 +8,7 @@ using UnityEngine;
namespace QSB.ShipSync.Patches; namespace QSB.ShipSync.Patches;
internal class ShipFlameWashPatches : QSBPatch public class ShipFlameWashPatches : QSBPatch
{ {
public override QSBPatchTypes Type => QSBPatchTypes.OnClientConnect; public override QSBPatchTypes Type => QSBPatchTypes.OnClientConnect;

View File

@ -13,7 +13,7 @@ using UnityEngine;
namespace QSB.ShipSync.Patches; namespace QSB.ShipSync.Patches;
[HarmonyPatch] [HarmonyPatch]
internal class ShipPatches : QSBPatch public class ShipPatches : QSBPatch
{ {
public override QSBPatchTypes Type => QSBPatchTypes.OnClientConnect; public override QSBPatchTypes Type => QSBPatchTypes.OnClientConnect;

View File

@ -16,7 +16,7 @@ using UnityEngine;
namespace QSB.ShipSync; namespace QSB.ShipSync;
internal class ShipManager : WorldObjectManager public class ShipManager : WorldObjectManager
{ {
public override WorldObjectScene WorldObjectScene => WorldObjectScene.SolarSystem; public override WorldObjectScene WorldObjectScene => WorldObjectScene.SolarSystem;
@ -184,6 +184,11 @@ internal class ShipManager : WorldObjectManager
private void UpdateElectricalComponent() private void UpdateElectricalComponent()
{ {
if (ShipElectricalComponent == null)
{
return;
}
var electricalSystem = ShipElectricalComponent._electricalSystem; var electricalSystem = ShipElectricalComponent._electricalSystem;
var damaged = ShipElectricalComponent._damaged; var damaged = ShipElectricalComponent._damaged;

View File

@ -22,11 +22,6 @@ internal class OccasionalManager : WorldObjectManager
foreach (var proxy in cannon._realDebrisSectorProxies) foreach (var proxy in cannon._realDebrisSectorProxies)
{ {
// NH can remove these
if (!proxy)
{
continue;
}
SpawnOccasional(proxy.transform.root.GetAttachedOWRigidbody(), gdBody); SpawnOccasional(proxy.transform.root.GetAttachedOWRigidbody(), gdBody);
} }

View File

@ -22,17 +22,7 @@
"Connecting": "CONNECTING...", "Connecting": "CONNECTING...",
"OK": "OK", "OK": "OK",
"ServerRefusedConnection": "Server refused connection.\n{0}", "ServerRefusedConnection": "Server refused connection.\n{0}",
"ClientDisconnectWithError": "Client disconnected with error!\n{0}\nMore info: {1}", "ClientDisconnectWithError": "Client disconnected with error!\n{0}",
"TransportErrors": {
"DnsResolve": "Failed to resolve host name.",
"Refused": "Connection refused.",
"Timeout": "Connection timed out.",
"Congestion": "Congestion on transport.",
"InvalidReceive": "Error receiving message.",
"InvalidSend": "Error sending message.",
"ConnectionClosed": "Connection closed.",
"Unexpected": "Unexpected error."
},
"QSBVersionMismatch": "QSB version does not match. (Client:{0}, Server:{1})", "QSBVersionMismatch": "QSB version does not match. (Client:{0}, Server:{1})",
"OWVersionMismatch": "Outer Wilds version does not match. (Client:{0}, Server:{1})", "OWVersionMismatch": "Outer Wilds version does not match. (Client:{0}, Server:{1})",
"DLCMismatch": "DLC installation state does not match. (Client:{0}, Server:{1})", "DLCMismatch": "DLC installation state does not match. (Client:{0}, Server:{1})",

View File

@ -0,0 +1,74 @@
using QSB.HUD;
using QSB.Messaging;
using QSB.ShipSync;
using QSB.ShipSync.Messages;
using QSB.WorldSync;
using System.Linq;
using UnityEngine;
namespace QSB.Utility;
public class CommandInterpreter : MonoBehaviour, IAddComponentOnStart
{
public static bool InterpretCommand(string message)
{
if (message[0] != '/')
{
return false;
}
var commandParts = message.ToLower().Substring(1).Split(' ');
var command = commandParts[0];
switch (command)
{
case "ship":
ShipCommand(commandParts.Skip(1).ToArray());
break;
default:
MultiplayerHUDManager.Instance.WriteMessage($"Unknown command \"{command}\".", Color.red);
break;
}
return true;
}
public static void ShipCommand(string[] arguments)
{
var command = arguments[0];
switch (command)
{
case "explode":
MultiplayerHUDManager.Instance.WriteMessage($"Blowing up the ship.", Color.green);
var shipDamageController = Locator.GetShipTransform().GetComponentInChildren<ShipDamageController>();
shipDamageController.Explode();
break;
case "repair":
case "damage":
var damage = command == "damage";
switch (arguments[1])
{
case "headlight":
var headlight = QSBWorldSync.GetUnityObject<ShipHeadlightComponent>();
headlight.SetDamaged(damage);
break;
default:
break;
}
MultiplayerHUDManager.Instance.WriteMessage($"{(damage ? "Damaging" : "Repairing")} the {arguments[1]}.", Color.green);
break;
case "open-hatch":
QSBWorldSync.GetUnityObject<HatchController>().OpenHatch();
new HatchMessage(true).Send();
break;
case "close-hatch":
QSBWorldSync.GetUnityObject<HatchController>().CloseHatch();
new HatchMessage(false).Send();
break;
default:
MultiplayerHUDManager.Instance.WriteMessage($"Unknown ship command \"{command}\".", Color.red);
break;
}
}
}

View File

@ -1,8 +1,8 @@
using OWML.Common; using OWML.Common;
using OWML.Logging; using OWML.Logging;
using OWML.Utils;
using System.Diagnostics; using System.Diagnostics;
using System.Linq; using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
#pragma warning disable CS0618 #pragma warning disable CS0618
@ -28,7 +28,20 @@ public static class DebugLog
} }
else else
{ {
QSBCore.Helper.Console.WriteLine(message, type, GetCallingType()); var socket = QSBCore.Helper.Console.GetValue<IModSocket>("_socket");
socket.WriteToSocket(new ModSocketMessage
{
SenderName = "QSB",
SenderType = GetCallingType(),
Type = type,
Message = message
});
if (type == MessageType.Fatal)
{
socket.Close();
Process.GetCurrentProcess().Kill();
}
} }
} }

View File

@ -4,6 +4,9 @@ using System.Collections.Generic;
namespace QSB.Utility; namespace QSB.Utility;
/// <summary>
/// A LIFO collection with List<> functionality.
/// </summary>
public class ListStack<T> : IEnumerable<T> public class ListStack<T> : IEnumerable<T>
{ {
private List<T> _items = new(); private List<T> _items = new();
@ -12,14 +15,21 @@ public class ListStack<T> : IEnumerable<T>
private readonly bool _removeDuplicates; private readonly bool _removeDuplicates;
/// <param name="removeDuplicates">If true, all elements equal to the added item will be removed prior to adding the new element.</param>
public ListStack(bool removeDuplicates) public ListStack(bool removeDuplicates)
{ {
_removeDuplicates = removeDuplicates; _removeDuplicates = removeDuplicates;
} }
/// <summary>
/// Removes all items from the stack.
/// </summary>
public void Clear() public void Clear()
=> _items.Clear(); => _items.Clear();
/// <summary>
/// Pushes an element onto the front of the stack.
/// </summary>
public void Push(T item) public void Push(T item)
{ {
if (_removeDuplicates && _items.Contains(item)) if (_removeDuplicates && _items.Contains(item))
@ -30,7 +40,10 @@ public class ListStack<T> : IEnumerable<T>
_items.Add(item); _items.Add(item);
} }
public T Pop() /// <summary>
/// Pops an element off the front of the stack.
/// </summary>
public T PopFromFront()
{ {
if (_items.Count > 0) if (_items.Count > 0)
{ {
@ -42,7 +55,10 @@ public class ListStack<T> : IEnumerable<T>
return default; return default;
} }
public T RemoveFirstElementAndShift() /// <summary>
/// Pops an element off the back of the stack and shifts the entire stack backwards.
/// </summary>
public T PopFromBack()
{ {
if (_items.Count == 0) if (_items.Count == 0)
{ {
@ -50,32 +66,54 @@ public class ListStack<T> : IEnumerable<T>
} }
var firstElement = _items[0]; var firstElement = _items[0];
_items.RemoveAt(0);
if (_items.Count == 0)
{
return firstElement;
}
// shift list left
// allocates blehhh who cares
_items = _items.GetRange(1, _items.Count - 1);
return firstElement; return firstElement;
} }
public T Peek() => _items.Count > 0 /// <summary>
/// Returns the element at the front of the stack.
/// </summary>
public T PeekFront() => _items.Count > 0
? _items[_items.Count - 1] ? _items[_items.Count - 1]
: default; : default;
/// <summary>
/// Returns the element at the back of the stack.
/// </summary>
public T PeekBack() => _items.Count > 0
? _items[0]
: default;
/// <summary>
/// Removes the element at the given index, where 0 is the back of the stack. The stack will shift backwards to fill empty space.
/// </summary>
public void RemoveAt(int index) public void RemoveAt(int index)
=> _items.RemoveAt(index); => _items.RemoveAt(index);
/// <summary>
/// Removes the first occurence (back to front) of an item.
/// </summary>
public bool Remove(T item) public bool Remove(T item)
=> _items.Remove(item); => _items.Remove(item);
/// <summary>
/// Removes all elements that match the given predicate.
/// </summary>
public int RemoveAll(Predicate<T> match) public int RemoveAll(Predicate<T> match)
=> _items.RemoveAll(match); => _items.RemoveAll(match);
/// <summary>
/// Returns the index of the given item, where 0 is the back of the stack.
/// </summary>
public int IndexOf(T item)
=> _items.IndexOf(item);
public T this[int index]
{
get => _items[index];
set => _items[index] = value;
}
IEnumerator<T> IEnumerable<T>.GetEnumerator() => ((IEnumerable<T>)_items).GetEnumerator(); IEnumerator<T> IEnumerable<T>.GetEnumerator() => ((IEnumerable<T>)_items).GetEnumerator();
public IEnumerator GetEnumerator() => ((IEnumerable<T>)_items).GetEnumerator(); public IEnumerator GetEnumerator() => ((IEnumerable<T>)_items).GetEnumerator();
} }

View File

@ -208,8 +208,7 @@ public static class QSBWorldSync
{ {
// So objects have time to be deleted, made, whatever // So objects have time to be deleted, made, whatever
// i.e. wait until Start has been called // i.e. wait until Start has been called
// TODO: see if this number of frames actually works. TWEAK! Delay.RunNextFrame(() => BuildWorldObjects(loadScene).Forget());
Delay.RunFramesLater(10, () => BuildWorldObjects(loadScene).Forget());
} }
}; };

View File

@ -37,6 +37,12 @@
"type": "toggle", "type": "toggle",
"value": true, "value": true,
"tooltip": "Show extra HUD elements, like player status and minimap icons." "tooltip": "Show extra HUD elements, like player status and minimap icons."
},
"textChatInput": {
"title": "Text Chat Input",
"type": "toggle",
"value": true,
"tooltip": "Disable this if using NomaiVR, or any other mod with conflicting inputs."
} }
} }
} }

View File

@ -7,8 +7,8 @@
"body": "- Disable *all* other mods. (Can heavily affect performance)\n- Make sure you are not running any other network-intensive applications." "body": "- Disable *all* other mods. (Can heavily affect performance)\n- Make sure you are not running any other network-intensive applications."
}, },
"uniqueName": "Raicuparta.QuantumSpaceBuddies", "uniqueName": "Raicuparta.QuantumSpaceBuddies",
"version": "0.28.1", "version": "0.29.1",
"owmlVersion": "2.9.0", "owmlVersion": "2.9.3",
"dependencies": [ "_nebula.MenuFramework", "JohnCorby.VanillaFix" ], "dependencies": [ "_nebula.MenuFramework", "JohnCorby.VanillaFix" ],
"pathsToPreserve": [ "debugsettings.json" ], "pathsToPreserve": [ "debugsettings.json" ],
"requireLatestVersion": true "requireLatestVersion": true

View File

@ -19,13 +19,13 @@ Spoilers within!
### Easy installation (recommended) ### Easy installation (recommended)
- [Install the Outer Wilds Mod Manager](https://github.com/Raicuparta/ow-mod-manager#how-do-i-use-this); - [Install the Outer Wilds Mod Manager](https://outerwildsmods.com/mod-manager/);
- Install Quantum Space Buddies from the mod list displayed in the application; - Install Quantum Space Buddies from the mod list displayed in the application;
- If you can't get the mod manager to work, follow the instructions for manual installation. - If you can't get the mod manager to work, follow the instructions for manual installation.
### Manual installation ### Manual installation
- [Install OWML](https://github.com/amazingalek/owml#installation); - [Install OWML](https://github.com/ow-mods/owml#installation);
- [Download the latest Quantum Space Buddies release](https://github.com/misternebula/quantum-space-buddies/releases/latest); - [Download the latest Quantum Space Buddies release](https://github.com/misternebula/quantum-space-buddies/releases/latest);
- Extract the `QSB` directory to the `OWML/Mods` directory; - Extract the `QSB` directory to the `OWML/Mods` directory;
- Run `OWML.Launcher.exe` to start the game. - Run `OWML.Launcher.exe` to start the game.
@ -47,7 +47,7 @@ Spoilers within!
## Frequently Asked Questions ## Frequently Asked Questions
### I keep timing out when trying to connect! ### I keep timing out when trying to connect!
Check the mod settings for "Use KCP Transport". You have to forward port 7777 as TCP/UDP, or use Hamachi. ALL PLAYERS MUST HAVE THIS AS THE SAME VALUE. Check the mod settings for "Use KCP Transport". You have to forward port 7777 as TCP/UDP, or use Hamachi. ***All players must either be using KCP, or not using KCP.***
### Requirements ### Requirements
- Latest version of OWML. - Latest version of OWML.
@ -89,102 +89,22 @@ QSB is a fully synced game. The other players are actually there in the world, a
Outer Wilds Online is easier to set up, but much more basic in its features. The other players cannot affect your game, and do not contribute to anything in your save. The loop is entirely per-player. Outer Wilds Online is easier to set up, but much more basic in its features. The other players cannot affect your game, and do not contribute to anything in your save. The loop is entirely per-player.
### Why would someone make this mod? Seems like a lot of effort for no reward.
Good question.
Let me know if you find an answer.
**Update**: a plausible answer is the enjoyment you get seeing/hearing about others playing with their friends :)
## Translating ## Translating
See [TRANSLATING.md](TRANSLATING.md) See [TRANSLATING.md](TRANSLATING.md)
## Development Setup ## Development Setup / Contributing
- [Download the Outer Wilds Mod Manager](https://github.com/raicuparta/ow-mod-manager) and install it anywhere you like; See [DEVELOPMENT.md](DEVELOPMENT.md)
- Install OWML using the Mod Manager
- Clone QSB's source
- Open the file `DevEnv.targets` in your favorite text editor
- (optional if copying built dlls manually) Edit the entry `<OwmlDir>` to point to your OWML directory (it is installed inside the Mod Manager directory)
- (optional if no unity project) Edit the entry `<UnityAssetsDir>` to point to the Assets folder of the QSB unity project
- Open the project solution file `QSB.sln` in Visual Studio 2022
If developing with the Steam version of Outer Wilds you can't run multiple instances of the game by default. To do so, create a file called `steam_appid.txt` in your Outer Wilds directory and write `753640` inside it, then run the exe directly.
A powerful PC is needed for development, due to the high amount of RAM and CPU needed to run 2 or 3 instances of modded Outer Wilds.
It is also recommended to lower all graphics settings to minimum, be in windowed mode, and lower resolution to roughly a quarter of your monitor space. This lets you run multiple instances of Outer Wilds to quickly test QSB.
Some debugging options exist to make things easier. These come in the form of actions and settings.
### Debug Actions :
Hold Q and press :
- Numpad 1 - Teleport to nearest player.
- Numpad 2 - If holding LeftShift, warp to the dreamworld Vault fire. If not, warp to the Endless Canyon.
- Numpad 3 - Unlock the Sealed Vault.
- Numpad 4 - Damage the ship's electrical system.
- Numpad 5 - Trigger the supernova.
- Numpad 6 - Set the flags for having met Solanum and the Prisoner.
- Numpad 7 - Warp to the Vessel.
- Numpad 8 - Insert the Advanced Warp Core into the Vessel.
- Numpad 9 - If holding LeftShift, load the SolarSystem scene. If not, load the EyeOfTheUniverse scene.
- Numpad 0 - Revive a random dead player.
### Debug Settings :
Create a file called `debugsettings.json` in the mod folder.
The template for this file is this :
```
{
"dumpWorldObjects": false,
"instanceIdInLogs": false,
"hookDebugLogs": false,
"avoidTimeSync": false,
"autoStart": false,
"kickEveryone": false,
"disableLoopDeath": false,
"debugMode": false,
"drawGui": false,
"drawLines": false,
"drawLabels": false,
"drawQuantumVisibilityObjects": false,
"drawGhostAI": false,
"greySkybox": false
}
```
- dumpWorldObjects - Creates a file with information about the WorldObjects that were created.
- instanceIdInLogs - Appends the game instance id to every log message sent.
- hookDebugLogs - Print Unity logs and warnings.
- avoidTimeSync - Disables the syncing of time.
- autoStart - Host/connect automatically for faster testing.
- kickEveryone - Kick anyone who joins a game.
- disableLoopDeath - Make it so the loop doesn't end when everyone is dead.
- debugMode - Enables debug mode. If this is set to `false`, none of the following settings do anything.
- drawGui - Draws a GUI at the top of the screen that gives information on many things.
- drawLines - Draws gizmo-esque lines around things. Indicates reference sectors/transforms, triggers, etc. LAGGY.
- drawLabels - Draws GUI labels attached to some objects. LAGGY.
- drawQuantumVisibilityObjects - Indicates visibility objects with an orange shape.
- drawGhostAI - Draws debug lines and labels just for the ghosts.
- greySkybox - Turns the skybox grey. Useful in the Eye, where it's pretty dark.
**Warning : Mod development can lead to unexpected errors in your computer system.**
- **When editing the networking code, mistakes can lead to QSB overwhelming your network connection with excess packets**.
- **Too high RAM usage will lead to Outer Wilds sticking at ~31% loading, then crashing**.
- **There have been instances of graphics cards crashing, and needing to be disabled/re-enabled from Device Manager.**
## Authors and Special Thanks ## Authors and Special Thanks
### Authors ### Authors
- [\_nebula](https://github.com/misternebula) - Developer of v0.3.0 onwards - [\_nebula](https://github.com/misternebula) - Lead Dev *(v0.3.0 onwards.)*
- [JohnCorby](https://github.com/JohnCorby) - Co-developer of v0.13.0 onwards. - [JohnCorby](https://github.com/JohnCorby) - Lead Dev *(v0.13.0 onwards)*
- [AmazingAlek](https://github.com/amazingalek) - Developer of v0.1.0 - v0.7.1. - [AmazingAlek](https://github.com/amazingalek) - Ex-Developer *(v0.1.0 - v0.7.1)*
- [Raicuparta](https://github.com/Raicuparta) - Developer of v0.1.0 - v0.2.0. - [Raicuparta](https://github.com/Raicuparta) - Ex-Developer *(v0.1.0 - v0.2.0)*
### Contributers ### Contributers