Skip to content

Commit

Permalink
Merge pull request #42 from Sewer56/network-overhaul
Browse files Browse the repository at this point in the history
Added: Useful Networking Related APIs & Functionality to Mod Helper
  • Loading branch information
doombubbles authored Jun 3, 2022
2 parents 655152b + 0bc41c4 commit 29dee7a
Show file tree
Hide file tree
Showing 9 changed files with 220 additions and 43 deletions.
48 changes: 30 additions & 18 deletions BloonsTD6 Mod Helper/Api/Coop/MessageUtils.cs
Original file line number Diff line number Diff line change
@@ -1,42 +1,54 @@
using Newtonsoft.Json;
using System;
using System.Text;
using Newtonsoft.Json;
using NinjaKiwi.NKMulti;
using UnhollowerBaseLib;

namespace BTD_Mod_Helper.Api.Coop
{
/// <summary>
/// Utility functions used for sending messages over the network.
/// </summary>
public class MessageUtils
{
public static Message CreateMessage<T>(T objectToSend, string code = "") where T : Il2CppSystem.Object
/// <summary>
/// Creates a message to be sent over the network.
/// The message will be serialized as JSON.
/// </summary>
/// <param name="objectToSend">The object to be sent. The object's properties will be serialized.</param>
/// <param name="code">Unique code for your specific message.</param>
public static Message CreateMessageEx<T>(T objectToSend, string code = "")
{
var json = JsonConvert.SerializeObject(objectToSend);
var serialize = Il2CppSystem.Text.Encoding.Default.GetBytes(json);
var serialize = Encoding.Default.GetBytes(json);
code = string.IsNullOrEmpty(code) ? MelonMain.coopMessageCode : code;
return new Message(code, serialize);

//throw new System.Exception("This code was broken in BTD6 update 27.0");
// commented code below broke in update 27.0
//Il2CppStructArray<byte> serialize = SerialisationUtil.Serialise(objectToSend);

// this code is commented because code above is broken
/*code = string.IsNullOrEmpty(code) ? MelonMain.coopMessageCode : code;
return new Message(code, serialize);*/
}

/// <summary>
/// Reads a message sent from the network.
/// Assumes message is sent as JSON. (via <see cref="CreateMessageEx{T}"/>)
/// </summary>
/// <param name="serializedMessage">Raw bytes received from the network.</param>
public static T ReadMessage<T>(Il2CppStructArray<byte> serializedMessage)
{
var modMessage = Il2CppSystem.Text.Encoding.Default.GetString(serializedMessage);
var modMessage = Encoding.Default.GetString(serializedMessage);
return JsonConvert.DeserializeObject<T>(modMessage);

//throw new System.Exception("This code was broken in BTD6 update 27.0");
// commented code below broke in update 27.0
//return SerialisationUtil.Deserialise<T>(serializedMessage);
}

/// <summary>
/// Reads a message sent from the network.
/// Assumes message is sent as JSON. (via <see cref="CreateMessageEx{T}"/>)
/// </summary>
/// <param name="message">Message received from the network.</param>
public static T ReadMessage<T>(Message message)
{
//throw new System.Exception("This code was broken in BTD6 update 27.0");
// commented code below broke in update 27.0
return ReadMessage<T>(message.bytes);
}

#region Backwards Binary Compatibility
[Obsolete($"For backwards compatibility reasons only, please use {nameof(CreateMessageEx)}")]
public static Message CreateMessage<T>(T objectToSend, string code = "") where T : Il2CppSystem.Object => CreateMessage(objectToSend, code);
#endregion
}
}
6 changes: 4 additions & 2 deletions BloonsTD6 Mod Helper/BloonsTD6 Mod Helper.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,15 @@
<TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<Deterministic>true</Deterministic>
<LangVersion>latest</LangVersion>
<!-- I want my nullables goddamn it. -->
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'BloonsTD6 - Debug|AnyCPU'">
<DebugSymbols>true</DebugSymbols>
<OutputPath>bin\BloonsTD6 - Debug\</OutputPath>
<DefineConstants>BloonsTD6;TRACE</DefineConstants>
<DebugType>full</DebugType>
<PlatformTarget>AnyCPU</PlatformTarget>
<LangVersion>7.3</LangVersion>
<ErrorReport>prompt</ErrorReport>
<DocumentationFile>obj\BloonsTD6 - Debug\BloonsTD6 Mod Helper.xml</DocumentationFile>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
Expand All @@ -31,7 +32,6 @@
<Optimize>true</Optimize>
<DebugType>pdbonly</DebugType>
<PlatformTarget>AnyCPU</PlatformTarget>
<LangVersion>7.3</LangVersion>
<ErrorReport>prompt</ErrorReport>
<DocumentationFile>bin\BloonsTD6 - Release\BloonsTD6 Mod Helper.xml</DocumentationFile>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
Expand Down Expand Up @@ -123,6 +123,8 @@
<Compile Include="Patches\Player\Btd6Player_Save.cs" />
<Compile Include="Patches\Player\Btd6Player_SaveNow.cs" />
<Compile Include="Patches\Player\NKMultiConnection_Receive.cs" />
<Compile Include="Patches\Player\NKMultiGameInterface_Disconnect.cs" />
<Compile Include="Patches\Player\NKMultiGameInterface_Connect.cs" />
<Compile Include="Patches\Player\NKMultiGameInterface_Update.cs" />
<Compile Include="Patches\Player\ProfileModel_Validate.cs" />
<Compile Include="Patches\Projectiles\Projectile_Initialise.cs" />
Expand Down
56 changes: 54 additions & 2 deletions BloonsTD6 Mod Helper/BloonsTD6Mod.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
using Assets.Scripts.Unity.Display;
using Assets.Scripts.Simulation.Towers.Behaviors.Attack;
using Assets.Scripts.Simulation.Towers.Behaviors.Abilities;
using BTD_Mod_Helper.Api.Coop;

