// This function is run before all mods are finished loading. protected override void OnPreInitialize() { // The order of these is important for some things, others not. I'll document what each one does eventually. // Load the embedded DLL which has unsafe code. Allows duck game to compile this source and still use unsafe code. AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(CurrentDomain_AssemblyResolve); // Get the profile core. Its not supposed to be accessed outside the main thread but I won't tell if you won't. ProfilesCore profilecore = typeof(Profiles).GetField("_core", BindingFlags.Instance | BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public).GetValue(null) as ProfilesCore; // Normally generates 4 MPPlayer profiles, now does 8 Injection.install(2, "InitDefaultProfiles", "InitDefaultProfiles"); // Add our extra personas to the table PersonaEdits._personas(); // Replace the get_defaultProfiles() with one that handles the extra 4 player teams InputProfileCoreEdits.defaultProfiles(); // Duck Game would just check which profiles were past the first 4 and decide they were custom, changed to skip the first 8 ProfilesCoreEdits.allCustomProfiles(); // Generates the default Player1-Player8 Profiles now Injection.install(5, "Initialize", "Initialize"); // Returns the first 8 profiles as defaults Injection.install(5, "IsDefault", "IsDefault"); // Generates the extra 4 GenericControllers for the added players InputEdits.Initialize(); // Invokes the new InitDefaultProfiles(). This may not be needed as it may get injected before it gets run the first time. I'll check InputEdits.InitDefaultProfiles(); // Generates 8 Netduck profiles instead of the default 4 Injection.install(1, "RecreateProfiles", "RecreateProfiles"); // Allows the host to select between 2 and 8 players maximum for their lobby TeamSelect2Edits.OnlineSettings(); // Add the extra 4 default teams that will be used for the extra players TeamsCoreEdits.Initialize(); // Generate new profile boxes for the lobby level. Also tweaks the camera for it. Will make it not stretched at some point Injection.install(4, "UpdateModifierStatus", "UpdateModifierStatus"); // DoInvite would call Host(4, LobbyType.FriendsOnly) so we change it to 8 max players Injection.install(4, "DoInvite", "DoInvite"); // Would only purge the first 4 boxes by default. Now purges all 8 Injection.install(4, "FillMatchmakingProfiles", "FillMatchmakingProfiles"); // ClearTeam() would return if the index was >=4. We need it to be >=8 for the extra teams Injection.install(4, "ClearTeam", "ClearTeam"); // Return the extra inputprofiles for the extra teams Injection.install(6, "ControllerNumber", "ControllerNumber"); // Trying to detour ConfirmTeamSelection was proving a nightmare so I did this garbage solution. // A check at the bottom of ConfirmTeamSelection was returning incorrectly due to the extra teams and crashing // When I tried injecting my new method, it was never called for some reason. Not sure why, probably due to instancing // To deal with this I edited FilterTeam which gets called at the start of ConfirmTeamSelection and did a check where // if the _desiredteamselection was <= 7 then set it to 0. This meant the check that was crashing returned correctly // and as far as I can tell there aren't any noticeable side effects. Will find better method eventually. Injection.install(6, "FilterTeam", "FilterTeam"); // Because inline removes TeamSelect2.OnNetworkConnecting, I have to inject the methods that called it to change them Injection.install(3, "JoinLocalDuck", "JoinLocalDuck"); Injection.install(3, "OnMessageFromNewClient", "OnMessageFromNewClient"); Injection.install(3, "OnMessage", "OnMessage"); // New NMChangeSlot net message as the old one only handled 4 parameters and we need 8. Injection.install(3, "ChangeSlotSettings", "ChangeSlotSettings"); // DuckNetwork.Update calls Host( 4, LobbyType.FriendsOnly ) on inviting someone. Since performance would be garbage reflecting all // the private variables, we detour host instead. Injection.install(3, "Host", "Host"); // Only would clear custom data for the first 4 profiles so now it clears all 8 Injection.install(3, "Join", "Join"); // Adds our new netmessage to the list of netmessage types so that the game doesn't crash when it encounters it DuckNetworkEdits.UpdateNetmessageTypes(); // netProfileIndex determines how many bits are used to store it. Since we need to store up to the value of seven, we need 3 bits // rather than the default 2. Best way I thought would be change it when the ducks get added but idk. Injection.install(8, "Add", "AddReplace"); // Method to inject a call into LevelUpdateChange so that it calls our varient which then checks for rock scoreboard // then calls our patched Initialize. LevelEdits.UpdateLevelChange(); // NMVoteToSkip checks whether the player index is greater than 3 and discards it if it is. We want it to be discarded when greater // than 7. NMVoteToSkipEdits.Activate(); // Change the get method for determining if a room if on the right or left to Value % 2. Will return 1 if on the right. // We inject pure assembly so THIS WILL BREAK EVENTUALLY. ProfileBox2Edits.rightRoomCorrection(); // Injection.install(0, "UpdateQuack", "injectionMethod1"); // Disables quack to check everything loaded right // Base base.OnPreInitialize(); }
public static void UpdateLevelChangeReplace() { Type type = typeof(Level); Assembly assembly = Assembly.GetAssembly(type); Type HUDtype = assembly.GetType("DuckGame.HUD"); dynamic HUDClearCorners = HUDtype.GetMethod("ClearCorners", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static); Type ConnectionStatusUIType = assembly.GetType("DuckGame.ConnectionStatusUI"); dynamic ConnectionStatusUIShow = ConnectionStatusUIType.GetMethod("Show", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static); dynamic ConnectionStatusUIHide = ConnectionStatusUIType.GetMethod("Hide", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static); FieldInfo _readyForTransitionField = type.GetField("_readyForTransition", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static); dynamic _readyForTransition = _readyForTransitionField.GetValue(Level.current); if (Level.core.nextLevel != null) { FieldInfo _sentLevelChangeField = type.GetField("_sentLevelChange", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static); dynamic _sentLevelChange = _sentLevelChangeField.GetValue(Level.current); FieldInfo _networkStatusField = type.GetField("_networkStatus", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static); dynamic _networkStatus = _networkStatusField.GetValue(Level.current); if (Level.core.currentLevel is IHaveAVirtualTransition && Level.core.nextLevel is IHaveAVirtualTransition && !(Level.core.nextLevel is TeamSelect2)) { VirtualTransition.GoVirtual(); } if (Network.isActive && Level.activeLevel != null && !_sentLevelChange) { // DevConsole.Log(DCSection.GhostMan, "|DGYELLOW|Performing level swap (" + (object)DuckNetwork.levelIndex + ")", -1); GhostManager.context.Clear(); foreach (Profile profile in Profiles.active) { if (profile.connection != null) { profile.connection.manager.Reset(); } } if (Level.core.currentLevel is TeamSelect2 && !(Level.core.nextLevel is TeamSelect2)) { DuckNetwork.ClosePauseMenu(); } if (!(Level.core.currentLevel is TeamSelect2) && Level.core.nextLevel is TeamSelect2) { DuckNetwork.ClosePauseMenu(); } if (Network.isServer) { ++DuckNetwork.levelIndex; DuckNetwork.compressedLevelData = (MemoryStream)null; // DevConsole.Log(DCSection.GhostMan, "|DGYELLOW|Incrementing level index (" + (object)((int)DuckNetwork.levelIndex - 1) + "->" + (object)DuckNetwork.levelIndex + ")", -1); uint varChecksum = 0; bool varNeedsChecksum = false; string lev = ""; if (!(Level.core.currentLevel is TeamSelect2) && Level.core.nextLevel is TeamSelect2) { lev = "@TEAMSELECT"; } else if (Level.core.nextLevel is GameLevel) { GameLevel nextLevel = Level.core.nextLevel as GameLevel; if (nextLevel.customLevel) { varNeedsChecksum = true; varChecksum = nextLevel.checksum; DuckNetwork.compressedLevelData = new MemoryStream(nextLevel.compressedData, 0, nextLevel.compressedData.Length, false, true); } lev = nextLevel.level; } else if (!(Level.core.nextLevel is TeamSelect2) && !(Level.core.nextLevel is GameLevel) && !(Level.core.nextLevel is RockScoreboard)) { lev = "@ROCKINTRO"; // This need correcting at some point and probably will break things. Must get the internal type } else if (Level.core.nextLevel is RockScoreboard) { GhostManager.context.SetGhostIndex((NetIndex16)1); lev = (Level.core.nextLevel as RockScoreboard).mode != ScoreBoardMode.ShowScores ? (!(Level.core.nextLevel as RockScoreboard).afterHighlights ? "@ROCKTHROW|SHOWWINNER" : "@ROCKTHROW|SHOWEND") : "@ROCKTHROW|SHOWSCORE"; } if (lev != "") { foreach (Profile profile in DuckNetwork.profiles) { if (profile.connection != null) { profile.connection.manager.ClearAllMessages(); if (Level.core.nextLevel is GameLevel && (Level.core.nextLevel as GameLevel).level == "RANDOM") { Send.Message((NetMessage) new NMSwitchLevelRandom(lev, DuckNetwork.levelIndex, (ushort)(int)GhostManager.context.currentGhostIndex, (Level.core.nextLevel as GameLevel).seed), NetMessagePriority.ReliableOrdered, profile.connection); } else { Send.Message((NetMessage) new NMSwitchLevel(lev, DuckNetwork.levelIndex, (ushort)(int)GhostManager.context.currentGhostIndex, varNeedsChecksum, varChecksum), NetMessagePriority.ReliableOrdered, profile.connection); } } } } } _sentLevelChange = true; } if (!VirtualTransition.active) { Level.InitChanceGroups(); DamageManager.ClearHits(); Layer.ResetLayers(); HUDClearCorners.Invoke(null, null); if (Level.core.currentLevel != null) { Level.core.currentLevel.Terminate(); } Level.core.currentLevel = Level.core.nextLevel; Level.core.nextLevel = (Level)null; Layer.lighting = false; SequenceItem.sequenceItems.Clear(); GC.Collect(1, GCCollectionMode.Optimized); foreach (Profile profile in Profiles.active) { profile.duck = (Duck)null; } SFX.StopAllSounds(); if (Level.core.currentLevel is RockScoreboard) { LevelEdits.DoInitialize(); } else { Level.core.currentLevel.DoInitialize(); } if (MonoMain.pauseMenu != null) { Level.core.currentLevel.AddThing((Thing)MonoMain.pauseMenu); } if (Network.isActive && DuckNetwork.duckNetUIGroup != null && DuckNetwork.duckNetUIGroup.open) { Level.core.currentLevel.AddThing((Thing)DuckNetwork.duckNetUIGroup); } if (Recorder.globalRecording != null) { Recorder.globalRecording.UpdateAtlasFile(); } _networkStatus = NetLevelStatus.WaitingForDataTransfer; if (!(Level.core.currentLevel is IOnlyTransitionIn) && Level.core.currentLevel is IHaveAVirtualTransition && (!(Level.core.currentLevel is TeamSelect2) && VirtualTransition.isVirtual)) { if (_readyForTransition) { VirtualTransition.GoUnVirtual(); DuckGame.Graphics.fade = 1f; } else { Level.current._waitingOnTransition = true; if (Network.isActive) { ConnectionStatusUIShow.Invoke(null, null); } } } } } if (!Level.current._waitingOnTransition || !_readyForTransition) { return; } Level.current._waitingOnTransition = false; VirtualTransition.GoUnVirtual(); if (!Network.isActive) { return; } ConnectionStatusUIHide.Invoke(null, null); }