diff --git a/PolyMod.csproj b/PolyMod.csproj
index a09c089..b22dd4e 100644
--- a/PolyMod.csproj
+++ b/PolyMod.csproj
@@ -11,7 +11,7 @@
IL2CPP
PolyMod
- 1.2.13
+ 1.3.0-pre
2.16.8.15757
PolyModdingTeam
The Battle of Polytopia's mod loader.
diff --git a/src/Multiplayer/Client.cs b/src/Multiplayer/Client.cs
new file mode 100644
index 0000000..aba7195
--- /dev/null
+++ b/src/Multiplayer/Client.cs
@@ -0,0 +1,500 @@
+using HarmonyLib;
+using Il2CppMicrosoft.AspNetCore.SignalR.Client;
+using PolyMod.Multiplayer.ViewModels;
+using Polytopia.Data;
+using PolytopiaBackendBase;
+using PolytopiaBackendBase.Common;
+using PolytopiaBackendBase.Game;
+using PolytopiaBackendBase.Game.BindingModels;
+using UnityEngine;
+using Newtonsoft.Json;
+
+namespace PolyMod.Multiplayer;
+
+public static class Client
+{
+ internal const string DEFAULT_SERVER_URL = "https://dev.polydystopia.xyz";
+ internal const string LOCAL_SERVER_URL = "http://localhost:5051/";
+ private const string GldMarker = "##GLD:";
+ internal static bool allowGldMods = false;
+
+ // Cache parsed GLD by game Seed to handle rewinds/reloads
+ private static readonly Dictionary _gldCache = new();
+ private static readonly Dictionary _versionCache = new(); // Seed -> modGldVersion
+
+ internal static void Init()
+ {
+ Harmony.CreateAndPatchAll(typeof(Client));
+ BuildConfig buildConfig = BuildConfigHelper.GetSelectedBuildConfig();
+ buildConfig.buildServerURL = BuildServerURL.Custom;
+ buildConfig.customServerURL = LOCAL_SERVER_URL;
+
+ Plugin.logger.LogInfo($"Multiplayer> Server URL set to: {Plugin.config.backendUrl}");
+ Plugin.logger.LogInfo("Multiplayer> GLD patches applied");
+ }
+
+ [HarmonyPostfix]
+ [HarmonyPatch(typeof(MultiplayerScreen), nameof(MultiplayerScreen.Show))]
+ public static void MultiplayerScreen_Show(MultiplayerScreen __instance)
+ {
+ __instance.multiplayerSelectionScreen.TournamentsButton.gameObject.SetActive(false);
+ }
+
+ [HarmonyPostfix]
+ [HarmonyPatch(typeof(StartScreen), nameof(StartScreen.Start))]
+ private static void StartScreen_Start(StartScreen __instance)
+ {
+ __instance.highscoreButton.gameObject.SetActive(false);
+ __instance.weeklyChallengesButton.gameObject.SetActive(false);
+ }
+
+ [HarmonyPostfix]
+ [HarmonyPatch(typeof(SystemInfo), nameof(SystemInfo.deviceUniqueIdentifier), MethodType.Getter)]
+ public static void SteamClient_get_SteamId(ref string __result)
+ {
+ if (Plugin.config.overrideDeviceId != string.Empty)
+ {
+ __result = Plugin.config.overrideDeviceId;
+ }
+ }
+
+
+ // ///
+ // /// After GameState deserialization, check for trailing GLD version ID and set mockedGameLogicData.
+ // /// The server appends "##GLD:" + modGldVersion (int) after the normal serialized data.
+ // ///
+ // [HarmonyPostfix]
+ // [HarmonyPatch(typeof(GameState), nameof(GameState.Deserialize))]
+ // private static void Deserialize_Postfix(GameState __instance, BinaryReader __0)
+ // {
+ // if(!allowGldMods) return;
+
+ // Plugin.logger?.LogDebug("Deserialize_Postfix: Entered");
+
+ // try
+ // {
+ // var reader = __0;
+ // if (reader == null)
+ // {
+ // Plugin.logger?.LogWarning("Deserialize_Postfix: reader is null");
+ // return;
+ // }
+
+ // var position = reader.BaseStream.Position;
+ // var length = reader.BaseStream.Length;
+ // var remaining = length - position;
+
+ // Plugin.logger?.LogDebug($"Deserialize_Postfix: Stream position={position}, length={length}, remaining={remaining}");
+
+ // // Check if there's more data after normal deserialization
+ // if (position >= length)
+ // {
+ // Plugin.logger?.LogDebug("Deserialize_Postfix: No trailing data (position >= length)");
+
+ // var sd = __instance.Seed;
+ // if (_gldCache.TryGetValue(sd, out var cachedGld))
+ // {
+ // __instance.mockedGameLogicData = cachedGld;
+ // var cachedVersion = _versionCache.GetValueOrDefault(sd, -1);
+ // Plugin.logger?.LogInfo($"Deserialize_Postfix: Applied cached GLD for Seed={sd}, ModGldVersion={cachedVersion}");
+ // }
+ // return;
+ // }
+
+ // Plugin.logger?.LogDebug($"Deserialize_Postfix: Found {remaining} bytes of trailing data, attempting to read marker");
+
+ // var marker = reader.ReadString();
+ // Plugin.logger?.LogDebug($"Deserialize_Postfix: Read marker string: '{marker}'");
+
+ // if (marker != GldMarker)
+ // {
+ // Plugin.logger?.LogDebug($"Deserialize_Postfix: Marker mismatch - expected '{GldMarker}', got '{marker}'");
+ // return;
+ // }
+
+ // Plugin.logger?.LogInfo($"Deserialize_Postfix: Found GLD marker '{GldMarker}'");
+
+ // var modGldVersion = reader.ReadInt32();
+ // Plugin.logger?.LogInfo($"Deserialize_Postfix: Found embedded ModGldVersion: {modGldVersion}");
+
+ // Plugin.logger?.LogDebug($"Deserialize_Postfix: Fetching GLD from server for version {modGldVersion}");
+ // var gldJson = FetchGldById(modGldVersion);
+ // if (string.IsNullOrEmpty(gldJson))
+ // {
+ // Plugin.logger?.LogError($"Deserialize_Postfix: Failed to fetch GLD for ModGldVersion: {modGldVersion}");
+ // return;
+ // }
+
+ // Plugin.logger?.LogDebug($"Deserialize_Postfix: Parsing GLD JSON ({gldJson.Length} chars)");
+
+ // var customGld = new GameLogicData();
+ // customGld.Parse(gldJson);
+ // __instance.mockedGameLogicData = customGld;
+
+ // // Cache for subsequent deserializations (rewinds, reloads)
+ // var seed = __instance.Seed;
+ // _gldCache[seed] = customGld;
+ // _versionCache[seed] = modGldVersion;
+
+ // Plugin.logger?.LogInfo($"Deserialize_Postfix: Successfully set mockedGameLogicData from ModGldVersion: {modGldVersion}, cached for Seed={seed}");
+ // }
+ // catch (EndOfStreamException)
+ // {
+ // Plugin.logger?.LogDebug("Deserialize_Postfix: EndOfStreamException - no trailing data");
+ // }
+ // catch (Exception ex)
+ // {
+ // Plugin.logger?.LogError($"Deserialize_Postfix: Exception: {ex.GetType().Name}: {ex.Message}");
+ // Plugin.logger?.LogDebug($"Deserialize_Postfix: Stack trace: {ex.StackTrace}");
+ // }
+ // }
+
+ // ///
+ // /// Fetch GLD from server using ModGldVersion ID
+ // ///
+ // private static string? FetchGldById(int modGldVersion)
+ // {
+ // if(!allowGldMods) return null;
+ // try
+ // {
+ // using var client = new HttpClient();
+ // var url = $"{Plugin.config.backendUrl.TrimEnd('/')}/api/mods/gld/{modGldVersion}";
+ // Plugin.logger?.LogDebug($"FetchGldById: Requesting URL: {url}");
+
+ // var response = client.GetAsync(url).Result;
+ // Plugin.logger?.LogDebug($"FetchGldById: Response status: {response.StatusCode}");
+
+ // if (response.IsSuccessStatusCode)
+ // {
+ // var gld = response.Content.ReadAsStringAsync().Result;
+ // Plugin.logger?.LogInfo($"FetchGldById: Successfully fetched mod GLD ({gld.Length} chars)");
+ // return gld;
+ // }
+
+ // var errorContent = response.Content.ReadAsStringAsync().Result;
+ // Plugin.logger?.LogError($"FetchGldById: Failed with status {response.StatusCode}: {errorContent}");
+ // }
+ // catch (Exception ex)
+ // {
+ // Plugin.logger?.LogError($"FetchGldById: Exception: {ex.GetType().Name}: {ex.Message}");
+ // if (ex.InnerException != null)
+ // {
+ // Plugin.logger?.LogError($"FetchGldById: Inner exception: {ex.InnerException.Message}");
+ // }
+ // }
+ // return null;
+ // }
+
+ [HarmonyPrefix]
+ [HarmonyPatch(typeof(ClientBase), nameof(ClientBase.SendCommand))]
+ private static bool ClientBase_SendCommand(
+ ClientBase __instance,
+ CommandBase command)
+ {
+
+ Plugin.logger.LogInfo("Multiplayer> ClientBase_SendCommand");
+ Il2CppSystem.Threading.Tasks.Task> task = new();
+ var taskCompletionSource = new Il2CppSystem.Threading.Tasks.TaskCompletionSource>();
+
+ _ = HandleSendCommandModded(taskCompletionSource, __instance, command);
+
+ task = taskCompletionSource.Task;
+
+ return false;
+ }
+
+ private static async System.Threading.Tasks.Task HandleSendCommandModded(
+ Il2CppSystem.Threading.Tasks.TaskCompletionSource> tcs,
+ ClientBase client,
+ CommandBase command)
+ {
+ try
+ {
+ if (!client.CurrentGameId.HasValue)
+ {
+ Console.Write("Tried to perform and send command but no GameId was set");
+ return;
+ }
+ if (!ClientActionManager.CanReceiveCommand(command, client.GameState))
+ {
+ Console.Write("Tried to send invalid command");
+ return;
+ }
+ uint currentResetId = client.resets;
+ int count = client.GameState.CommandStack.Count;
+ var list = new Il2CppSystem.Collections.Generic.List();
+ list.Add(command);
+ client.ActionManager.ExecuteCommands(list);
+ await client.SendCommandToServer(command, count);
+
+ var serializedGameState = SerializationHelpers.ToByteArray(client.GameState, client.GameState.Version);
+
+ var succ = GameStateSummary.FromGameStateByteArray(serializedGameState,
+ out GameStateSummary stateSummary, out var gameState);
+
+ var serializedGameSummary = SerializationHelpers.ToByteArray(stateSummary, gameState.Version);
+
+
+ client.GameState.TryGetPlayer(client.GameState.CurrentPlayer, out PlayerState playerState);
+ var currentPlayerId = "";
+ if(playerState.AccountId.HasValue)
+ {
+ currentPlayerId = playerState.AccountId.Value.ToString();
+ }
+ var setupGameDataViewModel = new ModdedGameStateViewModel
+ {
+ gameId = client.gameId.ToString(),
+ serializedGameState = serializedGameState,
+ serializedGameSummary = serializedGameSummary,
+ gameSettingsJson = "",
+ currentPlayerId = currentPlayerId,
+ IsEndTurnCommand = command.GetCommandType() == CommandType.EndTurn
+ };
+
+
+
+ var setupData = System.Text.Json.JsonSerializer.Serialize(setupGameDataViewModel);
+
+ var serverResponse = await PolytopiaBackendAdapter.Instance.HubConnection.InvokeAsync>(
+ "UpdateGameStateModded",
+ setupData,
+ Il2CppSystem.Threading.CancellationToken.None
+ );
+ tcs.SetResult(serverResponse);
+ }
+ catch (Exception ex)
+ {
+ Plugin.logger.LogError("Multiplayer> Error during HandleSendCommandModded: " + ex.Message);
+ tcs.SetException(new Il2CppSystem.Exception(ex.Message));
+ }
+ }
+
+ [HarmonyPrefix]
+ [HarmonyPatch(typeof(BackendAdapter), nameof(BackendAdapter.StartLobbyGame))]
+ private static bool BackendAdapter_StartLobbyGame_Modded(
+ ref Il2CppSystem.Threading.Tasks.Task> __result,
+ BackendAdapter __instance,
+ StartLobbyBindingModel model)
+ {
+ Plugin.logger.LogInfo("Multiplayer> BackendAdapter_StartLobbyGame_Modded");
+ var taskCompletionSource = new Il2CppSystem.Threading.Tasks.TaskCompletionSource>();
+
+ _ = HandleStartLobbyGameModded(taskCompletionSource, __instance, model);
+
+ __result = taskCompletionSource.Task;
+
+ return false;
+ }
+
+ private static async System.Threading.Tasks.Task HandleStartLobbyGameModded(
+ Il2CppSystem.Threading.Tasks.TaskCompletionSource> tcs,
+ BackendAdapter instance,
+ StartLobbyBindingModel model)
+ {
+ try
+ {
+ var lobbyResponse = await PolytopiaBackendAdapter.Instance.GetLobby(new GetLobbyBindingModel
+ {
+ LobbyId = model.LobbyId
+ });
+
+ Plugin.logger.LogInfo($"Multiplayer> Lobby processed {lobbyResponse.Success}");
+ LobbyGameViewModel lobbyGameViewModel = lobbyResponse.Data;
+ Plugin.logger.LogInfo("Multiplayer> Lobby received");
+
+ (byte[] serializedGameState, string gameSettingsJson) = CreateMultiplayerGame(
+ lobbyGameViewModel,
+ VersionManager.GameVersion,
+ VersionManager.GameLogicDataVersion
+ );
+
+ Plugin.logger.LogInfo("Multiplayer> GameState and Settiings created");
+
+ var succ = GameStateSummary.FromGameStateByteArray(serializedGameState,
+ out GameStateSummary stateSummary, out var gameState);
+
+ var serializedGameSummary = SerializationHelpers.ToByteArray(stateSummary, gameState.Version);
+ var setupGameDataViewModel = new ModdedGameStateViewModel
+ {
+ lobbyId = lobbyGameViewModel.Id.ToString(),
+ serializedGameState = serializedGameState,
+ serializedGameSummary = serializedGameSummary,
+ gameSettingsJson = gameSettingsJson
+ };
+
+ var setupData = System.Text.Json.JsonSerializer.Serialize(setupGameDataViewModel);
+
+ var serverResponse = await instance.HubConnection.InvokeAsync>(
+ "StartLobbyGameModded",
+ setupData,
+ Il2CppSystem.Threading.CancellationToken.None
+ );
+ Plugin.logger.LogInfo("Multiplayer> Invoked StartLobbyGameModded");
+ tcs.SetResult(serverResponse);
+ }
+ catch (Exception ex)
+ {
+ Plugin.logger.LogError("Multiplayer> Error during HandleStartLobbyGameModded: " + ex.Message);
+ tcs.SetException(new Il2CppSystem.Exception(ex.Message));
+ }
+ }
+
+ public static (byte[] serializedGameState, string gameSettingsJson) CreateMultiplayerGame(LobbyGameViewModel lobby,
+ int gameVersion, int gameLogicVersion)
+ {
+ var lobbyMapSize = lobby.MapSize;
+ var settings = new GameSettings();
+ settings.ApplyLobbySettings(lobby);
+ if (settings.LiveGamePreset)
+ {
+ settings.SetLiveModePreset();
+ }
+ foreach (var participatorViewModel in lobby.Participators)
+ {
+ var humanPlayer = new PlayerData
+ {
+ type = PlayerDataType.LocalUser,
+ state = PlayerDataFriendshipState.Accepted,
+ knownTribe = true,
+ tribe = (TribeType)participatorViewModel.SelectedTribe,
+ tribeMix = TribeType.None, // TribeMix is byte too
+ skinType = (SkinType)participatorViewModel.SelectedTribeSkin,
+ defaultName = participatorViewModel.GetNameInternal()
+ };
+ humanPlayer.profile.id = participatorViewModel.UserId;
+ humanPlayer.profile.SetName(participatorViewModel.GetNameInternal());
+ SerializationHelpers.FromByteArray(participatorViewModel.AvatarStateData, out var avatarState);
+ humanPlayer.profile.avatarState = avatarState;
+
+ settings.AddPlayer(humanPlayer);
+ }
+
+ foreach (var botDifficulty in lobby.Bots)
+ {
+ var botGuid = Il2CppSystem.Guid.NewGuid();
+
+ var botPlayer = new PlayerData
+ {
+ type = PlayerDataType.Bot,
+ state = PlayerDataFriendshipState.Accepted,
+ knownTribe = true,
+ tribe = Enum.GetValues().Where(t => t != TribeType.None)
+ .OrderBy(x => Il2CppSystem.Guid.NewGuid()).First()
+ };
+ ;
+ botPlayer.botDifficulty = (BotDifficulty)botDifficulty;
+ botPlayer.skinType = SkinType.Default;
+ botPlayer.defaultName = "Bot" + botGuid;
+ botPlayer.profile.id = botGuid;
+
+ settings.AddPlayer(botPlayer);
+ }
+
+ GameState gameState = new GameState()
+ {
+ Version = gameVersion,
+ Settings = settings,
+ PlayerStates = new Il2CppSystem.Collections.Generic.List()
+ };
+
+ for (int index = 0; index < settings.GetPlayerCount(); ++index)
+ {
+ PlayerData player = settings.GetPlayer(index);
+ if (player.type != PlayerDataType.Bot)
+ {
+ var nullableGuid = new Il2CppSystem.Nullable(player.profile.id);
+ if (!nullableGuid.HasValue)
+ {
+ throw new Exception("GUID was not set properly!");
+ }
+ PlayerState playerState = new PlayerState()
+ {
+ Id = (byte)(index + 1),
+ AccountId = nullableGuid,
+ AutoPlay = player.type == PlayerDataType.Bot,
+ UserName = player.GetNameInternal(),
+ tribe = player.tribe,
+ tribeMix = player.tribeMix,
+ hasChosenTribe = true,
+ skinType = player.skinType
+ };
+ gameState.PlayerStates.Add(playerState);
+ Plugin.logger.LogInfo($"Multiplayer> Created player: {playerState}");
+ }
+ else
+ {
+ GameStateUtils.AddAIOpponent(gameState, GameStateUtils.GetRandomPickableTribe(gameState),
+ GameSettings.HandicapFromDifficulty(player.botDifficulty), player.skinType);
+ }
+ }
+
+ GameStateUtils.SetPlayerColors(gameState);
+ GameStateUtils.AddNaturePlayer(gameState);
+
+ Plugin.logger.LogInfo("Multiplayer> Creating world...");
+
+ ushort num = (ushort)Math.Max(lobbyMapSize,
+ (int)MapDataExtensions.GetMinimumMapSize(gameState.PlayerCount));
+ gameState.Map = new MapData(num, num);
+ MapGeneratorSettings generatorSettings = settings.GetMapGeneratorSettings();
+ new MapGenerator().Generate(gameState, generatorSettings);
+
+ Plugin.logger.LogInfo($"Multiplayer> Creating initial state for {gameState.PlayerCount} players...");
+
+ foreach (PlayerState player in gameState.PlayerStates)
+ {
+ foreach (PlayerState otherPlayer in gameState.PlayerStates)
+ player.aggressions[otherPlayer.Id] = 0;
+
+ if (player.Id != byte.MaxValue && gameState.GameLogicData.TryGetData(player.tribe, out TribeData tribeData))
+ {
+ player.Currency = tribeData.startingStars;
+ TileData tile = gameState.Map.GetTile(player.startTile);
+ UnitState unitState = ActionUtils.TrainUnitScored(gameState, player, tile, tribeData.startingUnit);
+ unitState.attacked = false;
+ unitState.moved = false;
+ }
+ }
+
+ Plugin.logger.LogInfo("Multiplayer> Session created successfully");
+
+ gameState.CommandStack.Add((CommandBase)new StartMatchCommand((byte)1));
+
+ var serializedGameState = SerializationHelpers.ToByteArray(gameState, gameState.Version);
+
+ return (serializedGameState,
+ JsonConvert.SerializeObject(gameState.Settings));
+ }
+
+ // FIX FOR NATURE PLAYER. BOTS ARENT IMPLEMENTED YET
+
+ [HarmonyPrefix]
+ [HarmonyPatch(typeof(GameState), nameof(GameState.EndPlayerTurn))]
+ private static bool GameState_EndPlayerTurn(GameState __instance, bool newTurn = false)
+ {
+ Console.Write("GameState_EndPlayerTurn");
+ __instance.CurrentPlayerIndex++;
+ if (__instance.CurrentPlayerIndex >=__instance. PlayerStates.Count)
+ {
+ __instance.CurrentPlayerIndex = 0;
+ newTurn = true;
+ }
+
+ var currentPlayer = __instance.PlayerStates[__instance.CurrentPlayerIndex];
+ if (!currentPlayer.IsAlive(__instance))
+ {
+ __instance.EndPlayerTurn(newTurn);
+ }
+ else if (newTurn)
+ {
+ __instance.CurrentTurn++;
+ }
+
+ if(currentPlayer.AutoPlay)
+ {
+ __instance.CommandStack.Add(new EndTurnCommand(currentPlayer.Id));
+ }
+ Console.Write("finished");
+ return false;
+ }
+}
diff --git a/src/Multiplayer/SerializationUtils.cs b/src/Multiplayer/SerializationUtils.cs
new file mode 100644
index 0000000..e91a46b
--- /dev/null
+++ b/src/Multiplayer/SerializationUtils.cs
@@ -0,0 +1,74 @@
+using HarmonyLib;
+using Polytopia.Data;
+using PolytopiaBackendBase.Common;
+
+namespace PolyMod.Multiplayer;
+
+public static class SerializationUtils
+{
+ internal static void Init()
+ {
+ Harmony.CreateAndPatchAll(typeof(SerializationUtils));
+ }
+
+ [HarmonyPrefix]
+ [HarmonyPatch(typeof(GamePlayerSummary), nameof(GamePlayerSummary.Serialize))]
+ public static bool GamePlayerSummary_Serialize(GamePlayerSummary __instance, Il2CppSystem.IO.BinaryWriter writer, int version)
+ {
+ Plugin.logger.LogInfo("Multiplayer> GamePlayerSummary_Serialize");
+ var memoryStream = new Il2CppSystem.IO.MemoryStream();
+ var binaryWriter = new Il2CppSystem.IO.BinaryWriter(memoryStream);
+ binaryWriter.Write(__instance.Id);
+ binaryWriter.Write(__instance.PolytopiaId.ToString());
+ binaryWriter.Write(__instance.UserName ?? "");
+ binaryWriter.Write((int)__instance.TribeType);
+ binaryWriter.Write(__instance.AutoPlay);
+ binaryWriter.Write(__instance.HasChosenTribe);
+ binaryWriter.Write(__instance.Handicap);
+ binaryWriter.Write(__instance.IsDead);
+ if (version >= 86)
+ {
+ binaryWriter.Write((int)__instance.SkinType);
+ }
+ writer.Write((int)memoryStream.Length);
+ memoryStream.WriteTo(writer.BaseStream);
+ return false;
+ }
+
+ [HarmonyPrefix]
+ [HarmonyPatch(typeof(GamePlayerSummary), nameof(GamePlayerSummary.Deserialize))]
+ public static bool GamePlayerSummary_Deserialize(GamePlayerSummary __instance, Il2CppSystem.IO.BinaryReader reader, int version)
+ {
+ Plugin.logger.LogInfo("Multiplayer> GamePlayerSummary_Deserialize");
+ int num = reader.ReadInt32();
+ long position = reader.BaseStream.Position;
+ __instance.Id = reader.ReadByte();
+ string g = reader.ReadString();
+ Il2CppSystem.Guid parsed;
+ Il2CppSystem.Nullable nullableGuid;
+ if (Il2CppSystem.Guid.TryParse(g, out parsed))
+ nullableGuid = new Il2CppSystem.Nullable(parsed);
+ else
+ nullableGuid = new Il2CppSystem.Nullable();
+ __instance.PolytopiaId = nullableGuid;
+ __instance.UserName = reader.ReadString();
+ __instance.TribeType = (TribeType)reader.ReadInt32();
+ __instance.AutoPlay = reader.ReadBoolean();
+ __instance.HasChosenTribe = reader.ReadBoolean();
+ __instance.Handicap = reader.ReadInt32();
+ __instance.IsDead = reader.ReadBoolean();
+ if (version >= 86)
+ {
+ __instance.SkinType = (SkinType)reader.ReadInt32();
+ }
+ reader.BaseStream.Position = position + num;
+ return false;
+ }
+
+ [HarmonyPostfix]
+ [HarmonyPatch(typeof(PlayerState), nameof(PlayerState.Deserialize))]
+ public static void PlayerState_Deserialize(PlayerState __instance, Il2CppSystem.IO.BinaryReader reader, int version)
+ {
+ __instance.climate = __instance.tribe;
+ }
+}
\ No newline at end of file
diff --git a/src/Multiplayer/ViewModels/ModdedGameStateViewModel.cs b/src/Multiplayer/ViewModels/ModdedGameStateViewModel.cs
new file mode 100644
index 0000000..176a997
--- /dev/null
+++ b/src/Multiplayer/ViewModels/ModdedGameStateViewModel.cs
@@ -0,0 +1,13 @@
+namespace PolyMod.Multiplayer.ViewModels;
+public class ModdedGameStateViewModel
+{
+ public string gameId { get; set; } = "";
+ public string lobbyId { get; set; } = "";
+
+ public byte[] serializedGameState { get; set; } = new byte[0];
+ public byte[] serializedGameSummary { get; set; } = new byte[0];
+
+ public string gameSettingsJson { get; set; } = "";
+ public string currentPlayerId { get; set; } = "";
+ public bool IsEndTurnCommand { get; set; } = false;
+}
\ No newline at end of file
diff --git a/src/Plugin.cs b/src/Plugin.cs
index 96ae942..597609c 100644
--- a/src/Plugin.cs
+++ b/src/Plugin.cs
@@ -24,7 +24,9 @@ internal record PolyConfig(
bool debug = false,
bool autoUpdate = true,
bool updatePrerelease = false,
- bool allowUnsafeIndexes = false
+ bool allowUnsafeIndexes = false,
+ string backendUrl = Multiplayer.Client.DEFAULT_SERVER_URL,
+ string overrideDeviceId = ""
);
///
@@ -132,6 +134,8 @@ public override void Load()
Hub.Init();
Main.Init();
+ Multiplayer.SerializationUtils.Init();
+ Multiplayer.Client.Init();
}
///