public void Awake() { SurvivorDict.Add("commando", SurvivorIndex.Commando); SurvivorDict.Add("mult", SurvivorIndex.Toolbot); SurvivorDict.Add("mul-t", SurvivorIndex.Toolbot); SurvivorDict.Add("toolbot", SurvivorIndex.Toolbot); SurvivorDict.Add("hunt", SurvivorIndex.Huntress); SurvivorDict.Add("huntress", SurvivorIndex.Huntress); SurvivorDict.Add("engi", SurvivorIndex.Engi); SurvivorDict.Add("engineer", SurvivorIndex.Engi); SurvivorDict.Add("mage", SurvivorIndex.Mage); SurvivorDict.Add("arti", SurvivorIndex.Mage); SurvivorDict.Add("artificer", SurvivorIndex.Mage); SurvivorDict.Add("merc", SurvivorIndex.Merc); SurvivorDict.Add("mercenary", SurvivorIndex.Merc); SurvivorDict.Add("rex", SurvivorIndex.Treebot); SurvivorDict.Add("treebot", SurvivorIndex.Treebot); SurvivorDict.Add("loader", SurvivorIndex.Loader); SurvivorDict.Add("acrid", SurvivorIndex.Croco); SurvivorDict.Add("croco", SurvivorIndex.Croco); // Config InitialRandomBots = Config.Wrap("Starting Bots", "StartingBots.Random", "Starting amount of bots to spawn at the start of a run. (Random)", 0); for (int i = 0; i < RandomSurvivors.Length; i++) { string name = RandomSurvivors[i].ToString(); InitialBots[i] = Config.Wrap("Starting Bots", "StartingBots." + name, "Starting amount of bots to spawn at the start of a run. (" + name + ")", 0); } AutoPurchaseItems = Config.Wrap("Bot Inventory", "AutoPurchaseItems", "Maximum amount of purchases a playerbot can do per stage. Items are purchased directly instead of from chests. (Default: true)", true); MaxBotPurchasesPerStage = Config.Wrap("Bot Inventory", "MaxBotPurchasesPerStage", "Maximum amount of putchases a playerbot can do per stage. (Default: 8)", 8); Tier1ChestBotWeight = Config.Wrap("Bot Inventory", "Tier1ChestBotWeight", "Weight of a bot picking an item from a small chest's loot table. (Default: 0.8)", 0.8f); Tier2ChestBotWeight = Config.Wrap("Bot Inventory", "Tier2ChestBotWeight", "Weight of a bot picking an item from a large chest's loot table. (Default: 0.2)", 0.2f); Tier3ChestBotWeight = Config.Wrap("Bot Inventory", "Tier3ChestBotWeight", "Weight of a bot picking an item from a legendary chest's loot table. (Default: 0)", 0f); Tier1ChestBotCost = Config.Wrap("Bot Inventory", "Tier1ChestBotCost", "Base price of a small chest for the bot. (Default: 25)", 25); Tier2ChestBotCost = Config.Wrap("Bot Inventory", "Tier2ChestBotCost", "Base price of a large chest for the bot. (Default: 50)", 50); Tier3ChestBotCost = Config.Wrap("Bot Inventory", "Tier3ChestBotCost", "Base price of a legendary chest for the bot. (Default: 400)", 400); EquipmentBuyChance = Config.Wrap("Bot Inventory", "EquipmentBuyChance", "Chance between 0 and 100 for a bot to buy from an equipment barrel instead of a tier 1 chest. Only active while the bot does not have a equipment item. (Default: 15)", 15); HostOnlySpawnBots = Config.Wrap("Misc", "HostOnlySpawnBots", "Set true so that only the host may spawn bots", true); ShowNameplates = Config.Wrap("Misc", "ShowNameplates", "Show player nameplates on playerbots if SpawnAsPlayers == false. (Host only)", true); PlayerMode = Config.Wrap("Experimental", "SpawnAsPlayers", "Makes the game treat playerbots like how regular players are treated. The bots now show up on the scoreboard, can pick up items, influence the map scaling, etc.", false); TpFix = Config.Wrap("Player Mode", "Teleport Fix", "Fixes long teleporter charging times by making the bots count towards the charging timer. Only active is PlayerMode is true.", true); DontScaleInteractables = Config.Wrap("Player Mode", "DontScaleInteractables", "Prevents interactables spawn count from scaling with bots. Only active is PlayerMode is true.", false); R2API.Utils.CommandHelper.AddToConsoleWhenReady(); // Ugh. On.RoR2.CharacterAI.BaseAI.OnBodyLost += (orig, self, body) => { if (self.name.Equals("PlayerBot")) { return; } orig(self, body); }; // Maybe there is a better way to do this if (ShowNameplates.Value && !PlayerMode.Value) { IL.RoR2.TeamComponent.SetupIndicator += il => { ILCursor c = new ILCursor(il); c.GotoNext(x => x.MatchCallvirt <CharacterBody>("get_isPlayerControlled")); bool isPlayerBot = false; c.EmitDelegate <Func <CharacterBody, CharacterBody> >(x => { isPlayerBot = x.master.name.Equals("PlayerBot"); return(x); } ); c.Index += 1; c.EmitDelegate <Func <bool, bool> >(x => { if (isPlayerBot) { return(true); } return(x); } ); }; } if (!PlayerMode.Value && AutoPurchaseItems.Value) { // Give bots money On.RoR2.TeamManager.GiveTeamMoney += (orig, self, teamIndex, money) => { orig(self, teamIndex, money); if (playerbots.Count > 0) { int num = Run.instance ? Run.instance.livingPlayerCount : 0; if (num != 0) { money = (uint)Mathf.CeilToInt(money / (float)num); } foreach (GameObject playerbot in playerbots) { if (!playerbot) { continue; } CharacterMaster master = playerbot.GetComponent <CharacterMaster>(); if (master && master.alive && master.teamIndex == teamIndex) { master.GiveMoney(money); } } } }; } if (AutoPurchaseItems.Value) { On.RoR2.Run.BeginStage += (orig, self) => { foreach (GameObject playerbot in playerbots.ToArray()) { if (!playerbot) { playerbots.Remove(playerbot); continue; } ItemManager itemManager = playerbot.GetComponent <ItemManager>(); if (itemManager) { itemManager.ResetPurchases(); itemManager.master.money = 0; } } orig(self); }; } if (PlayerMode.Value && TpFix.Value) { On.RoR2.TeleporterInteraction.GetPlayerCountInRadius += (orig, self) => { Vector3 position = self.gameObject.transform.position; float num2 = self.clearRadius * self.clearRadius; return(PlayerCharacterMasterController.instances.Count((PlayerCharacterMasterController c) => c.master.alive && (c.master.GetBody().transform.position - position).sqrMagnitude <= num2)); }; } On.RoR2.Stage.Start += (orig, self) => { orig(self); if (NetworkServer.active) { if (PlayerMode.Value) { foreach (GameObject playerbot in playerbots.ToArray()) { if (!playerbot) { playerbots.Remove(playerbot); continue; } CharacterMaster master = playerbot.GetComponent <CharacterMaster>(); if (master) { Stage.instance.RespawnCharacter(master); } } } // Spawn starting bots if (Run.instance.stageClearCount == 0) { if (InitialRandomBots.Value > 0) { SpawnRandomPlayerbots(NetworkUser.readOnlyInstancesList[0].master, InitialRandomBots.Value); } for (int randomSurvivorsIndex = 0; randomSurvivorsIndex < InitialBots.Length; randomSurvivorsIndex++) { if (InitialBots[randomSurvivorsIndex].Value > 0) { SpawnPlayerbots(NetworkUser.readOnlyInstancesList[0].master, RandomSurvivors[randomSurvivorsIndex], InitialBots[randomSurvivorsIndex].Value); } } } } }; if (PlayerMode.Value) { On.RoR2.SceneDirector.PlaceTeleporter += (orig, self) => { if (DontScaleInteractables.Value) { int count = PlayerCharacterMasterController.instances.Count((PlayerCharacterMasterController v) => v.networkUser); float num = 0.5f + (float)count * 0.5f; ClassicStageInfo component = SceneInfo.instance.GetComponent <ClassicStageInfo>(); self.SetFieldValue("interactableCredit", (int)((float)component.sceneDirectorInteractibleCredits * num)); } else if (Run.instance.stageClearCount == 0 && GetInitialBotCount() > 0) { int count = PlayerCharacterMasterController.instances.Count((PlayerCharacterMasterController v) => v.networkUser); count += GetInitialBotCount(); float num = 0.5f + (float)count * 0.5f; ClassicStageInfo component = SceneInfo.instance.GetComponent <ClassicStageInfo>(); self.SetFieldValue("interactableCredit", (int)((float)component.sceneDirectorInteractibleCredits * num)); } orig(self); }; IL.RoR2.UI.AllyCardManager.PopulateCharacterDataSet += il => { ILCursor c = new ILCursor(il); c.GotoNext(x => x.MatchCallvirt <CharacterMaster>("get_playerCharacterMasterController")); c.Index += 2; c.EmitDelegate <Func <bool, bool> >(x => false); }; } }
public static void AddHooks() { // Ugh. On.RoR2.CharacterAI.BaseAI.OnBodyLost += (orig, self, body) => { if (self.name.Equals("PlayerBot")) { // Reset player bot state when body is lost so errors dont pop up self.stateMachine.SetNextState(EntityStateCatalog.InstantiateState(self.scanState)); return; } orig(self, body); }; // Random fix to make captains spawnable without errors in PlayerMode, theres probably a better way of doing this too On.RoR2.CaptainDefenseMatrixController.OnServerMasterSummonGlobal += (orig, self, summonReport) => { if (self.GetFieldValue <CharacterBody>("characterBody") == null) { return; } orig(self, summonReport); }; // Maybe there is a better way to do this if (PlayerBotManager.ShowNameplates.Value && !PlayerBotManager.PlayerMode.Value) { IL.RoR2.TeamComponent.SetupIndicator += il => { ILCursor c = new ILCursor(il); c.GotoNext(x => x.MatchCallvirt <CharacterBody>("get_isPlayerControlled")); bool isPlayerBot = false; c.EmitDelegate <Func <CharacterBody, CharacterBody> >(x => { isPlayerBot = x.master.name.Equals("PlayerBot"); return(x); } ); c.Index += 1; c.EmitDelegate <Func <bool, bool> >(x => { if (isPlayerBot) { return(true); } return(x); } ); }; } if (!PlayerBotManager.PlayerMode.Value && PlayerBotManager.AutoPurchaseItems.Value) { // Give bots money On.RoR2.TeamManager.GiveTeamMoney += (orig, self, teamIndex, money) => { orig(self, teamIndex, money); if (PlayerBotManager.playerbots.Count > 0) { int num = Run.instance ? Run.instance.livingPlayerCount : 0; if (num != 0) { money = (uint)Mathf.CeilToInt(money / (float)num); } foreach (GameObject playerbot in PlayerBotManager.playerbots) { if (!playerbot) { continue; } CharacterMaster master = playerbot.GetComponent <CharacterMaster>(); if (master && !master.IsDeadAndOutOfLivesServer() && master.teamIndex == teamIndex) { master.GiveMoney(money); } } } }; } if (PlayerBotManager.AutoPurchaseItems.Value) { On.RoR2.Run.BeginStage += (orig, self) => { foreach (GameObject playerbot in PlayerBotManager.playerbots.ToArray()) { if (!playerbot) { PlayerBotManager.playerbots.Remove(playerbot); continue; } ItemManager itemManager = playerbot.GetComponent <ItemManager>(); if (itemManager) { itemManager.ResetPurchases(); itemManager.master.money = 0; } } orig(self); }; } On.RoR2.Stage.Start += (orig, self) => { orig(self); if (NetworkServer.active) { if (PlayerBotManager.PlayerMode.Value) { foreach (GameObject playerbot in PlayerBotManager.playerbots.ToArray()) { if (!playerbot) { PlayerBotManager.playerbots.Remove(playerbot); continue; } CharacterMaster master = playerbot.GetComponent <CharacterMaster>(); if (master) { Stage.instance.RespawnCharacter(master); } } } // Spawn starting bots if (Run.instance.stageClearCount == 0) { if (PlayerBotManager.InitialRandomBots.Value > 0) { PlayerBotManager.SpawnRandomPlayerbots(NetworkUser.readOnlyInstancesList[0].master, PlayerBotManager.InitialRandomBots.Value); } for (int randomSurvivorsIndex = 0; randomSurvivorsIndex < PlayerBotManager.InitialBots.Length; randomSurvivorsIndex++) { if (PlayerBotManager.InitialBots[randomSurvivorsIndex].Value > 0) { PlayerBotManager.SpawnPlayerbots(NetworkUser.readOnlyInstancesList[0].master, PlayerBotManager.RandomSurvivorsList[randomSurvivorsIndex], PlayerBotManager.InitialBots[randomSurvivorsIndex].Value); } } } } }; // Fix custom targets On.RoR2.CharacterAI.BaseAI.Target.GetBullseyePosition += Hook_GetBullseyePosition; if (PlayerBotManager.PlayerMode.Value) { On.RoR2.SceneDirector.PlaceTeleporter += (orig, self) => { if (PlayerBotManager.DontScaleInteractables.Value) { int count = PlayerCharacterMasterController.instances.Count((PlayerCharacterMasterController v) => v.networkUser); float num = 0.5f + (float)count * 0.5f; ClassicStageInfo component = SceneInfo.instance.GetComponent <ClassicStageInfo>(); int credit = (int)((float)component.sceneDirectorInteractibleCredits * num); if (component.bonusInteractibleCreditObjects != null) { for (int i = 0; i < component.bonusInteractibleCreditObjects.Length; i++) { ClassicStageInfo.BonusInteractibleCreditObject bonusInteractibleCreditObject = component.bonusInteractibleCreditObjects[i]; if (bonusInteractibleCreditObject.objectThatGrantsPointsIfEnabled.activeSelf) { credit += bonusInteractibleCreditObject.points; } } } self.interactableCredit = credit; } else if (Run.instance.stageClearCount == 0 && PlayerBotManager.GetInitialBotCount() > 0) { int count = PlayerCharacterMasterController.instances.Count((PlayerCharacterMasterController v) => v.networkUser); count += PlayerBotManager.GetInitialBotCount(); float num = 0.5f + (float)count * 0.5f; ClassicStageInfo component = SceneInfo.instance.GetComponent <ClassicStageInfo>(); int credit = (int)((float)component.sceneDirectorInteractibleCredits * num); if (component.bonusInteractibleCreditObjects != null) { for (int i = 0; i < component.bonusInteractibleCreditObjects.Length; i++) { ClassicStageInfo.BonusInteractibleCreditObject bonusInteractibleCreditObject = component.bonusInteractibleCreditObjects[i]; if (bonusInteractibleCreditObject.objectThatGrantsPointsIfEnabled.activeSelf) { credit += bonusInteractibleCreditObject.points; } } } self.interactableCredit = credit; } orig(self); }; // Required for bots to even move, maybe switch to il later On.RoR2.PlayerCharacterMasterController.Update += (orig, self) => { if (self.name.Equals("PlayerBot")) { //self.InvokeMethod("SetBody", new object[] { self.master.GetBodyObject() }); return; } orig(self); }; IL.RoR2.UI.AllyCardManager.PopulateCharacterDataSet += il => { ILCursor c = new ILCursor(il); c.GotoNext(x => x.MatchCallvirt <CharacterMaster>("get_playerCharacterMasterController")); c.Index += 2; c.EmitDelegate <Func <bool, bool> >(x => false); }; // Spectator fix On.RoR2.CameraRigController.CanUserSpectateBody += (orig, viewer, body) => { return(body.isPlayerControlled || orig(viewer, body)); }; // Dont end game on dying if (PlayerBotManager.ContinueAfterDeath.Value) { IL.RoR2.Stage.FixedUpdate += il => { ILCursor c = new ILCursor(il); c.GotoNext(x => x.MatchCallvirt <PlayerCharacterMasterController>("get_isConnected")); c.Index += 1; c.EmitDelegate <Func <bool, bool> >(x => true); }; } } }