-
-
Notifications
You must be signed in to change notification settings - Fork 10
v1.3 #139
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Draft
IExploitableMan
wants to merge
13
commits into
main
Choose a base branch
from
dev1.3
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Draft
v1.3 #139
Changes from 8 commits
Commits
Show all changes
13 commits
Select commit
Hold shift + click to select a range
3967b7c
Minor stuff
johnklipi 8176701
Merge branch 'main' into dev1.3
Artemis21 8bc3eb0
Merge
johnklipi e77df1f
Added dystopia stuff
johnklipi 21b16a1
Added bool which lets u disable gld mods
johnklipi fc1d549
Started implementing client side creation of GameState
johnklipi 3db6ce2
fixed malformed player account id
johnklipi 03498e8
Moved stuff a bit and finished implementing client side creation of g…
johnklipi 06ba9b2
unhardcoded backend url
johnklipi 8080356
Merge branch 'main' into dev1.3
johnklipi 9a9f213
added half working modded games
johnklipi f09d591
merge
johnklipi e67a728
Implemented working online battles with mods
johnklipi File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,379 @@ | ||
| 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<int, GameLogicData> _gldCache = new(); | ||
| private static readonly Dictionary<int, int> _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; | ||
| } | ||
| } | ||
|
|
||
|
|
||
| /// <summary> | ||
| /// After GameState deserialization, check for trailing GLD version ID and set mockedGameLogicData. | ||
| /// The server appends "##GLD:" + modGldVersion (int) after the normal serialized data. | ||
| /// </summary> | ||
| [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}"); | ||
| } | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Fetch GLD from server using ModGldVersion ID | ||
| /// </summary> | ||
| 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(BackendAdapter), nameof(BackendAdapter.StartLobbyGame))] | ||
| private static bool BackendAdapter_StartLobbyGame_Modded( | ||
| ref Il2CppSystem.Threading.Tasks.Task<ServerResponse<LobbyGameViewModel>> __result, | ||
| BackendAdapter __instance, | ||
| StartLobbyBindingModel model) | ||
| { | ||
| Plugin.logger.LogInfo("Multiplayer> BackendAdapter_StartLobbyGame_Modded"); | ||
| var taskCompletionSource = new Il2CppSystem.Threading.Tasks.TaskCompletionSource<ServerResponse<LobbyGameViewModel>>(); | ||
|
|
||
| _ = HandleStartLobbyGameModded(taskCompletionSource, __instance, model); | ||
|
|
||
| __result = taskCompletionSource.Task; | ||
|
|
||
| return false; | ||
| } | ||
|
|
||
| private static async System.Threading.Tasks.Task HandleStartLobbyGameModded( | ||
| Il2CppSystem.Threading.Tasks.TaskCompletionSource<ServerResponse<LobbyGameViewModel>> 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 setupGameDataViewModel = new SetupGameDataViewModel | ||
| { | ||
| lobbyId = lobbyGameViewModel.Id.ToString(), | ||
| serializedGameState = serializedGameState, | ||
| gameSettingsJson = gameSettingsJson | ||
| }; | ||
|
|
||
| var setupData = System.Text.Json.JsonSerializer.Serialize(setupGameDataViewModel); | ||
|
|
||
| var serverResponse = await instance.HubConnection.InvokeAsync<ServerResponse<LobbyGameViewModel>>( | ||
| "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)participatorViewModel.SelectedTribe, | ||
| skinType = (SkinType)participatorViewModel.SelectedTribeSkin, | ||
| defaultName = participatorViewModel.GetNameInternal() | ||
| }; | ||
| humanPlayer.profile.id = participatorViewModel.UserId; | ||
| humanPlayer.profile.SetName(participatorViewModel.GetNameInternal()); | ||
| SerializationHelpers.FromByteArray<AvatarState>(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<TribeType>().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<PlayerState>() | ||
| }; | ||
|
|
||
| for (int index = 0; index < settings.GetPlayerCount(); ++index) | ||
| { | ||
| PlayerData player = settings.GetPlayer(index); | ||
| if (player.type != PlayerDataType.Bot) | ||
| { | ||
| var nullableGuid = new Il2CppSystem.Nullable<Il2CppSystem.Guid>(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)); | ||
| } | ||
| } | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.