diff --git a/CHANGELOG.md b/CHANGELOG.md index ac33cf4..38b2e78 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,67 @@ - -## Changelog ## - +### 3.22 ### +* **3.22.0** + + * Added commands: + * 'spawn_minion': Spawns characeter as your minion, specified amount and tier + * 'remove_all_minions': Removes all minions, leaving no bodies or broken ones. + + * 'set_difficulty' Sets the runs selected difficulty. + * 'set_stat' Sets the given baseStat to the specified value, or resets it to the default value. For clean testing. + * 'nocooldowns': Toggles your skill cooldowns. + + * 'list_drone': List drone names and ids. + * 'list_pickups': List pickup names and ids. + * 'list_difficulty': List difficulty names and ids. + * 'dump_mods': Lists all loaded mods and if they are tagged as RequiredByAll. Useful to get internal names to check for. + + * 'godenemy': God mode for all monsters, to make them unhurtable. + * 'buddhaenemy': Buddha mode for all monsters, to make them unkillable. + * 'hide_model': Toggles your models visibiltiy, for screenshotting/recording. + * 'toggle_time' Toggles 'time_scale' between 0 and what it it was before. + + * 'give_all_items' Gives all items of specified itemTier. + * 'give_void': Gives Void Markers. + * 'no_interactables': Prevent interactables from spawning + + * 'goto_boss': Teleports you to boss, teleporter or boss arenas on stage, (i.e. Skip directly to Mithrix on Moon2) + * 'evolve_lemurians': Triggers Artifact of Devotion evolution. + + * Added macro/short commands: + * 'dtscanner': 100 BoostEquipmentRechrage & Radar Scanner equip for easy map searching. + * 'dtpeace': 'kill_all Monster & Void', 'no_enemies true', 'god true' + * 'dtcleanse': 'remove_all_buffs 0 & 1' & 'remove_all_dots' + * 'random_equip' alternative for 'give_equip random' + * 'rich', -> Set money to 2 billion. + * 'poor' -> Set money to 0. + * 'skill': shorthand for 'loadout_set_skill_variant self' + * 'skin': shorthand for 'loadout_set_skin_variant self' + * 'hide_hud': toggle 'hud_enable' convar + + * Updated command functionality: + * Item commands can now grant/remove channeled items + * Item commands now mention you can type 0/1/2 instead of the name type. + * 'spawn_interactable' now also shows interactable names in auto complete. + * 'spawn_interactable' now accepts amount. + * 'create_pickup' now supports dropping drones & pickups, and can use numbers to abbreviate search. + * 'create_pickup' switched {search} and {permanent/temp} argument spots. + * 'give_equip' now accepts '-1|none' to remove your equipment. + * 'give .. remove_buff' now accept negative amounts to remove/give, like item commands. + * 'loadout_set_skill_variant' now accepts 'self' + * 'random_items' changed default value to just give Tier1,2,3 + * 'hurt' third optional argument for direct damage. (bypassArmor bypassDamageCalcs). + * 'kill_all' default value now kills Monster & Void teams. + * 'true_kill' now bypasses godmode and accepts 'pinged' & 'all' as target + * 'charge_zone' default value now 100% charge. + + + * 'spawn_as' will set your permanent character again. + * 'run_set_stages_cleared' will now also set the loopClearCount to the expected amount. (Which is a seperate thing as of AC) + * Modded spawn cards should be usable from the start now. + * Food items can now be given by {dropTable} + * Fixed 'god 0/1'/'buddha 0/1' not setting the toggle's state. + * Fixed 'loadout_set_skin_variant' crashing the game. + + ### 3.21 ### * **3.21.1** diff --git a/Code/AutoCompletion/AutoCompleteManager.cs b/Code/AutoCompletion/AutoCompleteManager.cs index 2b2e146..d3a63c1 100644 --- a/Code/AutoCompletion/AutoCompleteManager.cs +++ b/Code/AutoCompletion/AutoCompleteManager.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.Linq; +using DebugToolkit.Commands; namespace DebugToolkit { @@ -69,18 +70,29 @@ internal static void RegisterAutoCompleteCommands() var parser = new AutoCompleteParser(); parser.RegisterStaticVariable("0", "0"); parser.RegisterStaticVariable("1", "1"); + parser.RegisterStaticVariable("count", "count"); + parser.RegisterStaticVariable("droneTier", "droneTier"); + parser.RegisterStaticVariable("duration", "duration"); + parser.RegisterStaticVariable("skill_slot", "skill_slot"); + parser.RegisterStaticVariable("skill_variant", "skill_variant"); parser.RegisterStaticVariable("ai", MasterCatalog.allAiMasters.Select(i => $"{(int)i.masterIndex}|{i.name}|{StringFinder.GetLangInvar(StringFinder.GetMasterName(i))}"), 1); parser.RegisterStaticVariable("artifact", ArtifactCatalog.artifactDefs.Select(i => $"{(int)i.artifactIndex}|{i.cachedName}|{StringFinder.GetLangInvar(i.nameToken)}"), 1); + parser.RegisterStaticVariable("difficulty", R2API.DifficultyAPI.difficultyDefinitions.Select(i => $"{(int)i.Key}|{StringFinder.GetLangInvar(i.Value.nameToken)}"), 1); parser.RegisterStaticVariable("body", BodyCatalog.allBodyPrefabBodyBodyComponents.Select(i => $"{(int)i.bodyIndex}|{i.name}|{StringFinder.GetLangInvar(i.baseNameToken)}"), 1); parser.RegisterStaticVariable("buff", BuffCatalog.buffDefs.Select(i => $"{(int)i.buffIndex}|{StringFinder.GetLangInvar(i.name)}"), 1); parser.RegisterStaticVariable("droptable", ItemTierCatalog.allItemTierDefs.OrderBy(i => i.tier).Select(i => $"{(int)i.tier}|{i.name}"), 1); + parser.RegisterStaticVariable("itemTier", ItemTierCatalog.allItemTierDefs.OrderBy(i => i.tier).Select(i => $"{(int)i.tier}|{i.name}"), 1); parser.RegisterStaticVariable("dot", DotController.dotDefs.Select((d, i) => $"{i}|{(DotController.DotIndex)i}"), 1); parser.RegisterStaticVariable("elite", new string[] { "-1|None" }. Concat(EliteCatalog.eliteDefs.Select(i => $"{(int)i.eliteIndex}|{i.name}|{StringFinder.GetLangInvar(i.modifierToken)}")), 1 ); - parser.RegisterStaticVariable("equip", EquipmentCatalog.equipmentDefs.Select(i => $"{(int)i.equipmentIndex}|{i.name}|{StringFinder.GetLangInvar(i.nameToken)}"), 1); + parser.RegisterStaticVariable("equip", new string[] { "-1|None" }. + Concat(EquipmentCatalog.equipmentDefs.Select(i => $"{(int)i.equipmentIndex}|{i.name}|{StringFinder.GetLangInvar(i.nameToken)}")), + 1 + ); parser.RegisterStaticVariable("item", ItemCatalog.allItemDefs.Select(i => $"{(int)i.itemIndex}|{i.name}|{StringFinder.GetLangInvar(i.nameToken)}"), 1); + parser.RegisterStaticVariable("drone", DroneCatalog.allDroneDefs.Select(i => $"{(int)i.droneIndex}|{i.name}|{StringFinder.GetLangInvar(i.nameToken)}"), 1); parser.RegisterStaticVariable("specific_stage", SceneCatalog.allSceneDefs.Where(i => !i.isOfflineScene).Select(i => $"{(int)i.sceneDefIndex}|{i.cachedName}|{StringFinder.GetLangInvar(i.nameToken)}"), 1); parser.RegisterStaticVariable("team", new string[] { "-1|None" }. Concat(TeamCatalog.teamDefs.Select((t, i) => $"{i}|{(TeamIndex)i}")), @@ -89,10 +101,18 @@ internal static void RegisterAutoCompleteCommands() parser.RegisterStaticVariable("permission_level", CollectEnumNames(typeof(Permissions.Level), typeof(int)), 1); - parser.RegisterDynamicVariable("director_card", StringFinder.Instance.DirectorCards, "spawnCard", autocompleteIndex: 1); - parser.RegisterDynamicVariable("interactable", StringFinder.Instance.InteractableSpawnCards, autocompleteIndex: 1); + parser.RegisterStaticVariable("director_card", StringFinder.Instance.DirectorCards.Select(i => $"{i}|{i.GetSpawnCard().name}"), autocompleteIndex: 1); + parser.RegisterStaticVariable("interactable", StringFinder.Instance.InteractableSpawnCards.Select(i => $"{StringFinder.Instance.InteractableSpawnCards.IndexOf(i)}|{i.name}|{StringFinder.GetLangInvar(i.prefab?.GetComponent()?.GetDisplayName())}"), autocompleteIndex: 1); + parser.RegisterDynamicVariable("player", NetworkUser.instancesList, "userName"); + parser.RegisterStaticVariable("item_type", CollectEnumNames(typeof(Items.ItemType), typeof(int)), 1); + parser.RegisterStaticVariable("pickup_type", CollectEnumNames(typeof(Items.PickupType), typeof(int)), 1); + parser.RegisterStaticVariable("search", CollectEnumNames(typeof(Items.PickupSearch), typeof(int)), 1); + + parser.RegisterStaticVariable("stat", CollectEnumNames(typeof(PlayerCommands.Stat), typeof(int)), 1); + parser.RegisterStaticVariable("portal", Spawners.portals.Select(i => $"{i.Key}"), 1); + parser.Scan(System.Reflection.Assembly.GetExecutingAssembly()); } diff --git a/Code/DT-Commands/Buffs.cs b/Code/DT-Commands/Buffs.cs index 36d0d71..ca437b7 100644 --- a/Code/DT-Commands/Buffs.cs +++ b/Code/DT-Commands/Buffs.cs @@ -90,7 +90,8 @@ private static void CCGiveBuff(ConCommandArgs args) } if (iCount < 0) { - Log.MessageNetworked(String.Format(Lang.NEGATIVE_ARG, "count"), args, LogLevel.MessageClientOnly); + args.userArgs[1] = (-iCount).ToString(); + CCRemoveBuff(args); return; } @@ -147,7 +148,7 @@ private static void CCGiveBuff(ConCommandArgs args) { body.AddTimedBuff(buff, duration); } - Log.MessageNetworked($"Gave {iCount} {name} to {target.name} for {duration} seconds", args); + Log.MessageNetworked($"Gave {iCount} {name} to {target.name} for {duration} seconds", args); } } @@ -175,7 +176,8 @@ private static void CCRemoveBuff(ConCommandArgs args) } if (iCount < 0) { - Log.MessageNetworked(String.Format(Lang.NEGATIVE_ARG, "count"), args, LogLevel.MessageClientOnly); + args.userArgs[1] = (-iCount).ToString(); + CCGiveBuff(args); return; } @@ -615,7 +617,7 @@ internal static CommandTarget ParseTarget(ConCommandArgs args, int index) target = targetMaster?.GetBody(); } } - if (target == null) + if (target == null && failMessage == null) { failMessage = Lang.PLAYER_NOTFOUND; } diff --git a/Code/DT-Commands/CurrentRun.cs b/Code/DT-Commands/CurrentRun.cs index d8cf306..21db635 100644 --- a/Code/DT-Commands/CurrentRun.cs +++ b/Code/DT-Commands/CurrentRun.cs @@ -13,6 +13,7 @@ public static class CurrentRun { internal static bool noEnemies = false; + internal static bool noInteractables = false; internal static bool lockExp = false; internal static ulong seed; @@ -161,6 +162,39 @@ private static void CCNoEnemies(ConCommandArgs args) Log.MessageNetworked(String.Format(noEnemies ? Lang.SETTING_ENABLED : Lang.SETTING_DISABLED, "no_enemies"), args); } + [ConCommand(commandName = "no_interactables", flags = ConVarFlags.ExecuteOnServer, helpText = Lang.NOINTERACTABLES_HELP)] + [AutoComplete(Lang.ENABLE_ARGS)] + private static void CCNoInteractaböes(ConCommandArgs args) + { + bool enabled = !noInteractables; + if (args.Count > 0) + { + if (!Util.TryParseBool(args[0], out enabled)) + { + Log.MessageNetworked(string.Format(Lang.PARSE_ERROR, "enable", "bool"), args, LogLevel.MessageClientOnly); + return; + } + } + noInteractables = enabled; + if (noInteractables) + { + SceneDirector.onPrePopulateSceneServer += PreventInteractableSpawns; + } + else + { + SceneDirector.onPrePopulateSceneServer -= PreventInteractableSpawns; + } + Log.MessageNetworked(String.Format(noInteractables ? Lang.SETTING_ENABLED : Lang.SETTING_DISABLED, "no_interactables"), args); + } + + private static void PreventInteractableSpawns(SceneDirector obj) + { + if (noInteractables) + { + obj.onPopulateCreditMultiplier = 0; + } + } + [ConCommand(commandName = "lock_exp", flags = ConVarFlags.ExecuteOnServer, helpText = Lang.LOCKEXP_HELP)] [AutoComplete(Lang.ENABLE_ARGS)] private static void CCLockExperience(ConCommandArgs args) @@ -186,6 +220,7 @@ private static void CCLockExperience(ConCommandArgs args) Log.MessageNetworked(String.Format(lockExp ? Lang.SETTING_ENABLED : Lang.SETTING_DISABLED, "lock_exp"), args); } + [ConCommand(commandName = "kill_all", flags = ConVarFlags.ExecuteOnServer, helpText = Lang.KILLALL_HELP)] [AutoComplete(Lang.KILLALL_ARGS)] private static void CCKillAll(ConCommandArgs args) @@ -195,15 +230,19 @@ private static void CCKillAll(ConCommandArgs args) Log.MessageNetworked(Lang.NOTINARUN_ERROR, args, LogLevel.MessageClientOnly); return; } + if (args.Count == 0 || args[0] == Lang.DEFAULT_VALUE) + { + DebugToolkit.InvokeCMD(args.sender, "kill_all", ((int)TeamIndex.Monster).ToString()); + DebugToolkit.InvokeCMD(args.sender, "kill_all", ((int)TeamIndex.Void).ToString()); + return; + } + TeamIndex team = TeamIndex.Monster; - if (args.Count > 0 && args[0] != Lang.DEFAULT_VALUE) + team = StringFinder.Instance.GetTeamFromPartial(args[0]); + if (team == StringFinder.TeamIndex_NotFound) { - team = StringFinder.Instance.GetTeamFromPartial(args[0]); - if (team == StringFinder.TeamIndex_NotFound) - { - Log.MessageNetworked(Lang.TEAM_NOTFOUND, args, LogLevel.MessageClientOnly); - return; - } + Log.MessageNetworked(Lang.TEAM_NOTFOUND, args, LogLevel.MessageClientOnly); + return; } int count = 0; @@ -241,6 +280,23 @@ private static void CCTimeScale(ConCommandArgs args) TimescaleNet.Invoke(scale); } + public static float prevTimeScale = 0; + [ConCommand(commandName = "toggle_time", flags = ConVarFlags.None, helpText = Lang.TOGGLETIME_HELP)] + private static void CCTogglePause(ConCommandArgs args) + { + float newTime = 0; + if (Time.timeScale == 0) + { + newTime = prevTimeScale == 0 ? 1 : prevTimeScale; + } + else + { + prevTimeScale = Time.timeScale; + } + Time.timeScale = newTime; + TimescaleNet.Invoke(newTime); + } + [ConCommand(commandName = "stop_timer", flags = ConVarFlags.ExecuteOnServer, helpText = Lang.STOPTIMER_HELP)] [AutoComplete(Lang.ENABLE_ARGS)] private static void CCPauseTimer(ConCommandArgs args) @@ -469,16 +525,13 @@ private static void CCChargeZone(ConCommandArgs args) Log.MessageNetworked(Lang.NOTINARUN_ERROR, args, LogLevel.MessageClientOnly); return; } - if (args.Count == 0) - { - Log.MessageNetworked(Lang.INSUFFICIENT_ARGS + Lang.CHARGEZONE_ARGS, args, LogLevel.MessageClientOnly); - return; - } - if (!TextSerialization.TryParseInvariant(args[0], out float charge)) + float charge = 100; + if (args.Count > 0 && !TextSerialization.TryParseInvariant(args[0], out charge)) { Log.MessageNetworked(string.Format(Lang.PARSE_ERROR, "charge", "float"), args, LogLevel.MessageClientOnly); return; } + Log.MessageNetworked(string.Format("Setting charge for all active holdout zones to {0}%", charge), args, LogLevel.Message); charge /= 100f; foreach (var zone in InstanceTracker.GetInstancesList()) @@ -681,6 +734,108 @@ private static void CCSetTime(ConCommandArgs args) Run.instance.SetRunStopwatch(setTime); Log.MessageNetworked("Run timer set to " + setTime, args); } + + + [ConCommand(commandName = "evolve_lemurians", flags = ConVarFlags.ExecuteOnServer, helpText = Lang.EVOLVE_LEMURIAN_HELP)] + [AutoComplete(Lang.PLAYER_OR_ALL_OPTIONAL_ARGS)] + public static void CCEvolveLemurians(ConCommandArgs args) + { + if (!Run.instance) + { + Log.MessageNetworked(Lang.NOTINARUN_ERROR, args, LogLevel.MessageClientOnly); + return; + } + bool all = false; + bool succeeded = false; + + NetworkUser player = args.sender; + if (args.Count > 0) + { + player = Util.GetNetUserFromString(args.userArgs, 0); + if (player == null) + { + Log.MessageNetworked(Lang.PLAYER_NOTFOUND, args, LogLevel.MessageClientOnly); + return; + } + } + if (args.Count > 0 && args[0].ToUpperInvariant() == Lang.ALL) + { + all = true; + } + else if (!all && player == null) + { + Log.MessageNetworked(Lang.PLAYER_NOTFOUND, args, LogLevel.MessageClientOnly); + return; + } + + foreach (DevotionInventoryController devotionInventoryController in DevotionInventoryController.InstanceList) + { + if (all || devotionInventoryController.SummonerMaster == player.master) + { + succeeded = true; + devotionInventoryController.UpdateAllMinions(true); + } + } + if (all) + { + Log.MessageNetworked(succeeded ? "Evolved all devoted Lemurians." : "There are no devoted lemurians", args); + } + else + { + Log.MessageNetworked(succeeded ? $"Evolved {player.userName}'s devoted Lemurians." : $"{player.userName} does not own any devoted lemurians", args); + } + + } + + [ConCommand(commandName = "set_difficulty", flags = ConVarFlags.ExecuteOnServer, helpText = Lang.SETDIFFICULTY_HELP)] + [AutoComplete(Lang.SETDIFFICULTY_ARGS)] + public static void CCSetDifficulty(ConCommandArgs args) + { + if (!Run.instance) + { + Log.MessageNetworked(Lang.NOTINARUN_ERROR, args, LogLevel.MessageClientOnly); + return; + } + if (args.Count < 1) + { + Log.MessageNetworked(Lang.INSUFFICIENT_ARGS + Lang.SETDIFFICULTY_ARGS, args, LogLevel.MessageClientOnly); + return; + } + + DifficultyIndex difficultyIndex = StringFinder.Instance.GetDifficultyFromPartial(args[0]); + DifficultyDef difficultyDef = DifficultyCatalog.GetDifficultyDef(difficultyIndex); + if (difficultyIndex == DifficultyIndex.Invalid || difficultyDef == null) + { + Log.MessageNetworked(string.Format(Lang.OBJECT_NOTFOUND, "difficulty", args[0]), args, LogLevel.MessageClientOnly); + return; + } + + Run.instance.selectedDifficulty = difficultyIndex; + Log.MessageNetworked("Setting the runs selected difficulty to: "+ Language.GetString(difficultyDef.nameToken), args); + + if (Run.instance.uiInstances.Count > 0) + { + //Refreshes HUD to match set difficulty + //Is there a way to do this on both client & server? + Run.instance.uiInstances[0].GetComponentInChildren()?.Start(); + } + foreach (var player in PlayerCharacterMasterController.instances) + { + //Ensure proper Helper items, maybe not most accurate for modded difficulties. + player.master.inventory.ResetItemPermanent(RoR2Content.Items.DrizzlePlayerHelper); + player.master.inventory.ResetItemPermanent(RoR2Content.Items.MonsoonPlayerHelper); + if (difficultyIndex == DifficultyIndex.Easy) + { + player.master.inventory.GiveItemPermanent(RoR2Content.Items.DrizzlePlayerHelper, 1); + } + else if (difficultyDef.countsAsHardMode) + { + player.master.inventory.GiveItemPermanent(RoR2Content.Items.MonsoonPlayerHelper, 1); + } + } + + } + } // ReSharper disable once ClassNeverInstantiated.Global @@ -704,7 +859,7 @@ internal static void Invoke(float scale) private void RpcApplyTimescale(float scale) { Time.timeScale = scale; - Message("Timescale set to: " + scale + ". "); + Message("Timescale set to: " + scale); } } } diff --git a/Code/DT-Commands/Items.cs b/Code/DT-Commands/Items.cs index 7f85621..7659d4f 100644 --- a/Code/DT-Commands/Items.cs +++ b/Code/DT-Commands/Items.cs @@ -67,6 +67,25 @@ private static void CCListEquip(ConCommandArgs args) var s = sb.Length > 0 ? sb.ToString().TrimEnd('\n') : string.Format(Lang.NOMATCH_ERROR, "equipment", arg); Log.MessageNetworked(s, args, LogLevel.MessageClientOnly); } + + [ConCommand(commandName = "list_pickups", flags = ConVarFlags.None, helpText = Lang.LISTDRONE_HELP)] + [AutoComplete(Lang.LISTQUERY_ARGS)] + private static void CCListPickup(ConCommandArgs args) + { + var sb = new StringBuilder(); + var arg = args.Count > 0 ? args[0] : ""; + var indices = StringFinder.Instance.GetPickupsFromPartial(arg); + foreach (var index in indices) + { + var definition = PickupCatalog.GetPickupDef(index); + var realName = Language.currentLanguage.GetLocalizedStringByToken(definition.nameToken); + bool enabled = Run.instance && Run.instance.IsPickupAvailable(index); + sb.AppendLine($"[{index.value}]{definition.internalName} \"{realName}\" (enabled={enabled})"); + } + var s = sb.Length > 0 ? sb.ToString().TrimEnd('\n') : string.Format(Lang.NOMATCH_ERROR, "pickups", arg); + Log.MessageNetworked(s, args, LogLevel.MessageClientOnly); + } + [ConCommand(commandName = "dump_inventories", flags = ConVarFlags.None, helpText = Lang.DUMPINVENTORIES_HELP)] private static void CCDumpInventories(ConCommandArgs args) @@ -138,7 +157,7 @@ private static void CCGiveItem(ConCommandArgs args) } var type = ParseItemType(args, 2); - if (type == ItemType.None) + if (type <= ItemType.None || type >= ItemType.Count) { Log.MessageNetworked(String.Format(Lang.INVALID_ARG_VALUE, "type"), args, LogLevel.MessageClientOnly); return; @@ -159,6 +178,9 @@ private static void CCGiveItem(ConCommandArgs args) } var itemDef = ItemCatalog.GetItemDef(item); var name = itemDef.name; + + name = getItemTypeName(type) + name; + var amount = (args.commandName == "give_item" ? 1 : -1) * iCount; var inventory = target.inventory; if (amount > 0) @@ -187,6 +209,18 @@ private static void CCGiveItem(ConCommandArgs args) } } + public static string getItemTypeName(ItemType type) + { + switch (type) + { + case ItemType.Permanent: + return ""; + case ItemType.Temp: + return "Temporary "; //Saturated by 50% Temp color + } + return type.ToString(); + } + [ConCommand(commandName = "random_items", flags = ConVarFlags.ExecuteOnServer, helpText = Lang.RANDOMITEM_HELP)] [AutoComplete(Lang.RANDOMITEM_ARGS)] private static void CCRandomItems(ConCommandArgs args) @@ -216,7 +250,7 @@ private static void CCRandomItems(ConCommandArgs args) } var type = ParseItemType(args, 2); - if (type == ItemType.None) + if (type <= ItemType.None || type >= ItemType.Count) { Log.MessageNetworked(String.Format(Lang.INVALID_ARG_VALUE, "type"), args, LogLevel.MessageClientOnly); return; @@ -247,7 +281,7 @@ private static void CCRandomItems(ConCommandArgs args) { target.devotionController.UpdateAllMinions(false); } - Log.MessageNetworked($"Generated {iCount} items for {target.name}!", args); + Log.MessageNetworked($"Generated {iCount} {getItemTypeName(type)}items for {target.name}!", args); } else { @@ -255,6 +289,57 @@ private static void CCRandomItems(ConCommandArgs args) } } + [ConCommand(commandName = "give_all_items", flags = ConVarFlags.ExecuteOnServer, helpText = Lang.GIVEALLITEMS_HELP)] + [AutoComplete(Lang.GIVEALLITEMS_ARGS)] + private static void CCGiveAllItems(ConCommandArgs args) + { + if (!Run.instance) + { + Log.MessageNetworked(Lang.NOTINARUN_ERROR, args, LogLevel.MessageClientOnly); + return; + } + bool isDedicatedServer = args.sender == null; + if (args.Count == 0 || (isDedicatedServer && (args.Count < 2 || args[1] == Lang.DEFAULT_VALUE))) + { + Log.MessageNetworked(Lang.INSUFFICIENT_ARGS + Lang.GIVEALLITEMS_ARGS, args, LogLevel.MessageClientOnly); + return; + } + + bool all = args[0].ToUpperInvariant() == Lang.ALL; + bool consumed = args[0].ToUpperInvariant() == "CONSUMED"; + ItemTier itemTier = StringFinder.Instance.GetItemTierFromPartial(args[0]); + if (!all && !consumed && itemTier == StringFinder.ItemTier_NotFound) + { + Log.MessageNetworked(string.Format(Lang.OBJECT_NOTFOUND, "item tier", args[0]), args, LogLevel.MessageClientOnly); + return; + } + + var target = ParseTarget(args, 1); + if (target.failMessage != null) + { + Log.MessageNetworked(target.failMessage, args, LogLevel.MessageClientOnly); + return; + } + + for (int i = 0; i < ItemCatalog.allItemDefs.Length; i++) + { + ItemDef def = ItemCatalog.allItemDefs[i]; + if (!def.hidden) + { + if (def.tier == itemTier || all && def.tier != ItemTier.NoTier || consumed && def.isConsumed) + { + target.inventory.GiveItemPermanent(def); + } + } + } + if (target.devotionController) + { + target.devotionController.UpdateAllMinions(false); + } + Log.MessageNetworked($"Gave all items of tier(s) {args[0]} to {target.name}!", args); + } + + [ConCommand(commandName = "give_equip", flags = ConVarFlags.ExecuteOnServer, helpText = Lang.GIVEEQUIP_HELP)] [AutoComplete(Lang.GIVEEQUIP_ARGS)] private static void CCGiveEquipment(ConCommandArgs args) @@ -285,6 +370,12 @@ private static void CCGiveEquipment(ConCommandArgs args) inventory.GiveRandomEquipment(); equip = inventory.GetEquipmentIndex(); } + else if (args[0] == "-1" || args[0].ToUpperInvariant() == Lang.NONE) + { + inventory.SetEquipmentIndex(EquipmentIndex.None, true); + Log.MessageNetworked(string.Format(Lang.REMOVEDEQUIP, target.name), args); + return; + } else { equip = StringFinder.Instance.GetEquipFromPartial(args[0]); @@ -305,6 +396,14 @@ private static void CCGiveEquipment(ConCommandArgs args) Log.MessageNetworked($"Gave {name} to {target.name}", args); } + [ConCommand(commandName = "random_equip", flags = ConVarFlags.ExecuteOnServer, helpText = Lang.RANDOMEQUIP_HELP)] + [AutoComplete(Lang.RANDOMEQUIP_ARGS)] + private static void CCGiveRandomEquipment(ConCommandArgs args) + { + DebugToolkit.InvokeCMD(args.sender, "give_equip", "RANDOM"); + } + + [ConCommand(commandName = "create_pickup", flags = ConVarFlags.ExecuteOnServer, helpText = Lang.CREATEPICKUP_HELP)] [AutoComplete(Lang.CREATEPICKUP_ARGS)] private static void CCCreatePickup(ConCommandArgs args) @@ -337,32 +436,31 @@ private static void CCCreatePickup(ConCommandArgs args) return; } - var type = ParseItemType(args, 1); - if (type == ItemType.None) + PickupType type = PickupType.Permanent; + if (args.Count > 2 && args[2] != Lang.DEFAULT_VALUE) { - Log.MessageNetworked(String.Format(Lang.INVALID_ARG_VALUE, "type"), args, LogLevel.MessageClientOnly); - return; + type = ParsePickupType(args, 2); + if (type <= PickupType.None || type >= PickupType.Count) + { + Log.MessageNetworked(String.Format(Lang.INVALID_ARG_VALUE, "type"), args, LogLevel.MessageClientOnly); + return; + } } - bool searchEquip = true, searchItem = true; - if (args.Count > 2 && args[2] != Lang.DEFAULT_VALUE) + bool searchEquip = true, searchItem = true, searchDrone = true; + + PickupSearch searchType = PickupSearch.All; + if (args.Count > 1 && args[1] != Lang.DEFAULT_VALUE) { - switch (args[2].ToUpperInvariant()) + searchType = ParsePickupSearch(args, 1); + if (searchType < PickupSearch.All || searchType >= PickupSearch.Count) { - case Lang.BOTH: - break; - case Lang.ITEM: - searchEquip = false; - break; - case Lang.EQUIP: - searchItem = false; - break; - default: - Log.MessageNetworked(String.Format(Lang.INVALID_ARG_VALUE, "search"), args, LogLevel.MessageClientOnly); - return; + Log.MessageNetworked(String.Format(Lang.INVALID_ARG_VALUE, "search"), args, LogLevel.MessageClientOnly); + return; } } PickupIndex final = PickupIndex.none; + DroneIndex drone = DroneIndex.None; EquipmentIndex equipment = EquipmentIndex.None; ItemIndex item = ItemIndex.None; @@ -375,31 +473,76 @@ private static void CCCreatePickup(ConCommandArgs args) final = PickupCatalog.FindPickupIndex("MiscPickupIndex.VoidCoin"); break; default: - if (searchEquip) - { - equipment = StringFinder.Instance.GetEquipFromPartial(args[0]); - } - if (searchItem) - { - item = StringFinder.Instance.GetItemFromPartial(args[0]); - } - if (item == ItemIndex.None && equipment == EquipmentIndex.None) - { - Log.MessageNetworked(Lang.CREATEPICKUP_NOTFOUND, args, LogLevel.MessageClientOnly); - return; - } - else if (item != ItemIndex.None && equipment != EquipmentIndex.None) - { - Log.MessageNetworked(string.Format(Lang.CREATEPICKUP_AMBIGIOUS_2, item, equipment), args, LogLevel.MessageClientOnly); - return; - } - else if (equipment != EquipmentIndex.None) + if (searchType == PickupSearch.Pickup) { - final = PickupCatalog.FindPickupIndex(equipment); + int? pickup2 = args.TryGetArgInt(0); + if (pickup2 != null) + { + final.value = (int)pickup2; + } + else + { + final = StringFinder.Instance.GetPickupFromPartial(args[0]); + } } else { - final = PickupCatalog.FindPickupIndex(item); + if (searchType == PickupSearch.All || searchType == PickupSearch.Item) + { + item = StringFinder.Instance.GetItemFromPartial(args[0]); + } + if (searchType == PickupSearch.All || searchType == PickupSearch.Equip) + { + equipment = StringFinder.Instance.GetEquipFromPartial(args[0]); + } + if (searchType == PickupSearch.All || searchType == PickupSearch.Drone) + { + drone = StringFinder.Instance.GetDroneFromPartial(args[0]); + } + if (item == ItemIndex.None && equipment == EquipmentIndex.None && drone == DroneIndex.None) + { + Log.MessageNetworked(Lang.CREATEPICKUP_NOTFOUND, args, LogLevel.MessageClientOnly); + return; + } + else if(item != ItemIndex.None && equipment != EquipmentIndex.None|| + drone != DroneIndex.None && equipment != EquipmentIndex.None || + item != ItemIndex.None && drone != DroneIndex.None ) + { + var def1 = ItemCatalog.GetItemDef(item); + var def2 = EquipmentCatalog.GetEquipmentDef(equipment); + var def3 = DroneCatalog.GetDroneDef(drone); + string foundResults = string.Empty; + if (def1) + { + foundResults += $"{item}|{def1.name}|{Language.GetString(def1.nameToken)}\n"; + } + if (def2) + { + foundResults += $"{equipment}|{def2.name}|{Language.GetString(def2.nameToken)}\n"; + } + if (def3) + { + foundResults += $"{drone}|{def3.name}|{Language.GetString(def3.nameToken)}\n"; + } + Log.MessageNetworked(string.Format(Lang.CREATEPICKUP_AMBIGIOUS, foundResults), args, LogLevel.MessageClientOnly); + return; + } + else if (equipment != EquipmentIndex.None) + { + final = PickupCatalog.FindPickupIndex(equipment); + } + else if (drone != DroneIndex.None) + { + final = PickupCatalog.FindPickupIndex(drone); + } + else if (item != ItemIndex.None) + { + final = PickupCatalog.FindPickupIndex(item); + } + else + { + final.value = args.TryGetArgInt(0).GetValueOrDefault(-1); + } } break; } @@ -410,7 +553,7 @@ private static void CCCreatePickup(ConCommandArgs args) pickup = new UniquePickup { pickupIndex = final, - decayValue = type == ItemType.Temp ? 1f : 0f, + decayValue = type == PickupType.Temp ? 1f : 0f, }, }, body.transform.position, body.inputBank.aimDirection * 30f); } @@ -598,7 +741,7 @@ private static void CCRemoveEquipment(ConCommandArgs args) } target.inventory.SetEquipmentIndex(EquipmentIndex.None, true); - Log.MessageNetworked($"Removed current Equipment from {target.name}", args); + Log.MessageNetworked(string.Format(Lang.REMOVEDEQUIP, target.name), args); } [ConCommand(commandName = "restock_equip", flags = ConVarFlags.ExecuteOnServer, helpText = Lang.RESTOCKEQUIP_HELP)] @@ -645,9 +788,27 @@ private static void CCRestockEquip(ConCommandArgs args) internal enum ItemType { - None, + None = -1, + Permanent, + Temp, + Channeled, + Count, + } + internal enum PickupType + { + None = -1, Permanent, Temp, + Count, + } + internal enum PickupSearch + { + All = -1, + Pickup, + Item, + Equip, + Drone, + Count, } private static int GetItemCount(Inventory inventory, ItemIndex itemIndex, ItemType type) @@ -658,6 +819,8 @@ private static int GetItemCount(Inventory inventory, ItemIndex itemIndex, ItemTy return inventory.GetItemCountPermanent(itemIndex); case ItemType.Temp: return inventory.GetItemCountTemp(itemIndex); + case ItemType.Channeled: + return inventory.GetItemCountChanneled(itemIndex); default: Log.Message(Lang.NOMESSAGE, LogLevel.Warning); return 0; @@ -674,6 +837,9 @@ private static void GiveItem(Inventory inventory, ItemIndex itemIndex, int count case ItemType.Temp: inventory.GiveItemTemp(itemIndex, count); break; + case ItemType.Channeled: + inventory.GiveItemChanneled(itemIndex, count); + break; default: Log.Message(Lang.NOMESSAGE, LogLevel.Warning); break; @@ -690,6 +856,9 @@ private static void RemoveItem(Inventory inventory, ItemIndex itemIndex, int cou case ItemType.Temp: inventory.RemoveItemTemp(itemIndex, count); break; + case ItemType.Channeled: + inventory.RemoveItemChanneled(itemIndex, count); + break; default: Log.Message(Lang.NOMESSAGE, LogLevel.Warning); break; @@ -704,8 +873,24 @@ private static ItemType ParseItemType(ConCommandArgs args, int index) } return ItemType.Permanent; } + private static PickupType ParsePickupType(ConCommandArgs args, int index) + { + if (args.Count > index && args[index] != Lang.DEFAULT_VALUE) + { + return Enum.TryParse(args[index], true, out PickupType itemType) ? itemType : PickupType.None; + } + return PickupType.Permanent; + } + private static PickupSearch ParsePickupSearch(ConCommandArgs args, int index) + { + if (args.Count > index && args[index] != Lang.DEFAULT_VALUE) + { + return Enum.TryParse(args[index], true, out PickupSearch searchType) ? searchType : PickupSearch.All; + } + return PickupSearch.All; + } - private static CommandTarget ParseTarget(ConCommandArgs args, int index) + public static CommandTarget ParseTarget(ConCommandArgs args, int index) { string failMessage = null; Inventory inventory = null; @@ -783,7 +968,7 @@ private static CommandTarget ParseTarget(ConCommandArgs args, int index) { inventory = target.inventory; var player = target.playerCharacterMasterController?.networkUser; - targetName = player?.masterController.GetDisplayName() ?? target.gameObject.name; + targetName = target.bodyInstanceObject ? RoR2.Util.GetBestBodyName(target.bodyInstanceObject) : player?.masterController.GetDisplayName() ?? target.gameObject.name; } } } @@ -824,7 +1009,13 @@ private static BasicPickupDropTable ParseDroptable(ConCommandArgs args, int inde { droptable.selector.Clear(); droptable.canDropBeReplaced = canDropBeReplaced; - if (args.Count < index + 1 || args[index] == Lang.DEFAULT_VALUE || args[index].ToUpperInvariant() == Lang.ALL) + if (args.Count < index + 1 || args[index] == Lang.DEFAULT_VALUE) + { + droptable.Add(availableDropLists[ItemTier.Tier1], 100f); + droptable.Add(availableDropLists[ItemTier.Tier2], 60f); + droptable.Add(availableDropLists[ItemTier.Tier3], 4f); + } + else if (args[index].ToUpperInvariant() == Lang.ALL) { foreach (var itemTier in StringFinder.Instance.GetItemTiersFromPartial("")) { @@ -911,7 +1102,7 @@ internal static void CollectItemTiers(Run run) foreach (var itemIndex in ItemCatalog.allItems) { var itemDef = ItemCatalog.GetItemDef(itemIndex); - if (run.availableItems.Contains(itemIndex) && itemDef.DoesNotContainTag(ItemTag.WorldUnique)) + if (run.availableItems.Contains(itemIndex) || itemDef.tier == ItemTier.FoodTier || itemDef.DoesNotContainTag(ItemTag.WorldUnique)) { if (customTiers.TryGetValue(itemDef.tier, out var list)) { diff --git a/Code/DT-Commands/Lists.cs b/Code/DT-Commands/Lists.cs index 4c32146..ff13ad2 100644 --- a/Code/DT-Commands/Lists.cs +++ b/Code/DT-Commands/Lists.cs @@ -1,5 +1,6 @@ using RoR2; using System.Collections.Generic; +using System.Linq; using System.Text; using UnityEngine.Networking; using static DebugToolkit.Log; @@ -71,6 +72,40 @@ private static void CCListArtifact(ConCommandArgs args) Log.MessageNetworked(s, args, LogLevel.MessageClientOnly); } + [ConCommand(commandName = "list_difficulty", flags = ConVarFlags.None, helpText = Lang.LISTDIFFICULTY_HELP)] + private static void CCListDifficulty(ConCommandArgs args) + { + StringBuilder sb = new StringBuilder(); + var arg = args.Count > 0 ? args[0] : ""; + var indices = StringFinder.Instance.GetDifficultiesFromPartial(arg); + foreach (var index in indices) + { + var difficultyDef = DifficultyCatalog.GetDifficultyDef(index); + var langInvar = StringFinder.GetLangInvar(difficultyDef.nameToken); + sb.AppendLine($"[{(int)index}]{difficultyDef.nameToken}={langInvar}"); + } + var s = sb.Length > 0 ? sb.ToString().TrimEnd('\n') : string.Format(Lang.NOMATCH_ERROR, "difficulty", arg); + Log.MessageNetworked(s, args, LogLevel.MessageClientOnly); + } + + [ConCommand(commandName = "list_drone", flags = ConVarFlags.None, helpText = Lang.LISTDRONE_HELP)] + [AutoComplete(Lang.LISTQUERY_ARGS)] + private static void CCListDrone(ConCommandArgs args) + { + var sb = new StringBuilder(); + var arg = args.Count > 0 ? args[0] : ""; + var indices = StringFinder.Instance.GetDronesFromPartial(arg); + foreach (var index in indices) + { + var definition = DroneCatalog.GetDroneDef(index); + var realName = Language.currentLanguage.GetLocalizedStringByToken(definition.nameToken); + bool enabled = Run.instance && Run.instance.IsDroneAvailable(index); + sb.AppendLine($"[{(int)index}]{definition.name} \"{realName}\" (enabled={enabled})"); + } + var s = sb.Length > 0 ? sb.ToString().TrimEnd('\n') : string.Format(Lang.NOMATCH_ERROR, "drone", arg); + Log.MessageNetworked(s, args, LogLevel.MessageClientOnly); + } + [ConCommand(commandName = "list_ai", flags = ConVarFlags.None, helpText = Lang.LISTAI_HELP)] [AutoComplete(Lang.LISTQUERY_ARGS)] private static void CCListAI(ConCommandArgs args) diff --git a/Code/DT-Commands/LobbyManagement.cs b/Code/DT-Commands/LobbyManagement.cs index bd29c65..74e437b 100644 --- a/Code/DT-Commands/LobbyManagement.cs +++ b/Code/DT-Commands/LobbyManagement.cs @@ -10,13 +10,13 @@ namespace DebugToolkit.Commands class LobbyManagement { [ConCommand(commandName = "kick", flags = ConVarFlags.ExecuteOnServer, helpText = Lang.KICK_HELP)] - [AutoComplete(Lang.KICK_ARGS)] + [AutoComplete(Lang.PLAYER_ARGS)] [RequiredLevel] private static void CCKick(ConCommandArgs args) { if (args.Count == 0) { - Log.MessageNetworked(Lang.INSUFFICIENT_ARGS + Lang.KICK_ARGS, args, LogLevel.Error); + Log.MessageNetworked(Lang.INSUFFICIENT_ARGS + Lang.PLAYER_ARGS, args, LogLevel.Error); return; } var client = GetClientFromArgs(args); @@ -47,29 +47,50 @@ private static void CCBan(ConCommandArgs args) } [ConCommand(commandName = "true_kill", flags = ConVarFlags.ExecuteOnServer, helpText = Lang.TRUEKILL_HELP)] - [AutoComplete(Lang.TRUEKILL_ARGS)] + [AutoComplete(Lang.PLAYERPINGED_OR_ALL)] private static void CCTrueKill(ConCommandArgs args) { if (args.sender == null && (args.Count < 1 || args[0] == Lang.DEFAULT_VALUE)) { - Log.Message(Lang.INSUFFICIENT_ARGS + Lang.TRUEKILL_ARGS, LogLevel.Error); + Log.Message(Lang.INSUFFICIENT_ARGS + Lang.PLAYER_OR_PINGED, LogLevel.Error); return; - } - CharacterMaster master = args.sender?.master; + } + CharacterMaster master = args.sender.master; if (args.Count > 0) { - NetworkUser player = Util.GetNetUserFromString(args.userArgs); - if (player == null) + if (args[0].ToUpperInvariant() == Lang.ALL) { - Log.MessageNetworked(Lang.PLAYER_NOTFOUND, args, LogLevel.MessageClientOnly); + foreach (var _master in CharacterMaster.instancesList) + { + _master.godMode = false; + _master.UpdateBodyGodMode(); + _master.TrueKill(); + } + Log.MessageNetworked("All things were killed by server.", args); return; } - master = player.master; + else + { + var target = Buffs.ParseTarget(args, 0); + if (target.failMessage != null) + { + Log.MessageNetworked(target.failMessage, args, LogLevel.MessageClientOnly); + return; + } + master = target.body.master; + } } - + if (!master) + { + Log.MessageNetworked(Lang.BODY_NOTFOUND, args, LogLevel.MessageClientOnly); + return; + } + master.godMode = false; + master.UpdateBodyGodMode(); master.TrueKill(); Log.MessageNetworked(master.name + " was killed by server.", args); } + private static NetworkConnection GetClientFromArgs(ConCommandArgs args) { diff --git a/Code/DT-Commands/Macros.cs b/Code/DT-Commands/Macros.cs index 7d0209c..5becd9f 100644 --- a/Code/DT-Commands/Macros.cs +++ b/Code/DT-Commands/Macros.cs @@ -35,6 +35,15 @@ private static void LateGame(ConCommandArgs args) } Invoke(a, "set_scene", "bazaar"); } + + [ConCommand(commandName = "dtpeace", flags = ConVarFlags.ExecuteOnServer, helpText = Lang.MACRO_DTPEACE_HELP)] + private static void Peace(ConCommandArgs args) + { + Invoke(args.sender, "kill_all", "2", "1"); + Invoke(args.sender, "kill_all", "4", "1"); + Invoke(args.sender, "no_enemies", "1"); + Invoke(args.sender, "god", "1"); + } [ConCommand(commandName = "dtzoom", flags = ConVarFlags.ExecuteOnServer, helpText = Lang.MACRO_DTZOOM_HELP)] private static void Zoom(ConCommandArgs args) @@ -42,8 +51,24 @@ private static void Zoom(ConCommandArgs args) Invoke(args.sender, "give_item", "hoof", "20"); Invoke(args.sender, "give_item", "feather", "200"); } + + [ConCommand(commandName = "dtcleanse", flags = ConVarFlags.ExecuteOnServer, helpText = Lang.MACRO_DTCLEANSE_HELP)] + private static void CCCleanse(ConCommandArgs args) + { + Macros.Invoke(args.sender, "remove_all_buffs"); + Macros.Invoke(args.sender, "remove_all_buffs", "1"); + Macros.Invoke(args.sender, "remove_all_dots"); + } + + [ConCommand(commandName = "dtscanner", flags = ConVarFlags.ExecuteOnServer, helpText = Lang.MACRO_SCANNER_HELP)] + public static void CCScanner(ConCommandArgs args) + { + Invoke(args.sender, "give_item", "BoostEquipmentRecharge", "100"); + Invoke(args.sender, "give_equip", "Scanner"); + } + - private static void Invoke(NetworkUser user, string commandname, params string[] args) + public static void Invoke(NetworkUser user, string commandname, params string[] args) { DebugToolkit.InvokeCMD(user, commandname, args); } diff --git a/Code/DT-Commands/Miscellaneous.cs b/Code/DT-Commands/Miscellaneous.cs index 139a648..f2f9d07 100644 --- a/Code/DT-Commands/Miscellaneous.cs +++ b/Code/DT-Commands/Miscellaneous.cs @@ -1,6 +1,7 @@ using BepInEx.Bootstrap; using RoR2; using System.Collections; +using System.Text; using UnityEngine; namespace DebugToolkit.Commands @@ -79,5 +80,36 @@ static IEnumerator InvokeRoutine(System.Action action, float delay) action(); } } + + [ConCommand(commandName = "dump_mods", flags = ConVarFlags.None, helpText = Lang.DUMPMODS_HELP)] + public static void CCMods(ConCommandArgs args) + { + int requiredByAll = args.TryGetArgInt(0).GetValueOrDefault(0); + + StringBuilder log = new StringBuilder(); + + log.Append("All loaded mods\n\n"); + foreach (var a in BepInEx.Bootstrap.Chainloader.PluginInfos) + { + log.Append(a.ToString()); + log.Append("\n"); + } + log.Append("\n"); + if (NetworkModCompatibilityHelper._networkModList.Length == 0) + { + log.Append("No mods tagged as RequiredByAll. This mod pack is Vanilla compatible."); + } + else + { + log.Append("Mods tagged as RequiredByAll\n\n"); + foreach (var a in NetworkModCompatibilityHelper.networkModList) + { + log.Append(a.ToString()); + log.Append("\n"); + } + } + Log.Message(log.ToString()); + } + } } diff --git a/Code/DT-Commands/Money.cs b/Code/DT-Commands/Money.cs index 34a0842..6ecc0e9 100644 --- a/Code/DT-Commands/Money.cs +++ b/Code/DT-Commands/Money.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.Runtime.CompilerServices; +using System.Xml.Linq; using UnityEngine; using static DebugToolkit.Log; @@ -10,6 +11,7 @@ namespace DebugToolkit.Commands class Money { [ConCommand(commandName = "give_lunar", flags = ConVarFlags.ExecuteOnServer, helpText = Lang.GIVELUNAR_HELP)] + [ConCommand(commandName = "give_void", flags = ConVarFlags.ExecuteOnServer, helpText = Lang.GIVEVOID_HELP)] [AutoComplete(Lang.GIVELUNAR_ARGS)] private static void CCGiveLunar(ConCommandArgs args) { @@ -20,7 +22,7 @@ private static void CCGiveLunar(ConCommandArgs args) } if (args.sender == null) { - Log.Message("Can't modify Lunar coins of other users directly.", LogLevel.MessageClientOnly); + Log.Message("Can't modify Lunar Coins/Void Markers of other users directly.", LogLevel.MessageClientOnly); return; } int amount = 1; @@ -31,20 +33,42 @@ private static void CCGiveLunar(ConCommandArgs args) } string str = "Nothing happened. Big surprise."; NetworkUser target = args.sender; - if (amount > 0) - { - target.AwardLunarCoins((uint)amount); - str = string.Format(Lang.GIVELUNAR_2, "Gave", amount); - } - if (amount < 0) + + + if (amount != 0) { - amount *= -1; - target.DeductLunarCoins((uint)(amount)); - str = string.Format(Lang.GIVELUNAR_2, "Removed", amount); + bool voidMarker = args.commandName == "give_void"; + if (voidMarker) + { + if (amount < 0) + { + amount = -amount; + target.master.voidCoins = HGMath.UintSafeSubtract(target.master.voidCoins, (uint)amount); + } + else + { + target.master.GiveVoidCoins((uint)amount); + } + str = string.Format(Lang.GIVEVOIDC_2, amount > 0 ? "Gave" : "Removed", amount); + } + else + { + if (amount < 0) + { + amount = -amount; + target.DeductLunarCoins((uint)(amount)); + } + else + { + target.AwardLunarCoins((uint)amount); + } + str = string.Format(Lang.GIVELUNAR_2, amount > 0 ? "Gave" : "Removed", amount); + } } Log.MessageNetworked(str, args); } + [ConCommand(commandName = "give_money", flags = ConVarFlags.ExecuteOnServer, helpText = Lang.GIVEMONEY_HELP)] [AutoComplete(Lang.GIVEMONEY_ARGS)] private static void CCGiveMoney(ConCommandArgs args) @@ -97,6 +121,59 @@ private static void CCGiveMoney(ConCommandArgs args) Log.MessageNetworked("$$$", args); } + [ConCommand(commandName = "rich", flags = ConVarFlags.ExecuteOnServer, helpText = Lang.ALLMONEY_HELP)] + [ConCommand(commandName = "poor", flags = ConVarFlags.ExecuteOnServer, helpText = Lang.NO_MONEY_HELP)] + [AutoComplete(Lang.PLAYER_OR_ALL_OPTIONAL_ARGS)] + public static void CCSetMoney(ConCommandArgs args) + { + bool rich = args.commandName == "rich"; + if (args.Count > 0 && args[0].ToUpperInvariant() != Lang.ALL && args[0].ToUpperInvariant() != Lang.DEFAULT_VALUE) + { + NetworkUser player = Util.GetNetUserFromString(args.userArgs, 0); + if (player != null) + { + if (rich) + { + player.master.GiveMoney(2000000000 - player.master.money); + Log.MessageNetworked(string.Format("Made {0} rich.", player.userName), args); + } + else + { + player.master.money = 0; + Log.MessageNetworked(string.Format("Set {0}'s money to 0.", player.userName), args); + } + } + else + { + Log.MessageNetworked(Lang.PLAYER_NOTFOUND, args, LogLevel.MessageClientOnly); + return; + } + } + else + { + foreach (PlayerCharacterMasterController player in PlayerCharacterMasterController.instances) + { + if (rich) + { + player.master.GiveMoney(2000000000 - player.master.money); + } + else + { + player.master.money = 0; + } + } + if (rich) + { + Log.MessageNetworked("We're rich.", args); + } + else + { + Log.MessageNetworked("Reset all money back to 0.", args); + } + } + } + + private static void GiveMasterMoney(CharacterMaster master, int amount) { if (amount > 0) diff --git a/Code/DT-Commands/PlayerCommands.cs b/Code/DT-Commands/PlayerCommands.cs index ed94224..97e200b 100644 --- a/Code/DT-Commands/PlayerCommands.cs +++ b/Code/DT-Commands/PlayerCommands.cs @@ -1,4 +1,5 @@ using RoR2; +using RoR2.ContentManagement; using RoR2.ExpansionManagement; using System; using System.Collections.Generic; @@ -21,6 +22,7 @@ private static void CCGodModeToggle(ConCommandArgs args) Log.MessageNetworked(string.Format(Lang.PARSE_ERROR, "enable", "bool"), args, LogLevel.MessageClientOnly); return; } + Hooks.god = modeOn; } else { @@ -49,14 +51,101 @@ private static void CCBuddhaModeToggle(ConCommandArgs args) Log.MessageNetworked(string.Format(Lang.PARSE_ERROR, "enable", "bool"), args, LogLevel.MessageClientOnly); return; } + Hooks.buddha = modeOn; } else { modeOn = Hooks.ToggleBuddha(); } + foreach (var player in PlayerCharacterMasterController.instances) + { + var body = player.master.GetBody(); + if (body != null) + { + if (modeOn) + { + body.bodyFlags |= CharacterBody.BodyFlags.Buddha; + } + else + { + body.bodyFlags &= ~CharacterBody.BodyFlags.Buddha; + } + } + } + + Log.MessageNetworked(String.Format(modeOn ? Lang.SETTING_ENABLED : Lang.SETTING_DISABLED, "Buddha mode"), args); } + [ConCommand(commandName = "buddhaenemy", flags = ConVarFlags.ExecuteOnServer, helpText = Lang.BUDDHAENEMY_HELP)] + [ConCommand(commandName = "budaenemy", flags = ConVarFlags.ExecuteOnServer, helpText = Lang.BUDDHAENEMY_HELP)] + [AutoComplete(Lang.ENABLE_ARGS)] + private static void CCBuddhaMonstersToggle(ConCommandArgs args) + { + bool modeOn; + if (args.Count > 0) + { + if (!Util.TryParseBool(args[0], out modeOn)) + { + Log.MessageNetworked(string.Format(Lang.PARSE_ERROR, "enable", "bool"), args, LogLevel.MessageClientOnly); + return; + } + Hooks.buddhaMonsters = modeOn; + } + else + { + Hooks.buddhaMonsters = !Hooks.buddhaMonsters; + modeOn = Hooks.buddhaMonsters; + } + foreach (TeamComponent teamComponent in TeamComponent.GetTeamMembers(TeamIndex.Monster)) + { + if (teamComponent.body) + { + if (modeOn) + { + teamComponent.body.bodyFlags |= RoR2.CharacterBody.BodyFlags.Buddha; + } + else + { + teamComponent.body.bodyFlags &= ~RoR2.CharacterBody.BodyFlags.Buddha; + } + } + } + Log.MessageNetworked(String.Format(modeOn ? Lang.SETTING_ENABLED : Lang.SETTING_DISABLED, "Buddha mode for monsters"), args); + } + + [ConCommand(commandName = "godenemy", flags = ConVarFlags.ExecuteOnServer, helpText = Lang.GOD_HELP)] + [AutoComplete(Lang.ENABLE_ARGS)] + private static void CCGodMonstersToggle(ConCommandArgs args) + { + bool modeOn; + if (args.Count > 0) + { + if (!Util.TryParseBool(args[0], out modeOn)) + { + Log.MessageNetworked(string.Format(Lang.PARSE_ERROR, "enable", "bool"), args, LogLevel.MessageClientOnly); + return; + } + Hooks.godMonsters = modeOn; + } + else + { + Hooks.godMonsters = !Hooks.godMonsters; + modeOn = Hooks.godMonsters; + } + foreach (TeamComponent teamComponent in TeamComponent.GetTeamMembers(TeamIndex.Monster)) + { + HealthComponent component = teamComponent.GetComponent(); + component.godMode = modeOn; + if (teamComponent.body.master) + { + teamComponent.body.master.godMode = modeOn; + } + } + Log.MessageNetworked(String.Format(modeOn ? Lang.SETTING_ENABLED : Lang.SETTING_DISABLED, "God mode for monsters"), args); + } + + [ConCommand(commandName = "noclip", flags = ConVarFlags.ExecuteOnServer, helpText = Lang.NOCLIP_HELP)] [AutoComplete(Lang.ENABLE_ARGS)] private static void CCNoclip(ConCommandArgs args) @@ -107,6 +196,93 @@ private static void CCCursorTeleport(ConCommandArgs args) TeleportNet.Invoke(args.sender); // callback } + + [ConCommand(commandName = "goto_boss", flags = ConVarFlags.ExecuteOnServer, helpText = Lang.GOTOBOSS_HELP)] + private static void CCGoto_Boss(ConCommandArgs args) + { + if (!Run.instance) + { + Log.MessageNetworked(Lang.NOTINARUN_ERROR, args, LogLevel.MessageClientOnly); + return; + } + + string stage = Stage.instance.sceneDef.cachedName; + Vector3 newPosition = Vector3.zero; + string destination = string.Empty; + + if (TeleporterInteraction.instance) + { + destination = "Teleporter"; + newPosition = TeleporterInteraction.instance.transform.position; + newPosition += new Vector3(0, 1, 0); + } + else if (BossGroup.GetTotalBossCount() > 0) + { + for (int i = 0; i < CharacterBody.instancesList.Count; i++) + { + if (CharacterBody.instancesList[i].isBoss) + { + newPosition = CharacterBody.instancesList[i].corePosition; + destination = CharacterBody.instancesList[i].name; + break; + } + } + } + else if (stage == "moon2") + { + newPosition = new Vector3(-11, 490, 80); + destination = "Mithrix Arena"; + } + else if (stage == "solutionalhaunt") + { + newPosition = new Vector3(252.5426f, -549.5432f, -90.2127f); //Cutscene Trigger + destination = "Solus Wing Hallway"; + GameObject cutsceneTrigger = GameObject.Find("/HOLDER2: Mission and Meta-Related Systems/Cutscene/Trigger Hallway Trap"); + cutsceneTrigger.GetComponent().Triggered = true; //Because it's OnExitTrigger, can't just teleport there + } + else if (stage == "meridian") + { + newPosition = new Vector3(85.2065f, 146.5167f, -70.5265f); //Cutscene Trigger + destination = "False Son Arena"; + } + else if (stage == "mysteryspace") + { + newPosition = new Vector3(362.9097f, -151.5964f, 213.0157f); //Obelisk + destination = "Obelisk"; + } + else if (stage == "voidraid") + { + destination = "Voidling Arena"; + newPosition = new Vector3(-105f, 0.2f, 92f); + } + if (stage == "conduitcanyon") + { + //Auto complete the power pedestals + List instancesList = InstanceTracker.GetInstancesList(); + foreach (PowerPedestal powerPedestal in instancesList) + { + powerPedestal.SetComplete(true); + } + Debug.Log("Autocompleting Sentry Terminals"); + } + if (newPosition == Vector3.zero) + { + Debug.Log("No Teleporter, Specific Location or Boss Monster found."); + return; + } + + foreach (PlayerCharacterMasterController player in PlayerCharacterMasterController.instances) + { + if (player.master.bodyInstanceObject) + { + TeleportHelper.TeleportGameObject(player.master.bodyInstanceObject, newPosition); + } + } + Debug.Log($"Teleported players to {destination}"); + + } + + [ConCommand(commandName = "spawn_as", flags = ConVarFlags.ExecuteOnServer, helpText = Lang.SPAWNAS_HELP)] [AutoComplete(Lang.SPAWNAS_ARGS)] private static void CCSpawnAs(ConCommandArgs args) @@ -223,6 +399,12 @@ private static void CCHurt(ConCommandArgs args) Log.MessageNetworked(Lang.INSUFFICIENT_ARGS + Lang.HURT_ARGS, args, LogLevel.MessageClientOnly); return; } + bool bypassCalc = false; + if (args.Count > 2 && args[2] != Lang.DEFAULT_VALUE && !Util.TryParseBool(args[2], out bypassCalc)) + { + Log.MessageNetworked(String.Format(Lang.PARSE_ERROR, "direct", "bool"), args, LogLevel.MessageClientOnly); + return; + } var target = Buffs.ParseTarget(args, 1); if (target.failMessage != null) { @@ -248,6 +430,7 @@ private static void CCHurt(ConCommandArgs args) { damage = amount, position = target.body.corePosition, + damageType = !bypassCalc ? DamageTypeCombo.Generic : new DamageTypeCombo(DamageType.BypassArmor & DamageType.BypassBlock & DamageType.BypassOneShotProtection, DamageTypeExtended.BypassDamageCalculations, DamageSource.NoneSpecified) }); Log.MessageNetworked($"Damaged {target.name} for {amount} hp.", args); } @@ -577,7 +760,7 @@ public static void CCLoadoutSetSkinVariant(ConCommandArgs args) var modelSkinController = args.senderBody.modelLocator.modelTransform.GetComponent(); if (modelSkinController) { - modelSkinController.ApplySkin(requestedSkinIndexChange); + modelSkinController.StartCoroutine(modelSkinController.ApplySkinAsync(requestedSkinIndexChange, AsyncReferenceHandleUnloadType.OnSceneUnload)); } } } @@ -606,5 +789,257 @@ internal static bool UpdateCurrentPlayerBody(out NetworkUser networkUser, out Ch return false; } + + [ConCommand(commandName = "skill", flags = ConVarFlags.ExecuteOnServer, helpText = Lang.LOADOUTSKILL_SHORT_ARGS)] + [AutoComplete(Lang.LOADOUTSKILL_ARGS)] + public static void CCSetSkillShort(ConCommandArgs args) + { + if (args.Count < 2) + { + Log.MessageNetworked(Lang.INSUFFICIENT_ARGS + Lang.LOADOUTSKILL_SHORT_ARGS, args, Log.LogLevel.MessageClientOnly); + return; + } + args.userArgs = new List + { + "self", + args[0], + args[1], + }; + UserProfile.CCLoadoutSetSkillVariant(args); } + + [ConCommand(commandName = "skin", flags = ConVarFlags.ExecuteOnServer, helpText = Lang.LOADOUTSKIN_SHORT_ARGS)] + public static void CCSetSkinShort(ConCommandArgs args) + { + if (!TextSerialization.TryParseInvariant(args[0], out int requestedSkinIndexChange)) + { + Log.MessageNetworked(String.Format(Lang.PARSE_ERROR, "skin_index", "int"), args, LogLevel.MessageClientOnly); + } + Macros.Invoke(args.sender, "loadout_set_skin_variant", "self", requestedSkinIndexChange.ToString()); + } + + + [ConCommand(commandName = "nocooldowns", flags = ConVarFlags.ExecuteOnServer, helpText = Lang.NOCOOLDOWN_HELP)] + [AutoComplete(Lang.PLAYER_OR_PINGED)] + public static void CCNoCooldowns(ConCommandArgs args) + { + var target = Buffs.ParseTarget(args, 0); + if (target.failMessage != null) + { + Log.MessageNetworked(target.failMessage, args, LogLevel.MessageClientOnly); + return; + } + bool NoCooldowns = false; + GenericSkill[] slots = target.body.GetComponents(); + foreach (GenericSkill slot in slots) + { + if (slot.skillDef.baseRechargeInterval != 0 && slot.cooldownOverride == 0) + { + NoCooldowns = true; + slot.cooldownOverride = 0.001f; + } + else + { + slot.cooldownOverride = 0; + } + } + if (NoCooldowns && slots[0].CalculateFinalRechargeInterval() > 0.001f) + { + //Some mod is preventing this command from working. + } + string enable = NoCooldowns ? "Disabled" : "Reenabled"; + Log.MessageNetworked($"{enable} Skill Cooldowns for {RoR2.Util.GetBestBodyName(target.body.gameObject)}", args); + } + + + //This does affect gameplay because it deactivates all your hurtboxes too, not sure what to do about that. + [ConCommand(commandName = "hide_model", flags = ConVarFlags.None, helpText = Lang.TOGGLEMODE_HELP)] + public static void CCToggleModel(ConCommandArgs args) + { + if (args.sender == null) + { + Log.MessageWarning(Lang.DS_NOTAVAILABLE); + return; + } + if (!Run.instance) + { + Log.MessageNetworked(Lang.NOTINARUN_ERROR, args, LogLevel.MessageClientOnly); + return; + } + if (!args.senderBody) + { + Log.MessageNetworked("Can't hide your model while you're dead. " + Lang.USE_RESPAWN, args, LogLevel.MessageClientOnly); + return; + } + GameObject mdl = args.senderBody.GetComponent().modelTransform.gameObject; + mdl.SetActive(!mdl.activeSelf); + Log.MessageNetworked(String.Format(RoR2.UI.HUD.cvHudEnable.value ? Lang.SETTING_ENABLED : Lang.SETTING_DISABLED, "Hidden Model"), args, LogLevel.MessageClientOnly); + } + + [ConCommand(commandName = "hide_hud", flags = ConVarFlags.None, helpText = Lang.TOGGLEHUD_HELP)] + public static void CCToggleHUD(ConCommandArgs args) + { + RoR2.UI.HUD.cvHudEnable.SetBool(!RoR2.UI.HUD.cvHudEnable.value); + Log.MessageNetworked(String.Format(RoR2.UI.HUD.cvHudEnable.value ? Lang.SETTING_ENABLED : Lang.SETTING_DISABLED, "Hud"), args, LogLevel.MessageClientOnly); + } + + [ConCommand(commandName = "set_stat", flags = ConVarFlags.SenderMustBeServer, helpText = Lang.SETSTAT_HELP)] + [AutoComplete(Lang.SETSTATS_ARGS)] + public static void CCSetStats(ConCommandArgs args) + { + if (!Run.instance) + { + Log.MessageNetworked(Lang.NOTINARUN_ERROR, args, LogLevel.MessageClientOnly); + return; + } + + if (args.Count == 0 || (args.sender == null && (args.Count < 3 || args[2] == Lang.DEFAULT_VALUE))) + { + Log.MessageNetworked(Lang.INSUFFICIENT_ARGS + Lang.SETSTATS_ARGS, args, LogLevel.MessageClientOnly); + return; + } + var target = Buffs.ParseTarget(args, 2); + if (target.failMessage != null) + { + Log.MessageNetworked(target.failMessage, args, LogLevel.MessageClientOnly); + return; + } + bool reset = false; + float amount = 0; + if (args.Count == 1 || args[1] == Lang.DEFAULT_VALUE || args[1].ToUpperInvariant() == "RESET") + { + reset = true; + } + else + { + if (!TextSerialization.TryParseInvariant(args[1], out amount)) + { + Log.MessageNetworked(string.Format(Lang.PARSE_ERROR, "amount", "float"), args, LogLevel.MessageClientOnly); + return; + } + } + + Stat statToSet = Enum.TryParse(args[0], true, out Stat itemType) ? itemType : Stat.None; + if (statToSet == Stat.None) + { + Log.MessageNetworked(string.Format(Lang.PARSE_ERROR, "stat", "string"), args, LogLevel.MessageClientOnly); + return; + } + SetStatsForBody(target.body, statToSet, amount, reset); + if (reset) + { + Log.MessageNetworked($"Reset {target.name}'s {statToSet} to it's base value.", args); + } + else + { + Log.MessageNetworked($"Set {target.name}'s {statToSet} to {amount}.", args); + } + + + } + + public enum Stat + { + None = -1, + Damage, + AttackSpeed, + Crit, + Health, + Shield, + Regen, + Armor, + MoveSpeed, + Acceleration, + JumpPower, + } + public static void SetStatsForBody(CharacterBody body, Stat stat, float amount, bool reset) + { + var ogBody = body.master.bodyPrefab.GetComponent(); + switch (stat) + { + case Stat.Damage: + body.baseDamage = reset ? ogBody.baseDamage : amount; + body.levelDamage = reset ? ogBody.levelDamage : 0; + break; + case Stat.AttackSpeed: + body.baseAttackSpeed = reset ? ogBody.baseAttackSpeed : amount; + body.levelAttackSpeed = reset ? ogBody.levelAttackSpeed : 0; + break; + case Stat.Crit: + body.baseCrit = reset ? ogBody.baseCrit : amount; + body.levelCrit = reset ? ogBody.levelCrit : 0; + break; + case Stat.Health: + body.baseMaxHealth = reset ? ogBody.baseMaxHealth : amount; + body.levelMaxHealth = reset ? ogBody.levelMaxHealth : 0; + break; + case Stat.Shield: + body.baseMaxShield = reset ? ogBody.baseMaxShield : amount; + body.levelMaxShield = reset ? ogBody.levelMaxShield : 0; + break; + case Stat.Regen: + body.baseRegen = reset ? ogBody.baseRegen : amount; + body.levelRegen = reset ? ogBody.levelRegen : 0; + break; + case Stat.Armor: + body.baseArmor = reset ? ogBody.baseArmor : amount; + body.levelArmor = reset ? ogBody.levelArmor : 0; + break; + case Stat.MoveSpeed: + body.baseMoveSpeed = reset ? ogBody.baseMoveSpeed : amount; + body.levelMoveSpeed = reset ? ogBody.levelMoveSpeed : 0; + break; + case Stat.Acceleration: + body.baseAcceleration = reset ? ogBody.baseAcceleration : amount; + break; + case Stat.JumpPower: + body.baseJumpPower = reset ? ogBody.baseJumpPower : amount; + body.levelJumpPower = reset ? ogBody.levelJumpPower : 0; + break; + } + body.MarkAllStatsDirty(); + } + + + [ConCommand(commandName = "remove_all_minions", flags = ConVarFlags.ExecuteOnServer, helpText = Lang.REMOVEALLMINIONS_HELP)] + [AutoComplete(Lang.REMOVEALLMINIONS_ARGS)] + private static void CCRemoveDrones(ConCommandArgs args) + { + if (!Run.instance) + { + Log.MessageNetworked(Lang.NOTINARUN_ERROR, args, LogLevel.MessageClientOnly); + return; + } + bool isDedicatedServer = args.sender == null; + if (isDedicatedServer && (args.Count < 1 || args[0] == Lang.DEFAULT_VALUE)) + { + Log.MessageNetworked(Lang.INSUFFICIENT_ARGS + Lang.REMOVEALLMINIONS_ARGS, args, LogLevel.MessageClientOnly); + return; + } + + var target = Items.ParseTarget(args, 0); + if (target.failMessage != null) + { + Log.MessageNetworked(target.failMessage, args, LogLevel.MessageClientOnly); + return; + } + + + var owner = target.inventory.GetComponent(); + if (owner == null || owner.group == null || owner.group.memberCount == 0) + { + Log.MessageNetworked(string.Format(Lang.REMOVED_MINIONS, 0, target.name), args); + return; + } + int removedAmount = 0; + for (int i = 0; i < owner.group.memberCount; i++) + { + var master = owner.group.members[i].GetComponent(); + master.DestroyBody(); + UnityEngine.Object.Destroy(master.gameObject, 1f); + removedAmount++; + } + Log.MessageNetworked(string.Format(Lang.REMOVED_MINIONS, removedAmount, target.name), args); + } + } } diff --git a/Code/DT-Commands/Spawners.cs b/Code/DT-Commands/Spawners.cs index 1481d6b..1278e8d 100644 --- a/Code/DT-Commands/Spawners.cs +++ b/Code/DT-Commands/Spawners.cs @@ -13,7 +13,7 @@ namespace DebugToolkit.Commands { class Spawners { - private static readonly Dictionary portals = new Dictionary(); + public static readonly Dictionary portals = new Dictionary(); [ConCommand(commandName = "spawn_interactable", flags = ConVarFlags.ExecuteOnServer, helpText = Lang.SPAWNINTERACTABLE_HELP)] [ConCommand(commandName = "spawn_interactible", flags = ConVarFlags.ExecuteOnServer, helpText = Lang.SPAWNINTERACTABLE_HELP)] @@ -46,6 +46,12 @@ private static void CCSpawnInteractable(ConCommandArgs args) Log.MessageNetworked(Lang.INTERACTABLE_NOTFOUND, args, LogLevel.MessageClientOnly); return; } + int amount = 1; + if (args.Count > 1 && args[1] != Lang.DEFAULT_VALUE && !TextSerialization.TryParseInvariant(args[1], out amount)) + { + Log.MessageNetworked(String.Format(Lang.PARSE_ERROR, "count", "int"), args, LogLevel.MessageClientOnly); + return; + } // Putting interactables with a collider just far enough to not cause any clipping // or spawn under the character's feet. The few exceptions with MeshCollider aren't // treated but they aren't much of an issue. @@ -81,10 +87,19 @@ private static void CCSpawnInteractable(ConCommandArgs args) var direction = args.senderBody.inputBank.aimDirection; position = position + (args.senderBody.radius + distance) * new Vector3(direction.x, 0f, direction.z); } - var result = isc.DoSpawn(position, new Quaternion(), new DirectorSpawnRequest(isc, null, RoR2Application.rng)); - if (!result.success) + Log.MessageNetworked(string.Format(Lang.SPAWN_ATTEMPT_2, amount, isc.prefab.name), args); + int failed = 0; + for (int i = 0; i < amount; i++) { - Log.MessageNetworked("Failed to spawn interactable.", args, LogLevel.MessageClientOnly); + var result = isc.DoSpawn(position, new Quaternion(), new DirectorSpawnRequest(isc, null, RoR2Application.rng)); + if (!result.success) + { + failed++; + } + } + if (failed > 0) + { + Log.MessageNetworked($"Failed to spawn {failed} interactable.", args, LogLevel.MessageClientOnly); } } @@ -127,13 +142,19 @@ private static void CCSpawnPortal(ConCommandArgs args) } var position = args.senderBody.footPosition; // Some portals spawn into the ground - if (portal.name == "DeepVoidPortal") - { - position.y += 4f; - } - else if (portal.name == "PortalArtifactworld") - { - position.y += 10f; + switch (portal.name) + { + case "DeepVoidPortal": + position.y += 4f; + break; + case "SolusWebPortal": + case "CompExchangePortal": + case "EyePortal": + position.y += 5f; + break; + case "PortalArtifactworld": + position.y += 10f; + break; } var gameObject = UnityEngine.Object.Instantiate(portal, position, Quaternion.LookRotation(args.senderBody.characterDirection.forward)); @@ -141,7 +162,16 @@ private static void CCSpawnPortal(ConCommandArgs args) // The artifact portal erroneously points to mysteryspace by default if (portalName == "artifact") { - exit.destinationScene = SceneCatalog.FindSceneDef("artifactworld"); + int num = UnityEngine.Random.Range(0, 4); + SceneDef sceneDef = SceneCatalog.FindSceneDef("artifactworld0" + num); + if (sceneDef && sceneDef.requiredExpansion && Run.instance.IsExpansionEnabled(sceneDef.requiredExpansion)) + { + exit.destinationScene = sceneDef; + } + else + { + exit.destinationScene = SceneCatalog.FindSceneDef("artifactworld"); + } } if (currentScene.cachedName == "voidraid" && gameObject.name.Contains("VoidOutroPortal")) { @@ -154,9 +184,19 @@ private static void CCSpawnPortal(ConCommandArgs args) NetworkServer.Spawn(gameObject); } + [ConCommand(commandName = "spawn_minion", flags = ConVarFlags.ExecuteOnServer, helpText = Lang.SPAWNMINION_HELP)] + [AutoComplete(Lang.SPAWNMINION_ARGS)] + private static void CCSpawnMinion(ConCommandArgs args) + { + CCSpawnCharacter(args, true); + } [ConCommand(commandName = "spawn_ai", flags = ConVarFlags.ExecuteOnServer, helpText = Lang.SPAWNAI_HELP)] [AutoComplete(Lang.SPAWNAI_ARGS)] private static void CCSpawnAI(ConCommandArgs args) + { + CCSpawnCharacter(args, false); + } + private static void CCSpawnCharacter(ConCommandArgs args, bool spawnAsMinion) { if (!Run.instance) { @@ -194,23 +234,44 @@ private static void CCSpawnAI(ConCommandArgs args) return; } + int droneTier = 0; EliteDef eliteDef = null; if (args.Count > 2 && args[2] != Lang.DEFAULT_VALUE) { - var eliteIndex = StringFinder.Instance.GetEliteFromPartial(args[2]); - if (eliteIndex == StringFinder.EliteIndex_NotFound) + if (spawnAsMinion) { - Log.MessageNetworked(Lang.ELITE_NOTFOUND, args, LogLevel.MessageClientOnly); - return; + if (!TextSerialization.TryParseInvariant(args[2], out droneTier)) + { + Log.MessageNetworked(String.Format(Lang.PARSE_ERROR, "droneTier", "int"), args, LogLevel.MessageClientOnly); + return; + } + if (droneTier > 1 && Run.instance.IsItemExpansionLocked(DLC3Content.Items.DroneUpgradeHidden.itemIndex)) + { + Log.MessageNetworked("Tiered Minions require Alloyed Collective", args, LogLevel.WarningClientOnly); + } + if (droneTier < 0) + { + Log.MessageNetworked(string.Format(Lang.NEGATIVE_ARG, "droneTier"), args, LogLevel.MessageClientOnly); + return; + } } - eliteDef = EliteCatalog.GetEliteDef(eliteIndex); - if (eliteDef && eliteDef.eliteEquipmentDef && Run.instance.IsEquipmentExpansionLocked(eliteDef.eliteEquipmentDef.equipmentIndex)) + else { - var expansion = Util.GetExpansion(eliteDef.eliteEquipmentDef.requiredExpansion); - Log.MessageNetworked(string.Format(Lang.EXPANSION_LOCKED, "elite equipment", expansion), args, LogLevel.WarningClientOnly); + var eliteIndex = StringFinder.Instance.GetEliteFromPartial(args[2]); + if (eliteIndex == StringFinder.EliteIndex_NotFound) + { + Log.MessageNetworked(Lang.ELITE_NOTFOUND, args, LogLevel.MessageClientOnly); + return; + } + eliteDef = EliteCatalog.GetEliteDef(eliteIndex); + if (eliteDef && eliteDef.eliteEquipmentDef && Run.instance.IsEquipmentExpansionLocked(eliteDef.eliteEquipmentDef.equipmentIndex)) + { + var expansion = Util.GetExpansion(eliteDef.eliteEquipmentDef.requiredExpansion); + Log.MessageNetworked(string.Format(Lang.EXPANSION_LOCKED, "elite equipment", expansion), args, LogLevel.WarningClientOnly); + } } } - + bool braindead = false; if (args.Count > 3 && args[3] != Lang.DEFAULT_VALUE && !Util.TryParseBool(args[3], out braindead)) { @@ -218,23 +279,15 @@ private static void CCSpawnAI(ConCommandArgs args) return; } - bool isAlly = false; + TeamIndex teamIndex = TeamIndex.Monster; - if (args.Count > 4 && args[4] != Lang.DEFAULT_VALUE) + if (args.Count > 4 && args[4] != Lang.DEFAULT_VALUE && !spawnAsMinion) { - if (args[4].ToUpperInvariant() == Lang.ALLY) + teamIndex = StringFinder.Instance.GetTeamFromPartial(args[4]); + if (teamIndex == StringFinder.TeamIndex_NotFound) { - isAlly = true; - teamIndex = args.senderBody.teamComponent.teamIndex; - } - else - { - teamIndex = StringFinder.Instance.GetTeamFromPartial(args[4]); - if (teamIndex == StringFinder.TeamIndex_NotFound) - { - Log.MessageNetworked(Lang.TEAM_NOTFOUND, args, LogLevel.MessageClientOnly); - return; - } + Log.MessageNetworked(Lang.TEAM_NOTFOUND, args, LogLevel.MessageClientOnly); + return; } } @@ -262,8 +315,8 @@ private static void CCSpawnAI(ConCommandArgs args) }, RoR2Application.rng ); - spawnRequest.summonerBodyObject = isAlly ? args.senderBody.gameObject : null; - spawnRequest.teamIndexOverride = teamIndex; + spawnRequest.summonerBodyObject = spawnAsMinion ? args.senderBody.gameObject : null; + spawnRequest.teamIndexOverride = spawnAsMinion ? args.senderBody.teamComponent.teamIndex : teamIndex; spawnRequest.ignoreTeamMemberLimit = true; // The size of the monster's radius is required so multiple enemies do not spawn on the same spot. @@ -331,6 +384,10 @@ private static void CCSpawnAI(ConCommandArgs args) } master.aiComponents = Array.Empty(); } + if (droneTier > 1) + { + master.inventory.GiveItemPermanent(DLC3Content.Items.DroneUpgradeHidden, droneTier-1); + } } } } @@ -378,11 +435,20 @@ internal static void InitPortals() portals.Add("artifact", Addressables.LoadAssetAsync("RoR2/Base/PortalArtifactworld/PortalArtifactworld.prefab").WaitForCompletion()); portals.Add("blue", Addressables.LoadAssetAsync("RoR2/Base/PortalShop/PortalShop.prefab").WaitForCompletion()); portals.Add("celestial", Addressables.LoadAssetAsync("RoR2/Base/PortalMS/PortalMS.prefab").WaitForCompletion()); - portals.Add("deepvoid", Addressables.LoadAssetAsync("RoR2/DLC1/DeepVoidPortal/DeepVoidPortal.prefab").WaitForCompletion()); portals.Add("gold", Addressables.LoadAssetAsync("RoR2/Base/PortalGoldshores/PortalGoldshores.prefab").WaitForCompletion()); - portals.Add("green", Addressables.LoadAssetAsync("RoR2/DLC2/PortalColossus.prefab").WaitForCompletion()); portals.Add("null", Addressables.LoadAssetAsync("RoR2/Base/PortalArena/PortalArena.prefab").WaitForCompletion()); + //DLC1 portals.Add("void", Addressables.LoadAssetAsync("RoR2/DLC1/PortalVoid/PortalVoid.prefab").WaitForCompletion()); + portals.Add("deepvoid", Addressables.LoadAssetAsync("RoR2/DLC1/DeepVoidPortal/DeepVoidPortal.prefab").WaitForCompletion()); + portals.Add("simulacrum", Addressables.LoadAssetAsync("RoR2/DLC1/GameModes/InfiniteTowerRun/PortalInfiniteTower.prefab").WaitForCompletion()); + //DLC2 + portals.Add("green", Addressables.LoadAssetAsync("RoR2/DLC2/PortalColossus.prefab").WaitForCompletion()); + portals.Add("destination", Addressables.LoadAssetAsync("RoR2/DLC2/PM DestinationPortal.prefab").WaitForCompletion()); + //DLC3 + portals.Add("encrypted", Addressables.LoadAssetAsync("RoR2/DLC3/HardwareProgPortal.prefab").WaitForCompletion()); + portals.Add("decrypted", Addressables.LoadAssetAsync("RoR2/DLC3/SolusWebPortal.prefab").WaitForCompletion()); + portals.Add("virtual", Addressables.LoadAssetAsync("RoR2/DLC3/CompExchangePortal.prefab").WaitForCompletion()); + portals.Add("mainline", Addressables.LoadAssetAsync("RoR2/DLC3/EyePortal/EyePortal.prefab").WaitForCompletion()); } internal static CombatDirector.EliteTierDef GetTierDef(EliteDef eliteDef) diff --git a/Code/DebugToolkit.cs b/Code/DebugToolkit.cs index f9ec603..b950d29 100644 --- a/Code/DebugToolkit.cs +++ b/Code/DebugToolkit.cs @@ -73,6 +73,7 @@ private void LogBuildInfo() private void Start() { var _ = StringFinder.Instance; + RoR2Application.onLoad += AutoCompleteManager.RegisterAutoCompleteCommands; } private void Update() diff --git a/Code/Hooks.cs b/Code/Hooks.cs index 39d8b6a..8c5e400 100644 --- a/Code/Hooks.cs +++ b/Code/Hooks.cs @@ -14,7 +14,6 @@ using UnityEngine; using UnityEngine.Networking; using Console = RoR2.Console; - namespace DebugToolkit { public sealed class Hooks @@ -26,8 +25,10 @@ public sealed class Hooks private static int[] autoCompleteIndices; private static On.RoR2.Console.orig_RunCmd _origRunCmd; - private static bool buddha; - private static bool god; + public static bool buddha; + public static bool buddhaMonsters; + public static bool god; + public static bool godMonsters; private static GameObject commandSignatureText; private static CombatDirector bossDirector; @@ -58,7 +59,7 @@ public static void InitializeHooks() On.RoR2.Console.AutoComplete.SetSearchString += BetterAutoCompletion; RoR2Application.onLoad += Items.InitDroptableData; RoR2Application.onLoad += Spawners.InitPortals; - RoR2Application.onLoad += AutoCompleteManager.RegisterAutoCompleteCommands; + Run.onRunStartGlobal += Items.CollectItemTiers; On.RoR2.UI.ConsoleWindow.Start += AddConCommandSignatureHint; On.RoR2.UI.ConsoleWindow.OnInputFieldValueChanged += UpdateCommandSignature; @@ -93,12 +94,91 @@ public static void InitializeHooks() Run.onRunDestroyGlobal += Command_Noclip.DisableOnRunDestroy; //Buddha Mode hook - On.RoR2.HealthComponent.TakeDamage += NonLethalDamage; + On.RoR2.CharacterBody.Start += SetBuddhaMode; On.RoR2.CharacterMaster.Awake += SetGodMode; // Console logging fixes - temporary IL.RoR2.Console.ShowHelpText += FixCommandHelpText; IL.RoR2.Console.CCFind += FixCCFind; + + On.RoR2.ConCommandArgExtensions.TryGetArgBodyIndex += AllowBodyIndexToBeUsedInsteadOfBodyName; + On.RoR2.UserProfile.CCLoadoutSetSkillVariant += LoadoutSetSkillVariant_AcceptSELF; + On.RoR2.Run.CCRunSetStagesCleared += RunSetStagesCleared_SetLoopCountToo; + } + + private static void RunSetStagesCleared_SetLoopCountToo(On.RoR2.Run.orig_CCRunSetStagesCleared orig, ConCommandArgs args) + { + orig(args); + if (Run.instance) + { + Run.instance.Network_loopClearCount = Run.instance.NetworkstageClearCount / 5; + } + } + + private static void LoadoutSetSkillVariant_AcceptSELF(On.RoR2.UserProfile.orig_CCLoadoutSetSkillVariant orig, ConCommandArgs args) + { + if (args.Count < 3) + { + Log.MessageNetworked(Lang.INSUFFICIENT_ARGS + Lang.LOADOUTSKILL_ARGS, args, Log.LogLevel.MessageClientOnly); + return; + } + string isSelf = args.TryGetArgString(0); + if(isSelf != null && isSelf.ToUpperInvariant() == "SELF") + { + if (args.sender == null) + { + Log.Message("Can't choose self if not in-game!", Log.LogLevel.Error); + return; + } + BodyIndex body = BodyIndex.None; + if (args.senderBody) + { + body = args.senderBody.bodyIndex; + } + else + { + if (args.senderMaster && args.senderMaster.bodyPrefab) + { + body = args.senderMaster.bodyPrefab.GetComponent().bodyIndex; + } + else + { + body = args.sender.bodyIndexPreference; + } + } + args.userArgs[0] = ((int)body).ToString(); + + } + orig(args); + } + + private static BodyIndex? AllowBodyIndexToBeUsedInsteadOfBodyName(On.RoR2.ConCommandArgExtensions.orig_TryGetArgBodyIndex orig, ConCommandArgs args, int index) + { + BodyIndex? temp = orig(args, index); + if (temp == null) + { + if (index < args.userArgs.Count) + { + return new BodyIndex?((BodyIndex)args.TryGetArgInt(index).GetValueOrDefault(-1)); + } + } + return temp; + } + + private static void SetBuddhaMode(On.RoR2.CharacterBody.orig_Start orig, CharacterBody self) + { + orig(self); + if (self.isPlayerControlled) + { + if (buddha) + { + self.bodyFlags |= CharacterBody.BodyFlags.Buddha; + } + } + else if(buddhaMonsters && self.teamComponent && (self.teamComponent.teamIndex == TeamIndex.Monster)) + { + self.bodyFlags |= CharacterBody.BodyFlags.Buddha; + } } private static void ToggleConsoleWindowWithCustomKey(ILContext il) @@ -156,18 +236,10 @@ private static void SetGodMode(On.RoR2.CharacterMaster.orig_Awake orig, Characte if (NetworkServer.active) { self.godMode |= self.playerCharacterMasterController && god; + self.godMode |= self.teamIndex == TeamIndex.Monster && godMonsters; } } - - private static void NonLethalDamage(On.RoR2.HealthComponent.orig_TakeDamage orig, HealthComponent self, DamageInfo damageInfo) - { - if (buddha && self.body.isPlayerControlled) - { - damageInfo.damageType |= DamageType.NonLethal; - } - orig(self, damageInfo); - } - + private static void InfiniteTowerRun_BeginNextWave(ILContext il) { var c = new ILCursor(il); diff --git a/Code/Lang.cs b/Code/Lang.cs index a1790eb..6323ef0 100644 --- a/Code/Lang.cs +++ b/Code/Lang.cs @@ -11,8 +11,8 @@ public const string BIND_ARGS = "Requires 2 arguments: {key} [console_commands]", BIND_DELETE_ARGS = "Requires 1 argument: {key}", CHANGETEAM_ARGS = "Requires 1 (2 if from server) argument: {team} [player:]", - CHARGEZONE_ARGS = "Requires 1 argument: {charge}", - CREATEPICKUP_ARGS = "Requires 1 (3 if from server) argument: {object (item|equip|'lunarcoin'|'voidcoin')} [type ('permanent'|'temp'):'permanent'] [search ('item'|'equip'|'both'):'both'] *[player:]", + CHARGEZONE_ARGS = "Requires 0 argument: [charge:100]", + CREATEPICKUP_ARGS = "Requires 1 (3 if from server) argument: {object (item|equip|drone|'lunarcoin'|'voidcoin')} [search:'all'] [pickup_type:'permanent'] player:]", CREATEPOTENTIAL_ARGS = "Requires 0 (3 if from server) arguments: [droptable (droptable|'all'):'all'] [count:3] *[player:]", DELAY_ARGS = "Requires 2 arguments: {delay} {console_commands}", DUMPSTATE_ARGS = "Requires 0 (1 if from server) argument: [target (player|'pinged'):]", @@ -23,24 +23,36 @@ public const string GIVEBUFF_ARGS = "Requires 1 (4 if from server) arguments: {buff} [count:1] [duration:0] [target (player|'pinged'):]", GIVEDOT_ARGS = "Requires 1 (4 if from server) argument: {dot} [count:1] [target (player|'pinged'):] [attacker (player|'pinged'):]", GIVEEQUIP_ARGS = "Requires 1 (2 if from server) argument: {equip (equip|'random')} [target (player|'pinged'|'evolution'|'simulacrum'|'voidfields'|'devotion'):]", - GIVEITEM_ARGS = "Requires 1 (3 if from server) argument: {item} [count:1] [type ('permanent'|'temp'):'permanent'] [target (player|'pinged'|'evolution'|'simulacrum'|'voidfields'|'devotion'):]", + RANDOMEQUIP_ARGS = "Requires 0 (1 if from server) argument: [target (player|'pinged'):]", + GIVEITEM_ARGS = "Requires 1 (3 if from server) argument: {item} [count:1] [item_type:'permanent'] [target (player|'pinged'|'evolution'|'simulacrum'|'voidfields'|'devotion'):]", + GIVEDRONE_ARGS = "Requires 1 (4 if from server) argument: {drone} [count:1] [droneTier:1] [target (player|'pinged'):]", + REMOVEALLMINIONS_ARGS = "Requires 0 (1 if from server) argument: [target (player|'pinged'):]", GIVELUNAR_ARGS = "Requires 0 arguments: [amount:1]", GIVEMONEY_ARGS = "Requires 1 argument: {amount} [target (player|'all')]", + PLAYER_OR_ALL_OPTIONAL_ARGS = "Requires 0 argument: [target (player|'all'):]", + PLAYER_OR_ALL_ARGS = "Requires 1 argument: {target (player|'all')}", + SETSTATS_ARGS = "Requires 1 argument: {stat} [amount|'reset'):'reset'] [target (player|'pinged'):]", + PLAYER_OR_PINGED = "Requires 0 (1 if from server) arguments: [target (player|'pinged'):]", + PLAYERPINGED_OR_ALL = "Requires 0 (1 if from server) arguments: [target (player|'pinged'|'all'):]", HEAL_ARGS = "Requires 1 argument: {amount} [target (player|'pinged'):]", - HURT_ARGS = "Requires 1 argument: {amount} [target (player|'pinged'):]", - KICK_ARGS = "Requires 1 argument: {player}", - KILLALL_ARGS = "Requires 0 arguments: [team:Monster]", + HURT_ARGS = "Requires 1 argument: {amount} [target (player|'pinged'):] [direct (0|1):0/false]", + PLAYER_ARGS = "Requires 1 argument: {player}", + KILLALL_ARGS = "Requires 0 arguments: [team:Monster&Void]", LISTSKIN_ARGS = "Requires 0 arguments: [selection (body|'all'|'body'|'self'):'all']", LISTQUERY_ARGS = "Requires 0 arguments: [query]", LOADOUTSKIN_ARGS = "Requires 2 argument: {body_name (body|'self')} {skin_index}", + LOADOUTSKIN_SHORT_ARGS = "Requires 1 argument: {skin_index}", + LOADOUTSKILL_ARGS = "Requires 3 argument: {bodyIndex|'self'} {skill_slot} {skill_variant}", + LOADOUTSKILL_SHORT_ARGS = "Requires 2 argument: {skill_slot} {skill_variant}", NEXTBOSS_ARGS = "Requires 1 argument: {director_card} [count:1] [elite:None]", NEXTSTAGE_ARGS = "Requires 0 arguments: [specific_stage]", NO_ARGS = "Requires 0 arguments.", PERM_MOD_ARGS = "Requires 2 arguments: {permission_level} {player}", POSTSOUNDEVENT_ARGS = "Requires 1 argument: {sound_event (event_name|event_id)}", - RANDOMITEM_ARGS = "Requires 1 (3 if from server) argument: {count} [droptable (droptable|'all'):'all'] [type ('permanent'|'temp'):'permanent'] [target (player|'pinged'|'evolution'|'simulacrum'|'voidfields'|'devotion'):]", + RANDOMITEM_ARGS = "Requires 1 (3 if from server) argument: {count} [droptable (droptable|'all'):'\"Tier1:100,Tier2:60,Tier3:4\"'] [item_type:'permanent'] [target (player|'pinged'|'evolution'|'simulacrum'|'voidfields'|'devotion'):]", REMOVEALLBUFFS_ARGS = "Requires 0 (2 if from server) arguments: [timed (0|1):0/false] [target (player|'pinged'):]", REMOVEALLDOTS_ARGS = "Requires 0 (1 if from server) arguments: [target (player|'pinged'):]", + GIVEALLITEMS_ARGS = "Requires 1 (2 if from server) arguments: {itemTier (itemTier|'consumed'|'all')} [target (player|'pinged'|'evolution'|'simulacrum'|'voidfields'|'devotion'):]", REMOVEALLITEMS_ARGS = "Requires 0 (1 if from server) arguments: [target (player|'pinged'|'evolution'|'simulacrum'|'voidfields'|'devotion'):]", REMOVEBUFF_ARGS = "Requires 1 (4 if from server) arguments: {buff} [count:1] [timed (0|1):0/false] [target (player|'pinged'):]", REMOVEBUFFSTACKS_ARGS = "Requires 1 (3 if from server) arguments: {buff} [timed (0|1):0/false] [target (player|'pinged'):]", @@ -53,13 +65,14 @@ public const string RUNSETWAVESCLEARED_ARGS = "Requires 1 argument {wave}", SEED_ARGS = "Requires 0 or 1 argument: [new_seed]", SETARTIFACT_ARGS = "Requires 1 (2 if using 'all') argument: {artifact (artifact|'all')} [enable (0|1)]", + SETDIFFICULTY_ARGS = "Requires 1 argument: {difficulty}", SPAWNAI_ARGS = "Requires 1 argument: {ai} [count:1] [elite:None] [braindead (0|1):0/false] [team (team|'ally'):Monster]", + SPAWNMINION_ARGS = "Requires 1 argument: {ai} [count:1] [droneTier:1] [braindead (0|1):0/false]", SPAWNAS_ARGS = "Requires 1 (2 if from server) argument: {body} [player:]", SPAWNBODY_ARGS = "Requires 1 argument: {body}", - SPAWNINTERACTABLE_ARGS = "Requires 1 argument: {interactable}", - SPAWNPORTAL_ARGS = "Requires 1 argument: {portal ('artifact'|'blue'|'celestial'|'deepvoid'|'gold'|'green'|'null'|'void')}", - TIMESCALE_ARGS = "Requires 1 argument: {time_scale}", - TRUEKILL_ARGS = "Requires 0 (1 if from server) arguments: [player:]" + SPAWNINTERACTABLE_ARGS = "Requires 1 argument: {interactable} [count:1]", + SPAWNPORTAL_ARGS = "Requires 1 argument: {portal ('artifact'|'blue'|'celestial'|'deepvoid'|'gold'|'green'|'null'|'void'|'simulacrum'|'encrypted'|'decrypted'|'virtual'|'mainline')}", + TIMESCALE_ARGS = "Requires 1 argument: {time_scale}" ; // Command help texts @@ -69,39 +82,55 @@ public const string BIND_HELP = "Bind a key to execute specific commands. " + BIND_ARGS, BIND_DELETE_HELP = "Remove a custom bind from the macro system of DebugToolkit. " + BIND_DELETE_ARGS, BIND_RELOAD_HELP = "Reload the macro system of DebugToolkit. " + NO_ARGS, - BUDDHA_HELP = "Become immortal. Instead of refusing damage you just refuse to take lethal damage.\nIt works by giving all damage a player takes DamageType.NonLethal. " + ENABLE_ARGS, + BUDDHA_HELP = "Players become immortal, but remain attackable. " + ENABLE_ARGS, + BUDDHAENEMY_HELP = "Enemies become immortal, but remain attackable. " + ENABLE_ARGS, CHANGETEAM_HELP = "Change the specified player to the specified team. " + CHANGETEAM_ARGS, CHARGEZONE_HELP = "Set the charge of all active holdout zones. " + CHARGEZONE_ARGS, CREATEPICKUP_HELP = "Creates a PickupDroplet infront of your position. " + CREATEPICKUP_ARGS, CREATEPOTENTIAL_HELP = "Creates a potential PickupDroplet infront of your position. " + CREATEPOTENTIAL_ARGS, - CURSORTELEPORT_HELP = "Teleport you to where your cursor is currently aiming at. " + NO_ARGS, + CURSORTELEPORT_HELP = "Teleports you to where your cursor is currently aiming at. " + NO_ARGS, + GOTOBOSS_HELP = "Teleports you to the Teleporter, first Boss creature or specific arena depending on the stage. " + NO_ARGS, DELAY_HELP = "Execute any commands after a delay in seconds. " + DELAY_ARGS, DUMPBUFFS_HELP = "List the buffs/debuffs of all spawned bodies. " + NO_ARGS, DUMPINVENTORIES_HELP = "List the inventory items and equipment of all spawned bodies. " + NO_ARGS, DUMPSTATE_HELP = "List the current stats, entity state, and skill cooldown of a specified body. " + DUMPSTATE_ARGS, DUMPSTATS_HELP = "List the base stats of a specific body. " + DUMPSTATS_ARGS, + DUMPMODS_HELP = "Lists installed mods and mods that are required by everyone." + NO_ARGS, + EVOLVE_LEMURIAN_HELP = "Triggers evolution for Artifact of Devotion Lemurians." + PLAYER_OR_ALL_OPTIONAL_ARGS, FAMILYEVENT_HELP = "Forces a family event to occur during the next stage. " + NO_ARGS, FIXEDTIME_HELP = "Sets the run timer to the specified value. " + FIXEDTIME_ARGS, FORCEWAVE_HELP = "Set the next wave prefab. Leave empty to see all options. " + FORCEWAVE_ARGS, GIVEBUFF_HELP = "Gives the specified buff to a character. A duration of 0 means permanent. " + GIVEBUFF_ARGS, GIVEDOT_HELP = "Gives the specified DoT to a character. " + GIVEDOT_ARGS, GIVEEQUIP_HELP = "Gives the specified equipment to a target. " + GIVEEQUIP_ARGS, + RANDOMEQUIP_HELP = "Gives a random equipment to a target. " + RANDOMEQUIP_ARGS, GIVEITEM_HELP = "Gives the specified item to a target. " + GIVEITEM_ARGS, - GIVELUNAR_HELP = "Gives a lunar coin to you. " + GIVELUNAR_ARGS, + GIVELUNAR_HELP = "Gives amount of lunar coin to you. " + GIVELUNAR_ARGS, + GIVEVOID_HELP = "Gives amount of void markers to you. " + GIVELUNAR_ARGS, GIVEMONEY_HELP = "Gives the specified amount of money to the specified player. " + GIVEMONEY_ARGS, + NO_MONEY_HELP = "Sets money to 0 for specified player. " + GIVEMONEY_ARGS, + ALLMONEY_HELP = "Sets money to 2'000'000'000 for specified player. " + GIVEMONEY_ARGS, GOD_HELP = "Become invincible. " + ENABLE_ARGS, + GODENEMY_HELP = "All monster team members become invincible. " + ENABLE_ARGS, + SETSTAT_HELP = "Sets the base stat to the specified value, or reset it if no value given. Use respawn to undo all changes on self." + SETSTATS_ARGS, + GIVEDRONE_HELP = "Summons the specified drone to a target. " + GIVEDRONE_ARGS, + REMOVEALLMINIONS_HELP = "Destroys the targets minions, leaving no bodies or broken ones. " + REMOVEALLMINIONS_ARGS, + HEAL_HELP = "Heal yourself or another target. " + HEAL_ARGS, HURT_HELP = "Deal generic damage to yourself or another target. " + HURT_ARGS, - KICK_HELP = "Kicks the specified player from the session. " + KICK_ARGS, + KICK_HELP = "Kicks the specified player from the session. " + PLAYER_ARGS, KILLALL_HELP = "Kill all entities on the specified team. " + KILLALL_ARGS, LISTAI_HELP = "List all Masters and their language invariants. " + LISTQUERY_ARGS, LISTARTIFACT_HELP = "List all Artifacts and their language invariants. " + LISTQUERY_ARGS, + LISTDIFFICULTY_HELP = "List all Difficulties and their language invariants. " + LISTQUERY_ARGS, LISTBODY_HELP = "List all Bodies and their language invariants. " + LISTQUERY_ARGS, LISTBUFF_HELP = "List all Buffs and whether they stack. " + LISTQUERY_ARGS, LISTDIRECTORCARDS_HELP = "List all Director Cards. " + LISTQUERY_ARGS, LISTDOT_HELP = "List all DoTs. " + LISTQUERY_ARGS, LISTELITE_HELP = "List all Elites and their language invariants. " + LISTQUERY_ARGS, LISTEQUIP_HELP = "List all equipment and their availability. " + LISTQUERY_ARGS, + LISTDRONE_HELP = "List all drones and their availability. " + LISTQUERY_ARGS, + LISTPICKUP_HELP = "List all pickups and their availability. " + LISTQUERY_ARGS, LISTINTERACTABLE_HELP = "Lists all interactables. " + LISTQUERY_ARGS, LISTITEMTIER_HELP = "List all item tiers. " + LISTQUERY_ARGS, LISTITEM_HELP = "List all items and their availability. " + LISTQUERY_ARGS, @@ -111,8 +140,14 @@ public const string LISTSURVIVOR_HELP = "List all survivors and their body/ai names. " + LISTQUERY_ARGS, LISTTEAM_HELP = "List all Teams and their language invariants. " + LISTQUERY_ARGS, LOADOUTSKIN_HELP = "Change your loadout's skin. " + LOADOUTSKIN_ARGS, + LOADOUTSKILL_HELP = "Sets your loadout's skill variant for the given slot. " + LOADOUTSKILL_SHORT_ARGS, LOCKEXP_HELP = "Prevent Experience gain. " + ENABLE_ARGS, + NOCOOLDOWN_HELP = "Toggles skill cooldowns of the target. " + PLAYER_OR_PINGED, + MACRO_DTPEACE_HELP = "Kills all enemies, voids, disables enemies spawning and enables godmode.", MACRO_DTZOOM_HELP = "Gives you 20 hooves and 200 feathers for getting around quickly.", + MACRO_SCANNER_HELP = "Gives you 100 boostEquipmentRecharge and the Radar Scanner equipment to easily search stages.", + MACRO_DTCLEANSE_HELP = "Removes all your buffs, timed buffs & dots.", + MACRO_LATEGAME_HELP = "Sets the current run to the 'lategame' as defined by HG. This command is DESTRUCTIVE.", MACRO_MIDGAME_HELP = "Sets the current run to the 'midgame' as defined by HG. This command is DESTRUCTIVE.", NEXTBOSS_HELP = "Sets the next teleporter/simulacrum boss to the specified boss. " + NEXTBOSS_ARGS, @@ -120,6 +155,7 @@ public const string NEXTWAVE_HELP = "Advance to the next Simulacrum wave. " + NO_ARGS, NOCLIP_HELP = "Allow flying and going through objects. Sprinting will double the speed. " + ENABLE_ARGS, NOENEMIES_HELP = "Prevent Monster spawning. " + ENABLE_ARGS, + NOINTERACTABLES_HELP = "Prevent random Interactables from spawning. " + ENABLE_ARGS, PERM_ENABLE_HELP = "Enable or disable the permission system." + ENABLE_ARGS, PERM_MOD_HELP = "Change the permission level of the specified playerid/username" + PERM_MOD_ARGS, PERM_RELOAD_HELP = "Reload the permission system, updates user and commands permissions.", @@ -129,6 +165,7 @@ public const string RELOADCONFIG_HELP = "Reload all default config files from all loaded plugins.", REMOVEALLBUFFS_HELP = "Removes all buffs from a character. " + REMOVEALLBUFFS_ARGS, REMOVEALLDOTS_HELP = "Removes all DoTs from a character. " + REMOVEALLDOTS_ARGS, + GIVEALLITEMS_HELP = "Gives all items of the tier to the target. " + GIVEALLITEMS_ARGS, REMOVEALLITEMS_HELP = "Removes all items from a target. " + REMOVEALLITEMS_ARGS, REMOVEBUFF_HELP = "Removes a specified buff from a character. Timed buffs prioritise the longest expiration stack. " + REMOVEBUFF_ARGS, REMOVEBUFFSTACKS_HELP = "Removes all stacks of a specified buff from a character. " + REMOVEBUFFSTACKS_ARGS, @@ -142,24 +179,34 @@ public const string RUNSETWAVESCLEARED_HELP = "Set the Simulacrum waves cleared. Must be positive. " + RUNSETWAVESCLEARED_ARGS, SEED_HELP = "Gets/Sets the game seed until game close. Use 0 to reset to vanilla generation. " + SEED_ARGS, SETARTIFACT_HELP = "Enable/disable an Artifact. " + SETARTIFACT_ARGS, + SETDIFFICULTY_HELP = "Sets the runs selected difficulty. " + SETDIFFICULTY_ARGS, SPAWNAS_HELP = "Respawn the specified player using the specified body prefab. " + SPAWNAS_ARGS, SPAWNAI_HELP = "Spawns the specified CharacterMaster. " + SPAWNAI_ARGS, + SPAWNMINION_HELP = "Spawns the specified CharacterMaster as a minion. " + SPAWNMINION_ARGS, SPAWNBODY_HELP = "Spawns the specified dummy body. " + SPAWNBODY_ARGS, SPAWNINTERACTABLE_HELP = "Spawns the specified interactable. List_Interactable for options. " + SPAWNINTERACTABLE_ARGS, SPAWNPORTAL_HELP = "Spawns a portal in front of the player. " + SPAWNPORTAL_ARGS, STOPTIMER_HELP = "Pause/unpause the run timer. " + ENABLE_ARGS, TIMESCALE_HELP = "Sets the Time Delta. " + TIMESCALE_ARGS, - TRUEKILL_HELP = "Ignore Dio's and kill the entity. " + TRUEKILL_ARGS + TRUEKILL_HELP = "Kill the entity bypassing revives, buddha & godmode. " + PLAYERPINGED_OR_ALL, + TOGGLETIME_HELP = "Toggles timescale between 0 and normal value. " + NO_ARGS, + TOGGLEMODE_HELP = "Turn off your model for screenshots" + NO_ARGS, + TOGGLEHUD_HELP = "Toggle hud_enable. Enable/disable the HUD." + NO_ARGS ; // Messages public const string - CREATEPICKUP_AMBIGIOUS_2 = "Could not choose between {0} and {1}, please be more precise. Consider using 'equip' or 'item' as the second argument.", - CREATEPICKUP_NOTFOUND = "Could not find any item nor equipment with that name.", + CREATEPICKUP_AMBIGIOUS = "Found multiple results. Please be more precise or specify using 'item', 'equip', 'drone'\n{0}", + CREATEPICKUP_NOTFOUND = "Could not find any item, equipment or drone with that name or index.", CREATEPICKUP_SUCCESS_1 = "Successfully created the pickup {0}.", CREATEPICKUP_SUCCESS_2 = "Successfully created a potential with {0} options.", GIVELUNAR_2 = "{0} {1} lunar coin(s).", + GIVEVOIDC_2 = "{0} {1} void marker(s).", GIVEOBJECT = "Gave {0} {1} to {2}.", + GIVEDRONE = "Summoned {0} {1} (Tier {3}) to {2}.", + REMOVED_MINIONS = "Removed {0} minions from {1}.", + REMOVEDEQUIP = "Removed current Equipment from {0}.", + Kill_MINIONS = "Killed all minions owned by {0}.", OBSOLETEWARNING = "This command has become obsolete and will be removed in the next version. ", NETWORKING_OTHERPLAYER_4 = "{0}({1}) issued: {2} {3}", NOMESSAGE = "Yell at the modmakers if you see this message!", @@ -168,8 +215,11 @@ public const string PLAYER_SKINCHANGERESPAWN = "Player will spawn with the specified skin next round. " + USE_RESPAWN, REMOVEOBJECT = "Removed {0} {1} from {2}.", RUNSETSTAGESCLEARED_HELP = "Sets the amount of stages cleared. This does not change the current stage.", + TEMP = "{0} temporary", SETTING_ENABLED = "{0} enabled", SETTING_DISABLED = "{0} disabled", + SETTING_ENABLED2 = "Enabled", + SETTING_DISABLED2 = "Disabled", SPAWN_ATTEMPT_1 = "Attempting to spawn: {0}", SPAWN_ATTEMPT_2 = "Attempting to spawn {0}: {1}", USE_RESPAWN = "Use 'respawn' to skip the wait." @@ -191,7 +241,7 @@ public const string NOTINARUN_ERROR = "This command only works when in a Run!", NOTINASIMULACRUMRUN_ERROR = "Must be in a Simulacrum Run!", NOTINVOIDFIELDS_ERROR = "Must be on Void Fields!", - OBJECT_NOTFOUND = "No {0} with the name \"{1}\" could be found.", + OBJECT_NOTFOUND = "No {0} with the name or index \"{1}\" could be found.", PARSE_ERROR = "Unable to parse {0} to {1}.", PINGEDBODY_NOTFOUND = "Pinged target not found. Either the last ping was not a character, or it has been destroyed since.", PLAYER_NOTFOUND = "Specified player not found or isn't alive. Please use list_player for options.", @@ -210,10 +260,13 @@ public const string DEFAULT_VALUE = "", DEVOTION = "DEVOTION", EQUIP = "EQUIP", + DRONE = "DRONE", + PICKUP = "PICKUP", EVOLUTION = "EVOLUTION", ITEM = "ITEM", PINGED = "PINGED", RANDOM = "RANDOM", + NONE = "NONE", SIMULACRUM = "SIMULACRUM", VOIDFIELDS = "VOIDFIELDS" ; diff --git a/Code/StringFinder.cs b/Code/StringFinder.cs index 65d800a..2589249 100644 --- a/Code/StringFinder.cs +++ b/Code/StringFinder.cs @@ -39,28 +39,74 @@ private StringFinder() private static void GatherCSCs() { - GatherAddressableAssets("/csc", (asset) => + RoR2Application.onLoad += () => { - characterSpawnCard.Add(new DirectorCard + //I imagine this would fail to get modded MultiCSC + var CSCList = Resources.FindObjectsOfTypeAll(typeof(CharacterSpawnCard)) as CharacterSpawnCard[]; + + foreach (var resourceLocator in Addressables.ResourceLocators) { - spawnCard = asset, - forbiddenUnlockableDef = null, - minimumStageCompletions = 0, - preventOverhead = true, - spawnDistance = DirectorCore.MonsterSpawnDistance.Standard, - }); - }); + foreach (var key in resourceLocator.Keys) + { + var keyString = key.ToString(); + if (keyString.Contains("/csc")) + { + characterSpawnCard.Add(new DirectorCard + { + spawnCard = Addressables.LoadAssetAsync(keyString).WaitForCompletion(), + preventOverhead = true, + }); + } + } + } + + var filteredList = + CSCList.Where(spawnCard => spawnCard is CharacterSpawnCard && characterSpawnCard.All(existingCSC => existingCSC.spawnCard != spawnCard)); + foreach (var card in filteredList) + { + characterSpawnCard.Add(new DirectorCard + { + spawnCard = card, + preventOverhead = true, + }); + } + }; + + } private void GatherISCs() { - GatherAddressableAssets("/isc", (asset) => interactableSpawnCards.Add(asset)); - On.RoR2.ClassicStageInfo.Start += AddCurrentStageIscsToCache; + RoR2Application.onLoad += () => + { + //Doing this first means only getting loaded spawn cards. + //Which for vanilla isn't a lot of them (38) + //But itll find any modded ones and mod-loaded ones. + var ISCList = Resources.FindObjectsOfTypeAll(typeof(InteractableSpawnCard)) as InteractableSpawnCard[]; + + //Unless there's some issue with WaitForFinished() + //Have do it this way to get vanillaInteractables first then moddedInteractables, without jumbling up the order + foreach (var resourceLocator in Addressables.ResourceLocators) + { + foreach (var key in resourceLocator.Keys) + { + var keyString = key.ToString(); + if (keyString.Contains("/isc")) + { + interactableSpawnCards.Add(Addressables.LoadAssetAsync(keyString).WaitForCompletion()); + } + } + } + + var filteredList = + ISCList.Where(spawnCard => spawnCard is InteractableSpawnCard && interactableSpawnCards.All(existingIsc => existingIsc != spawnCard)); + interactableSpawnCards.AddRange(filteredList); + }; } private static void GatherAddressableAssets(string filterKey, Action onAssetLoaded) { - RoR2Application.onLoad += () => + RoR2Application.onLoadFinished += () => { foreach (var resourceLocator in Addressables.ResourceLocators) { @@ -253,6 +299,47 @@ public DotController.DotIndex GetDotFromPartial(string name) } } + /// + /// Returns an DifficultyIndex when provided with an index or partial/invariant. + /// + /// Matches either the exact (int)Index or Partial Invariant + /// Returns the DifficultyIndex if a match is found, or returns DifficultyIndex.Invalid + public DifficultyIndex GetDifficultyFromPartial(string name) + { + return GetDifficultiesFromPartial(name).DefaultIfEmpty(DifficultyIndex.Invalid).First(); + } + + /// + /// Returns an iterator of DifficultyIndex's when provided with an index or partial/invariant. + /// + /// Matches either the exact (int)Index or Partial Invariant + /// Returns an iterator with all DifficultyIndex's matched + /// Vanilla game has no DifficultyDef to DifficultyIndex. + /// Modded difficulties are not usually stored in DifficultyCatalog. + /// R2API is used instead. + public IEnumerable GetDifficultiesFromPartial(string name) + { + if (TextSerialization.TryParseInvariant(name, out int i)) + { + var index = (DifficultyIndex)i; + if (DifficultyCatalog.GetDifficultyDef(index) != null) + { + yield return index; + } + yield break; + } + name = name.ToUpperInvariant(); + foreach (var dict in R2API.DifficultyAPI.difficultyDefinitions) + { + var langInvar = GetLangInvar(dict.Value.nameToken).ToUpper(); + if (dict.Value.nameToken.ToUpper().Contains(name) || langInvar.Contains(name)) + { + yield return dict.Key; + } + } + } + + /// /// Returns an EquipmentIndex when provided with an index or partial/invariant. /// @@ -359,6 +446,26 @@ public ItemIndex GetItemFromPartial(string name) return GetItemsFromPartial(name).DefaultIfEmpty(ItemIndex.None).First(); } + /// + /// Returns an DroneIndex when provided with an index or partial/invariant. + /// + /// Matches either the exact (int)Index or Partial Invariant + /// Returns the DroneIndex if a match is found, or returns DroneIndex.None + public DroneIndex GetDroneFromPartial(string name) + { + return GetDronesFromPartial(name).DefaultIfEmpty(DroneIndex.None).First(); + } + + /// + /// Returns an PickupIndex when provided with an index or partial/invariant. + /// + /// Matches either the exact (int)Index or Partial Invariant + /// Returns the PickupIndex if a match is found, or returns PickupIndex.none + public PickupIndex GetPickupFromPartial(string name) + { + return GetPickupsFromPartial(name).DefaultIfEmpty(PickupIndex.none).First(); + } + /// /// Returns an iterator of ItemIndex's when provided with an index or partial/invariant. /// @@ -395,6 +502,82 @@ public IEnumerable GetItemsFromPartial(string name) } } + + /// + /// Returns an iterator of DroneIndex's when provided with an index or partial/invariant. + /// + /// Matches either the exact (int)Index or Partial Invariant + /// Returns an iterator with all DroneIndex's matched + public IEnumerable GetDronesFromPartial(string name) + { + if (TextSerialization.TryParseInvariant(name, out int i)) + { + var index = (DroneIndex)i; + //if (DroneCatalog.IsIndexValid(index)) + if (index < (DroneIndex)DroneCatalog.droneCount) + { + yield return index; + } + yield break; + } + name = name.ToUpperInvariant(); + var matches = new List(); + foreach (var drone in DroneCatalog.allDroneDefs) + { + var langInvar = GetLangInvar(drone.nameToken).ToUpper(); + if (drone.name.ToUpper().Contains(name) || langInvar.Contains(name)) + { + matches.Add(new MatchSimilarity + { + similarity = Math.Max(GetSimilarity(drone.name, name), GetSimilarity(langInvar, name)), + item = drone.droneIndex + }); + } + } + foreach (var match in matches.OrderByDescending(m => m.similarity)) + { + yield return (DroneIndex)match.item; + } + } + + + /// + /// Returns an iterator of PickupIndex's when provided with an index or partial/invariant. + /// + /// Matches either the exact (int)Index or Partial Invariant + /// Returns an iterator with all PickupIndex's matched + public IEnumerable GetPickupsFromPartial(string name) + { + if (TextSerialization.TryParseInvariant(name, out int i)) + { + var index = i; + if (index < PickupCatalog.pickupCount) + { + yield return PickupIndex.none; + } + yield break; + } + name = name.ToUpperInvariant(); + var matches = new List(); + foreach (var pickup in PickupCatalog.allPickups) + { + var langInvar = GetLangInvar(pickup.nameToken).ToUpper(); + if (pickup.internalName.ToUpper().Contains(name) || langInvar.Contains(name)) + { + //yield return pickup.pickupIndex; + matches.Add(new MatchSimilarity + { + similarity = Math.Max(GetSimilarity(pickup.internalName, name), GetSimilarity(langInvar, name)), + item = pickup.pickupIndex + }); + } + } + foreach (var match in matches.OrderByDescending(m => m.similarity)) + { + yield return (PickupIndex)match.item; + } + } + /// /// Returns a NetworkUser when provided with an index or partial/invariant. /// diff --git a/DebugToolkit.csproj b/DebugToolkit.csproj index 0cb2604..c1031d4 100644 --- a/DebugToolkit.csproj +++ b/DebugToolkit.csproj @@ -89,7 +89,8 @@ - + + diff --git a/Thunderstore/thunderstore.toml b/Thunderstore/thunderstore.toml index 4632137..ed3b98b 100644 --- a/Thunderstore/thunderstore.toml +++ b/Thunderstore/thunderstore.toml @@ -15,6 +15,7 @@ RiskofThunder-R2API_Core = "5.1.7" RiskofThunder-R2API_ContentManagement = "1.0.10" RiskofThunder-R2API_Networking = "1.0.3" RiskofThunder-R2API_Prefab = "1.1.1" +RiskofThunder-R2API_Difficulty = "1.1.2" [build] icon = "./icon.png"