Merge branch 'dev' into steamworks-v3

This commit is contained in:
_nebula 2023-09-17 22:03:13 +01:00
commit ac8e7d6ee9
25 changed files with 188 additions and 83 deletions

View File

@ -1,6 +1,11 @@
name: Build
on: push
on:
push:
paths-ignore:
- "*.md"
- "LICENSE"
- ".gitignore"
jobs:
build:

View File

@ -23,6 +23,11 @@ public class APITestMod : ModBehaviour
qsbAPI.OnPlayerJoin().AddListener((uint playerId) => ModHelper.Console.WriteLine($"{playerId} joined the game!", MessageType.Success));
qsbAPI.OnPlayerLeave().AddListener((uint playerId) => ModHelper.Console.WriteLine($"{playerId} left the game!", MessageType.Success));
qsbAPI.OnChatMessage().AddListener((string message, uint from) => ModHelper.Console.WriteLine($"Chat message \"{message}\" from {from} ({(from == uint.MaxValue ? "QSB" : qsbAPI.GetPlayerName(from))})"));
qsbAPI.RegisterHandler<string>("apitest-string", MessageHandler);
qsbAPI.RegisterHandler<int>("apitest-int", MessageHandler);
qsbAPI.RegisterHandler<float>("apitest-float", MessageHandler);
button.onClick.AddListener(() =>
{
@ -42,16 +47,16 @@ public class APITestMod : ModBehaviour
ModHelper.Console.WriteLine($"Retreiving custom data : {qsbAPI.GetCustomData<string>(qsbAPI.GetLocalPlayerID(), "APITEST.TESTSTRING")}");
ModHelper.Console.WriteLine("Sending string message test...");
qsbAPI.RegisterHandler<string>("apitest-string", MessageHandler);
qsbAPI.SendMessage("apitest-string", "STRING MESSAGE", receiveLocally: true);
ModHelper.Console.WriteLine("Sending int message test...");
qsbAPI.RegisterHandler<int>("apitest-int", MessageHandler);
qsbAPI.SendMessage("apitest-int", 123, receiveLocally: true);
ModHelper.Console.WriteLine("Sending float message test...");
qsbAPI.RegisterHandler<float>("apitest-float", MessageHandler);
qsbAPI.SendMessage("apitest-float", 3.14f, receiveLocally: true);
qsbAPI.SendChatMessage("Non-system chat message", false, Color.white);
qsbAPI.SendChatMessage("System chat message", true, Color.cyan);
});
};
}

View File

@ -10,7 +10,7 @@
<ItemGroup>
<PackageReference Include="OuterWildsGameLibs" Version="1.1.13.457" IncludeAssets="compile" />
<PackageReference Include="OWML" Version="2.9.5" IncludeAssets="compile" />
<PackageReference Include="OWML" Version="2.9.7" IncludeAssets="compile" />
</ItemGroup>
<ItemGroup>

View File

@ -1,5 +1,6 @@
using System;
using OWML.Common;
using UnityEngine;
using UnityEngine.Events;
public interface IQSBAPI
@ -56,7 +57,7 @@ public interface IQSBAPI
/// <summary>
/// Sets some arbitrary data for a given player.
/// </summary>
/// <typeparam name="T">The type of the data.</typeparam>
/// <typeparam name="T">The type of the data. If not serializable, data will not be synced.</typeparam>
/// <param name="playerId">The ID of the player.</param>
/// <param name="key">The unique key to access this data by.</param>
/// <param name="data">The data to set.</param>
@ -96,4 +97,23 @@ public interface IQSBAPI
void RegisterHandler<T>(string messageType, Action<uint, T> handler);
#endregion
#region Chat
/// <summary>
/// Invoked when a chat message is received.
/// The string is the message body.
/// The uint is the player who sent the message. If it's a system message, this is uint.MaxValue.
/// </summary>
UnityEvent<string, uint> OnChatMessage();
/// <summary>
/// Sends a message in chat.
/// </summary>
/// <param name="message">The text of the message.</param>
/// <param name="systemMessage">If false, the message is sent as if the local player wrote it manually. If true, the message has no player attached to it, like the player join messages.</param>
/// <param name="color">The color of the message.</param>
void SendChatMessage(string message, bool systemMessage, Color color);
#endregion
}

View File

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

View File