namespace BTD_Mod_Helper
{
Expand All @@ -41,22 +42,73 @@ public static void AddTowerToGame(TowerModel newTowerModel, TowerDetailsModel to
Game.instance.model.AddTowerToGame(newTowerModel, towerDetailsModel);
}

#region Networking Hooks

#region Misc Hooks
/// <summary>
/// Executed once the user has connected to a game session.
/// Note: Only invoked if <see cref="BloonsMod.CheatMod"/> == true.
/// </summary>
/// <param name="nkGi">The interface used to interact with the game.</param>
public virtual void OnConnected(NKMultiGameInterface nkGi)
{
}

/// <summary>
/// Executed once the user has tried to connect to a server, but failed to do so.
/// Note: Only invoked if <see cref="BloonsMod.CheatMod"/> == true.
/// </summary>
/// <param name="nkGi">The interface used to interact with the game.</param>
public virtual void OnConnectFail(NKMultiGameInterface nkGi)
{
}

/// <summary>
/// Executed once the player has disconnected from a server.
/// Note: Only invoked if <see cref="BloonsMod.CheatMod"/> == true.
/// </summary>
/// <param name="nkGi">The interface used to interact with the game.</param>
public virtual void OnDisconnected(NKMultiGameInterface nkGi)
{
}

/// <summary>
/// Executed when a new client has joined the game session.
/// Note: Only invoked if <see cref="BloonsMod.CheatMod"/> == true.
/// </summary>
/// <param name="nkGi">The interface used to interact with the game.</param>
/// <param name="peerId">Index of the peer in question.</param>
public virtual void OnPeerConnected(NKMultiGameInterface nkGi, int peerId)
{
}

/// <summary>
/// Executed when a new client has left the game session.
/// Note: Only invoked if <see cref="BloonsMod.CheatMod"/> == true.
/// </summary>
/// <param name="nkGi">The interface used to interact with the game.</param>
/// <param name="peerId">Index of the peer in question.</param>
public virtual void OnPeerDisconnected(NKMultiGameInterface nkGi, int peerId)
{
}

/// <summary>
/// Acts on a Network message that's been sent to the client
/// <br/>
/// Use Game.instance.GetNKgI().ReadMessage&lt;YOUR_CLASS_NAME&gt;(message) to get back the same object/class you sent.
/// Use <see cref="MessageUtils.ReadMessage{T}(Message)"/> to read back the message you sent.
/// <br/>
/// If this is one of your messages and you're consuming and acting on it, return true.
/// Otherwise, return false. Seriously.
/// Note: Only invoked if <see cref="BloonsMod.CheatMod"/> == true.
/// </summary>
public virtual bool ActOnMessage(Message message)
{
return false;
}

#endregion


#region Misc Hooks

/// <summary>
/// Called when the player's ProfileModel is loaded.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using Assets.Scripts.Unity;
using System;
using Assets.Scripts.Unity;
using Assets.Scripts.Unity.UI_New.InGame;
using BTD_Mod_Helper.Api.Coop;
using NinjaKiwi.NKMulti;
using UnhollowerBaseLib;
Expand All @@ -8,28 +10,42 @@ namespace BTD_Mod_Helper.Extensions
public static class NKMultiGameInterfaceExt
{
/// <summary>
/// Send a Message to all players in the lobby
/// Returns true if the player is a host in a co-op game.
/// Works for both lobby and in-game.
/// </summary>
/// <param name="message">Message to send</param>
public static void SendMessage(this NKMultiGameInterface nkGI, Message message)
public static bool IsCoOpHost(this NKMultiGameInterface nkGi)
{
nkGI.relayConnection.Writer.Write(message);
var inGame = InGame.instance;
var game = Game.instance;

// Check lobby first.
var connection = game?.GetCoopLobbyConnection();
if (connection != null)
return connection.IsAuthority;

// Then check game.
if (inGame == null || !inGame.IsCoOpReady())
return nkGi.PeerID == 1; // The game does this! for lobby etc. Checked in IDA.

return inGame.coopGame.IsAuthority();
}

/// <summary>
/// Send a Message to all players in the lobby
/// </summary>
/// <param name="message">Message to send</param>
public static void SendMessage(this NKMultiGameInterface nkGI, Message message) => nkGI.relayConnection.Writer.Write(message);

/// <summary>
/// Convert an object to json and send it players or a player in the lobby
/// </summary>
/// <param name="objectToSend">Object you want to send</param>
/// <param name="objectToSend">Object you want to send. The properties of the object will be serialised as JSON.</param>
/// <param name="peerId">The id of the peer you want the message to go to. Leave null if you want to send to all players</param>
/// <param name="code">Coop code used to distinguish this message from others. Like a lock and key for reading messages</param>
public static void SendMessage<T>(this NKMultiGameInterface nkGI, T objectToSend, byte? peerId = null, string code = "") where T : Il2CppSystem.Object
public static void SendMessageEx<T>(this NKMultiGameInterface nkGI, T objectToSend, byte? peerId = null, string code = "")
{
Message message = MessageUtils.CreateMessage(objectToSend, code);

var str = Il2CppSystem.Text.Encoding.Default.GetString(message.bytes);
MelonLoader.MelonLogger.Msg($"str: {str}");

if (peerId.HasValue && peerId != null)
var message = MessageUtils.CreateMessageEx(objectToSend, code);
if (peerId.HasValue && peerId.HasValue)
nkGI.SendToPeer(peerId.Value, message);
else
nkGI.relayConnection.Writer.Write(message);
Expand All @@ -43,12 +59,8 @@ public static void SendMessage<T>(this NKMultiGameInterface nkGI, T objectToSend
/// <param name="code">Coop code used to distinguish this message from others. Like a lock and key for reading messages</param>
public static void SendMessage(this NKMultiGameInterface nkGI, Il2CppSystem.String objectToSend, byte? peerId = null, string code = "")
{
Message message = MessageUtils.CreateMessage(objectToSend, code);

var str = Il2CppSystem.Text.Encoding.Default.GetString(message.bytes);
MelonLoader.MelonLogger.Msg($"str: {str}");

if (peerId.HasValue && peerId != null)
var message = MessageUtils.CreateMessage(objectToSend, code);
if (peerId.HasValue && peerId.HasValue)
nkGI.SendToPeer(peerId.Value, message);
else
nkGI.relayConnection.Writer.Write(message);
Expand Down Expand Up @@ -82,9 +94,21 @@ public static Chat_Message ReadChatMessage(this NKMultiGameInterface nkGI, Messa
{
if (message.Code != Chat_Message.chatCoopCode)
return null;

string json = nkGI.ReadMessage<string>(message.bytes);
Chat_Message deserialized = Game.instance.GetJsonSerializer().DeserializeJson<Chat_Message>(json);
return deserialized;
}

#region Backwards Binary Compatibility
/// <summary>
/// Convert an object to json and send it players or a player in the lobby
/// </summary>
/// <param name="objectToSend">Object you want to send. The properties of the object will be serialised as JSON.</param>
/// <param name="peerId">The id of the peer you want the message to go to. Leave null if you want to send to all players</param>
/// <param name="code">Coop code used to distinguish this message from others. Like a lock and key for reading messages</param>
[Obsolete($"For backwards compatibility reasons only, please use {nameof(SendMessageEx)}")]
public static void SendMessage<T>(this NKMultiGameInterface nkGI, T objectToSend, byte? peerId = null, string code = "") where T : Il2CppSystem.Object => SendMessageEx(nkGI, objectToSend, peerId, code);
#endregion
}
}
15 changes: 15 additions & 0 deletions BloonsTD6 Mod Helper/Extensions/GameExt.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,27 @@
using NinjaKiwi.NKMulti;
using NinjaKiwi.Players.Files.SaveStrategies;
using System.Collections.Generic;
using Assets.Scripts.Unity.UI_New.Coop;
using NinjaKiwi.LiNK.Lobbies;
using UnhollowerBaseLib;

namespace BTD_Mod_Helper.Extensions
{
public static partial class GameExt
{
/// <summary>
/// Returns the current lobby screen.
/// </summary>
public static CoopLobbyScreen? GetCoopLobbyScreen(this Game game)
{
return (game.GetMenuManager()?.currMenu?.Item2?.TryCast<CoopLobbyScreen>());
}

/// <summary>
/// Returns the current lobby connection.
/// </summary>
public static LobbyConnection? GetCoopLobbyConnection(this Game game) => game.GetCoopLobbyScreen()?.lobbyConnection;

/// <summary>
/// Returns the directory where the Player's Profile.save file is located.
/// Not set until after reaching the Main Menu for the first time
Expand Down
15 changes: 15 additions & 0 deletions BloonsTD6 Mod Helper/Extensions/InGameExt.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,21 @@ namespace BTD_Mod_Helper.Extensions
{
public static partial class InGameExt
{
/// <summary>
/// Returns true if the initial co-op handshake has finished and user has co-op game details.
/// </summary>
/// <param name="inGame">The game.</param>
public static bool IsCoOpReady(this InGame? inGame)
{
if (inGame == null)
return false;

if (!inGame.IsCoop)
return false;

return inGame.coopGame != null;
}

/// <summary>
/// Custom API method that changes the game's round set to a custom RoundSetModel.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
using System;
using HarmonyLib;
using MelonLoader;
using NinjaKiwi.NKMulti;
using Task = Il2CppSystem.Threading.Tasks.Task;

namespace BTD_Mod_Helper.Patches.Player
{
[HarmonyPatch(typeof(NKMultiGameInterface), nameof(NKMultiGameInterface.Connect))]
internal class NKMultiGameInterface_Connect
{
[HarmonyPostfix]
public static void Postfix(NKMultiGameInterface __instance, ref Task __result) => __result.ContinueWith(new Action<Task>(task => OnConnectTaskFinished(__instance, task)));

private static void OnConnectTaskFinished(NKMultiGameInterface instance, Task obj)
{
if (instance.IsConnected)
{
MelonMain.PerformHook(mod => { mod.OnConnected(instance); });
instance.add_PeerConnectedEvent(new Action<int>((peerId) => OnPeerConnected(instance, peerId)));
instance.add_PeerDisconnectedEvent(new Action<int>((peerId) => OnPeerDisconnected(instance, peerId)));
}
else
{
MelonMain.PerformHook(mod => { mod.OnConnectFail(instance); });
}
}

private static void OnPeerDisconnected(NKMultiGameInterface nkGi, int peerId) => MelonMain.PerformHook(mod => { mod.OnPeerDisconnected(nkGi, peerId); });

private static void OnPeerConnected(NKMultiGameInterface nkGi, int peerId) => MelonMain.PerformHook(mod => { mod.OnPeerConnected(nkGi, peerId); });
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using HarmonyLib;
using NinjaKiwi.NKMulti;

namespace BTD_Mod_Helper.Patches.Player
{
[HarmonyPatch(typeof(NKMultiGameInterface), nameof(NKMultiGameInterface.Disconnect))]
public class NKMultiGameInterface_Disconnect
{
[HarmonyPostfix]
public static void Postfix(NKMultiGameInterface __instance) => MelonMain.PerformHook(mod => { mod.OnDisconnected(__instance); });
}
}
Loading

0 comments on commit 29dee7a

Please sign in to comment.