@ -19,12 +19,12 @@ We recommend using the Outer Wilds Mod Manager, but you can use OWML on its own
- 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
## Multiple instances on 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.
- 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.
- Either turn on "Force Exe" in the mod manager, or run OuterWilds.exe directly.
## Building
Simply build the solution normally. (`Build > Build Solution` or CTRL-SHIFT-B)
@ -48,22 +48,24 @@ Use the API by copying [the API definition](https://github.com/misternebula/quan
## Debugging
### Debug Actions :
Press Q + Numpad Enter to toggle debug mode in game (corresponds with the debug setting "debugMode" in the section below).
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 2 - If holding LeftShift, warp to the dreamworld Vault fire. If not, warp to the Endless Canyon. If already in dreamworld, pick up lantern.
- 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 7 - Warp to the Vessel and insert the warp core.
- Numpad 8 - Spawn a fake player. For Ghostbuster testing.
- 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.
Create a file called `debugsettings.json` in the QSB folder.
The template for this file is this :
```json

View File

@ -12,7 +12,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="OuterWildsGameLibs" Version="1.1.13.457" />
<PackageReference Include="OWML" Version="2.9.5" />
<PackageReference Include="OWML" Version="2.9.7" />
<Reference Include="../Mirror/*.dll" />
</ItemGroup>
<ItemGroup>

View File

@ -11,7 +11,6 @@ public static class AddonDataManager
public static void OnReceiveDataMessage(int hash, object data, uint from)
{
DebugLog.DebugWrite($"Received addon message of with hash {hash} from {from}!");
if (!_handlers.TryGetValue(hash, out var handler))
{
DebugLog.DebugWrite($"unknown addon message type with hash {hash}", MessageType.Error);
@ -22,7 +21,6 @@ public static class AddonDataManager
public static void RegisterHandler<T>(int hash, Action<uint, T> handler)
{
DebugLog.DebugWrite($"Registering addon message handler for hash {hash} with type {typeof(T).Name}");
_handlers.Add(hash, (from, data) => handler(from, (T)data));
}
}

View File

@ -1,5 +1,6 @@
using System;
using OWML.Common;
using UnityEngine;
using UnityEngine.Events;
public interface IQSBAPI
@ -55,10 +56,8 @@ public interface IQSBAPI
/// <summary>
/// Sets some arbitrary data for a given player.
///
/// Not synced.
/// </summary>
/// <typeparam name="T">The type of the data.</typeparam>
/// <typeparam name="T">The type of the data. If not serializable, data will not be synced.</typeparam>
/// <param name="playerId">The ID of the player.</param>
/// <param name="key">The unique key to access this data by.</param>
/// <param name="data">The data to set.</param>
@ -66,8 +65,6 @@ public interface IQSBAPI
/// <summary>
/// Returns some arbitrary data from a given player.
///
/// Not synced.
/// </summary>
/// <typeparam name="T">The type of the data.</typeparam>
/// <param name="playerId">The ID of the player.</param>
@ -100,4 +97,23 @@ public interface IQSBAPI
void RegisterHandler<T>(string messageType, Action<uint, T> handler);
#endregion
#region Chat
/// <summary>
/// Invoked when a chat message is received.
/// The string is the message body.
/// The uint is the player who sent the message. If it's a system message, this is uint.MaxValue.
/// </summary>
UnityEvent<string, uint> OnChatMessage();
/// <summary>
/// Sends a message in chat.
/// </summary>
/// <param name="message">The text of the message.</param>
/// <param name="systemMessage">If false, the message is sent as if the local player wrote it manually. If true, the message has no player attached to it, like the player join messages.</param>
/// <param name="color">The color of the message.</param>
void SendChatMessage(string message, bool systemMessage, Color color);
#endregion
}

View File

@ -5,7 +5,10 @@ using QSB.Messaging;
using QSB.Player;
using System;
using System.Linq;
using QSB.HUD;
using QSB.HUD.Messages;
using UnityEngine.Events;
using UnityEngine;
namespace QSB.API;
@ -35,6 +38,17 @@ public class QSBAPI : IQSBAPI
public void RegisterHandler<T>(string messageType, Action<uint, T> handler)
=> AddonDataManager.RegisterHandler(messageType.GetStableHashCode(), handler);
public UnityEvent<string, uint> OnChatMessage() => MultiplayerHUDManager.OnChatMessageEvent;
public void SendChatMessage(string message, bool systemMessage, Color color)
{
var fromName = systemMessage
? "QSB"
: QSBPlayerManager.LocalPlayer.Name;
new ChatMessage($"{fromName}: {message}", color).Send();
}
}
internal static class QSBAPIEvents

View File

@ -4,6 +4,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using QSB.Player;
using UnityEngine;
namespace QSB.HUD.Messages;
@ -17,5 +18,27 @@ public class ChatMessage : QSBMessage<(string message, Color color)>
public override void OnReceiveRemote()
{
MultiplayerHUDManager.Instance.WriteMessage(Data.message, Data.color);
var fromPlayer = QSBPlayerManager.GetPlayer(From);
var qsb = false;
string name;
if (Data.message.StartsWith("QSB: "))
{
name = "QSB: ";
qsb = true;
}
else if (Data.message.StartsWith($"{fromPlayer.Name}: "))
{
name = $"{fromPlayer.Name}: ";
}
else
{
// uhhh idk what happened
MultiplayerHUDManager.OnChatMessageEvent.Invoke(Data.message, From);
return;
}
var messageWithoutName = Data.message.Remove(Data.message.IndexOf(name), name.Length);
MultiplayerHUDManager.OnChatMessageEvent.Invoke(messageWithoutName, qsb ? uint.MaxValue : From);
}
}

View File

@ -9,6 +9,7 @@ using QSB.WorldSync;
using System;
using System.Linq;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.InputSystem;
using UnityEngine.SceneManagement;
using UnityEngine.UI;
@ -41,6 +42,9 @@ public class MultiplayerHUDManager : MonoBehaviour, IAddComponentOnStart
public static readonly ListStack<HUDIcon> HUDIconStack = new(true);
public class ChatEvent : UnityEvent<string, uint> { }
public static readonly ChatEvent OnChatMessageEvent = new();
private void Start()
{
Instance = this;
@ -81,7 +85,8 @@ public class MultiplayerHUDManager : MonoBehaviour, IAddComponentOnStart
// perks of being a qsb dev :-)
public void WriteSystemMessage(string message, Color color)
{
WriteMessage(message, color);
WriteMessage($"QSB: {message}", color);
OnChatMessageEvent.Invoke(message, uint.MaxValue);
}
public void WriteMessage(string message, Color color)

View File

@ -5,50 +5,6 @@ namespace QSB.Inputs;
public class QSBInputManager : MonoBehaviour, IAddComponentOnStart
{
// TODO : finish instruments - disabled for 0.7.0 release
/*
public static event Action ChertTaunt;
public static event Action EskerTaunt;
public static event Action RiebeckTaunt;
public static event Action GabbroTaunt;
public static event Action FeldsparTaunt;
public static event Action ExitTaunt;
public void Update()
{
if (Input.GetKey(KeyCode.T))
{
// Listed order is from sun to dark bramble
if (Input.GetKeyDown(KeyCode.Alpha1))
{
ChertTaunt?.Invoke();
}
else if (Input.GetKeyDown(KeyCode.Alpha2))
{
EskerTaunt?.Invoke();
}
else if (Input.GetKeyDown(KeyCode.Alpha5))
{
RiebeckTaunt?.Invoke();
}
else if (Input.GetKeyDown(KeyCode.Alpha4))
{
GabbroTaunt?.Invoke();
}
else if (Input.GetKeyDown(KeyCode.Alpha3))
{
FeldsparTaunt?.Invoke();
}
}
if (OWInput.GetValue(InputLibrary.moveXZ, InputMode.None) != Vector2.zero
|| OWInput.GetValue(InputLibrary.jump, InputMode.None) != 0f)
{
ExitTaunt?.Invoke();
}
}
*/
public static QSBInputManager Instance { get; private set; }
public void Start()

View File

@ -93,6 +93,11 @@ public static class QSBMessageManager
private static void OnClientReceive(QSBMessage msg)
{
if (msg == null)
{
return;
}
if (PlayerTransformSync.LocalInstance == null)
{
DebugLog.ToConsole($"Warning - Tried to handle message {msg} before local player was established.", MessageType.Warning);

View File

@ -55,6 +55,11 @@ public class RequestStateResyncMessage : QSBMessage
// Initial sync of all custom data from APIs
foreach (var kvp in QSBPlayerManager.LocalPlayer._customData)
{
if (!kvp.Value.GetType().IsSerializable)
{
continue;
}
new AddonCustomDataSyncMessage(QSBPlayerManager.LocalPlayerId, kvp.Key, kvp.Value) { To = From }.Send();
}
}

View File

@ -188,7 +188,7 @@ public partial class PlayerInfo
{
_customData[key] = data;
if (!QSBPatch.Remote)
if (!QSBPatch.Remote && typeof(T).IsSerializable)
{
new AddonCustomDataSyncMessage(PlayerId, key, data).Send();
}

View File

@ -72,7 +72,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="OuterWildsGameLibs" Version="1.1.13.457" IncludeAssets="compile" />
<PackageReference Include="OWML" Version="2.9.5" IncludeAssets="compile" />
<PackageReference Include="OWML" Version="2.9.7" IncludeAssets="compile" />
<Reference Include="..\Mirror\*.dll" />
<Reference Include="..\UniTask\*.dll" />
<ProjectReference Include="..\FizzySteamworks\FizzySteamworks.csproj" />

View File

@ -0,0 +1,2 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:String x:Key="/Default/CodeInspection/CSharpLanguageProject/LanguageLevel/@EntryValue">Default</s:String></wpf:ResourceDictionary>

View File

@ -291,7 +291,7 @@ public class QSBCore : ModBehaviour
/// <summary>
/// Registers an addon that shouldn't be considered for hash checks when joining.
/// This addon MUST NOT send any network messages, or create any worldobjects.
/// This addon MUST NOT create any WorldObjects or NetworkBehaviours.
/// </summary>
/// <param name="addon">The behaviour of the addon.</param>
public static void RegisterNotRequiredForAllPlayers(IModBehaviour addon)
@ -301,7 +301,9 @@ public class QSBCore : ModBehaviour
foreach (var type in addonAssembly.GetTypes())
{
if (typeof(WorldObjectManager).IsAssignableFrom(type) || typeof(IWorldObject).IsAssignableFrom(type))
if (typeof(WorldObjectManager).IsAssignableFrom(type) ||
typeof(IWorldObject).IsAssignableFrom(type) ||
typeof(NetworkBehaviour).IsAssignableFrom(type))
{
DebugLog.ToConsole($"Addon \"{uniqueName}\" cannot be cosmetic, as it creates networking objects.", MessageType.Error);
return;

View File

@ -68,6 +68,7 @@ public class QSBNetworkManager : NetworkManager, IAddComponentOnStart
private (TransportError error, string reason) _lastTransportError = (TransportError.Unexpected, "transport did not give an error. uh oh");
private static LatencySimulation _latencyTransport;
private static kcp2k.KcpTransport _kcpTransport;
private static FizzySteamworks _steamTransport;
@ -83,7 +84,15 @@ public class QSBNetworkManager : NetworkManager, IAddComponentOnStart
_steamTransport = gameObject.AddComponent<FizzySteamworks>();
}
transport = QSBCore.UseKcpTransport ? _kcpTransport : _steamTransport;
{
_latencyTransport = gameObject.AddComponent<LatencySimulation>();
_latencyTransport.reliableLatency = _latencyTransport.unreliableLatency = QSBCore.DebugSettings.LatencySimulation;
_latencyTransport.wrap = QSBCore.UseKcpTransport ? _kcpTransport : _steamTransport;
}
transport = QSBCore.DebugSettings.LatencySimulation > 0
? _latencyTransport
: QSBCore.UseKcpTransport ? _kcpTransport : _steamTransport;
gameObject.SetActive(true);
@ -152,10 +161,20 @@ public class QSBNetworkManager : NetworkManager, IAddComponentOnStart
{
return;
}
if (singleton != null)
{
singleton.transport = Transport.active = QSBCore.UseKcpTransport ? _kcpTransport : _steamTransport;
if (QSBCore.DebugSettings.LatencySimulation > 0)
{
_latencyTransport.wrap = QSBCore.UseKcpTransport ? _kcpTransport : _steamTransport;
singleton.transport = Transport.active = _latencyTransport;
}
else
{
singleton.transport = Transport.active = QSBCore.UseKcpTransport ? _kcpTransport : _steamTransport;
}
}
if (MenuManager.Instance != null)
{
MenuManager.Instance.OnLanguageChanged(); // hack to update text

View File

@ -1,6 +1,6 @@
using QSB.HUD;
using EpicTransport;
using QSB.HUD;
using QSB.Messaging;
using QSB.ShipSync;
using QSB.ShipSync.Messages;
using QSB.WorldSync;
using System.Linq;
@ -25,14 +25,39 @@ public class CommandInterpreter : MonoBehaviour, IAddComponentOnStart
case "ship":
ShipCommand(commandParts.Skip(1).ToArray());
break;
case "copy-id":
CopyProductUserID();
break;
default:
MultiplayerHUDManager.Instance.WriteMessage($"Unknown command \"{command}\".", Color.red);
WriteToChat($"Unknown command \"{command}\".", Color.red);
break;
}
return true;
}
private static void WriteToChat(string message, Color color)
{
// TODO : make italics work in chat so we can use them here
MultiplayerHUDManager.Instance.WriteMessage(message, color);
}
public static void CopyProductUserID()
{
if (QSBCore.UseKcpTransport)
{
WriteToChat($"Cannot get Product User ID for KCP-hosted server.", Color.red);
return;
}
var productUserID = QSBCore.IsHost
? EOSSDKComponent.LocalUserProductIdString
: QSBNetworkManager.singleton.networkAddress;
GUIUtility.systemCopyBuffer = productUserID;
WriteToChat($"Copied {productUserID} to the clipboard.", Color.green);
}
public static void ShipCommand(string[] arguments)
{
var command = arguments[0];
@ -40,7 +65,7 @@ public class CommandInterpreter : MonoBehaviour, IAddComponentOnStart
switch (command)
{
case "explode":
MultiplayerHUDManager.Instance.WriteMessage($"Blowing up the ship.", Color.green);
WriteToChat($"Blowing up the ship.", Color.green);
var shipDamageController = Locator.GetShipTransform().GetComponentInChildren<ShipDamageController>();
shipDamageController.Explode();
break;
@ -56,7 +81,7 @@ public class CommandInterpreter : MonoBehaviour, IAddComponentOnStart
default:
break;
}
MultiplayerHUDManager.Instance.WriteMessage($"{(damage ? "Damaging" : "Repairing")} the {arguments[1]}.", Color.green);
WriteToChat($"{(damage ? "Damaging" : "Repairing")} the {arguments[1]}.", Color.green);
break;
case "open-hatch":
QSBWorldSync.GetUnityObject<HatchController>().OpenHatch();
@ -67,7 +92,7 @@ public class CommandInterpreter : MonoBehaviour, IAddComponentOnStart
new HatchMessage(false).Send();
break;
default:
MultiplayerHUDManager.Instance.WriteMessage($"Unknown ship command \"{command}\".", Color.red);
WriteToChat($"Unknown ship command \"{command}\".", Color.red);
break;
}
}

View File

@ -171,7 +171,7 @@ public class DebugActions : MonoBehaviour, IAddComponentOnStart
var dreamLanternItem = QSBWorldSync.GetWorldObjects<QSBDreamLanternItem>().First(x =>
x.AttachedObject._lanternType == DreamLanternType.Functioning &&
QSBPlayerManager.PlayerList.All(y => y.HeldItem != x) &&
!x.AttachedObject.GetLanternController().IsLit()
!x.AttachedObject.GetLanternController().IsLit() // lit = someone else is holding. backup in case held item isnt initial state synced
).AttachedObject;
Locator.GetToolModeSwapper().GetItemCarryTool().PickUpItemInstantly(dreamLanternItem);
}

View File

@ -29,6 +29,9 @@ public class DebugSettings
[JsonProperty("disableLoopDeath")]
public bool DisableLoopDeath;
[JsonProperty("latencySimulation")]
public int LatencySimulation;
[JsonProperty("debugMode")]
public bool DebugMode;

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."
},
"uniqueName": "Raicuparta.QuantumSpaceBuddies",
"version": "0.30.2",
"owmlVersion": "2.9.5",
"version": "0.30.3",
"owmlVersion": "2.9.7",
"dependencies": [ "_nebula.MenuFramework", "JohnCorby.VanillaFix" ],
"pathsToPreserve": [ "debugsettings.json" ],
"requireLatestVersion": true

View File

@ -110,7 +110,7 @@ See [DEVELOPMENT.md](DEVELOPMENT.md)
- [Chris Yeninas](https://github.com/PhantomGamers) - Help with project files and GitHub workflows.
- [Tlya](https://github.com/Tllya) - Russian translation.
- [Xen](https://github.com/xen-42) - French translation, and help with particle effects and sounds.
- [xen](https://github.com/xen-42) - French translation, and help with syncing particle/sound effects, fixing lantern item bugs, and syncing addon data.
- [ShoosGun](https://github.com/ShoosGun) - Portuguese translation.
- [DertolleDude](https://github.com/DertolleDude) - German translation.
- [SakuradaYuki](https://github.com/SakuradaYuki) - Chinese translation.