From 3730bc1432f4f6539a5fcb284ad1368fc88b5d1b Mon Sep 17 00:00:00 2001 From: Nebby <78170922+Nebby1999@users.noreply.github.com> Date: Wed, 16 Feb 2022 22:23:58 -0300 Subject: [PATCH] Changes to R2API's ContentPack System (#338) * ArtifactCodeAPI changes * Added hide in inspector attribute to ArtifactCompounds list * Added a header with the constant that says how to find a list of accepted vanilla compound values- * Added R2API Content Manager * Removed comments, added logger messages, modified UnlockableAPI. * Cleaned code. * Fixed typo and version issue * Added check to ensure there isnt an asset with whitespace, empty, or null as its name * Made LoadROR2ContentEarly public, Added Public methods to R2APIContentManager * Added missing documentation. * Made it so ContentPacks can opt out from r2api loading the pack automatically. * code cleanup * Moved content related classes to R2APIContentManagment, fixed typos and issues. * Fixed silent letter typo * Marked a bunch of classes & methods as obsolete * fixed another "entires" typo * Added ContentAdditionHelpers Also removed AddContent() and AddentityState() from R2APIContentManager * Added missing documentation * Added CatalogBlockers * updated obsolete messages to redirect to the ContentAdditionHelpers * Moved 3.x changelogs to archived, Added my PR to the readme --- Archived changelogs.md | 95 ++- R2API.Test/Tests/ReflectionTests.cs | 4 +- .../Tests/UnbundledResourcesProviderTests.cs | 4 +- R2API/ArtifactAPI.cs | 49 +- ...noreAccessModifiers.cs => AssemblyInfo.cs} | 6 + R2API/BuffAPI.cs | 42 +- R2API/ContentManagement/CatalogBlockers.cs | 105 ++++ .../ContentAdditionHelpers.cs | 474 ++++++++++++++ .../LoadRoR2ContentEarly.cs | 9 +- .../ContentManagement/R2APIContentManager.cs | 577 ++++++++++++++++++ .../R2APIContentPackProvider.cs | 85 +++ R2API/DamageAPI.cs | 8 +- R2API/DotAPI.cs | 13 +- R2API/EffectAPI.cs | 43 +- R2API/EliteAPI.cs | 26 +- R2API/ItemAPI.cs | 250 ++++---- R2API/ItemDrop/Catalog.cs | 4 +- R2API/ItemDrop/DropList.cs | 59 +- R2API/ItemDrop/DropOdds.cs | 2 +- R2API/ItemDrop/InteractableCalculator.cs | 2 +- R2API/ItemDropAPI.cs | 23 +- R2API/LoadoutAPI.cs | 56 +- R2API/MiscHelpers/ILCursorExtensions.cs | 5 +- R2API/MonsterItemsAPI.cs | 16 +- R2API/PrefabAPI.cs | 3 + R2API/ProjectileAPI.cs | 41 +- R2API/R2API.cs | 2 + R2API/R2APIContentPackProvider.cs | 49 -- R2API/RecalculateStatsAPI.cs | 24 +- R2API/ResourcesAPI.cs | 30 +- R2API/SoundAPI.cs | 36 +- R2API/SurvivorAPI.cs | 37 +- R2API/TempVisualEffectAPI.cs | 11 +- R2API/UnlockableAPI.cs | 53 +- R2API/Utils/EmbeddedResources.cs | 1 - R2API/Utils/NetworkCompatibility.cs | 2 - README.md | 106 +--- 37 files changed, 1708 insertions(+), 644 deletions(-) rename R2API/{IgnoreAccessModifiers.cs => AssemblyInfo.cs} (63%) create mode 100644 R2API/ContentManagement/CatalogBlockers.cs create mode 100644 R2API/ContentManagement/ContentAdditionHelpers.cs rename R2API/{ => ContentManagement}/LoadRoR2ContentEarly.cs (95%) create mode 100644 R2API/ContentManagement/R2APIContentManager.cs create mode 100644 R2API/ContentManagement/R2APIContentPackProvider.cs delete mode 100644 R2API/R2APIContentPackProvider.cs diff --git a/Archived changelogs.md b/Archived changelogs.md index e7f67a2b..19e40ca0 100644 --- a/Archived changelogs.md +++ b/Archived changelogs.md @@ -1,4 +1,97 @@ -All changelogs before 3.0.0 version +All changelogs before 4.0.0 version + +**3.0.71** + +* [ItemAPI now warns that ItemDef/EquipmentDef.pickupModelPrefab should have an ItemDisplay attached to them when they have ParentedPrefab display rules linked to them](https://github.com/risk-of-thunder/R2API/pull/311) +* [EliteAPI now exposes the default elite tiers array (through VanillaEliteTiers) before any changes are made to it for modder that want to change the vanilla elite tiers. Also, adding to the custom elite tier array now by default insert based on the cost multiplier of the elite tier.](https://github.com/risk-of-thunder/R2API/pull/308) +* [RecalculateStatsAPI now warns modders that the submodule could be not loaded](https://github.com/risk-of-thunder/R2API/pull/307) +* [Added Curse, Shield Multiplier, All Cooldown Reductions, Jump Power, Level Scaling, and Root to RecalculateStatsAPI](https://github.com/risk-of-thunder/R2API/pull/322) +* [Fix SoundAPI throwing on dedicated server](https://github.com/risk-of-thunder/R2API/pull/306) +* [Fix SoundAPI's music implementation stopping all music when an instance of a MusicTrackOverride gets destroyed](https://github.com/risk-of-thunder/R2API/pull/319) +* [Added TempVisualEffectAPI](https://github.com/risk-of-thunder/R2API/pull/313) +* [ArtifactCodeAPI's ArtifactCode Scriptable object now uses 3 Vector3Int for inputting the code, instead of a List of Ints](https://github.com/risk-of-thunder/R2API/pull/310) +* [Added aditional overloads for AddUnlockable that accept a type parameter instead of using generics](https://github.com/risk-of-thunder/R2API/pull/317) +* [UnlockableAPI can now add AchievementDefs directly](https://github.com/risk-of-thunder/R2API/pull/321) +* [DotAPI no longer throws an error when no BuffDef is provided for the asociated BuffDef parameter](https://github.com/risk-of-thunder/R2API/pull/325) + +**3.0.59** + +* [Extended SoundAPI for adding custom music](https://github.com/risk-of-thunder/R2API/pull/305) +* [Added support for using existing UnlockableDefs in UnlockableAPI](https://github.com/risk-of-thunder/R2API/pull/304) +* [fixing server unlockables](https://github.com/risk-of-thunder/R2API/pull/302) + +**3.0.52** + +* [Add NetworkSoundEventDef registration to SoundAPI](https://github.com/risk-of-thunder/R2API/pull/301) + +**3.0.50** + +* [Added ArtifactCodeAPI](https://github.com/risk-of-thunder/R2API/pull/299) +* [Added support for new Artifact Code compounds](https://github.com/risk-of-thunder/R2API/pull/300) + +**3.0.48** + +* [Documentation for ItemDropAPI](https://github.com/risk-of-thunder/R2API/blob/master/ItemDropAPI%20Instructions%20For%20Use.txt) +* [ItemDropAPI Overhall](https://github.com/risk-of-thunder/R2API/pull/295) +* [Added MonsterItemsAPI back in](https://github.com/risk-of-thunder/R2API/pull/295) + +**3.0.44** + +* [Fixed PrefabAPI network registration](https://github.com/risk-of-thunder/R2API/pull/294) + +**3.0.43** + +* **IMPORTANT FOR MOD DEVS:** [R2API will no longer register mods to network if they don't depend on it with HardDependecy](https://github.com/risk-of-thunder/R2API/pull/286) +* [Added DeployableAPI](https://github.com/risk-of-thunder/R2API/pull/279) +* [Added DamageAPI](https://github.com/risk-of-thunder/R2API/pull/284) +* [Added RecalcStatsAPI, migrated from TILER2](https://github.com/risk-of-thunder/R2API/pull/287) +* [Updated DifficultyAPI, now has sprite ref overload](https://github.com/risk-of-thunder/R2API/pull/288) +* [RecalcStatsAPI fixes](https://github.com/risk-of-thunder/R2API/pull/290) +* [Missing MMHOOK/Publicized Assembly methods fixes](https://github.com/risk-of-thunder/R2API/pull/289) + +**3.0.30** + +* Fixes for current patch + +**3.0.25** + +* **IMPORTANT FOR MOD DEVS:** [R2API will no longer register mods to network if they don't depend on it](https://github.com/risk-of-thunder/R2API/pull/269) +* [DotAPI fixes](https://github.com/risk-of-thunder/R2API/pull/270) +* [EliteAPI fixes](https://github.com/risk-of-thunder/R2API/pull/271) + +**3.0.13** + +* [Updated UnlockableAPI, ItemDropAPI Overhall](https://github.com/risk-of-thunder/R2API/pull/265) +* [Update internals for 1.1.1.2 game version](https://github.com/risk-of-thunder/R2API/pull/267) +* Removed MonsterItemsAPI +* Removed `patchers` folder + +**3.0.11** + +* [Updated ResourceAPI error messages](https://github.com/risk-of-thunder/R2API/pull/258) +* SurvivorAPI Fixes: [A](https://github.com/risk-of-thunder/R2API/pull/259) [B](https://github.com/risk-of-thunder/R2API/pull/261) + +**3.0.7** + +* [Added ArtifactAPI and ProjectileAPI, BuffAPI fix](https://github.com/risk-of-thunder/R2API/pull/256) + +**3.0.1** + +* [Fixes for EffectAPI, LoadoutAPI and SoundAPI](https://github.com/risk-of-thunder/R2API/pull/254) + +**3.0.0** + +* Updated for the game `Anniversary Update` +* No longer include `monomod` folder +* [Various API Fixes. Removed AssetsAPI, InvetoryAPI. Moved MMHook to separate mod called (HookGenPatcher)](https://github.com/risk-of-thunder/R2API/pull/252) +* Removed obsolete APIs and methods: [A](https://github.com/risk-of-thunder/R2API/pull/249) [B](https://github.com/risk-of-thunder/R2API/pull/243) +* ItemAPI, ItemDropApi overhall. Added MonsterItemAPI: [A](https://github.com/risk-of-thunder/R2API/pull/214) [B](https://github.com/risk-of-thunder/R2API/pull/223) [C](https://github.com/risk-of-thunder/R2API/pull/228) [D](https://github.com/risk-of-thunder/R2API/pull/233) [E](https://github.com/risk-of-thunder/R2API/pull/234) [F](https://github.com/risk-of-thunder/R2API/pull/240) [G](https://github.com/risk-of-thunder/R2API/pull/245) +* LanguageAPI refactoring and fixes: [A](https://github.com/risk-of-thunder/R2API/pull/229) [B](https://github.com/risk-of-thunder/R2API/pull/244) +* [Added ILLine](https://github.com/risk-of-thunder/R2API/pull/230) +* [Fix for networked achievements](https://github.com/risk-of-thunder/R2API/pull/208) +* [Added SceneAssetAPI](https://github.com/risk-of-thunder/R2API/pull/210) +* [Added InteractablesAPI](https://github.com/risk-of-thunder/R2API/pull/216) + **2.5.14** diff --git a/R2API.Test/Tests/ReflectionTests.cs b/R2API.Test/Tests/ReflectionTests.cs index 481332e4..7448ddde 100644 --- a/R2API.Test/Tests/ReflectionTests.cs +++ b/R2API.Test/Tests/ReflectionTests.cs @@ -1,8 +1,8 @@ +using R2API.Utils; +using RoR2; using System; using System.Collections; using System.Collections.Generic; -using R2API.Utils; -using RoR2; using Xunit; namespace R2API.Test { diff --git a/R2API.Test/Tests/UnbundledResourcesProviderTests.cs b/R2API.Test/Tests/UnbundledResourcesProviderTests.cs index 119cf389..94881f2a 100644 --- a/R2API.Test/Tests/UnbundledResourcesProviderTests.cs +++ b/R2API.Test/Tests/UnbundledResourcesProviderTests.cs @@ -1,5 +1,5 @@ -using System; -using R2API.Utils; +using R2API.Utils; +using System; using UnityEngine; using Xunit; diff --git a/R2API/ArtifactAPI.cs b/R2API/ArtifactAPI.cs index 0c52d420..a1b122d5 100644 --- a/R2API/ArtifactAPI.cs +++ b/R2API/ArtifactAPI.cs @@ -1,8 +1,8 @@ -using R2API.Utils; +using R2API.ContentManagement; +using R2API.Utils; using RoR2; -using RoR2.ContentManagement; using System; -using System.Collections.Generic; +using System.Reflection; using UnityEngine; namespace R2API { @@ -11,12 +11,8 @@ namespace R2API { /// API for adding custom artifact to the game. /// [R2APISubmodule] + [Obsolete($"The {nameof(ArtifactAPI)} is obsolete, please add your ArtifactDefs via R2API.ContentManagement.ContentAdditionHelpers.AddArtifactDef()")] public static class ArtifactAPI { - - private static readonly List Artifacts = new List(); - - private static bool _artifactCatalogInitialized; - /// /// Return true if the submodule is loaded. /// @@ -27,29 +23,6 @@ public static bool Loaded { private static bool _loaded; - #region ModHelper Events and Hooks - - [R2APISubmoduleInit(Stage = InitStage.SetHooks)] - internal static void SetHooks() { - R2APIContentPackProvider.WhenContentPackReady += AddArtifactsToGame; - } - - [R2APISubmoduleInit(Stage = InitStage.UnsetHooks)] - internal static void UnsetHooks() { - R2APIContentPackProvider.WhenContentPackReady -= AddArtifactsToGame; - } - - private static void AddArtifactsToGame(ContentPack r2apiContentPack) { - foreach (var artifact in Artifacts) { - R2API.Logger.LogInfo($"Custom Artifact: {artifact.cachedName} added"); - } - - r2apiContentPack.artifactDefs.Add(Artifacts.ToArray()); - _artifactCatalogInitialized = true; - } - - #endregion ModHelper Events and Hooks - #region Add Methods /// @@ -58,18 +31,19 @@ private static void AddArtifactsToGame(ContentPack r2apiContentPack) { /// /// The artifactDef to add. /// true if added, false otherwise + [Obsolete($"Add is obsolete, please add your ArtifactDefs via R2API.ContentManagement.ContentAdditionHelpers.AddArtifactDef()")] public static bool Add(ArtifactDef? artifactDef) { if (!Loaded) { throw new InvalidOperationException($"{nameof(ArtifactAPI)} is not loaded. Please use [{nameof(R2APISubmoduleDependency)}(nameof({nameof(ArtifactAPI)})]"); } - if (_artifactCatalogInitialized) { + if (!CatalogBlockers.GetAvailability()) { R2API.Logger.LogError( - $"Too late ! Tried to add artifact: {artifactDef.cachedName} after the artifact list was created"); + $"Too late ! Tried to add artifact: {artifactDef.cachedName} after the ArtifactCatalog has initialized!"); return false; } - Artifacts.Add(artifactDef); + R2APIContentManager.HandleContentAddition(Assembly.GetCallingAssembly(), artifactDef); return true; } @@ -78,6 +52,7 @@ public static bool Add(ArtifactDef? artifactDef) { /// If this is called after the ArtifactCatalog inits then this will return false and ignore the custom artifact. /// /// true if added, false otherwise + [Obsolete($"Add is obsolete, please add your ArtifactDefs via R2API.ContentManagement.ContentAdditionHelpers.AddArtifactDef()")] public static bool Add( string name, string descriptionToken, string nameToken, @@ -88,9 +63,9 @@ public static bool Add( throw new InvalidOperationException($"{nameof(ArtifactAPI)} is not loaded. Please use [{nameof(R2APISubmoduleDependency)}(nameof({nameof(ArtifactAPI)})]"); } - if (_artifactCatalogInitialized) { + if (!CatalogBlockers.GetAvailability()) { R2API.Logger.LogError( - $"Too late ! Tried to add artifact: {name} after the artifact list was created"); + $"Too late ! Tried to add artifact: {name} after the ArtifactCatalog has initialized!"); return false; } @@ -103,7 +78,7 @@ public static bool Add( artifactDef.smallIconSelectedSprite = smallIconSelectedSprite; artifactDef.unlockableDef = unlockableDef; - Artifacts.Add(artifactDef); + R2APIContentManager.HandleContentAddition(Assembly.GetCallingAssembly(), artifactDef); return true; } diff --git a/R2API/IgnoreAccessModifiers.cs b/R2API/AssemblyInfo.cs similarity index 63% rename from R2API/IgnoreAccessModifiers.cs rename to R2API/AssemblyInfo.cs index 5f527368..3b880f56 100644 --- a/R2API/IgnoreAccessModifiers.cs +++ b/R2API/AssemblyInfo.cs @@ -12,3 +12,9 @@ [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] #pragma warning restore CS0618 // Type or member is obsolete [module: UnverifiableCode] + +//This allows us to use the SystemInitializer attribute +//the attribute will call whatever method it's attached to once ror2 starts loading. +//We can add dependencies to the SystemInitializer attribute, an example would be run +//a piece of code that gets automatically ran once the ItemCatalog is initialized +[assembly: HG.Reflection.SearchableAttribute.OptIn] diff --git a/R2API/BuffAPI.cs b/R2API/BuffAPI.cs index b5d167ce..25a5e2fc 100644 --- a/R2API/BuffAPI.cs +++ b/R2API/BuffAPI.cs @@ -1,10 +1,9 @@ -using MonoMod.Cil; +using R2API.ContentManagement; using R2API.Utils; using RoR2; -using RoR2.ContentManagement; using System; -using System.Collections.Generic; using System.Collections.ObjectModel; +using System.Reflection; using UnityEngine; namespace R2API { @@ -13,15 +12,15 @@ namespace R2API { /// API for adding custom buffs to the game. Previously included in ItemAPI. /// [R2APISubmodule] + [Obsolete($"The {nameof(BuffAPI)} is obsolete, please add your BuffDefs via R2API.ContentManagement.ContentAdditionHelpers.AddBuffDef()")] public static class BuffAPI { /// /// All custom buffs added by the API. /// + [Obsolete($"This observable collection is obsolete, if you want to look at the buffDefs added by R2API, look at R2API.ContentManagement.R2APIContentManager.ManagedContentPacks and do a SelectMany on the buffDefs.")] public static ObservableCollection? BuffDefinitions = new ObservableCollection(); - private static bool _buffCatalogInitialized; - /// /// Return true if the submodule is loaded. /// @@ -32,32 +31,6 @@ public static bool Loaded { private static bool _loaded; - #region ModHelper Events and Hooks - - [R2APISubmoduleInit(Stage = InitStage.SetHooks)] - internal static void SetHooks() { - R2APIContentPackProvider.WhenContentPackReady += AddBuffsToGame; - } - - [R2APISubmoduleInit(Stage = InitStage.UnsetHooks)] - internal static void UnsetHooks() { - R2APIContentPackProvider.WhenContentPackReady -= AddBuffsToGame; - } - - private static void AddBuffsToGame(ContentPack r2apiContentPack) { - var buffDefs = new List(); - foreach (var customBuff in BuffDefinitions) { - buffDefs.Add(customBuff.BuffDef); - - R2API.Logger.LogInfo($"Custom Buff: {customBuff.BuffDef.name} added"); - } - - r2apiContentPack.buffDefs.Add(buffDefs.ToArray()); - _buffCatalogInitialized = true; - } - - #endregion ModHelper Events and Hooks - #region Add Methods /// @@ -68,18 +41,19 @@ private static void AddBuffsToGame(ContentPack r2apiContentPack) { /// /// The buff to add. /// true if added, false otherwise + [Obsolete($"Add is obsolete, please add your BuffDefs via R2API.ContentManagement.ContentAdditionHelpers.AddBuffDef()")] public static bool Add(CustomBuff? buff) { if (!Loaded) { throw new InvalidOperationException($"{nameof(BuffAPI)} is not loaded. Please use [{nameof(R2APISubmoduleDependency)}(nameof({nameof(BuffAPI)})]"); } - if (_buffCatalogInitialized) { + if (!CatalogBlockers.GetAvailability()) { R2API.Logger.LogError( - $"Too late ! Tried to add buff: {buff.BuffDef.name} after the buff list was created"); + $"Too late ! Tried to add buff: {buff.BuffDef.name} after the BuffCatalog has initialized!"); return false; } - BuffDefinitions.Add(buff); + R2APIContentManager.HandleContentAddition(Assembly.GetCallingAssembly(), buff.BuffDef); return true; } diff --git a/R2API/ContentManagement/CatalogBlockers.cs b/R2API/ContentManagement/CatalogBlockers.cs new file mode 100644 index 00000000..ac1b7542 --- /dev/null +++ b/R2API/ContentManagement/CatalogBlockers.cs @@ -0,0 +1,105 @@ +using EntityStates; +using RoR2; +using RoR2.Projectile; +using RoR2.Skills; +using System; +using System.Collections.Generic; +using UnityEngine.Networking; + +namespace R2API.ContentManagement { + internal static class CatalogBlockers { + private static Dictionary CanAddContentToCatalog = new Dictionary { + {typeof(CharacterBody), true }, + {typeof(CharacterMaster), true }, + {typeof(ProjectileController), true }, + {typeof(Run), true }, + {typeof(NetworkIdentity), true }, + {typeof(EffectComponent), true }, + {typeof(SkillDef), true }, + {typeof(SkillFamily), true }, + {typeof(SceneDef), true }, + {typeof(ItemDef), true }, + {typeof(EquipmentDef), true }, + {typeof(BuffDef), true }, + {typeof(EliteDef), true }, + {typeof(UnlockableDef), true }, + {typeof(SurvivorDef), true }, + {typeof(ArtifactDef), true }, + {typeof(SurfaceDef), true }, + {typeof(NetworkSoundEventDef), true }, + {typeof(MusicTrackDef), true }, + {typeof(GameEndingDef), true }, + {typeof(EntityStateConfiguration), true }, + {typeof(EntityState), true }, + + //The rest are catalogs that arent added by scriptable objects or game objects yet. + }; + /// + /// Returns if the Catalog that manages the type T has finished initializing or not. + /// + /// The type that the catalog manages + /// True or False depending on wether the catalog has initialized or not. False if the dictionary doesnt contain T as a Key + internal static bool GetAvailability() { + Type t = typeof(T); + if (CanAddContentToCatalog.ContainsKey(t)) { + return CanAddContentToCatalog[t]; + } + return false; + } + + private static void SetAvailability(bool availability) { + Type t = typeof(T); + if (CanAddContentToCatalog.ContainsKey(t)) { + CanAddContentToCatalog[t] = availability; + } + } + + #region CatalogBlocker Methods + [SystemInitializer(typeof(BodyCatalog))] + private static void BlockBodies() => SetAvailability(false); + [SystemInitializer(typeof(MasterCatalog))] + private static void BlockMasters() => SetAvailability(false); + [SystemInitializer(typeof(ProjectileCatalog))] + private static void BlockProjectiles() => SetAvailability(false); + [SystemInitializer(typeof(GameModeCatalog))] + private static void BlockGameModes() => SetAvailability(false); + [SystemInitializer(typeof(RoR2.Networking.GameNetworkManager))] + private static void BlockNetworkedPrefabs() => SetAvailability(false); + [SystemInitializer(typeof(EffectCatalog))] + private static void BlockEffects() => SetAvailability(false); + [SystemInitializer(typeof(SkillCatalog))] + private static void BlockSkills() => SetAvailability(false); + [SystemInitializer(typeof(SkillCatalog))] + private static void BlockSkillFamilies() => SetAvailability(false); + [SystemInitializer(typeof(SceneCatalog))] + private static void BlockScenes() => SetAvailability(false); + [SystemInitializer(typeof(ItemCatalog))] + private static void BlockItems() => SetAvailability(false); + [SystemInitializer(typeof(EquipmentCatalog))] + private static void BlockEquipments() => SetAvailability(false); + [SystemInitializer(typeof(BuffCatalog))] + private static void BlockBuffs() => SetAvailability(false); + [SystemInitializer(typeof(EliteCatalog))] + private static void BlockElites() => SetAvailability(false); + [SystemInitializer(typeof(UnlockableCatalog))] + private static void BlockUnlockables() => SetAvailability(false); + [SystemInitializer(typeof(SurvivorCatalog))] + private static void BlockSurvivors() => SetAvailability(false); + [SystemInitializer(typeof(ArtifactCatalog))] + private static void BlockArtifacts() => SetAvailability(false); + [SystemInitializer(typeof(SurfaceDefCatalog))] + private static void BlockSurfaceDefs() => SetAvailability(false); + [SystemInitializer(typeof(RoR2.Audio.NetworkSoundEventCatalog))] + private static void BlockNetworkSoundEvent() => SetAvailability(false); + [SystemInitializer(typeof(MusicTrackCatalog))] + private static void BlockMusicTracks() => SetAvailability(false); + [SystemInitializer(typeof(GameEndingCatalog))] + private static void BlockGameEndings() => SetAvailability(false); + [SystemInitializer(typeof(EntityStateCatalog))] + private static void BlockEntityStateConfigurations() => SetAvailability(false); + [SystemInitializer(typeof(EntityStateCatalog))] + private static void BlockEntityStates() => SetAvailability(false); + #endregion + //The rest are catalogs that arent added by scriptable objects or game objects yet. + } +} diff --git a/R2API/ContentManagement/ContentAdditionHelpers.cs b/R2API/ContentManagement/ContentAdditionHelpers.cs new file mode 100644 index 00000000..cc24c4fc --- /dev/null +++ b/R2API/ContentManagement/ContentAdditionHelpers.cs @@ -0,0 +1,474 @@ +using EntityStates; +using RoR2; +using RoR2.Projectile; +using RoR2.Skills; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using UnityEngine; +using UnityEngine.Networking; + +namespace R2API.ContentManagement { + /// + /// Class for adding Content Assets to your Mod's ContentPack. + /// + public static class ContentAdditionHelpers { + #region Add Prefab Methods + /// + /// Adds a BodyPrefab to your Mod's ContentPack + /// BodyPrefab requires a CharacterBody component. + /// + /// The BodyPrefab to add. + /// true if valid and added, false if one of the requirements is not met + public static bool AddBody(GameObject bodyPrefab) { + var asm = Assembly.GetCallingAssembly(); + if (CatalogBlockers.GetAvailability()) { + if (!HasComponent(bodyPrefab)) { + RejectContent(bodyPrefab, asm, "BodyPrefab", $"but it has no {nameof(CharacterBody)} component!"); + return false; + } + R2APIContentManager.HandleContentAddition(asm, bodyPrefab); + return true; + } + RejectContent(bodyPrefab, asm, "BodyPrefab", $"but the BodyCatalog has already initialized!"); + return false; + } + /// + /// Adds a MasterPrefab to your Mod's ContentPack + /// MasterPrefab requires a CharacterMaster component. + /// + /// The MasterPrefab to add. + /// true if valid and added, false if one of the requirements is not met + public static bool AddMaster(GameObject masterPrefab) { + var asm = Assembly.GetCallingAssembly(); + if (CatalogBlockers.GetAvailability()) { + if (!HasComponent(masterPrefab)) { + RejectContent(masterPrefab, asm, "MasterPrefab", $"but it has no {nameof(CharacterMaster)} component!"); + return false; + } + R2APIContentManager.HandleContentAddition(asm, masterPrefab); + return true; + } + RejectContent(masterPrefab, asm, "MasterPrefab", $"but the MasterCatalog has already initialized!"); + return false; + } + /// + /// Adds a ProjectilePrefab to your Mod's ContentPack + /// ProjectilePrefab requires a ProjectileController component. + /// Throws a warning if it has no assigned ghost prefab. + /// + /// The ProjectilePrefab to add. + /// true if valid and added, false if one of the requirements is not met + public static bool AddProjectile(GameObject projectilePrefab) { + var asm = Assembly.GetCallingAssembly(); + if (CatalogBlockers.GetAvailability()) { + if (!HasComponent(projectilePrefab)) { + RejectContent(projectilePrefab, asm, "ProjectilePrefab", $"but it has no {nameof(ProjectileController)} component!"); + return false; + } + var pc = projectilePrefab.GetComponent(); + if (!pc.ghostPrefab) { + R2API.Logger.LogWarning($"Projectile {projectilePrefab} has no ghost prefab assigned! is this intentional?"); + } + R2APIContentManager.HandleContentAddition(asm, projectilePrefab); + return true; + } + RejectContent(projectilePrefab, asm, "ProjectilePrefab", "but the ProjectileCatalog has already initialized!"); + return false; + } + /// + /// Adds a GameModePrefab to your Mod's ContentPack + /// GameModePrefab requires a Run component. + /// + /// The GameModePrefab to add. + /// true if valid and added, false if one of the requirements is not met + public static bool AddGameMode(GameObject gameModePrefab) { + var asm = Assembly.GetCallingAssembly(); + if (CatalogBlockers.GetAvailability()) { + if (!HasComponent(gameModePrefab)) { + RejectContent(gameModePrefab, asm, "GameMode", $"but it has no {nameof(Run)} component!"); + return false; + } + R2APIContentManager.HandleContentAddition(asm, gameModePrefab); + return true; + } + RejectContent(gameModePrefab, asm, "GameMode", "but the GameModeCatalog has already initialized!"); + return false; + } + /// + /// Adds a NetworkedObject prefab to your Mod's ContentPack + /// NetworkedObject requires a NetworkIdentity component. + /// NetworkedObject isnt in PrefabAPI's Objects to Network. + /// + /// The NetworkedObjectPrefab to add. + /// true if valid and added, false if one of the requirements is not met + public static bool AddNetworkedObject(GameObject networkedObject) { + var asm = Assembly.GetCallingAssembly(); + if (CatalogBlockers.GetAvailability()) { + if (!HasComponent(networkedObject)) { + RejectContent(networkedObject, asm, "NetworkedObject", $"but it has no {nameof(NetworkIdentity)} component!"); + return false; + } + if (PrefabAPI.IsPrefabHashed(networkedObject)) { + RejectContent(networkedObject, asm, "NetworkedObject", $"but its already being networked by {nameof(PrefabAPI)}!"); + return false; + } + R2APIContentManager.HandleContentAddition(asm, networkedObject); + return true; + } + RejectContent(networkedObject, asm, "NetworkedObject", "but the GameNetworkManager has already networked all the prefabs!"); + return false; + } + /// + /// Adds an EffectPrefab to your Mod's ContentPack + /// EffectPrefab requires an EffectComponent. + /// Throws a warning if it has no VFXAttributes component. + /// + /// The EffectPrefab to add. + /// true if valid and added, false if one of the requirements is not met. + public static bool AddEffect(GameObject effectPrefab) { + var asm = Assembly.GetCallingAssembly(); + if (CatalogBlockers.GetAvailability()) { + if (!HasComponent(effectPrefab)) { + RejectContent(effectPrefab, asm, "EffectPrefab", $"but it has no {nameof(EffectComponent)} component!"); + return false; + } + if (!HasComponent(effectPrefab)) { + R2API.Logger.LogWarning($"Effect {effectPrefab} has no {nameof(VFXAttributes)} component! is this intentional?"); + } + R2APIContentManager.HandleContentAddition(asm, effectPrefab); + return true; + } + RejectContent(effectPrefab, asm, "EffectPrefab", "but the EffectCatalog has already initialized!"); + return false; + } + /// + /// Adds a SkillDef to your Mod's ContentPack + /// SkillDef Requires a valid activationState + /// SkillDef's activationStateMachine cannot be Null, Empty or Whitespace + /// + /// the SkillDef to Add. + /// true if valid and added, false if one of the requirements is not met + public static bool AddSkillDef(SkillDef skillDef) { + var asm = Assembly.GetCallingAssembly(); + if (CatalogBlockers.GetAvailability()) { + if (skillDef.activationState.stateType == null) { + RejectContent(skillDef, asm, "SkillDef", $"but it's activation state type is null!"); + return false; + } + if (string.IsNullOrEmpty(skillDef.activationStateMachineName) || string.IsNullOrWhiteSpace(skillDef.activationStateMachineName)) { + RejectContent(skillDef, asm, "SkillDef", $"but it's activation state machine name is Null, Whitespace or Empty!"); + return false; + } + R2APIContentManager.HandleContentAddition(asm, skillDef); + return true; + } + RejectContent(skillDef, asm, "SkillDef", "but the SkillCatalog has already initialized!"); + return false; + } + /// + /// Adds a SkillFamily to your Mod's ContentPack + /// SkillFamily's Variant's SkillDef cannot be null + /// + /// The SkillFamily to Add. + /// true if valid and added, false if one of the requirements is not met + public static bool AddSkillFamily(SkillFamily skillFamily) { + var asm = Assembly.GetCallingAssembly(); + if (CatalogBlockers.GetAvailability()) { + if (skillFamily.variants.Any(v => v.skillDef == null)) { + RejectContent(skillFamily, asm, "SkillFamily", $"but one of it's variant's skillDefs is null!"); + return false; + } + R2APIContentManager.HandleContentAddition(asm, skillFamily); + return true; + } + RejectContent(skillFamily, asm, "SkillFamily", "but the SkillCatalog has already initialized!"); + return false; + } + /// + /// Adds a SceneDef to your Mod's ContentPack + /// If you want he scene to be weaved with vanilla stages, use RainOfStages + /// + /// The SceneDef to Add. + /// true if valid and added, false if one of the requirements is not met + public static bool AddSceneDef(SceneDef sceneDef) { + //Add stuff here, i dont know what qualifies as a "valid" sceneDef, then again, people should just use ROS for handling sceneDefs, r2api just lets you add them this way for the sake of completion + var asm = Assembly.GetCallingAssembly(); + if (CatalogBlockers.GetAvailability()) { + R2API.Logger.LogInfo($"Assembly {asm.GetName().Name} is trying to add a SceneDef, R2API does not support weaving of Scenes, Use RainOfStages instead for weaving SceneDefs."); + R2APIContentManager.HandleContentAddition(asm, sceneDef); + return true; + } + RejectContent(sceneDef, asm, "SceneDef", "but the SceneCatalog has already initialized!"); + return false; + } + //ItemDefs should be added by ItemAPI, but this method is here purely for completion sake. + /// + /// Adds an ItemDef to your Mod's ContentPack + /// ItemDefs should be added by ItemAPI's Add methods. + /// + /// The ItemDef to Add. + /// true if valid and added, false if one of the requirements is not met + public static bool AddItemDef(ItemDef itemDef) { + var asm = Assembly.GetCallingAssembly(); + if (CatalogBlockers.GetAvailability()) { + R2API.Logger.LogInfo($"Assembly {asm.GetName().Name} is adding an {itemDef} via {nameof(ContentAdditionHelpers)}.{nameof(AddItemDef)}()" + + $"The assembly should ideally add them via {nameof(ItemAPI)} so that they can use ItemAPI's IDRS systems, adding anyways."); + ItemAPI.Add(new CustomItem(itemDef, Array.Empty())); + return true; + } + RejectContent(itemDef, asm, "ItemDef", "but the ItemCatalog has already initialized!"); + return false; + } + //EquipmentDefs should be added by ItemAPI, but this method is here purely for completion sake. + /// + /// Adds an EquipmentDef to your Mod's ContentPack + /// EquipmentDef should be added by ItemAPI's Add methods. + /// + /// The EquipmentDef to Add. + /// true if valid and added, false if one of the requirements is not met + public static bool AddEquipmentDef(EquipmentDef equipmentDef) { + var asm = Assembly.GetCallingAssembly(); + if (CatalogBlockers.GetAvailability()) { + R2API.Logger.LogInfo($"Assembly {asm.GetName().Name} is adding an {equipmentDef} via {nameof(ContentAdditionHelpers)}.{nameof(AddEquipmentDef)}()" + + $"The assembly should ideally add them via {nameof(ItemAPI)} so that they can use ItemAPI's IDRS systems, adding anyways."); + ItemAPI.Add(new CustomEquipment(equipmentDef, Array.Empty())); + return true; + } + RejectContent(equipmentDef, asm, "EquipmentDef", "but the EquipmnetCatalog has already initialized!"); + return false; + } + /// + /// Adds a BuffDef to your Mod's ContentPack + /// Throws a warning if the buffDef's EliteDef's EquipmentDef's passive buffDef is not the buffDef you pass through + /// Throws a warning if the buffDef has a startSFX, but the startSFX's eventName is Null, Empty or White space. + /// + /// The BuffDef to Add + /// true if valid and added, false if one of the requirements is not met + public static bool AddBuffDef(BuffDef buffDef) { + var asm = Assembly.GetCallingAssembly(); + if (CatalogBlockers.GetAvailability()) { + if (buffDef.eliteDef && buffDef.eliteDef.eliteEquipmentDef && buffDef.eliteDef.eliteEquipmentDef.passiveBuffDef != buffDef) { + R2API.Logger.LogWarning($"Assembly {asm.GetName().Name} is adding an {buffDef} which has an eliteDef assigned, but said eliteDef's equipmentDef's passiveBuffDef is not {buffDef}! is this intentional?"); + } + if (buffDef.startSfx && (string.IsNullOrEmpty(buffDef.startSfx.eventName) || string.IsNullOrWhiteSpace(buffDef.startSfx.eventName))) { + R2API.Logger.LogWarning($"Assembly {asm.GetName().Name} is adding an {buffDef} that has a startSFX, but the startSFX's NetworkedSoundEventDef's eventName is Null, Empty or Whitespace! is this intentional?"); + } + R2APIContentManager.HandleContentAddition(asm, buffDef); + return true; + } + RejectContent(buffDef, asm, "BuffDef", "but the BuffCatalog has already initialized!"); + return false; + } + //EliteDefs should be added by EliteAPI, but this method is here purely for completion sake. + /// + /// Adds an EliteDef to your Mod's ContentPack + /// EliteDef should be added by EliteAPI's Add methods. + /// + /// The EliteDef to Add + /// true if valid and added, false if one of the requirements is not met + public static bool AddEliteDef(EliteDef eliteDef) { + var asm = Assembly.GetCallingAssembly(); + if (CatalogBlockers.GetAvailability()) { + R2API.Logger.LogInfo($"Assembly {asm.GetName().Name} is adding an {eliteDef} via {nameof(ContentAdditionHelpers)}.{nameof(AddEliteDef)}()" + + $"The assembly should ideally add them via {nameof(EliteAPI)} so that they can use EliteAPI's elite tier systems, adding the elite anyways as Tier1 elite."); + EliteAPI.Add(new CustomElite(eliteDef, new List { EliteAPI.VanillaEliteTiers[1], EliteAPI.VanillaEliteTiers[2] })); + return true; + } + RejectContent(eliteDef, asm, "EliteDef", "but the EliteCatalog has already initialized!"); + return false; + } + //UnlockableDefs should be added by UnlockableAPI, despite this, players could make new unlockableDefs for usage with DirectorCards (such as forbidden unlockables) or for Log entries, which also require unlockableDefs. + /// + /// Adds an UnlockableDef to your Mod's ContentPack + /// If you want the unlockable to be tied to an achievement, use UnlockableAPI instead. + /// + /// The UnlockableDef to Add + /// true if valid and added, false if one of the requirements is not met + public static bool AddUnlockableDef(UnlockableDef unlockableDef) { + var asm = Assembly.GetCallingAssembly(); + if (CatalogBlockers.GetAvailability()) { + R2APIContentManager.HandleContentAddition(asm, unlockableDef); + return true; + } + RejectContent(unlockableDef, asm, "UnlockableDef", "but the UnlockableCatalog has already initialized"); + return false; + } + /// + /// Adds a SurvivorDef to your Mod's ContentPack + /// Requires the bodyPrefab to be assigned + /// BodyPrefab requires a CharacterBody component + /// Throws a warning if no displayPrefab is assigned + /// + /// The SurvivorDef to Add + /// true if valid and added, false if one of the requirements is not met + public static bool AddSurvivorDef(SurvivorDef survivorDef) { + var asm = Assembly.GetCallingAssembly(); + if (CatalogBlockers.GetAvailability()) { + if (!survivorDef.bodyPrefab) { + RejectContent(survivorDef, asm, "SurvivorDef", $"but it's bodyPrefab is not assigned!"); + return false; + } + if (!survivorDef.bodyPrefab.GetComponent()) { + RejectContent(survivorDef, asm, "SurvivorDef", $"but it's bodyPrefab does not have a {nameof(CharacterBody)} component!"); + return false; + } + if (!survivorDef.displayPrefab) { + R2API.Logger.LogWarning($"Assembly {asm.GetName().Name} is adding an {survivorDef} that does not have a displayPrefab! is this intentional?"); + } + R2APIContentManager.HandleContentAddition(asm, survivorDef); + return true; + } + RejectContent(survivorDef, asm, "SurvivorDef", "but the SurvivorCatalog has already initialized!"); + return false; + } + /// + /// Adds an ArtifactDef to your Mod's ContentPack + /// Requires the ArtifactDef's icon sprites to not be null. + /// + /// The ArtifactDef to Add + /// true if valid and added, false if one of the requirements is not met + public static bool AddArtifactDef(ArtifactDef artifactDef) { + var asm = Assembly.GetCallingAssembly(); + if (CatalogBlockers.GetAvailability()) { + if (artifactDef.smallIconDeselectedSprite == null || artifactDef.smallIconSelectedSprite == null) { + RejectContent(artifactDef, asm, "ArtifactDef", $"but one of it's icons are null! this is not allowed!"); + return false; + } + R2APIContentManager.HandleContentAddition(asm, artifactDef); + return true; + } + RejectContent(artifactDef, asm, "ArtifactDef", "but the ArtifactCatalog has already initialized!"); + return false; + } + /// + /// Adds a SurfaceDef to your Mod's ContentPack + /// Requires the surfaceDef's impactEffect or footstepEffect prefabs to not be null + /// + /// The SurfaceDef to Add + /// true if valid and added, false if one of the requirements is not met + public static bool AddSurfaceDef(SurfaceDef surfaceDef) { + var asm = Assembly.GetCallingAssembly(); + if (CatalogBlockers.GetAvailability()) { + if (surfaceDef.impactEffectPrefab == null || surfaceDef.footstepEffectPrefab == null) { + RejectContent(surfaceDef, asm, "SurfaceDef", $"but one of it's effect prefabs are null! this is not allowed!"); + return false; + } + R2APIContentManager.HandleContentAddition(asm, surfaceDef); + return true; + } + RejectContent(surfaceDef, asm, "SurfaceDef", "but the SurfaceDefCatalog has already initialized!"); + return false; + } + /// + /// Adds a NetworkSoundEventDef to your Mod's ContentPack + /// Requires that the event's name is not null, empty or whitespace + /// + /// The NetworkSoundEventDef to Add + /// true if valid and added, false if one of the requirements is not met + public static bool AddNetworkSoundEventDef(NetworkSoundEventDef networkSoundEventDef) { + var asm = Assembly.GetCallingAssembly(); + if (CatalogBlockers.GetAvailability()) { + if (string.IsNullOrEmpty(networkSoundEventDef.eventName) || string.IsNullOrWhiteSpace(networkSoundEventDef.eventName)) { + RejectContent(networkSoundEventDef, asm, "NetworkSoundEventDef", $"but it's eventName is Null, Empty or Whitespace!"); + return false; + } + R2APIContentManager.HandleContentAddition(asm, networkSoundEventDef); + return true; + } + RejectContent(networkSoundEventDef, asm, "NetworkSoundEventDef", "but the NetworkSoundEventCatalog has already initialized!"); + return false; + } + /// + /// Adds a MusicTrackDef to your Mod's ContentPack + /// MusicTrackDefs should only be created in the editor due to WWise's unity integration. If you want to add new songs, use SoundAPI's MusicAPI + /// + /// The MusicTrackDef to Add. + /// true if valid and added, false if one of the requirements is not met + public static bool AddMusicTrackDef(MusicTrackDef musicTrackDef) { + var asm = Assembly.GetCallingAssembly(); + if (CatalogBlockers.GetAvailability()) { + R2APIContentManager.HandleContentAddition(asm, musicTrackDef); + return true; + } + RejectContent(musicTrackDef, asm, "MusicTrackDef", "but the MusicTrackCatalog has already initialized!"); + return false; + } + /// + /// Adds a GameEndingDef to your Mod's ContentPack + /// + /// The GameEndingDef to Add + /// true if valid and added, false if one of the requirements is not met + public static bool AddGameEndingDef(GameEndingDef gameEndingDef) { + var asm = Assembly.GetCallingAssembly(); + if (CatalogBlockers.GetAvailability()) { + R2APIContentManager.HandleContentAddition(asm, gameEndingDef); + return true; + } + RejectContent(gameEndingDef, asm, "GameEndingDef", "but the GameEndingCatalog has already initalized!"); + return false; + } + /// + /// Adds an EntityStateConfiguration to your Mod's ContentPack + /// ESC's Target Type must inherit from EntityState + /// ESC's Target Type cannot be Abstract + /// + /// The EntityStateConfiguration to Add + /// true if valid and added, false if one of the requirements is not met + public static bool AddEntityStateConfiguration(EntityStateConfiguration entityStateConfiguration) { + var asm = Assembly.GetCallingAssembly(); + if (CatalogBlockers.GetAvailability()) { + Type type = Type.GetType(entityStateConfiguration.targetType.assemblyQualifiedName); + if (!type.IsSubclassOf(typeof(EntityState))) { + RejectContent(entityStateConfiguration, asm, "EntityStateConfiguration", $"but it's targetType ({type.Name}) is not a type that derives from EntityState!"); + return false; + } + if (type.IsAbstract) { + RejectContent(entityStateConfiguration, asm, "EntityStateConfiguration", $"but it's targetType ({type.Name}) is abstract!"); + return false; + } + R2APIContentManager.HandleContentAddition(asm, entityStateConfiguration); + return true; + } + RejectContent(entityStateConfiguration, asm, "EntityStateConfiguration", "but the EntityStateCatalog has already initialized!"); + return false; + + } + /// + /// Adds an EntitySateType to your Mod's ContentPack + /// State Type cannot be abstract + /// + /// The State's Type + /// Wether or not the state Type was succesfully added or not + /// A SerializableEntityStateType, the StateType will be null if "wasAdded" is false. + public static SerializableEntityStateType AddEntityState(out bool wasAdded) where T : EntityState { + var asm = Assembly.GetCallingAssembly(); + Type t = typeof(T); + if (CatalogBlockers.GetAvailability()) { + if (t.IsAbstract) { + RejectContent(t, asm, "EntityStateType", "but the entity state type is markeed as abstract!"); + wasAdded = false; + return new SerializableEntityStateType(); + } + wasAdded = true; + R2APIContentManager.HandleEntityState(asm, t); + return new SerializableEntityStateType(t); + } + RejectContent(t, asm, "EntityStateType", "but the EntityStateCatalog has already initialzed!"); + wasAdded = false; + return new SerializableEntityStateType(); + } + #endregion + + #region Util Methods + private static void RejectContent(object content, Assembly assembly, string contentType, string problem) { + try { + throw new InvalidOperationException($"Assembly {assembly.GetName().Name} is trying to add a {content} as a {contentType}, {problem}"); + } + catch (Exception e) { R2API.Logger.LogError(e); } + } + private static bool HasComponent(GameObject obj) where T : Component => obj.GetComponent(); + #endregion + } +} diff --git a/R2API/LoadRoR2ContentEarly.cs b/R2API/ContentManagement/LoadRoR2ContentEarly.cs similarity index 95% rename from R2API/LoadRoR2ContentEarly.cs rename to R2API/ContentManagement/LoadRoR2ContentEarly.cs index 9385b670..68ac0c31 100644 --- a/R2API/LoadRoR2ContentEarly.cs +++ b/R2API/ContentManagement/LoadRoR2ContentEarly.cs @@ -12,10 +12,14 @@ using System.Reflection; using UnityEngine; -namespace R2API { - internal static class LoadRoR2ContentEarly { +namespace R2API.ContentManagement { + public static class LoadRoR2ContentEarly { private static bool _ror2ContentLoaded; private static RoR2Content RoR2Content; + /// + /// A ReadOnly version of RoR2's ContentPack. + /// + public static ReadOnlyContentPack ReadOnlyRoR2ContentPack { get; private set; } internal static void Init() { @@ -104,6 +108,7 @@ where typeof(EntityState).IsAssignableFrom(type) ContentLoadHelper.PopulateTypeFields(typeof(RoR2Content.Survivors), RoR2Content.contentPack.survivorDefs); RoR2Content.contentPack.effectDefs.Find("CoinEmitter").cullMethod = (EffectData effectData) => SettingsConVars.cvExpAndMoneyEffects.value; + ReadOnlyRoR2ContentPack = new ReadOnlyContentPack(RoR2Content.contentPack); _ror2ContentLoaded = true; } diff --git a/R2API/ContentManagement/R2APIContentManager.cs b/R2API/ContentManagement/R2APIContentManager.cs new file mode 100644 index 00000000..7ab74b06 --- /dev/null +++ b/R2API/ContentManagement/R2APIContentManager.cs @@ -0,0 +1,577 @@ +using BepInEx; +using EntityStates; +using R2API.MiscHelpers; +using RoR2; +using RoR2.ContentManagement; +using RoR2.Projectile; +using RoR2.Skills; +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using System.Reflection; +using UnityEngine; +using UnityEngine.Networking; +using Object = UnityEngine.Object; + +namespace R2API.ContentManagement { + + /// + /// A struct that represents a ContentPack managed by R2API in some way shape or form + /// + internal struct R2APIManagedContentPack { + internal SerializableContentPack serializableContentPack; + internal bool shouldManageLoading { get; } + + public R2APIManagedContentPack(SerializableContentPack contentPack, bool shouldManageLoading = true) { + this.serializableContentPack = contentPack; + this.shouldManageLoading = shouldManageLoading; + } + } + /// + /// A class that's used for managing ContentPacks created by R2API + /// + public static class R2APIContentManager { + /// + /// Returns a read only collection of all the ContentPacks created by R2API + /// + public static ReadOnlyCollection ManagedContentPacks { + get { + if (!contentPacksCreated) { + R2API.Logger.LogError($"Cannot return ContentPacks when they havent been created!"); + return null; + } + return _managedContentPacks; + } + } + private static ReadOnlyCollection _managedContentPacks = null; + internal static List genericContentPacks = new List(); + + /// + /// When R2API finishes creating the ContentPacks that it manages, this Action is ran. + /// + public static Action OnContentPacksCreated; + private static bool contentPacksCreated = false; + + //This is an easy way of storing the new content packs that are being created. not to mention that the ContentPack's identifier will be the plugin's GUID + private static Dictionary BepInModNameToSerializableContentPack = new Dictionary(); + //Cache-ing the Assembly's main plugin in a dictionary for ease of access. + private static Dictionary AssemblyToBepInModName = new Dictionary(); + //Due to the fact that all contents should have unique names to avoid issues with the catalogs, we need to make sure there are no duplicate names whatsoever. + //This dictionary which gets populated in Init() can be used to get all the currently registered names of a content depending on it's type. + //We use this to do a While() loop later to ensure no duplicate names between the RoR2ContentPacks, and our own. + //there might be a better way of doing this by ILHooking each catalog's init and adding a check to handle duplicate names, but i'm not smart enough to do this. + private static Dictionary> TypeToAllCurrentlyRegisteredNames = new Dictionary>(); + + #region Public Methods + /// + /// Adds a Pre-Existing SerializableContentPack as your mod's content pack. + /// Example usage would be a Thunderkit mod adding their items via ItemAPI to get the advantage of using ItemAPI's IDRS Systems + /// + /// The serializable content pack that will be tied to your mod. + /// If this is set to true, R2API will create a ContentPackProvider for your ContentPack and handle the loading for you. + public static void AddPreExistingSerializableContentPack(SerializableContentPack serializableContentPack, bool shouldManageLoadingContentPack = true) { + try { + Assembly assembly = Assembly.GetCallingAssembly(); + if (!AssemblyToBepInModName.ContainsKey(assembly)) { + Type mainClass = assembly.GetTypes() + .Where(t => t.GetCustomAttribute() != null) + .FirstOrDefault(); + + if (mainClass != null) { + BepInPlugin attribute = mainClass.GetCustomAttribute(); + if (attribute != null) { + AssemblyToBepInModName.Add(assembly, attribute.Name); + } + } + } + if (AssemblyToBepInModName.TryGetValue(assembly, out string modName)) { + serializableContentPack.name = modName; + if (!BepInModNameToSerializableContentPack.ContainsKey(modName)) { + BepInModNameToSerializableContentPack.Add(modName, new R2APIManagedContentPack(serializableContentPack, shouldManageLoadingContentPack)); + R2API.Logger.LogInfo($"Added Pre-Existing SerializableContentPack from mod {modName}"); + return; + } + throw new InvalidOperationException($"The Mod {modName} already has a Serializable Content Pack assigned to it!"); + } + throw new NullReferenceException($"The assembly {assembly} does not have a class that has a BepInPlugin attribute! Cannot assign Serializable Content Pack for {modName}!"); + } + catch (Exception e) { + R2API.Logger.LogError(e); + } + } + + /// + /// Reserves a SerializableContentPack for your mod and returns it + /// If the SerializableContentPack already exists, it returns it. + /// + /// The reserved SerializableContentPack + public static SerializableContentPack ReserveSerializableContentPack() => GetOrCreateSerializableContentPack(Assembly.GetCallingAssembly()); + #endregion + + #region Main Methods + internal static void Init() { + string[] BodyPrefabs() { + return LoadRoR2ContentEarly.ReadOnlyRoR2ContentPack.bodyPrefabs + .Union(BepInModNameToSerializableContentPack.Values + .SelectMany(scp => scp.serializableContentPack.bodyPrefabs)) + .Select(go => go.name) + .ToArray(); + } + TypeToAllCurrentlyRegisteredNames.Add(typeof(CharacterBody), BodyPrefabs); + + string[] MasterPrefabs() { + return LoadRoR2ContentEarly.ReadOnlyRoR2ContentPack.masterPrefabs + .Union(BepInModNameToSerializableContentPack.Values + .SelectMany(scp => scp.serializableContentPack.masterPrefabs)) + .Select(go => go.name) + .ToArray(); + } + TypeToAllCurrentlyRegisteredNames.Add(typeof(CharacterMaster), MasterPrefabs); + + string[] ProjectilePrefabs() { + return LoadRoR2ContentEarly.ReadOnlyRoR2ContentPack.projectilePrefabs + .Union(BepInModNameToSerializableContentPack.Values + .SelectMany(scp => scp.serializableContentPack.projectilePrefabs)) + .Select(go => go.name) + .ToArray(); + } + TypeToAllCurrentlyRegisteredNames.Add(typeof(ProjectileController), ProjectilePrefabs); + + string[] RunPrefabs() { + return LoadRoR2ContentEarly.ReadOnlyRoR2ContentPack.gameModePrefabs + .Union(BepInModNameToSerializableContentPack.Values + .SelectMany(scp => scp.serializableContentPack.gameModePrefabs)) + .Select(go => go.name) + .ToArray(); + } + TypeToAllCurrentlyRegisteredNames.Add(typeof(Run), RunPrefabs); + + string[] NetworkedPrefabs() { + return LoadRoR2ContentEarly.ReadOnlyRoR2ContentPack.networkedObjectPrefabs + .Union(BepInModNameToSerializableContentPack.Values + .SelectMany(scp => scp.serializableContentPack.networkedObjectPrefabs)) + .Select(go => go.name) + .ToArray(); + } + TypeToAllCurrentlyRegisteredNames.Add(typeof(NetworkIdentity), NetworkedPrefabs); + + string[] EffectPrefabs() { + return LoadRoR2ContentEarly.ReadOnlyRoR2ContentPack.effectDefs + .Select(ed => ed.prefab) + .Union(BepInModNameToSerializableContentPack.Values + .SelectMany(scp => scp.serializableContentPack.effectDefs + .Select(ed => ed.prefab))) + .Select(go => go.name) + .ToArray(); + } + TypeToAllCurrentlyRegisteredNames.Add(typeof(EffectDef), EffectPrefabs); + + string[] SkillDefs() { + return LoadRoR2ContentEarly.ReadOnlyRoR2ContentPack.skillDefs + .Union(BepInModNameToSerializableContentPack.Values + .SelectMany(scp => scp.serializableContentPack.skillDefs)) + .Select(sd => sd as ScriptableObject) + .Select(so => so.name) + .ToArray(); + } + TypeToAllCurrentlyRegisteredNames.Add(typeof(SkillDef), SkillDefs); + + string[] SkillFamilies() { + return LoadRoR2ContentEarly.ReadOnlyRoR2ContentPack.skillFamilies + .Union(BepInModNameToSerializableContentPack.Values + .SelectMany(scp => scp.serializableContentPack.skillFamilies)) + .Select(sf => sf as ScriptableObject) + .Select(so => so.name) + .ToArray(); + } + TypeToAllCurrentlyRegisteredNames.Add(typeof(SkillFamily), SkillFamilies); + + string[] SceneDefs() { + return LoadRoR2ContentEarly.ReadOnlyRoR2ContentPack.sceneDefs + .Union(BepInModNameToSerializableContentPack.Values + .SelectMany(scp => scp.serializableContentPack.sceneDefs)) + .Select(sd => sd.cachedName) + .ToArray(); + } + TypeToAllCurrentlyRegisteredNames.Add(typeof(SceneDef), SceneDefs); + + string[] ItemDefs() { + return LoadRoR2ContentEarly.ReadOnlyRoR2ContentPack.itemDefs + .Union(BepInModNameToSerializableContentPack.Values + .SelectMany(scp => scp.serializableContentPack.itemDefs)) + .Select(id => id.name) + .ToArray(); + } + TypeToAllCurrentlyRegisteredNames.Add(typeof(ItemDef), ItemDefs); + + string[] EquipmentDefs() { + return LoadRoR2ContentEarly.ReadOnlyRoR2ContentPack.equipmentDefs + .Union(BepInModNameToSerializableContentPack.Values + .SelectMany(scp => scp.serializableContentPack.equipmentDefs)) + .Select(ed => ed.name) + .ToArray(); + } + TypeToAllCurrentlyRegisteredNames.Add(typeof(EquipmentDef), EquipmentDefs); + + string[] BuffDefs() { + return LoadRoR2ContentEarly.ReadOnlyRoR2ContentPack.buffDefs + .Union(BepInModNameToSerializableContentPack.Values + .SelectMany(scp => scp.serializableContentPack.buffDefs)) + .Select(bd => bd.name) + .ToArray(); + } + TypeToAllCurrentlyRegisteredNames.Add(typeof(BuffDef), BuffDefs); + + string[] EliteDefs() { + return LoadRoR2ContentEarly.ReadOnlyRoR2ContentPack.eliteDefs + .Union(BepInModNameToSerializableContentPack.Values + .SelectMany(scp => scp.serializableContentPack.eliteDefs)) + .Select(ed => ed.name) + .ToArray(); + } + TypeToAllCurrentlyRegisteredNames.Add(typeof(EliteDef), EliteDefs); + + string[] UnlockableDefs() { + return LoadRoR2ContentEarly.ReadOnlyRoR2ContentPack.unlockableDefs + .Union(BepInModNameToSerializableContentPack.Values + .SelectMany(scp => scp.serializableContentPack.unlockableDefs)) + .Select(ud => ud.cachedName) + .ToArray(); + } + TypeToAllCurrentlyRegisteredNames.Add(typeof(UnlockableDef), UnlockableDefs); + + string[] SurvivorDefs() { + return LoadRoR2ContentEarly.ReadOnlyRoR2ContentPack.survivorDefs + .Union(BepInModNameToSerializableContentPack.Values + .SelectMany(scp => scp.serializableContentPack.survivorDefs)) + .Select(sd => sd.cachedName) + .ToArray(); + } + TypeToAllCurrentlyRegisteredNames.Add(typeof(SurvivorDef), SurvivorDefs); + + string[] ArtifactDefs() { + return LoadRoR2ContentEarly.ReadOnlyRoR2ContentPack.artifactDefs + .Union(BepInModNameToSerializableContentPack.Values + .SelectMany(scp => scp.serializableContentPack.artifactDefs)) + .Select(ad => ad.cachedName) + .ToArray(); + } + TypeToAllCurrentlyRegisteredNames.Add(typeof(ArtifactDef), ArtifactDefs); + + string[] SurfaceDefs() { + return LoadRoR2ContentEarly.ReadOnlyRoR2ContentPack.surfaceDefs + .Union(BepInModNameToSerializableContentPack.Values + .SelectMany(scp => scp.serializableContentPack.surfaceDefs)) + .Select(sd => sd.name) + .ToArray(); + } + TypeToAllCurrentlyRegisteredNames.Add(typeof(SurfaceDef), SurfaceDefs); + + string[] NetworkSoundEventDefs() { + return LoadRoR2ContentEarly.ReadOnlyRoR2ContentPack.networkSoundEventDefs + .Union(BepInModNameToSerializableContentPack.Values + .SelectMany(scp => scp.serializableContentPack.networkSoundEventDefs)) + .Select(nsed => nsed.name) + .ToArray(); + } + TypeToAllCurrentlyRegisteredNames.Add(typeof(NetworkSoundEventDef), NetworkSoundEventDefs); + + string[] MusicTrackDefs() { + return LoadRoR2ContentEarly.ReadOnlyRoR2ContentPack.musicTrackDefs + .Union(BepInModNameToSerializableContentPack.Values + .SelectMany(scp => scp.serializableContentPack.musicTrackDefs)) + .Select(mtd => mtd.cachedName) + .ToArray(); + } + TypeToAllCurrentlyRegisteredNames.Add(typeof(MusicTrackDef), MusicTrackDefs); + + string[] GameEndingDefs() { + return LoadRoR2ContentEarly.ReadOnlyRoR2ContentPack.gameEndingDefs + .Union(BepInModNameToSerializableContentPack.Values + .SelectMany(scp => scp.serializableContentPack.gameEndingDefs)) + .Select(ged => ged.cachedName) + .ToArray(); + } + TypeToAllCurrentlyRegisteredNames.Add(typeof(GameEndingDef), GameEndingDefs); + + string[] EntityStateConfigurations() { + return LoadRoR2ContentEarly.ReadOnlyRoR2ContentPack.entityStateConfigurations + .Union(BepInModNameToSerializableContentPack.Values + .SelectMany(scp => scp.serializableContentPack.entityStateConfigurations)) + .Select(esc => esc.name) + .ToArray(); + } + TypeToAllCurrentlyRegisteredNames.Add(typeof(EntityStateConfiguration), EntityStateConfigurations); + } + + internal static void HandleContentAddition(Assembly assembly, Object content) { + SerializableContentPack scp = GetOrCreateSerializableContentPack(assembly); + content = EnsureSafeContentName(content, scp.name); + if (scp) { + try { + bool added = false; + switch (content) { + case GameObject go: HandleGameObject(go, scp); added = true; break; + case SkillDef skd: AddSafe(ref scp.skillDefs, skd, scp.name); added = true; break; + case SkillFamily sf: AddSafe(ref scp.skillFamilies, sf, scp.name); added = true; break; + case SceneDef scd: AddSafe(ref scp.sceneDefs, scd, scp.name); added = true; break; + case ItemDef id: AddSafe(ref scp.itemDefs, id, scp.name); added = true; break; + case EquipmentDef eqd: AddSafe(ref scp.equipmentDefs, eqd, scp.name); added = true; break; + case BuffDef bd: AddSafe(ref scp.buffDefs, bd, scp.name); added = true; break; + case EliteDef ed: AddSafe(ref scp.eliteDefs, ed, scp.name); added = true; break; + case UnlockableDef ud: AddSafe(ref scp.unlockableDefs, ud, scp.name); added = true; break; + case SurvivorDef sd: AddSafe(ref scp.survivorDefs, sd, scp.name); added = true; break; + case ArtifactDef ad: AddSafe(ref scp.artifactDefs, ad, scp.name); added = true; break; + case SurfaceDef surd: AddSafe(ref scp.surfaceDefs, surd, scp.name); added = true; break; + case NetworkSoundEventDef nsed: AddSafe(ref scp.networkSoundEventDefs, nsed, scp.name); added = true; break; + case MusicTrackDef mtd: AddSafe(ref scp.musicTrackDefs, mtd, scp.name); added = true; break; + case GameEndingDef ged: AddSafe(ref scp.gameEndingDefs, ged, scp.name); added = true; break; + case EntityStateConfiguration esc: AddSafe(ref scp.entityStateConfigurations, esc, scp.name); added = true; break; + } + if (!added) { + throw new ArgumentException($"The content {content.name} ({content.GetType()}) is not supported by the ContentManager! \n" + + $"If you think this is an Error and it should be supported, please file a bug report."); + } + } + catch (Exception e) { R2API.Logger.LogError(e); } + } + } + + internal static void HandleEntityState(Assembly assembly, Type type) { + SerializableContentPack scp = GetOrCreateSerializableContentPack(assembly); + if (scp) { + AddSafeType(ref scp.entityStateTypes, new SerializableEntityStateType(type), scp.name); + } + } + + private static void HandleGameObject(GameObject go, SerializableContentPack scp) { + try { + bool alreadyNetworked = false; + bool addedToAnyCatalogs = false; + if (go.GetComponent()) { + AddSafe(ref scp.bodyPrefabs, go, scp.name); + alreadyNetworked = true; + addedToAnyCatalogs = true; + } + if (go.GetComponent()) { + AddSafe(ref scp.masterPrefabs, go, scp.name); + alreadyNetworked = true; + addedToAnyCatalogs = true; + } + if (go.GetComponent()) { + AddSafe(ref scp.projectilePrefabs, go, scp.name); + alreadyNetworked = true; + addedToAnyCatalogs = true; + } + if (go.GetComponent()) { + AddSafe(ref scp.gameModePrefabs, go, scp.name); + alreadyNetworked = true; + addedToAnyCatalogs = true; + } + //ror2 automatically networks prefabs that are in the arrays above this one. (since all of them already have network identities) + if (!alreadyNetworked && !PrefabAPI.IsPrefabHashed(go) && go.GetComponent()) { + AddSafe(ref scp.networkedObjectPrefabs, go, scp.name); + addedToAnyCatalogs = true; + } + //Modify this once dlc 1 comes out, as EffectDefs will be a game object array instead of an EffectDef array. + if (go.GetComponent()) { + AddSafeType(ref scp.effectDefs, new EffectDef(go), scp.name); + addedToAnyCatalogs = true; + } + if (!addedToAnyCatalogs) { + throw new ArgumentException($"The GameObject {go.name} ({go.GetType()}) does not have any components that are supported by the ContentManager! \n" + + $"If you think this is an Error and it should be supported, please file a bug report."); + } + } + catch (Exception e) { R2API.Logger.LogError(e); } + } + + internal static void CreateContentPacks() { + if (!contentPacksCreated) { + R2API.Logger.LogInfo($"Generating a total of {BepInModNameToSerializableContentPack.Values.Count} ContentPacks..."); + List contentPacks = new List(); + foreach (var (bepInModName, r2apiContentPack) in BepInModNameToSerializableContentPack) { + if (ShouldContentPackBeLoadedByR2API(r2apiContentPack, out SerializableContentPack scp)) { + ContentPack cp = scp.CreateContentPack(); + cp.identifier = bepInModName; + contentPacks.Add(cp); + genericContentPacks.Add(new R2APIGenericContentPack(cp)); + R2API.Logger.LogDebug($"Content pack for {bepInModName} created."); + } + else { + R2API.Logger.LogDebug($"Not creating ContentPack for {bepInModName}, since it has declared r2api should not manage loading the content pack."); + } + } + _managedContentPacks = new ReadOnlyCollection(contentPacks); + contentPacksCreated = true; + OnContentPacksCreated?.Invoke(); + } + else { + throw new InvalidOperationException($"The Content Pack collection has already been created!"); + } + } + #endregion + + #region Util + private static SerializableContentPack GetOrCreateSerializableContentPack(Assembly assembly) { + try { + //If the assembly that's adding the item has not been cached, find the GUID of the assembly and cache it. + if (!AssemblyToBepInModName.ContainsKey(assembly)) { + Type mainClass = assembly.GetTypes() + .Where(t => t.GetCustomAttribute() != null) + .FirstOrDefault(); + + if (mainClass != null) { + BepInPlugin attribute = mainClass.GetCustomAttribute(); + if (attribute != null) { + AssemblyToBepInModName.Add(assembly, attribute.Name); + } + } + } + + if (AssemblyToBepInModName.TryGetValue(assembly, out string modName)) { + SerializableContentPack serializableContentPack; + //If this assembly does not have a content pack assigned to it, create a new one and add it to the dictionary + if (!BepInModNameToSerializableContentPack.ContainsKey(modName)) { + serializableContentPack = ScriptableObject.CreateInstance(); + serializableContentPack.name = modName; + BepInModNameToSerializableContentPack.Add(modName, new R2APIManagedContentPack(serializableContentPack)); + R2API.Logger.LogInfo($"Created a SerializableContentPack for mod {modName}"); + } + return BepInModNameToSerializableContentPack[modName].serializableContentPack; + } + throw new NullReferenceException($"The assembly {assembly} does not have a class that has a BepInPlugin attribute! Cannot create ContentPack for {modName}!"); + } + catch (Exception e) { + R2API.Logger.LogError(e); + return null; + } + } + + private static void AddSafe(ref T[] assetArray, T asset, string identifier) where T : Object { + if (!assetArray.Contains(asset)) { + HG.ArrayUtils.ArrayAppend(ref assetArray, asset); + } + else { + R2API.Logger.LogWarning($"Cannot add {asset} to content pack {identifier} because the asset has already been added to it's corresponding array!"); + } + } + + private static void AddSafeType(ref T[] assetArray, T asset, string identifier) { + if (!assetArray.Contains(asset)) { + if (asset is EffectDef ed) { + HG.ArrayUtils.ArrayAppend(ref assetArray, asset); + } + else { + HG.ArrayUtils.ArrayAppend(ref assetArray, asset); + } + } + else { + R2API.Logger.LogWarning($"Cannot add {asset} to content pack {identifier} because the asset has already been added to it's corresponding array!"); + } + } + + private static bool ShouldContentPackBeLoadedByR2API(R2APIManagedContentPack managedContentPack, out SerializableContentPack contentPack) { + if (managedContentPack.shouldManageLoading) { + contentPack = managedContentPack.serializableContentPack; + return true; + } + contentPack = null; + return false; + } + #endregion + + #region Duplicate Naming Avoidance Methods + private static Object EnsureSafeContentName(Object obj, string identifier) { + switch (obj) { + case GameObject go: return EnsureSafeGameObjectName(go, identifier); + case SkillDef skd: return EnsureSafeScriptableObjectName(skd, identifier); + case SkillFamily sf: return EnsureSafeScriptableObjectName(sf, identifier); + case SceneDef scd: return EnsureSafeScriptableObjectName(scd, identifier); + case ItemDef id: return EnsureSafeScriptableObjectName(id, identifier); + case EquipmentDef eqd: return EnsureSafeScriptableObjectName(eqd, identifier); + case BuffDef bd: return EnsureSafeScriptableObjectName(bd, identifier); + case EliteDef ed: return EnsureSafeScriptableObjectName(ed, identifier); + case UnlockableDef ud: return EnsureSafeScriptableObjectName(ud, identifier); + case SurvivorDef sd: return EnsureSafeScriptableObjectName(sd, identifier); + case ArtifactDef ad: return EnsureSafeScriptableObjectName(ad, identifier); + case SurfaceDef surd: return EnsureSafeScriptableObjectName(surd, identifier); + case NetworkSoundEventDef nsed: return EnsureSafeScriptableObjectName(nsed, identifier); + case MusicTrackDef mtd: return EnsureSafeScriptableObjectName(mtd, identifier); + case GameEndingDef ged: return EnsureSafeScriptableObjectName(ged, identifier); + case EntityStateConfiguration esc: return EnsureSafeScriptableObjectName(esc, identifier); + } + return obj; + } + private static Object EnsureSafeGameObjectName(GameObject go, string identifier) { + if (go.GetComponent()) { + string[] allBodies = TypeToAllCurrentlyRegisteredNames[typeof(CharacterBody)](); + if ((string.IsNullOrWhiteSpace(go.name) || string.IsNullOrEmpty(go.name)) && allBodies.Contains(go.name)) { + R2API.Logger.LogInfo($"An object with name \"{go.name}\" already exists in the registered bodies! creating new name!"); + go.name = GetNewName(go, identifier, allBodies); + } + } + if (go.GetComponent()) { + string[] allMasters = TypeToAllCurrentlyRegisteredNames[typeof(CharacterMaster)](); + if ((string.IsNullOrWhiteSpace(go.name) || string.IsNullOrEmpty(go.name)) && allMasters.Contains(go.name)) { + R2API.Logger.LogInfo($"An object with name \"{go.name}\" already exists in the registered masters! creating new name!"); + go.name = GetNewName(go, identifier, allMasters); + } + } + if (go.GetComponent()) { + string[] allProjectiles = TypeToAllCurrentlyRegisteredNames[typeof(ProjectileController)](); + if ((string.IsNullOrWhiteSpace(go.name) || string.IsNullOrEmpty(go.name)) && allProjectiles.Contains(go.name)) { + R2API.Logger.LogInfo($"An object with name \"{go.name}\" already exists in the registered projectiles! creating new name!"); + go.name = GetNewName(go, identifier, allProjectiles); + } + } + if (go.GetComponent()) { + string[] allRuns = TypeToAllCurrentlyRegisteredNames[typeof(Run)](); + if ((string.IsNullOrWhiteSpace(go.name) || string.IsNullOrEmpty(go.name)) && allRuns.Contains(go.name)) { + R2API.Logger.LogInfo($"An object with name \"{go.name}\" already exists in the registered Runs! creating new name!"); + go.name = GetNewName(go, identifier, allRuns); + } + } + if (!PrefabAPI.IsPrefabHashed(go) && go.GetComponent()) { + string[] allNetworkedPrefabs = TypeToAllCurrentlyRegisteredNames[typeof(NetworkIdentity)](); + if ((string.IsNullOrWhiteSpace(go.name) || string.IsNullOrEmpty(go.name)) && allNetworkedPrefabs.Contains(go.name)) { + R2API.Logger.LogInfo($"An object with name \"{go.name}\" already exists in the registered networked prefabs! creating new name!"); + go.name = GetNewName(go, identifier, allNetworkedPrefabs); + } + } + if (go.GetComponent()) { + string[] allEffects = TypeToAllCurrentlyRegisteredNames[typeof(EffectDef)](); + if ((string.IsNullOrWhiteSpace(go.name) || string.IsNullOrEmpty(go.name)) && allEffects.Contains(go.name)) { + R2API.Logger.LogInfo($"An object with name \"{go.name}\" already exists in the registered Effects! creating new name!"); + go.name = GetNewName(go, identifier, allEffects); + } + } + return go; + } + + private static ScriptableObject EnsureSafeScriptableObjectName(ScriptableObject obj, string identifier) where T : ScriptableObject { + if (TypeToAllCurrentlyRegisteredNames.TryGetValue(typeof(T), out var func)) { + string[] allScriptablesOfTypeT = func(); + if ((string.IsNullOrWhiteSpace(obj.name) || string.IsNullOrEmpty(obj.name)) && allScriptablesOfTypeT.Contains(obj.name)) { + R2API.Logger.LogInfo($"An object with name \"{obj.name}\" already exists in the registered {typeof(T).Name}! creating new Name!"); + obj.name = GetNewName(obj, identifier, allScriptablesOfTypeT); + } + } + return obj; + } + + //Creates a new, generic name for a unity asset. + private static string GetNewName(Object obj, string identifier, string[] allAssets) { + int i = 0; + string newName = obj.name; + while (allAssets.Contains(newName)) { + newName = $"{identifier}{obj.GetType().Name}{i}"; + i++; + } + R2API.Logger.LogDebug($"The new name for {obj} is {newName}"); + return newName; + } + #endregion + } +} diff --git a/R2API/ContentManagement/R2APIContentPackProvider.cs b/R2API/ContentManagement/R2APIContentPackProvider.cs new file mode 100644 index 00000000..03efdbc5 --- /dev/null +++ b/R2API/ContentManagement/R2APIContentPackProvider.cs @@ -0,0 +1,85 @@ +using RoR2.ContentManagement; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; + +namespace R2API.ContentManagement { + internal class R2APIGenericContentPack : IContentPackProvider { + + internal R2APIGenericContentPack(ContentPack finalizedContentPack) { + contentPack = finalizedContentPack; + } + public string identifier => contentPack.identifier; + + private ContentPack contentPack; + + public IEnumerator FinalizeAsync(FinalizeAsyncArgs args) { + args.ReportProgress(1f); + yield break; + } + + public IEnumerator GenerateContentPackAsync(GetContentPackAsyncArgs args) { + ContentPack.Copy(contentPack, args.output); + LogContentsFromContentPack(); + args.ReportProgress(1f); + yield break; + } + + public IEnumerator LoadStaticContentAsync(LoadStaticContentAsyncArgs args) { + args.ReportProgress(1f); + yield break; + } + private void LogContentsFromContentPack() { + List log = new List(); + log.Add($"Content added from {contentPack.identifier}:"); + log.AddRange(contentPack.bodyPrefabs.assetInfos.Select(ai => $"{ai.assetName} ({ai.asset.GetType().Name})")); + log.AddRange(contentPack.masterPrefabs.assetInfos.Select(ai => $"{ai.assetName} ({ai.asset.GetType().Name})")); + log.AddRange(contentPack.projectilePrefabs.assetInfos.Select(ai => $"{ai.assetName} ({ai.asset.GetType().Name})")); + log.AddRange(contentPack.gameModePrefabs.assetInfos.Select(ai => $"{ai.assetName} ({ai.asset.GetType().Name})")); + log.AddRange(contentPack.networkedObjectPrefabs.assetInfos.Select(ai => $"{ai.assetName} ({ai.asset.GetType().Name})")); + log.AddRange(contentPack.skillDefs.assetInfos.Select(ai => $"{ai.assetName} ({ai.asset.GetType().Name})")); + log.AddRange(contentPack.skillFamilies.assetInfos.Select(ai => $"{ai.assetName} ({ai.asset.GetType().Name})")); + log.AddRange(contentPack.sceneDefs.assetInfos.Select(ai => $"{ai.assetName} ({ai.asset.GetType().Name})")); + log.AddRange(contentPack.itemDefs.assetInfos.Select(ai => $"{ai.assetName} ({ai.asset.GetType().Name})")); + log.AddRange(contentPack.equipmentDefs.assetInfos.Select(ai => $"{ai.assetName} ({ai.asset.GetType().Name})")); + log.AddRange(contentPack.buffDefs.assetInfos.Select(ai => $"{ai.assetName} ({ai.asset.GetType().Name})")); + log.AddRange(contentPack.eliteDefs.assetInfos.Select(ai => $"{ai.assetName} ({ai.asset.GetType().Name})")); + log.AddRange(contentPack.unlockableDefs.assetInfos.Select(ai => $"{ai.assetName} ({ai.asset.GetType().Name})")); + log.AddRange(contentPack.survivorDefs.assetInfos.Select(ai => $"{ai.assetName} ({ai.asset.GetType().Name})")); + log.AddRange(contentPack.artifactDefs.assetInfos.Select(ai => $"{ai.assetName} ({ai.asset.GetType().Name})")); + log.AddRange(contentPack.effectDefs.assetInfos.Select(ai => $"{ai.assetName} ({ai.asset.GetType().Name})")); + log.AddRange(contentPack.surfaceDefs.assetInfos.Select(ai => $"{ai.assetName} ({ai.asset.GetType().Name})")); + log.AddRange(contentPack.networkSoundEventDefs.assetInfos.Select(ai => $"{ai.assetName} ({ai.asset.GetType().Name})")); + log.AddRange(contentPack.musicTrackDefs.assetInfos.Select(ai => $"{ai.assetName} ({ai.asset.GetType().Name})")); + log.AddRange(contentPack.gameEndingDefs.assetInfos.Select(ai => $"{ai.assetName} ({ai.asset.GetType().Name})")); + log.AddRange(contentPack.entityStateConfigurations.assetInfos.Select(ai => $"{ai.assetName} ({ai.asset.GetType().Name})")); + log.AddRange(contentPack.entityStateTypes.assetInfos.Select(ai => $"{ai.assetName} ({ai.asset.GetType().Name})")); + R2API.Logger.LogDebug(string.Join("\n", log)); + } + } + internal class R2APIContentPackProvider { + internal static Action WhenAddingContentPacks; + + internal static void Init() { + ContentManager.collectContentPackProviders += AddCustomContent; + } + + private static void AddCustomContent(ContentManager.AddContentPackProviderDelegate addContentPackProvider) { + if (WhenAddingContentPacks != null) { + foreach (Action @event in WhenAddingContentPacks.GetInvocationList()) { + try { + @event(); + } + catch (Exception e) { + R2API.Logger.LogError(e); + } + } + } + R2APIContentManager.CreateContentPacks(); + foreach (R2APIGenericContentPack gcp in R2APIContentManager.genericContentPacks) { + addContentPackProvider(gcp); + } + } + } +} diff --git a/R2API/DamageAPI.cs b/R2API/DamageAPI.cs index 69b41db0..e56e7d38 100644 --- a/R2API/DamageAPI.cs +++ b/R2API/DamageAPI.cs @@ -59,10 +59,10 @@ internal static void SetHooks() { IL.RoR2.Projectile.ProjectileOverlapAttack.ResetOverlapAttack += ProjectileOverlapAttackResetOverlapAttackIL; IL.RoR2.Projectile.ProjectileProximityBeamController.UpdateServer += ProjectileProximityBeamControllerUpdateServerIL; IL.RoR2.Projectile.ProjectileSingleTargetImpact.OnProjectileImpact += ProjectileSingleTargetImpactOnProjectileImpactIL; - + IL.RoR2.DotController.EvaluateDotStacksForType += DotControllerEvaluateDotStacksForTypeIL; IL.RoR2.DotController.AddPendingDamageEntry += DotControllerAddPendingDamageEntryIL; - + IL.RoR2.BlastAttack.HandleHits += BlastAttackHandleHitsIL; IL.RoR2.BlastAttack.PerformDamageServer += BlastAttackPerformDamageServerIL; //MMHook can't handle private structs in parameters of On hooks @@ -72,12 +72,12 @@ internal static void SetHooks() { HookEndpointManager.Add( typeof(BlastAttack.BlastAttackDamageInfo).GetMethodCached(nameof(BlastAttack.BlastAttackDamageInfo.Read)), (BlastAttackDamageInfoReadDelegate)BlastAttackDamageInfoRead); - + IL.RoR2.OverlapAttack.ProcessHits += OverlapAttackProcessHitsIL; IL.RoR2.OverlapAttack.PerformDamage += OverlapAttackPerformDamageIL; On.RoR2.OverlapAttack.OverlapAttackMessage.Serialize += OverlapAttackMessageSerialize; On.RoR2.OverlapAttack.OverlapAttackMessage.Deserialize += OverlapAttackMessageDeserialize; - + IL.RoR2.GlobalEventManager.OnHitAll += GlobalEventManagerOnHitAllIL; IL.RoR2.HealthComponent.SendDamageDealt += HealthComponentSendDamageDealtIL; diff --git a/R2API/DotAPI.cs b/R2API/DotAPI.cs index 1b445773..04c8733e 100644 --- a/R2API/DotAPI.cs +++ b/R2API/DotAPI.cs @@ -1,10 +1,10 @@ -using System; -using System.Collections.Generic; -using System.Linq; using Mono.Cecil.Cil; using MonoMod.Cil; using R2API.Utils; using RoR2; +using System; +using System.Collections.Generic; +using System.Linq; using UnityEngine; using UnityEngine.Networking; @@ -96,11 +96,12 @@ public static DotController.DotIndex RegisterDotDef(DotController.DotDef? dotDef if (dotDef.associatedBuff != null) { R2API.Logger.LogInfo($"Custom Dot (Index: {dotDefIndex}) that uses Buff : {dotDef.associatedBuff.name} added"); - } else { + } + else { R2API.Logger.LogInfo($"Custom Dot (Index: {dotDefIndex}) with no associated Buff added"); } - - + + return (DotController.DotIndex)dotDefIndex; } diff --git a/R2API/EffectAPI.cs b/R2API/EffectAPI.cs index 5ff1eeb3..f57efcd3 100644 --- a/R2API/EffectAPI.cs +++ b/R2API/EffectAPI.cs @@ -1,8 +1,8 @@ +using R2API.ContentManagement; using R2API.Utils; using RoR2; -using RoR2.ContentManagement; using System; -using System.Collections.Generic; +using System.Reflection; using UnityEngine; // ReSharper disable MemberCanBePrivate.Global @@ -11,6 +11,7 @@ namespace R2API { [R2APISubmodule] + [Obsolete($"The {nameof(EffectAPI)} is obsolete, please add your Effects via R2API.ContentManagement.ContentAdditionHelpers.AddEffect()")] public static class EffectAPI { /// /// Return true if the submodule is loaded. @@ -24,26 +25,6 @@ public static bool Loaded { private static bool _loaded; - private static readonly List AddedEffects = new List(); - - [R2APISubmoduleInit(Stage = InitStage.SetHooks)] - internal static void SetHooks() { - R2APIContentPackProvider.WhenContentPackReady += AddAdditionalEntries; - } - - [R2APISubmoduleInit(Stage = InitStage.UnsetHooks)] - internal static void UnsetHooks() { - R2APIContentPackProvider.WhenContentPackReady -= AddAdditionalEntries; - } - - private static void AddAdditionalEntries(ContentPack r2apiContentPack) { - foreach (var customEffect in AddedEffects) { - R2API.Logger.LogInfo($"Custom Effect: {customEffect.prefabName} added"); - } - - r2apiContentPack.effectDefs.Add(AddedEffects.ToArray()); - } - /// /// Creates an EffectDef from a prefab and adds it to the EffectCatalog. /// The prefab must have an the following components: EffectComponent, VFXAttributes @@ -51,11 +32,18 @@ private static void AddAdditionalEntries(ContentPack r2apiContentPack) { /// /// The prefab of the effect to be added /// True if the effect was added + [Obsolete($"AddEffect is obsolete, please add your Effect Prefabs via R2API.ContentManagement.ContentAdditionHelpers.AddEffect()")] public static bool AddEffect(GameObject? effect) { if (!Loaded) { throw new InvalidOperationException($"{nameof(EffectAPI)} is not loaded. Please use [{nameof(R2APISubmoduleDependency)}(nameof({nameof(EffectAPI)})]"); } + if (!CatalogBlockers.GetAvailability()) { + R2API.Logger.LogError( + $"Too late ! Tried to add effect: {effect.name} after the EffectCatalog has initialized!"); + return false; + } + if (effect == null) { Debug.LogError("Effect prefab was null"); return false; @@ -81,7 +69,8 @@ public static bool AddEffect(GameObject? effect) { spawnSoundEventName = effectComp.soundName }; - return AddEffect(def); + R2APIContentManager.HandleContentAddition(Assembly.GetCallingAssembly(), effect); + return true; } /// @@ -89,16 +78,22 @@ public static bool AddEffect(GameObject? effect) { /// /// The EffectDef to addZ /// False if the EffectDef was null + [Obsolete($"This method is obsolete, please Add the EffectDef using R2API.ContentManagement.ContentAdditionHelpers.AddEffect()")] public static bool AddEffect(EffectDef? effect) { if (!Loaded) { throw new InvalidOperationException($"{nameof(EffectAPI)} is not loaded. Please use [{nameof(R2APISubmoduleDependency)}(nameof({nameof(EffectAPI)})]"); } + if (!CatalogBlockers.GetAvailability()) { + R2API.Logger.LogError( + $"Too late ! Tried to add effect: {effect.prefab} after the EffectCatalog has initialized!"); + return false; + } if (effect == null) { R2API.Logger.LogError("EffectDef was null."); return false; } - AddedEffects.Add(effect); + R2APIContentManager.HandleContentAddition(Assembly.GetCallingAssembly(), effect.prefab); return true; } } diff --git a/R2API/EliteAPI.cs b/R2API/EliteAPI.cs index b0cb757b..befe7152 100644 --- a/R2API/EliteAPI.cs +++ b/R2API/EliteAPI.cs @@ -1,11 +1,12 @@ using MonoMod.Cil; +using R2API.ContentManagement; using R2API.Utils; using RoR2; -using RoR2.ContentManagement; using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; +using System.Reflection; using UnityEngine; // ReSharper disable MemberCanBePrivate.Global @@ -18,8 +19,6 @@ namespace R2API { public static class EliteAPI { public static ObservableCollection? EliteDefinitions = new ObservableCollection(); - private static bool _eliteCatalogInitialized; - /// /// Return true if the submodule is loaded. /// @@ -37,7 +36,7 @@ internal static void SetHooks() { IL.RoR2.CombatDirector.Init += RetrieveVanillaEliteTierCount; On.RoR2.CombatDirector.Init += AddCustomEliteTiers; - R2APIContentPackProvider.WhenContentPackReady += AddElitesToGame; + R2APIContentPackProvider.WhenAddingContentPacks += AddElitesToGame; } [R2APISubmoduleInit(Stage = InitStage.UnsetHooks)] @@ -45,7 +44,7 @@ internal static void UnsetHooks() { IL.RoR2.CombatDirector.Init -= RetrieveVanillaEliteTierCount; On.RoR2.CombatDirector.Init -= AddCustomEliteTiers; - R2APIContentPackProvider.WhenContentPackReady -= AddElitesToGame; + R2APIContentPackProvider.WhenAddingContentPacks -= AddElitesToGame; } [R2APISubmoduleInit(Stage = InitStage.Load)] @@ -64,13 +63,10 @@ private static void LazyInitVanillaEliteTiers() { CombatDirector.Init(); } - private static void AddElitesToGame(ContentPack r2apiContentPack) { - var eliteDefs = new List(); - + private static void AddElitesToGame() { LazyInitVanillaEliteTiers(); foreach (var customElite in EliteDefinitions) { - eliteDefs.Add(customElite.EliteDef); var currentEliteTiers = GetCombatDirectorEliteTiers(); @@ -93,12 +89,7 @@ private static void AddElitesToGame(ContentPack r2apiContentPack) { } OverrideCombatDirectorEliteTiers(currentEliteTiers); - - R2API.Logger.LogInfo($"Custom Elite: {customElite.EliteDef.modifierToken} added"); } - - r2apiContentPack.eliteDefs.Add(eliteDefs.ToArray()); - _eliteCatalogInitialized = true; } private static void RetrieveVanillaEliteTierCount(ILContext il) { @@ -129,8 +120,8 @@ public static bool Add(CustomElite? elite) { throw new InvalidOperationException($"{nameof(EliteAPI)} is not loaded. Please use [{nameof(R2APISubmoduleDependency)}(nameof({nameof(EliteAPI)})]"); } - if (_eliteCatalogInitialized) { - R2API.Logger.LogError($"Too late ! Tried to add elite: {elite.EliteDef.modifierToken} after the elite list was created"); + if (!CatalogBlockers.GetAvailability()) { + R2API.Logger.LogError($"Too late ! Tried to add elite: {elite.EliteDef.modifierToken} after the EliteCatalog has initialized!"); return false; } @@ -139,6 +130,7 @@ public static bool Add(CustomElite? elite) { } + R2APIContentManager.HandleContentAddition(Assembly.GetCallingAssembly(), elite.EliteDef); EliteDefinitions.Add(elite); return true; } @@ -244,7 +236,7 @@ public static int AddCustomEliteTier(CombatDirector.EliteTierDef? eliteTierDef, Array.Copy(currentEliteTiers, indexToInsertAt, currentEliteTiers, indexToInsertAt + 1, currentEliteTiers.Length - indexToInsertAt - 1); } currentEliteTiers[indexToInsertAt] = eliteTierDef; - + OverrideCombatDirectorEliteTiers(currentEliteTiers); } diff --git a/R2API/ItemAPI.cs b/R2API/ItemAPI.cs index 3be93809..b7ad2c08 100644 --- a/R2API/ItemAPI.cs +++ b/R2API/ItemAPI.cs @@ -1,12 +1,13 @@ using Mono.Cecil.Cil; using MonoMod.Cil; +using R2API.ContentManagement; using R2API.Utils; using RoR2; -using RoR2.ContentManagement; using System; using System.Collections.Generic; using System.Collections.ObjectModel; -using System.Linq; +using System.Linq; +using System.Reflection; using System.Xml.Linq; using UnityEngine; using Object = UnityEngine.Object; @@ -22,9 +23,6 @@ public static class ItemAPI { public static ObservableCollection? ItemDefinitions = new ObservableCollection(); public static ObservableCollection EquipmentDefinitions = new ObservableCollection(); - private static bool _itemCatalogInitialized; - private static bool _equipmentCatalogInitialized; - private static ICollection noDefaultIDRSCharacterList = new List(); public static int CustomItemCount, CustomEquipmentCount; @@ -43,44 +41,18 @@ public static bool Loaded { [R2APISubmoduleInit(Stage = InitStage.SetHooks)] internal static void SetHooks() { - R2APIContentPackProvider.WhenContentPackReady += AddItemsToGame; - + R2APIContentPackProvider.WhenAddingContentPacks += LoadRelatedAPIs; IL.RoR2.CharacterModel.UpdateMaterials += MaterialFixForItemDisplayOnCharacter; On.RoR2.ItemDisplayRuleSet.Init += AddingItemDisplayRulesToCharacterModels; - } - + } + [R2APISubmoduleInit(Stage = InitStage.UnsetHooks)] internal static void UnsetHooks() { - R2APIContentPackProvider.WhenContentPackReady -= AddItemsToGame; - + R2APIContentPackProvider.WhenAddingContentPacks -= LoadRelatedAPIs; IL.RoR2.CharacterModel.UpdateMaterials -= MaterialFixForItemDisplayOnCharacter; On.RoR2.ItemDisplayRuleSet.Init -= AddingItemDisplayRulesToCharacterModels; } - private static void AddItemsToGame(ContentPack r2apiContentPack) { - var itemDefs = new List(); - foreach (var customItem in ItemDefinitions) { - itemDefs.Add(customItem.ItemDef); - - R2API.Logger.LogInfo($"Custom Item: {customItem.ItemDef.name} added"); - } - - r2apiContentPack.itemDefs.Add(itemDefs.ToArray()); - _itemCatalogInitialized = true; - - var equipmentDefs = new List(); - foreach (var customEquipment in EquipmentDefinitions) { - equipmentDefs.Add(customEquipment.EquipmentDef); - - R2API.Logger.LogInfo($"Custom Equipment: {customEquipment.EquipmentDef.name} added"); - } - - LoadRelatedAPIs(); - - r2apiContentPack.equipmentDefs.Add(equipmentDefs.ToArray()); - _equipmentCatalogInitialized = true; - } - private static void LoadRelatedAPIs() { if (!ItemDropAPI.Loaded) { try { @@ -111,8 +83,8 @@ public static bool Add(CustomItem? item) { throw new InvalidOperationException($"{nameof(ItemAPI)} is not loaded. Please use [{nameof(R2APISubmoduleDependency)}(nameof({nameof(ItemAPI)})]"); } - if (_itemCatalogInitialized) { - R2API.Logger.LogError($"Too late ! Tried to add item: {item.ItemDef.nameToken} after the item list was created"); + if (!CatalogBlockers.GetAvailability()) { + R2API.Logger.LogError($"Too late ! Tried to add item: {item.ItemDef.nameToken} after the ItemCatalog has Initialized!"); } if (!item.ItemDef) { @@ -121,17 +93,17 @@ public static bool Add(CustomItem? item) { if (string.IsNullOrEmpty(item.ItemDef.name)) { R2API.Logger.LogError("ItemDef.name is null or empty ! Can't add the custom item."); - } - - if (!item.ItemDef.pickupModelPrefab) { + } + + if (!item.ItemDef.pickupModelPrefab) { R2API.Logger.LogWarning($"No ItemDef.pickupModelPrefab ({item.ItemDef.name}), the game will show nothing when the item is on the ground."); - } - else if (item.ItemDisplayRules != null && - item.ItemDisplayRules.Dictionary.Values.Any(rules => rules.Any(rule => rule.ruleType == ItemDisplayRuleType.ParentedPrefab)) && - !item.ItemDef.pickupModelPrefab.GetComponent()) { + } + else if (item.ItemDisplayRules != null && + item.ItemDisplayRules.Dictionary.Values.Any(rules => rules.Any(rule => rule.ruleType == ItemDisplayRuleType.ParentedPrefab)) && + !item.ItemDef.pickupModelPrefab.GetComponent()) { R2API.Logger.LogWarning($"ItemDef.pickupModelPrefab ({item.ItemDef.name}) does not have an ItemDisplay component attached to it " + - "(there are ItemDisplayRuleType.ParentedPrefab rules), " + - "the pickup model should have one and have atleast a rendererInfo in it for having correct visibility levels."); + "(there are ItemDisplayRuleType.ParentedPrefab rules), " + + "the pickup model should have one and have atleast a rendererInfo in it for having correct visibility levels."); } bool xmlSafe = false; @@ -143,7 +115,7 @@ public static bool Add(CustomItem? item) { R2API.Logger.LogError($"Custom item '{item.ItemDef.name}' is not XMLsafe. Item not added."); } if (xmlSafe) { - ItemDefinitions.Add(item); + R2APIContentManager.HandleContentAddition(Assembly.GetCallingAssembly(), item.ItemDef); return true; } @@ -163,8 +135,8 @@ public static bool Add(CustomEquipment? item) { throw new InvalidOperationException($"{nameof(ItemAPI)} is not loaded. Please use [{nameof(R2APISubmoduleDependency)}(nameof({nameof(ItemAPI)})]"); } - if (_equipmentCatalogInitialized) { - R2API.Logger.LogError($"Too late ! Tried to add equipment item: {item.EquipmentDef.nameToken} after the equipment list was created"); + if (CatalogBlockers.GetAvailability()) { + R2API.Logger.LogError($"Too late ! Tried to add equipment item: {item.EquipmentDef.nameToken} after the EquipmentCatalog has initialized!"); } if (item.EquipmentDef == null) { @@ -173,19 +145,19 @@ public static bool Add(CustomEquipment? item) { if (string.IsNullOrEmpty(item.EquipmentDef.name)) { R2API.Logger.LogError("EquipmentDef.name is null or empty ! Can't add the custom Equipment."); - } - - if (!item.EquipmentDef.pickupModelPrefab) { + } + + if (!item.EquipmentDef.pickupModelPrefab) { R2API.Logger.LogWarning($"No EquipmentDef.pickupModelPrefab ({item.EquipmentDef.name}), the game will show nothing when the item is on the ground."); - } - else if (item.ItemDisplayRules != null && - item.ItemDisplayRules.Dictionary.Values.Any(rules => rules.Any(rule => rule.ruleType == ItemDisplayRuleType.ParentedPrefab)) && - !item.EquipmentDef.pickupModelPrefab.GetComponent()) { + } + else if (item.ItemDisplayRules != null && + item.ItemDisplayRules.Dictionary.Values.Any(rules => rules.Any(rule => rule.ruleType == ItemDisplayRuleType.ParentedPrefab)) && + !item.EquipmentDef.pickupModelPrefab.GetComponent()) { R2API.Logger.LogWarning($"EquipmentDef.pickupModelPrefab ({item.EquipmentDef.name}) does not have an ItemDisplay component attached to it " + - "(there are ItemDisplayRuleType.ParentedPrefab rules), " + - "the pickup model should have one and have atleast a rendererInfo in it for having correct visibility levels."); - } - + "(there are ItemDisplayRuleType.ParentedPrefab rules), " + + "the pickup model should have one and have atleast a rendererInfo in it for having correct visibility levels."); + } + bool xmlSafe = false; try { XElement element = new(item.EquipmentDef.name); @@ -195,95 +167,95 @@ public static bool Add(CustomEquipment? item) { R2API.Logger.LogError($"Custom equipment '{item.EquipmentDef.name}' is not XMLsafe. Item not added."); } if (xmlSafe) { - EquipmentDefinitions.Add(item); + R2APIContentManager.HandleContentAddition(Assembly.GetCallingAssembly(), item.EquipmentDef); return true; } return false; - } - - #endregion Add Methods - - #region Other Modded Content Support - /// - /// Prevents bodies and charactermodels matching this name from having nonspecific item display rules applied to them - /// - /// The string to match - public static void DoNotAutoIDRSFor(string bodyPrefabOrCharacterModelName) { - noDefaultIDRSCharacterList.Add(bodyPrefabOrCharacterModelName); - } - - /// - /// Prevent prefabs with this name having nonspecific item display rules applied to them - /// - /// The body prefab to match - public static void DoNotAutoIDRSFor(GameObject bodyPrefab) { - var characterModel = bodyPrefab.GetComponentInChildren(); - if (characterModel) { - DoNotAutoIDRSFor(bodyPrefab.name); - } - } + } + + #endregion Add Methods + + #region Other Modded Content Support + /// + /// Prevents bodies and charactermodels matching this name from having nonspecific item display rules applied to them + /// + /// The string to match + public static void DoNotAutoIDRSFor(string bodyPrefabOrCharacterModelName) { + noDefaultIDRSCharacterList.Add(bodyPrefabOrCharacterModelName); + } + + /// + /// Prevent prefabs with this name having nonspecific item display rules applied to them + /// + /// The body prefab to match + public static void DoNotAutoIDRSFor(GameObject bodyPrefab) { + var characterModel = bodyPrefab.GetComponentInChildren(); + if (characterModel) { + DoNotAutoIDRSFor(bodyPrefab.name); + } + } #endregion - + #region ItemDisplay Hooks - - // With how unfriendly it is to makes your 3D Prefab work with shaders from the game, - // makes it so that if the custom prefab doesnt have rendering support for when the player is cloaked, or burning, still display the item on the player. - // iDeath : This hook was made back when I didn't know that pickupModelPrefab - // just needed an ItemDisplay component attached to it for the game method to not complain + + // With how unfriendly it is to makes your 3D Prefab work with shaders from the game, + // makes it so that if the custom prefab doesnt have rendering support for when the player is cloaked, or burning, still display the item on the player. + // iDeath : This hook was made back when I didn't know that pickupModelPrefab + // just needed an ItemDisplay component attached to it for the game method to not complain private static void MaterialFixForItemDisplayOnCharacter(ILContext il) { var cursor = new ILCursor(il); var forCounterLoc = 0; - var itemDisplayLoc = 0; - - try { + var itemDisplayLoc = 0; + + try { cursor.GotoNext( i => i.MatchLdarg(0), - i => i.MatchLdfld("RoR2.CharacterModel", "parentedPrefabDisplays"), + i => i.MatchLdfld("RoR2.CharacterModel", "parentedPrefabDisplays"), i => i.MatchLdloc(out forCounterLoc) - ); - - cursor.GotoNext( - i => i.MatchCallOrCallvirt("RoR2.CharacterModel/ParentedPrefabDisplay", "get_itemDisplay"), - i => i.MatchStloc(out itemDisplayLoc) - ); - cursor.Index += 2; - - cursor.Emit(OpCodes.Ldloc, itemDisplayLoc); - cursor.Emit(OpCodes.Call, typeof(Object).GetMethodCached("op_Implicit")); - cursor.Emit(OpCodes.Brfalse, cursor.MarkLabel()); - var brFalsePos = cursor.Index - 1; - - cursor.GotoNext( - i => i.MatchLdloc(forCounterLoc) - ); - var label = cursor.MarkLabel(); - - cursor.Index = brFalsePos; - cursor.Next.Operand = label; - } - catch (Exception e) { - R2API.Logger.LogError($"Exception in {nameof(MaterialFixForItemDisplayOnCharacter)} : Item mods without the {nameof(ItemDisplay)} component may not work correctly.\n{e}"); + ); + + cursor.GotoNext( + i => i.MatchCallOrCallvirt("RoR2.CharacterModel/ParentedPrefabDisplay", "get_itemDisplay"), + i => i.MatchStloc(out itemDisplayLoc) + ); + cursor.Index += 2; + + cursor.Emit(OpCodes.Ldloc, itemDisplayLoc); + cursor.Emit(OpCodes.Call, typeof(Object).GetMethodCached("op_Implicit")); + cursor.Emit(OpCodes.Brfalse, cursor.MarkLabel()); + var brFalsePos = cursor.Index - 1; + + cursor.GotoNext( + i => i.MatchLdloc(forCounterLoc) + ); + var label = cursor.MarkLabel(); + + cursor.Index = brFalsePos; + cursor.Next.Operand = label; } - } - - // todo : allow override of existing item display rules - // This method only allow the addition of custom rules. - // - private static void AddingItemDisplayRulesToCharacterModels(On.RoR2.ItemDisplayRuleSet.orig_Init orig) { - orig(); - - foreach (var bodyPrefab in BodyCatalog.allBodyPrefabs) { + catch (Exception e) { + R2API.Logger.LogError($"Exception in {nameof(MaterialFixForItemDisplayOnCharacter)} : Item mods without the {nameof(ItemDisplay)} component may not work correctly.\n{e}"); + } + } + + // todo : allow override of existing item display rules + // This method only allow the addition of custom rules. + // + private static void AddingItemDisplayRulesToCharacterModels(On.RoR2.ItemDisplayRuleSet.orig_Init orig) { + orig(); + + foreach (var bodyPrefab in BodyCatalog.allBodyPrefabs) { var characterModel = bodyPrefab.GetComponentInChildren(); if (characterModel) { if (!characterModel.itemDisplayRuleSet) { characterModel.itemDisplayRuleSet = ScriptableObject.CreateInstance(); - } - var modelName = characterModel.name; - var bodyName = bodyPrefab.name; - bool allowDefault = true; - if (noDefaultIDRSCharacterList.Contains(modelName) || noDefaultIDRSCharacterList.Contains(bodyName)) { - allowDefault = false; + } + var modelName = characterModel.name; + var bodyName = bodyPrefab.name; + bool allowDefault = true; + if (noDefaultIDRSCharacterList.Contains(modelName) || noDefaultIDRSCharacterList.Contains(bodyName)) { + allowDefault = false; } foreach (var customItem in ItemDefinitions) { @@ -291,10 +263,10 @@ private static void AddingItemDisplayRulesToCharacterModels(On.RoR2.ItemDisplayR if (customRules != null) { //if a specific rule for this model exists, or the model has no rules for this item if (customRules.TryGetRules(modelName, out ItemDisplayRule[]? rules) || - customRules.TryGetRules(bodyName, out rules) || - ( - allowDefault && - characterModel.itemDisplayRuleSet.GetItemDisplayRuleGroup(customItem.ItemDef.itemIndex).rules == null + customRules.TryGetRules(bodyName, out rules) || + ( + allowDefault && + characterModel.itemDisplayRuleSet.GetItemDisplayRuleGroup(customItem.ItemDef.itemIndex).rules == null )) { characterModel.itemDisplayRuleSet.SetDisplayRuleGroup(customItem.ItemDef, new DisplayRuleGroup { rules = rules }); } @@ -307,9 +279,9 @@ private static void AddingItemDisplayRulesToCharacterModels(On.RoR2.ItemDisplayR //if a specific rule for this model exists, or the model has no rules for this equipment if (customRules.TryGetRules(modelName, out ItemDisplayRule[]? rules) || customRules.TryGetRules(bodyName, out rules) || - ( - allowDefault && - characterModel.itemDisplayRuleSet.GetEquipmentDisplayRuleGroup(customEquipment.EquipmentDef.equipmentIndex).rules == null + ( + allowDefault && + characterModel.itemDisplayRuleSet.GetEquipmentDisplayRuleGroup(customEquipment.EquipmentDef.equipmentIndex).rules == null )) { characterModel.itemDisplayRuleSet.SetDisplayRuleGroup(customEquipment.EquipmentDef, new DisplayRuleGroup { rules = rules }); } diff --git a/R2API/ItemDrop/Catalog.cs b/R2API/ItemDrop/Catalog.cs index ea07c78e..5a5c6986 100644 --- a/R2API/ItemDrop/Catalog.cs +++ b/R2API/ItemDrop/Catalog.cs @@ -1,6 +1,5 @@ using RoR2; using System.Collections.Generic; -using UnityEngine; namespace R2API { @@ -36,7 +35,8 @@ public static void PopulateCatalog() { if (!ScrapItems.ContainsKey(itemDef.tier)) { ScrapItems.Add(itemDef.tier, itemIndex); } - } else if (itemDef.ContainsTag(ItemTag.WorldUnique)) { + } + else if (itemDef.ContainsTag(ItemTag.WorldUnique)) { SpecialItems.Add(itemIndex); } } diff --git a/R2API/ItemDrop/DropList.cs b/R2API/ItemDrop/DropList.cs index 0ca630c0..920af793 100644 --- a/R2API/ItemDrop/DropList.cs +++ b/R2API/ItemDrop/DropList.cs @@ -1,8 +1,6 @@ -using R2API.ItemDrop; -using RoR2; +using RoR2; using System.Collections.Generic; using System.Linq; -using UnityEngine; namespace R2API { @@ -66,15 +64,20 @@ The original lists are saved because both the player drops api and monster drop public List GetDropList(ItemTier itemTier) { if (itemTier == ItemTier.Tier1) { return AvailableTier1DropList; - } else if (itemTier == ItemTier.Tier2) { + } + else if (itemTier == ItemTier.Tier2) { return AvailableTier2DropList; - } else if (itemTier == ItemTier.Tier3) { + } + else if (itemTier == ItemTier.Tier3) { return AvailableTier3DropList; - } else if (itemTier == ItemTier.Boss) { + } + else if (itemTier == ItemTier.Boss) { return AvailableBossDropList; - } else if (itemTier == ItemTier.Lunar) { + } + else if (itemTier == ItemTier.Lunar) { return AvailableLunarDropList; - } else { + } + else { return AvailableNormalEquipmentDropList; } } @@ -138,7 +141,7 @@ public void DuplicateDropLists(Run run) { SpecialItemsOriginal.Add(PickupCatalog.FindPickupIndex(itemIndex)); } } - + SpecialEquipmentOriginal.Clear(); foreach (var equipmentIndex in Catalog.EliteEquipment) { var equipmentDef = EquipmentCatalog.GetEquipmentDef(equipmentIndex); @@ -181,29 +184,38 @@ internal void GenerateDropLists( if (!special) { if (itemDef.tier == ItemTier.Tier1) { dropList = AvailableTier1DropList; - } else if (itemDef.tier == ItemTier.Tier2) { + } + else if (itemDef.tier == ItemTier.Tier2) { dropList = AvailableTier2DropList; - } else if (itemDef.tier == ItemTier.Tier3) { + } + else if (itemDef.tier == ItemTier.Tier3) { dropList = AvailableTier3DropList; - } else if (itemDef.tier == ItemTier.Boss) { + } + else if (itemDef.tier == ItemTier.Boss) { dropList = AvailableBossDropList; - } else if (itemDef.tier == ItemTier.Lunar) { + } + else if (itemDef.tier == ItemTier.Lunar) { dropList = AvailableLunarDropList; } - } else { + } + else { dropList = AvailableSpecialItems; } - } else if (equipmentIndex != EquipmentIndex.None) { + } + else if (equipmentIndex != EquipmentIndex.None) { EquipmentDef equipmentDef = EquipmentCatalog.GetEquipmentDef(equipmentIndex); if (!special) { if (equipmentDef.isLunar) { dropList = AvailableLunarDropList; - } else if (equipmentDef.isBoss) { + } + else if (equipmentDef.isBoss) { dropList = AvailableBossDropList; - } else { + } + else { dropList = AvailableEquipmentDropList; } - } else { + } + else { dropList = AvailableSpecialEquipment; } } @@ -212,7 +224,8 @@ internal void GenerateDropLists( if (dropList.Contains(pickupIndex) == false) { dropList.Add(pickupIndex); } - } else { + } + else { if (dropList.Contains(pickupIndex)) { dropList.Remove(pickupIndex); } @@ -227,7 +240,7 @@ internal void GenerateDropLists( } ListsGenerated.Invoke(); } - + // Sets the drop lists in Run using the adjusted, master lists. public void SetItems(Run run) { foreach (var pickupIndex in AvailableTier1DropList) { @@ -248,7 +261,8 @@ public void SetItems(Run run) { EquipmentIndex equipmentIndex = PickupCatalog.GetPickupDef(pickupIndex).equipmentIndex; if (itemIndex != ItemIndex.None) { run.availableItems.Add(itemIndex); - } else if (equipmentIndex != EquipmentIndex.None) { + } + else if (equipmentIndex != EquipmentIndex.None) { run.availableEquipment.Add(equipmentIndex); } } @@ -258,7 +272,8 @@ public void SetItems(Run run) { EquipmentIndex equipmentIndex = PickupCatalog.GetPickupDef(pickupIndex).equipmentIndex; if (itemIndex != ItemIndex.None) { run.availableItems.Add(itemIndex); - } else if (equipmentIndex != EquipmentIndex.None) { + } + else if (equipmentIndex != EquipmentIndex.None) { run.availableEquipment.Add(equipmentIndex); } } diff --git a/R2API/ItemDrop/DropOdds.cs b/R2API/ItemDrop/DropOdds.cs index a1ad0fad..14023264 100644 --- a/R2API/ItemDrop/DropOdds.cs +++ b/R2API/ItemDrop/DropOdds.cs @@ -56,7 +56,7 @@ public static void UpdateChestTierOdds(SpawnCard spawnCard, string interactableN chestBehavior.tier2Chance = ChestTierOdds[interactableName][1]; chestBehavior.tier3Chance = ChestTierOdds[interactableName][2]; } - + if (ItemDropAPI.PlayerInteractables.SubsetTiersPresent.ContainsKey(interactableName)) { // This is for damage, healing and utility chests if (!ItemDropAPI.PlayerInteractables.SubsetTiersPresent[interactableName][InteractableCalculator.DropType.tier1]) { diff --git a/R2API/ItemDrop/InteractableCalculator.cs b/R2API/ItemDrop/InteractableCalculator.cs index d2ab3e80..3a8f8c29 100644 --- a/R2API/ItemDrop/InteractableCalculator.cs +++ b/R2API/ItemDrop/InteractableCalculator.cs @@ -271,7 +271,7 @@ public void CalculateInvalidInteractables(DropList dropList) { } } } - + /* Updates the interactable types with which subset lists are populated. diff --git a/R2API/ItemDropAPI.cs b/R2API/ItemDropAPI.cs index 3ed7fb37..1c671087 100644 --- a/R2API/ItemDropAPI.cs +++ b/R2API/ItemDropAPI.cs @@ -1,18 +1,12 @@ -using BepInEx.Logging; -using MonoMod.RuntimeDetour; -using Mono.Cecil.Cil; -using MonoMod.Cil; +using MonoMod.RuntimeDetour; using R2API.ItemDrop; using R2API.ItemDropAPITools; -using R2API.MiscHelpers; using R2API.Utils; using RoR2; using System; using System.Collections.Generic; -using System.Linq; using System.Reflection; using UnityEngine; -using UnityEngine.Networking; namespace R2API { @@ -203,7 +197,8 @@ private static void PopulateScene(On.RoR2.SceneDirector.orig_PopulateScene orig, //if (new List().Contains(interactableName)) { //} else if (PlayerInteractables.InvalidInteractables.Contains(interactableName)) { - } else { + } + else { DropOdds.UpdateChestTierOdds(directorCard.spawnCard, interactableName); DropOdds.UpdateDropTableTierOdds(directorCard.spawnCard, interactableName); DropOdds.UpdateDropTableItemOdds(PlayerDropList, directorCard.spawnCard, interactableName); @@ -245,7 +240,8 @@ private static void GenerateNewPickupServer(On.RoR2.ShopTerminalBehavior.orig_Ge } if (PlayerInteractables.TiersPresent[dropType] || shopTerminalBehavior.dropTable != null) { orig(shopTerminalBehavior); - } else { + } + else { shopTerminalBehavior.SetNoPickup(); var purchaseInteraction = shopTerminalBehavior.GetComponent(); if (purchaseInteraction != null) { @@ -289,7 +285,8 @@ private static void DropRewards(On.RoR2.BossGroup.orig_DropRewards orig, BossGro var bossDropChanceOld = bossGroup.bossDropChance; if (!normalListValid) { bossGroup.bossDropChance = 1; - } else if (bossDropsAdjusted.Count == 0) { + } + else if (bossDropsAdjusted.Count == 0) { bossGroup.bossDropChance = 0; } @@ -310,7 +307,8 @@ private static void SetOptionsServer(On.RoR2.PickupPickerController.orig_SetOpti optionsAdjusted.Add(option); } } - } else if (pickupPickerController.contextString.Contains(CommandCubeContextString)) { + } + else if (pickupPickerController.contextString.Contains(CommandCubeContextString)) { foreach (var option in options) { optionsAdjusted.Add(option); } @@ -387,7 +385,8 @@ private static void EndRound(On.RoR2.ArenaMissionController.orig_EndRound orig, arenaMissionController.rewardSpawnPosition = null; orig(arenaMissionController); arenaMissionController.rewardSpawnPosition = rewardSpawnPositionOld; - } else { + } + else { orig(arenaMissionController); } } diff --git a/R2API/LoadoutAPI.cs b/R2API/LoadoutAPI.cs index d07ff8a3..67d5baeb 100644 --- a/R2API/LoadoutAPI.cs +++ b/R2API/LoadoutAPI.cs @@ -1,11 +1,11 @@ using EntityStates; +using R2API.ContentManagement; using R2API.Utils; using RoR2; -using RoR2.ContentManagement; using RoR2.Skills; using System; using System.Collections.Generic; -using System.Linq; +using System.Reflection; using Unity.Collections; using Unity.Jobs; using UnityEngine; @@ -29,33 +29,8 @@ public static bool Loaded { private static bool _loaded; - #region Submodule Hooks - - private static readonly HashSet AddedStateTypes = new HashSet(); - - private static readonly HashSet AddedSkills = new HashSet(); - private static readonly HashSet AddedSkillFamilies = new HashSet(); private static readonly HashSet AddedSkins = new HashSet(); - [R2APISubmoduleInit(Stage = InitStage.SetHooks)] - internal static void SetHooks() { - R2APIContentPackProvider.WhenContentPackReady += AddSkillsToGame; - } - - [R2APISubmoduleInit(Stage = InitStage.UnsetHooks)] - internal static void UnsetHooks() { - R2APIContentPackProvider.WhenContentPackReady -= AddSkillsToGame; - } - - private static void AddSkillsToGame(ContentPack r2apiContentPack) { - r2apiContentPack.entityStateTypes.Add(AddedStateTypes.ToArray()); - - r2apiContentPack.skillDefs.Add(AddedSkills.ToArray()); - r2apiContentPack.skillFamilies.Add(AddedSkillFamilies.ToArray()); - } - - #endregion Submodule Hooks - #region Adding Skills /// @@ -65,16 +40,21 @@ private static void AddSkillsToGame(ContentPack r2apiContentPack) { /// /// The type to add /// True if succesfully added + [Obsolete($"AddSkill is obsolete, please add your SkillTypes via R2API.ContentManagement.ContentAdditionHelpers.AddEntityState()")] public static bool AddSkill(Type? t) { if (!Loaded) { throw new InvalidOperationException($"{nameof(LoadoutAPI)} is not loaded. Please use [{nameof(R2APISubmoduleDependency)}(nameof({nameof(LoadoutAPI)})]"); } + if (CatalogBlockers.GetAvailability()) { + R2API.Logger.LogError($"Too late ! Tried to add skill type {t} after the EntityStateCatalog has initialized!"); + return false; + } if (t == null || !t.IsSubclassOf(typeof(EntityState)) || t.IsAbstract) { R2API.Logger.LogError("Invalid skill type."); return false; } - AddedStateTypes.Add(t); + R2APIContentManager.HandleEntityState(Assembly.GetCallingAssembly(), t); return true; } @@ -84,12 +64,17 @@ public static bool AddSkill(Type? t) { /// /// The state type /// The created SerializableEntityStateType + [Obsolete($"StateTypeOf is obsolete, please add your SkillTypes via R2API.ContentManagement.ContentAdditionHelpers.AddEntityState()")] public static SerializableEntityStateType StateTypeOf() where T : EntityState, new() { if (!Loaded) { throw new InvalidOperationException($"{nameof(LoadoutAPI)} is not loaded. Please use [{nameof(R2APISubmoduleDependency)}(nameof({nameof(LoadoutAPI)})]"); } - LoadoutAPI.AddSkill(typeof(T)); + if (CatalogBlockers.GetAvailability()) { + R2API.Logger.LogError($"Too late ! Tried to add skill type {typeof(T)} after the EntityStateCatalog has initialized!"); + return new SerializableEntityStateType(); + } + R2APIContentManager.HandleEntityState(Assembly.GetCallingAssembly(), typeof(T)); return new SerializableEntityStateType(typeof(T)); } @@ -99,15 +84,20 @@ public static SerializableEntityStateType StateTypeOf() /// /// The SkillDef to add /// True if the event was registered + [Obsolete($"AddSkillDef is obsolete, please add your SkillDefs via R2API.ContentManagement.ContentAdditionHelpers.AddSkillDef()")] public static bool AddSkillDef(SkillDef? s) { if (!Loaded) { throw new InvalidOperationException($"{nameof(LoadoutAPI)} is not loaded. Please use [{nameof(R2APISubmoduleDependency)}(nameof({nameof(LoadoutAPI)})]"); } + if (CatalogBlockers.GetAvailability()) { + R2API.Logger.LogError($"Too late ! Tried to add skillDef {s.skillName} after the SkillCatalog has initialized!"); + return false; + } if (!s) { R2API.Logger.LogError("Invalid SkillDef"); return false; } - AddedSkills.Add(s); + R2APIContentManager.HandleContentAddition(Assembly.GetCallingAssembly(), s); return true; } @@ -117,15 +107,19 @@ public static bool AddSkillDef(SkillDef? s) { /// /// The skillfamily to add /// True if the event was registered + [Obsolete($"AddSkillFamily is obsolete, please add your SkillFamilies via R2API.ContentManagement.ContentAdditionHelpers.AddSkillFamily()")] public static bool AddSkillFamily(SkillFamily? sf) { if (!Loaded) { throw new InvalidOperationException($"{nameof(LoadoutAPI)} is not loaded. Please use [{nameof(R2APISubmoduleDependency)}(nameof({nameof(LoadoutAPI)})]"); } + if (CatalogBlockers.GetAvailability()) { + R2API.Logger.LogError($"Too late ! Tried to add skillFamily after the SkillCatalog has initialized!"); + } if (!sf) { R2API.Logger.LogError("Invalid SkillFamily"); return false; } - AddedSkillFamilies.Add(sf); + R2APIContentManager.HandleContentAddition(Assembly.GetCallingAssembly(), sf); return true; } diff --git a/R2API/MiscHelpers/ILCursorExtensions.cs b/R2API/MiscHelpers/ILCursorExtensions.cs index baa5be39..704fc8c2 100644 --- a/R2API/MiscHelpers/ILCursorExtensions.cs +++ b/R2API/MiscHelpers/ILCursorExtensions.cs @@ -1,6 +1,5 @@ -using System; - -using MonoMod.Cil; +using MonoMod.Cil; +using System; namespace R2API.MiscHelpers { internal static class ILCursorExtensions { diff --git a/R2API/MonsterItemsAPI.cs b/R2API/MonsterItemsAPI.cs index 62674a13..254bfdae 100644 --- a/R2API/MonsterItemsAPI.cs +++ b/R2API/MonsterItemsAPI.cs @@ -1,13 +1,9 @@ using MonoMod.Cil; -using MonoMod.RuntimeDetour; -using R2API.ItemDrop; using R2API.ItemDropAPITools; using R2API.Utils; using RoR2; using RoR2.Artifacts; using System.Collections.Generic; -using System.Reflection; -using System.Linq; namespace R2API { @@ -202,7 +198,8 @@ This effects the artifact of evolution and the void fields. for (int listIndex = 0; listIndex < adjustedLists.Count; listIndex++) { if (scavDropList[listIndex].Count == 0) { tierValidScav.Add(false); - } else { + } + else { tierValidScav.Add(true); } } @@ -245,12 +242,15 @@ static public List> GetFilteredDropLists(List forbidd ItemTier itemTier = ItemCatalog.GetItemDef(itemIndex).tier; if (itemTier == ItemTier.Tier1) { adjustedDropLists[0].Add(pickupIndex); - } else if (itemTier == ItemTier.Tier2) { + } + else if (itemTier == ItemTier.Tier2) { adjustedDropLists[1].Add(pickupIndex); - } else if (itemTier == ItemTier.Tier3) { + } + else if (itemTier == ItemTier.Tier3) { adjustedDropLists[2].Add(pickupIndex); } - } else if (equipmentIndex != EquipmentIndex.None) { + } + else if (equipmentIndex != EquipmentIndex.None) { adjustedDropLists[3].Add(pickupIndex); } } diff --git a/R2API/PrefabAPI.cs b/R2API/PrefabAPI.cs index 4d278e7b..bdbb6bb1 100644 --- a/R2API/PrefabAPI.cs +++ b/R2API/PrefabAPI.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics; +using System.Linq; using System.Runtime.CompilerServices; using System.Security.Cryptography; using System.Text; @@ -34,6 +35,8 @@ public static bool Loaded { private static GameObject _parent; private static readonly List ThingsToHash = new List(); + internal static bool IsPrefabHashed(GameObject prefabToCheck) => ThingsToHash.Select(hash => hash.Prefab).Contains(prefabToCheck); + #pragma warning disable CS1573 // Parameter has no matching param tag in the XML comment (but other parameters do) /// diff --git a/R2API/ProjectileAPI.cs b/R2API/ProjectileAPI.cs index 87083710..83c5faca 100644 --- a/R2API/ProjectileAPI.cs +++ b/R2API/ProjectileAPI.cs @@ -1,7 +1,8 @@ -using R2API.Utils; -using RoR2.ContentManagement; +using R2API.ContentManagement; +using R2API.Utils; +using RoR2.Projectile; using System; -using System.Collections.Generic; +using System.Reflection; using UnityEngine; namespace R2API { @@ -10,12 +11,8 @@ namespace R2API { /// API for adding custom projectile to the game. /// [R2APISubmodule] + [Obsolete($"The {nameof(ProjectileAPI)} is obsolete, please add your Projectiles via R2API.ContentManagment.R2APIContentManager.AddContent()")] public static class ProjectileAPI { - - private static readonly List Projectiles = new List(); - - private static bool _projectileCatalogInitialized; - /// /// Return true if the submodule is loaded. /// @@ -26,29 +23,6 @@ public static bool Loaded { private static bool _loaded; - #region ModHelper Events and Hooks - - [R2APISubmoduleInit(Stage = InitStage.SetHooks)] - internal static void SetHooks() { - R2APIContentPackProvider.WhenContentPackReady += AddProjectilesToGame; - } - - [R2APISubmoduleInit(Stage = InitStage.UnsetHooks)] - internal static void UnsetHooks() { - R2APIContentPackProvider.WhenContentPackReady -= AddProjectilesToGame; - } - - private static void AddProjectilesToGame(ContentPack r2apiContentPack) { - foreach (var projectile in Projectiles) { - R2API.Logger.LogInfo($"Custom Projectile: {projectile.name} added"); - } - - r2apiContentPack.projectilePrefabs.Add(Projectiles.ToArray()); - _projectileCatalogInitialized = true; - } - - #endregion ModHelper Events and Hooks - #region Add Methods /// @@ -57,18 +31,19 @@ private static void AddProjectilesToGame(ContentPack r2apiContentPack) { /// /// The projectile prefab to add. /// true if added, false otherwise + [Obsolete($"Add is obsolete, please add your Projectiles via R2API.ContentManagement.ContentAdditionHelpers.AddProjectile()")] public static bool Add(GameObject? projectile) { if (!Loaded) { throw new InvalidOperationException($"{nameof(ProjectileAPI)} is not loaded. Please use [{nameof(R2APISubmoduleDependency)}(nameof({nameof(ProjectileAPI)})]"); } - if (_projectileCatalogInitialized) { + if (!CatalogBlockers.GetAvailability()) { R2API.Logger.LogError( $"Too late ! Tried to add projectile: {projectile.name} after the projectile list was created"); return false; } - Projectiles.Add(projectile); + R2APIContentManager.HandleContentAddition(Assembly.GetCallingAssembly(), projectile); return true; } diff --git a/R2API/R2API.cs b/R2API/R2API.cs index 85f70337..7dd83fe2 100644 --- a/R2API/R2API.cs +++ b/R2API/R2API.cs @@ -4,6 +4,7 @@ using MonoMod.Cil; using MonoMod.RuntimeDetour; using MonoMod.RuntimeDetour.HookGen; +using R2API.ContentManagement; using R2API.Utils; using RoR2; using System; @@ -54,6 +55,7 @@ public void Awake() { On.RoR2.UnitySystemConsoleRedirector.Redirect += orig => { }; LoadRoR2ContentEarly.Init(); + R2APIContentManager.Init(); var pluginScanner = new PluginScanner(); var submoduleHandler = new APISubmoduleHandler(GameBuild, Logger); diff --git a/R2API/R2APIContentPackProvider.cs b/R2API/R2APIContentPackProvider.cs deleted file mode 100644 index 5b87d7b9..00000000 --- a/R2API/R2APIContentPackProvider.cs +++ /dev/null @@ -1,49 +0,0 @@ -using RoR2.ContentManagement; -using System; -using System.Collections; - -namespace R2API { - public class R2APIContentPackProvider : IContentPackProvider { - public string identifier => "R2API"; - - internal static ContentPack ContentPack = new ContentPack(); - - internal static Action WhenContentPackReady; - - internal static void Init() { - ContentManager.collectContentPackProviders += AddCustomContent; - } - - private static void AddCustomContent(ContentManager.AddContentPackProviderDelegate addContentPackProvider) { - if (WhenContentPackReady != null) { - foreach (Action @event in WhenContentPackReady.GetInvocationList()) { - try { - @event(ContentPack); - } - catch (Exception e) { - R2API.Logger.LogError(e); - } - } - } - - addContentPackProvider(new R2APIContentPackProvider()); - } - - public IEnumerator LoadStaticContentAsync(LoadStaticContentAsyncArgs args) { - args.ReportProgress(1); - yield break; - } - - public IEnumerator GenerateContentPackAsync(GetContentPackAsyncArgs args) { - ContentPack.Copy(ContentPack, args.output); - - args.ReportProgress(1); - yield break; - } - - public IEnumerator FinalizeAsync(FinalizeAsyncArgs args) { - args.ReportProgress(1); - yield break; - } - } -} diff --git a/R2API/RecalculateStatsAPI.cs b/R2API/RecalculateStatsAPI.cs index bf88b6b3..13436434 100644 --- a/R2API/RecalculateStatsAPI.cs +++ b/R2API/RecalculateStatsAPI.cs @@ -84,8 +84,8 @@ public class StatHookEventArgs : EventArgs { /// Added to Curse Penalty.MAX_HEALTH ~ (BASE_HEALTH + baseHealthAdd) * (HEALTH_MULT + healthMultAdd) / (BASE_CURSE_PENALTY + baseCurseAdd) public float baseCurseAdd = 0f; - - /// Added to flat cooldown reduction. COOLDOWN ~ BASE_COOLDOWN * (BASE_COOLDOWN_MULT + cooldownMultAdd) - (BASE_FLAT_REDUCTION + cooldownReductionAdd) + + /// Added to flat cooldown reduction. COOLDOWN ~ BASE_COOLDOWN * (BASE_COOLDOWN_MULT + cooldownMultAdd) - (BASE_FLAT_REDUCTION + cooldownReductionAdd) public float cooldownReductionAdd = 0f; /// Added to the direct multiplier to cooldown timers. COOLDOWN ~ BASE_COOLDOWN * (BASE_COOLDOWN_MULT + cooldownMultAdd) - (BASE_FLAT_REDUCTION + cooldownReductionAdd) @@ -184,7 +184,7 @@ private static void GetStatMods(CharacterBody characterBody) { private static void ModifyCurseStat(ILCursor c) { c.Index = 0; - bool ILFound = c.TryGotoNext( MoveType.After, + bool ILFound = c.TryGotoNext(MoveType.After, x => x.MatchLdarg(0), x => x.MatchLdcR4(1), x => x.MatchCallOrCallvirt(typeof(CharacterBody).GetPropertySetter(nameof(CharacterBody.cursePenalty)) @@ -435,9 +435,9 @@ private static void ModifyShieldStat(ILCursor c) { ) && c.TryGotoNext( x => x.MatchStloc(out locBaseShieldIndex) ) && c.TryGotoNext( - x => x.MatchLdloc(locBaseShieldIndex), - x => x.MatchCallOrCallvirt(typeof(CharacterBody).GetPropertySetter(nameof(CharacterBody.maxShield))) - ); + x => x.MatchLdloc(locBaseShieldIndex), + x => x.MatchCallOrCallvirt(typeof(CharacterBody).GetPropertySetter(nameof(CharacterBody.maxShield))) + ); if (ILFound) { c.Index++; @@ -506,12 +506,12 @@ private static void ModifyMovementSpeedStat(ILCursor c) { x => x.MatchMul(), x => x.MatchStloc(locBaseSpeedIndex) ) && c.TryGotoNext(MoveType.After, - x => x.MatchLdloc(out _), - x => x.MatchLdloc(out _), - x => x.MatchOr(), - x => x.MatchLdloc(out _), - x => x.MatchOr() - ); + x => x.MatchLdloc(out _), + x => x.MatchLdloc(out _), + x => x.MatchOr(), + x => x.MatchLdloc(out _), + x => x.MatchOr() + ); if (ILFound) { c.EmitDelegate>(() => { diff --git a/R2API/ResourcesAPI.cs b/R2API/ResourcesAPI.cs index bc8d640c..9fb5877e 100644 --- a/R2API/ResourcesAPI.cs +++ b/R2API/ResourcesAPI.cs @@ -25,8 +25,8 @@ public static bool Loaded { internal set => _loaded = value; } - private static bool _loaded; - + private static bool _loaded; + private static readonly Dictionary Providers = new Dictionary(); private static NativeDetour ResourcesLoadDetour; @@ -51,21 +51,21 @@ public static bool Loaded { internal static void InitHooks() { ResourcesLoadDetour = new NativeDetour( typeof(Resources).GetMethod("Load", BindingFlags.Static | BindingFlags.Public, null, new[] { typeof(string), typeof(Type) }, null), - typeof(ResourcesAPI).GetMethod(nameof(OnResourcesLoad), BindingFlags.Static | BindingFlags.NonPublic) + typeof(ResourcesAPI).GetMethod(nameof(OnResourcesLoad), BindingFlags.Static | BindingFlags.NonPublic) ); _origLoad = ResourcesLoadDetour.GenerateTrampoline(); ResourcesLoadDetour.Apply(); ResourcesLoadAsyncDetour = new NativeDetour( typeof(Resources).GetMethod("LoadAsyncInternal", BindingFlags.Static | BindingFlags.NonPublic, null, new[] { typeof(string), typeof(Type) }, null), - typeof(ResourcesAPI).GetMethod(nameof(OnResourcesLoadAsync), BindingFlags.Static | BindingFlags.NonPublic) + typeof(ResourcesAPI).GetMethod(nameof(OnResourcesLoadAsync), BindingFlags.Static | BindingFlags.NonPublic) ); _origResourcesLoadAsync = ResourcesLoadAsyncDetour.GenerateTrampoline(); ResourcesLoadAsyncDetour.Apply(); ResourcesLoadAllDetour = new NativeDetour( typeof(Resources).GetMethod("LoadAll", BindingFlags.Static | BindingFlags.Public, null, new[] { typeof(string), typeof(Type) }, null), - typeof(ResourcesAPI).GetMethod(nameof(OnResourcesLoadAll), BindingFlags.Static | BindingFlags.NonPublic) + typeof(ResourcesAPI).GetMethod(nameof(OnResourcesLoadAll), BindingFlags.Static | BindingFlags.NonPublic) ); _origLoadAll = ResourcesLoadAllDetour.GenerateTrampoline(); ResourcesLoadAllDetour.Apply(); @@ -84,23 +84,23 @@ internal static void UnsetHooks() { /// More information on the R2API Wiki. /// /// assetbundle provider to give, usually made with an AssetBundleResourcesProvider(prefix, assetbundle) - public static void AddProvider(IResourceProvider? provider) { + public static void AddProvider(IResourceProvider? provider) { if (!Loaded) { throw new InvalidOperationException($"{nameof(ResourcesAPI)} is not loaded. Please use [{nameof(R2APISubmoduleDependency)}(nameof({nameof(ResourcesAPI)})]"); } if (provider == null) { throw new InvalidOperationException($"Given {nameof(IResourceProvider)} is null."); - } + } if (provider.ModPrefix == null) { throw new InvalidOperationException($"Given {nameof(IResourceProvider)}.{nameof(provider.ModPrefix)} is null."); - } + } + + if (!provider.ModPrefix.StartsWith("@")) { + throw new InvalidOperationException($"{nameof(IResourceProvider)}.{nameof(provider.ModPrefix)} value should start with '@' e.g. \"@ModPrefix\""); + } - if (!provider.ModPrefix.StartsWith("@")) { - throw new InvalidOperationException($"{nameof(IResourceProvider)}.{nameof(provider.ModPrefix)} value should start with '@' e.g. \"@ModPrefix\""); - } - Providers.Add(provider.ModPrefix, provider); } @@ -115,7 +115,7 @@ private static Object OnResourcesLoad(string? path, Type type) { private static Object ModResourcesLoad(string path, Type type) { var split = path.Split(':'); if (!Providers.TryGetValue(split[0], out var provider)) { - R2API.Logger.LogError($"Provider `{split[0]}` was not found"); + R2API.Logger.LogError($"Provider `{split[0]}` was not found"); return null; } @@ -151,11 +151,11 @@ private static Object[] OnResourcesLoadAll(string? path, Type type) { private static Object[] ModResourcesLoadAll(string path, Type type) { var split = path.Split(':'); if (!Providers.TryGetValue(split[0], out var provider)) { - R2API.Logger.LogError($"Provider `{split[0]}` was not found"); + R2API.Logger.LogError($"Provider `{split[0]}` was not found"); return Array.Empty(); } return provider.LoadAll(type); } } -} +} diff --git a/R2API/SoundAPI.cs b/R2API/SoundAPI.cs index 0a189716..cca52695 100644 --- a/R2API/SoundAPI.cs +++ b/R2API/SoundAPI.cs @@ -1,10 +1,10 @@ using BepInEx; using MonoMod.Cil; using MonoMod.RuntimeDetour; +using R2API.ContentManagement; using R2API.MiscHelpers; using R2API.Utils; using RoR2; -using RoR2.ContentManagement; using System; using System.Collections.Generic; using System.IO; @@ -35,8 +35,6 @@ public static bool Loaded { private static readonly List NetworkSoundEventDefs = new List(); - private static bool _NetworkSoundEventCatalogInitialized; - #region Soundbank Setup [R2APISubmoduleInit(Stage = InitStage.SetHooks)] @@ -281,44 +279,25 @@ internal AKRESULT UnLoad() { #endregion Soundbank Setup #region NetworkSoundEventCatalog Setup - - [R2APISubmoduleInit(Stage = InitStage.SetHooks)] - internal static void NetworkSetHooks() { - R2APIContentPackProvider.WhenContentPackReady += AddNetworkSoundEventDefsToGame; - } - - [R2APISubmoduleInit(Stage = InitStage.UnsetHooks)] - internal static void NetworkUnsetHooks() { - R2APIContentPackProvider.WhenContentPackReady -= AddNetworkSoundEventDefsToGame; - } - - private static void AddNetworkSoundEventDefsToGame(ContentPack r2apiContentPack) { - foreach (var networkSoundEventDef in NetworkSoundEventDefs) { - R2API.Logger.LogInfo($"Custom Network Sound Event: {networkSoundEventDef.eventName} added"); - } - - r2apiContentPack.networkSoundEventDefs.Add(NetworkSoundEventDefs.ToArray()); - _NetworkSoundEventCatalogInitialized = true; - } - /// /// Add a custom network sound event to the list of available network sound events. /// If this is called after the NetworkSoundEventCatalog inits then this will return false and ignore the custom network sound event. /// /// The network sound event def to add. /// true if added, false otherwise + [Obsolete($"AddNetworkedSoundEvent is obsolete, please add your NetworkSoundEventDefs via R2API.ContentManagement.ContentAdditionHelpers.AddNetworkSoundEventDef()")] public static bool AddNetworkedSoundEvent(NetworkSoundEventDef? networkSoundEventDef) { if (!Loaded) { throw new InvalidOperationException($"{nameof(SoundAPI)} is not loaded. " + $"Please use [{nameof(R2APISubmoduleDependency)}(nameof({nameof(SoundAPI)})]"); } - if (_NetworkSoundEventCatalogInitialized) { + if (!CatalogBlockers.GetAvailability()) { R2API.Logger.LogError( "Too late ! " + "Tried to add network sound event: " + $"{networkSoundEventDef.eventName} " + - "after the network sound event def list was created"); + "after the NetworkSoundEventCatalog has initalized!"); return false; } @@ -333,6 +312,7 @@ public static bool AddNetworkedSoundEvent(NetworkSoundEventDef? networkSoundEven } NetworkSoundEventDefs.Add(networkSoundEventDef); + R2APIContentManager.HandleContentAddition(Assembly.GetCallingAssembly(), networkSoundEventDef); return true; } @@ -342,15 +322,16 @@ public static bool AddNetworkedSoundEvent(NetworkSoundEventDef? networkSoundEven /// /// The name of the AKWwise Sound Event to add. /// true if added, false otherwise + [Obsolete($"AddNetworkedSoundEvent is obsolete, please add your NetworkSoundEventDefs via R2API.ContentManagement.ContentAdditionHelpers.AddNetworkSoundEventDef()")] public static bool AddNetworkedSoundEvent(string eventName) { if (!Loaded) { throw new InvalidOperationException($"{nameof(SoundAPI)} is not loaded. Please use [{nameof(R2APISubmoduleDependency)}(nameof({nameof(SoundAPI)})]"); } - if (_NetworkSoundEventCatalogInitialized) { + if (!CatalogBlockers.GetAvailability()) { R2API.Logger.LogError( $"Too late! Tried to add network sound event: {eventName} " + - "after the network sound event def list was created"); + "after the NetworkSoundEventCatalog has initalized!"); return false; } @@ -366,6 +347,7 @@ public static bool AddNetworkedSoundEvent(string eventName) { } NetworkSoundEventDefs.Add(networkSoundEventDef); + R2APIContentManager.HandleContentAddition(Assembly.GetCallingAssembly(), networkSoundEventDef); return true; } diff --git a/R2API/SurvivorAPI.cs b/R2API/SurvivorAPI.cs index 7d53dcc2..451591c2 100644 --- a/R2API/SurvivorAPI.cs +++ b/R2API/SurvivorAPI.cs @@ -1,18 +1,17 @@ -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Linq; using Mono.Cecil.Cil; using MonoMod.Cil; +using R2API.ContentManagement; using R2API.Utils; using RoR2; -using RoR2.ContentManagement; -using UnityEngine; +using System; +using System.Collections.ObjectModel; +using System.Reflection; namespace R2API { // ReSharper disable once InconsistentNaming [R2APISubmodule] + [Obsolete($"The {nameof(SurvivorAPI)} is obsolete, please add your SurvivorDefs, BodyPrefabs and MasterPrefabs via R2API.ContentManagment.R2APIContentManager.AddContent()")] public static class SurvivorAPI { /// @@ -24,10 +23,7 @@ public static bool Loaded { } private static bool _loaded; - - private static bool _survivorsAlreadyAdded; - - private static List SurvivorBodyPrefabs = new List(); + [Obsolete($"This observable collection is obsolete, if you want to look at the survivorDefs added by R2API, look at R2API.ContentManagement.R2APIContentManager.ManagedContentPacks and do a SelectMany on the SurvivorDefs.")] public static ObservableCollection SurvivorDefinitions = new ObservableCollection(); /// @@ -40,13 +36,14 @@ public static bool Loaded { /// /// The survivor to add. /// true if survivor will be added + [Obsolete($"AddSurvivor is obsolete, please add your SurvivorDefs via R2API.ContentManagement.ContentAdditionHelpers.AddSurvivorDef()\nYou can also add Your CharacterBody and MasterBody with AddBody() & AddMaster() respectively.")] public static bool AddSurvivor(SurvivorDef? survivor) { if (!Loaded) { throw new InvalidOperationException($"{nameof(SurvivorAPI)} is not loaded. Please use [{nameof(R2APISubmoduleDependency)}(nameof({nameof(SurvivorAPI)})]"); } - if (_survivorsAlreadyAdded) { - R2API.Logger.LogError($"Tried to add survivor: {survivor.displayNameToken} after survivor list was created"); + if (!CatalogBlockers.GetAvailability()) { + R2API.Logger.LogError($"Tried to add survivor: {survivor.displayNameToken} After the SurvivorCatalog has initialized!"); return false; } @@ -55,34 +52,24 @@ public static bool AddSurvivor(SurvivorDef? survivor) { return false; } - SurvivorBodyPrefabs.Add(survivor.bodyPrefab); SurvivorDefinitions.Add(survivor); + R2APIContentManager.HandleContentAddition(Assembly.GetCallingAssembly(), survivor.bodyPrefab); + R2APIContentManager.HandleContentAddition(Assembly.GetCallingAssembly(), survivor); + return true; } [R2APISubmoduleInit(Stage = InitStage.SetHooks)] internal static void SetHooks() { - R2APIContentPackProvider.WhenContentPackReady += AddSurvivorsToGame; IL.RoR2.CharacterSelectBarController.Build += DescriptionTokenSafetyCheck; } [R2APISubmoduleInit(Stage = InitStage.UnsetHooks)] internal static void UnsetHooks() { - R2APIContentPackProvider.WhenContentPackReady -= AddSurvivorsToGame; IL.RoR2.CharacterSelectBarController.Build -= DescriptionTokenSafetyCheck; } - private static void AddSurvivorsToGame(ContentPack r2apiContentPack) { - foreach (var customSurvivor in SurvivorDefinitions) { - R2API.Logger.LogInfo($"Custom Survivor: {customSurvivor.cachedName} added"); - } - - r2apiContentPack.bodyPrefabs.Add(SurvivorBodyPrefabs.ToArray()); - r2apiContentPack.survivorDefs.Add(SurvivorDefinitions.ToArray()); - _survivorsAlreadyAdded = true; - } - // Add a safety check for SurvivorDef that are lacking // a Environement.NewLine in their descriptionToken // The game code use IndexOf, assuming there is always one in there. diff --git a/R2API/TempVisualEffectAPI.cs b/R2API/TempVisualEffectAPI.cs index ab1995ed..affb53ef 100644 --- a/R2API/TempVisualEffectAPI.cs +++ b/R2API/TempVisualEffectAPI.cs @@ -1,8 +1,8 @@ using Mono.Cecil.Cil; using MonoMod.Cil; +using R2API.ContentManagement; using R2API.Utils; using RoR2; -using RoR2.ContentManagement; using System; using System.Collections.Generic; using System.Linq; @@ -92,7 +92,7 @@ public static bool AddTemporaryVisualEffect(GameObject effectPrefab, EffectCondi internal static void SetHooks() { On.RoR2.CharacterBody.UpdateAllTemporaryVisualEffects += UpdateAllHook; IL.RoR2.CharacterBody.UpdateSingleTemporaryVisualEffect += UpdateSingleHook; - R2APIContentPackProvider.WhenContentPackReady += DontAllowNewEntries; + R2APIContentPackProvider.WhenAddingContentPacks += DontAllowNewEntries; CharacterBody.onBodyStartGlobal += BodyStart; } @@ -100,7 +100,7 @@ internal static void SetHooks() { internal static void UnsetHooks() { On.RoR2.CharacterBody.UpdateAllTemporaryVisualEffects -= UpdateAllHook; IL.RoR2.CharacterBody.UpdateSingleTemporaryVisualEffect += UpdateSingleHook; - R2APIContentPackProvider.WhenContentPackReady -= DontAllowNewEntries; + R2APIContentPackProvider.WhenAddingContentPacks -= DontAllowNewEntries; CharacterBody.onBodyStartGlobal -= BodyStart; } @@ -110,7 +110,7 @@ private static void BodyStart(CharacterBody body) { } } - private static void DontAllowNewEntries(ContentPack r2apiContentPack) { + private static void DontAllowNewEntries() { _TVEsAdded = true; } @@ -129,8 +129,7 @@ private static void UpdateAllHook(On.RoR2.CharacterBody.orig_UpdateAllTemporaryV private static void UpdateSingleHook(ILContext il) { var cursor = new ILCursor(il); - GameObject GetCustomTVE(GameObject vanillaLoaded, string resourceString) - { + GameObject GetCustomTVE(GameObject vanillaLoaded, string resourceString) { if (!vanillaLoaded) { if (tveDict.TryGetValue(resourceString, out var customTVEPrefab)) { return customTVEPrefab.effectPrefab; diff --git a/R2API/UnlockableAPI.cs b/R2API/UnlockableAPI.cs index cde7d7c4..0bbaa70a 100644 --- a/R2API/UnlockableAPI.cs +++ b/R2API/UnlockableAPI.cs @@ -1,12 +1,13 @@ using Mono.Cecil.Cil; using MonoMod.Cil; +using R2API.ContentManagement; using R2API.Utils; using RoR2; using RoR2.Achievements; -using RoR2.ContentManagement; using System; using System.Collections.Generic; using System.Linq; +using System.Reflection; using UnityEngine; using BF = System.Reflection.BindingFlags; @@ -130,11 +131,8 @@ internal struct UnlockableInfo { public static class UnlockableAPI { private static readonly HashSet UnlockableIdentifiers = new HashSet(); - private static readonly List Unlockables = new List(); private static readonly List Achievements = new List(); - private static bool _unlockableCatalogInitialized; - /// /// Return true if the submodule is loaded. /// @@ -147,25 +145,14 @@ public static bool Loaded { [R2APISubmoduleInit(Stage = InitStage.SetHooks)] internal static void SetHooks() { - R2APIContentPackProvider.WhenContentPackReady += AddUnlockablesToGame; IL.RoR2.AchievementManager.CollectAchievementDefs += AddCustomAchievements; } [R2APISubmoduleInit(Stage = InitStage.UnsetHooks)] internal static void UnsetHooks() { - R2APIContentPackProvider.WhenContentPackReady -= AddUnlockablesToGame; IL.RoR2.AchievementManager.CollectAchievementDefs -= AddCustomAchievements; } - private static void AddUnlockablesToGame(ContentPack r2apiContentPack) { - foreach (var unlockable in Unlockables) { - R2API.Logger.LogInfo($"Custom Unlockable: {unlockable.cachedName} added"); - } - - r2apiContentPack.unlockableDefs.Add(Unlockables.ToArray()); - _unlockableCatalogInitialized = true; - } - private static void AddCustomAchievements(ILContext il) { var achievementIdentifierField = typeof(AchievementManager).GetField(nameof(AchievementManager.achievementIdentifiers), BF.Public | BF.Static | BF.NonPublic); if (achievementIdentifierField is null) { @@ -215,23 +202,20 @@ internal static UnlockableDef SetupUnlockable(UnlockableInfo unlockableInfo, Unl } [Obsolete("The bool parameter serverTracked is redundant. Instead, pass in a Type that inherits from BaseServerAchievement if it is server tracked, or nothing if it's not")] - public static UnlockableDef AddUnlockable(bool serverTracked) where TUnlockable : BaseAchievement, IModdedUnlockableDataProvider, new() - { - return AddUnlockable(null, null); + public static UnlockableDef AddUnlockable(bool serverTracked) where TUnlockable : BaseAchievement, IModdedUnlockableDataProvider, new() { + return AddUnlockableInternal(typeof(TUnlockable), Assembly.GetCallingAssembly(), null, null); } - public static UnlockableDef AddUnlockable(Type serverTrackerType) where TUnlockable : BaseAchievement, IModdedUnlockableDataProvider, new() - { - return AddUnlockable(serverTrackerType, null); + public static UnlockableDef AddUnlockable(Type serverTrackerType) where TUnlockable : BaseAchievement, IModdedUnlockableDataProvider, new() { + return AddUnlockableInternal(typeof(TUnlockable), Assembly.GetCallingAssembly(), serverTrackerType, null); } - public static UnlockableDef AddUnlockable(UnlockableDef unlockableDef) where TUnlockable : BaseAchievement, IModdedUnlockableDataProvider, new() - { - return AddUnlockable(null, unlockableDef); + public static UnlockableDef AddUnlockable(UnlockableDef unlockableDef) where TUnlockable : BaseAchievement, IModdedUnlockableDataProvider, new() { + return AddUnlockableInternal(typeof(TUnlockable), Assembly.GetCallingAssembly(), null, unlockableDef); } public static UnlockableDef AddUnlockable(Type unlockableType, Type serverTrackerType) { - return AddUnlockable(unlockableType, serverTrackerType, null); + return AddUnlockableInternal(unlockableType, Assembly.GetCallingAssembly(), serverTrackerType, null); } public static UnlockableDef AddUnlockable(Type unlockableType, UnlockableDef unlockableDef) { - return AddUnlockable(unlockableType, null, unlockableDef); + return AddUnlockableInternal(unlockableType, Assembly.GetCallingAssembly(), null, unlockableDef); } /// @@ -245,7 +229,7 @@ public static bool AddAchievement(AchievementDef achievementDef) { } var identifiers = Achievements.Select(achievementDef => achievementDef.identifier); try { - if(identifiers.Contains(achievementDef.identifier)) { + if (identifiers.Contains(achievementDef.identifier)) { throw new InvalidOperationException($"The achievement identifier '{achievementDef.identifier}' is already used by another mod."); } else { @@ -253,7 +237,7 @@ public static bool AddAchievement(AchievementDef achievementDef) { return true; } } - catch (Exception e){ + catch (Exception e) { R2API.Logger.LogError($"An error has occured while trying to add a new AchievementDef: {e}"); return false; } @@ -268,7 +252,7 @@ public static bool AddAchievement(AchievementDef achievementDef) { /// For UnlockableDefs created in advance. Leaving null will generate an UnlockableDef instead. /// public static UnlockableDef AddUnlockable(Type serverTrackerType = null, UnlockableDef unlockableDef = null) where TUnlockable : BaseAchievement, IModdedUnlockableDataProvider, new() { - return AddUnlockable(typeof(TUnlockable), serverTrackerType, unlockableDef); + return AddUnlockableInternal(typeof(TUnlockable), Assembly.GetCallingAssembly(), serverTrackerType, unlockableDef); } /// @@ -280,14 +264,18 @@ public static bool AddAchievement(AchievementDef achievementDef) { /// For UnlockableDefs created in advance. Leaving null will generate an UnlockableDef instead. /// public static UnlockableDef AddUnlockable(Type unlockableType, Type serverTrackerType = null, UnlockableDef unlockableDef = null) { + return AddUnlockableInternal(unlockableType, Assembly.GetCallingAssembly(), serverTrackerType, unlockableDef); + } + + private static UnlockableDef AddUnlockableInternal(Type unlockableType, Assembly assembly, Type serverTrackerType = null, UnlockableDef unlockableDef = null) { if (!Loaded) { throw new InvalidOperationException($"{nameof(UnlockableAPI)} is not loaded. Please use [{nameof(R2APISubmoduleDependency)}(nameof({nameof(UnlockableAPI)})]"); } var instance = Activator.CreateInstance(unlockableType) as IModdedUnlockableDataProvider; - if (_unlockableCatalogInitialized) { - throw new InvalidOperationException($"Too late ! Tried to add unlockable: {instance.UnlockableIdentifier} after the unlockable list was created"); + if (!CatalogBlockers.GetAvailability()) { + throw new InvalidOperationException($"Too late ! Tried to add unlockable: {instance.UnlockableIdentifier} after the UnlockableCatalog"); } string unlockableIdentifier = instance.UnlockableIdentifier; @@ -319,8 +307,7 @@ public static UnlockableDef AddUnlockable(Type unlockableType, Type serverTracke type = instance.GetType(), serverTrackerType = serverTrackerType, }; - - Unlockables.Add(unlockableDef); + R2APIContentManager.HandleContentAddition(assembly, unlockableDef); Achievements.Add(achievementDef); return unlockableDef; diff --git a/R2API/Utils/EmbeddedResources.cs b/R2API/Utils/EmbeddedResources.cs index c65d30a2..a9c7d457 100644 --- a/R2API/Utils/EmbeddedResources.cs +++ b/R2API/Utils/EmbeddedResources.cs @@ -1,6 +1,5 @@ using System.Reflection; using System.Runtime.CompilerServices; -using UnityEngine; namespace R2API.Utils { public static unsafe class EmbeddedResources { diff --git a/R2API/Utils/NetworkCompatibility.cs b/R2API/Utils/NetworkCompatibility.cs index 80af29a0..61b40325 100644 --- a/R2API/Utils/NetworkCompatibility.cs +++ b/R2API/Utils/NetworkCompatibility.cs @@ -3,9 +3,7 @@ using RoR2; using System; using System.Collections.Generic; -using System.ComponentModel; using System.Linq; -using System.Reflection; namespace R2API.Utils { diff --git a/README.md b/README.md index 6995d9f7..31c26c5a 100644 --- a/README.md +++ b/README.md @@ -45,97 +45,17 @@ The most recent changelog can always be found on the [GitHub](https://github.com **Current** +* Updated for the game `Survivors of The Void` +* [Complete Refractoring of Content Addition Systems, now uses the R2APIContentManager](https://github.com/risk-of-thunder/R2API/pull/338) +* [Each mod now has it's own ContentPack managed by R2API](https://github.com/risk-of-thunder/R2API/pull/338#issue-1137783592) +* [A Mod can add a PreExisting content pack to the R2APIContentManager](https://github.com/risk-of-thunder/R2API/pull/338#issuecomment-1040337885) +* [A PreExisting ContentPack can opt out from being loaded by R2API's Systems](https://github.com/risk-of-thunder/R2API/pull/338#issuecomment-1040337885) +* [Each mod can now add a valid content piece via ContentAdditionHelpers](https://github.com/risk-of-thunder/R2API/pull/338#issuecomment-1041783985) +* [Assets added by R2API will always have unique names](https://github.com/risk-of-thunder/R2API/pull/338#issue-1137783592) +* [Made LoadRoR2ContentEarly public, mods can now access a readonly version of RoR2's ContentPack](https://github.com/risk-of-thunder/R2API/pull/338#issuecomment-1040337885) +* [UnlockableAPI's Overload Methods now point to a single private method](https://github.com/risk-of-thunder/R2API/pull/338/commits/82d6edb8933af7974683f411c65a256378a45ae1) +* [Marked the following APIs As Obsolete: ArtifactAPI, BuffAPI, EffectAPI, ProjectileAPI and SurvivorAPI](https://github.com/risk-of-thunder/R2API/pull/338#issuecomment-1041484037) +* [Marked the following methods in LoadoutAPI as Obsolete: AddSkill, StateTypeOf, AddSkillDef, AddSkillFamily](https://github.com/risk-of-thunder/R2API/pull/338#issuecomment-1041484037) +* [Marked the AddNetworkedSoundEvent method in SoundAPI as Obsolete](https://github.com/risk-of-thunder/R2API/pull/338#issuecomment-1041484037) * [NetworkingAPI now has Send overloads for sending to specific network connections. Also have some documentation now](https://github.com/risk-of-thunder/R2API/pull/333) -* [Allow character mods to opt out from default item display rules](https://github.com/risk-of-thunder/R2API/pull/330) - -**3.0.71** - -* [ItemAPI now warns that ItemDef/EquipmentDef.pickupModelPrefab should have an ItemDisplay attached to them when they have ParentedPrefab display rules linked to them](https://github.com/risk-of-thunder/R2API/pull/311) -* [EliteAPI now exposes the default elite tiers array (through VanillaEliteTiers) before any changes are made to it for modder that want to change the vanilla elite tiers. Also, adding to the custom elite tier array now by default insert based on the cost multiplier of the elite tier.](https://github.com/risk-of-thunder/R2API/pull/308) -* [RecalculateStatsAPI now warns modders that the submodule could be not loaded](https://github.com/risk-of-thunder/R2API/pull/307) -* [Added Curse, Shield Multiplier, All Cooldown Reductions, Jump Power, Level Scaling, and Root to RecalculateStatsAPI](https://github.com/risk-of-thunder/R2API/pull/322) -* [Fix SoundAPI throwing on dedicated server](https://github.com/risk-of-thunder/R2API/pull/306) -* [Fix SoundAPI's music implementation stopping all music when an instance of a MusicTrackOverride gets destroyed](https://github.com/risk-of-thunder/R2API/pull/319) -* [Added TempVisualEffectAPI](https://github.com/risk-of-thunder/R2API/pull/313) -* [ArtifactCodeAPI's ArtifactCode Scriptable object now uses 3 Vector3Int for inputting the code, instead of a List of Ints](https://github.com/risk-of-thunder/R2API/pull/310) -* [Added aditional overloads for AddUnlockable that accept a type parameter instead of using generics](https://github.com/risk-of-thunder/R2API/pull/317) -* [UnlockableAPI can now add AchievementDefs directly](https://github.com/risk-of-thunder/R2API/pull/321) -* [DotAPI no longer throws an error when no BuffDef is provided for the asociated BuffDef parameter](https://github.com/risk-of-thunder/R2API/pull/325) - -**3.0.59** - -* [Extended SoundAPI for adding custom music](https://github.com/risk-of-thunder/R2API/pull/305) -* [Added support for using existing UnlockableDefs in UnlockableAPI](https://github.com/risk-of-thunder/R2API/pull/304) -* [fixing server unlockables](https://github.com/risk-of-thunder/R2API/pull/302) - -**3.0.52** - -* [Add NetworkSoundEventDef registration to SoundAPI](https://github.com/risk-of-thunder/R2API/pull/301) - -**3.0.50** - -* [Added ArtifactCodeAPI](https://github.com/risk-of-thunder/R2API/pull/299) -* [Added support for new Artifact Code compounds](https://github.com/risk-of-thunder/R2API/pull/300) - -**3.0.48** - -* [Documentation for ItemDropAPI](https://github.com/risk-of-thunder/R2API/blob/master/ItemDropAPI%20Instructions%20For%20Use.txt) -* [ItemDropAPI Overhall](https://github.com/risk-of-thunder/R2API/pull/295) -* [Added MonsterItemsAPI back in](https://github.com/risk-of-thunder/R2API/pull/295) - -**3.0.44** - -* [Fixed PrefabAPI network registration](https://github.com/risk-of-thunder/R2API/pull/294) - -**3.0.43** - -* **IMPORTANT FOR MOD DEVS:** [R2API will no longer register mods to network if they don't depend on it with HardDependecy](https://github.com/risk-of-thunder/R2API/pull/286) -* [Added DeployableAPI](https://github.com/risk-of-thunder/R2API/pull/279) -* [Added DamageAPI](https://github.com/risk-of-thunder/R2API/pull/284) -* [Added RecalcStatsAPI, migrated from TILER2](https://github.com/risk-of-thunder/R2API/pull/287) -* [Updated DifficultyAPI, now has sprite ref overload](https://github.com/risk-of-thunder/R2API/pull/288) -* [RecalcStatsAPI fixes](https://github.com/risk-of-thunder/R2API/pull/290) -* [Missing MMHOOK/Publicized Assembly methods fixes](https://github.com/risk-of-thunder/R2API/pull/289) - -**3.0.30** - -* Fixes for current patch - -**3.0.25** - -* **IMPORTANT FOR MOD DEVS:** [R2API will no longer register mods to network if they don't depend on it](https://github.com/risk-of-thunder/R2API/pull/269) -* [DotAPI fixes](https://github.com/risk-of-thunder/R2API/pull/270) -* [EliteAPI fixes](https://github.com/risk-of-thunder/R2API/pull/271) - -**3.0.13** - -* [Updated UnlockableAPI, ItemDropAPI Overhall](https://github.com/risk-of-thunder/R2API/pull/265) -* [Update internals for 1.1.1.2 game version](https://github.com/risk-of-thunder/R2API/pull/267) -* Removed MonsterItemsAPI -* Removed `patchers` folder - -**3.0.11** - -* [Updated ResourceAPI error messages](https://github.com/risk-of-thunder/R2API/pull/258) -* SurvivorAPI Fixes: [A](https://github.com/risk-of-thunder/R2API/pull/259) [B](https://github.com/risk-of-thunder/R2API/pull/261) - -**3.0.7** - -* [Added ArtifactAPI and ProjectileAPI, BuffAPI fix](https://github.com/risk-of-thunder/R2API/pull/256) - -**3.0.1** - -* [Fixes for EffectAPI, LoadoutAPI and SoundAPI](https://github.com/risk-of-thunder/R2API/pull/254) - -**3.0.0** - -* Updated for the game `Anniversary Update` -* No longer include `monomod` folder -* [Various API Fixes. Removed AssetsAPI, InvetoryAPI. Moved MMHook to separate mod called (HookGenPatcher)](https://github.com/risk-of-thunder/R2API/pull/252) -* Removed obsolete APIs and methods: [A](https://github.com/risk-of-thunder/R2API/pull/249) [B](https://github.com/risk-of-thunder/R2API/pull/243) -* ItemAPI, ItemDropApi overhall. Added MonsterItemAPI: [A](https://github.com/risk-of-thunder/R2API/pull/214) [B](https://github.com/risk-of-thunder/R2API/pull/223) [C](https://github.com/risk-of-thunder/R2API/pull/228) [D](https://github.com/risk-of-thunder/R2API/pull/233) [E](https://github.com/risk-of-thunder/R2API/pull/234) [F](https://github.com/risk-of-thunder/R2API/pull/240) [G](https://github.com/risk-of-thunder/R2API/pull/245) -* LanguageAPI refactoring and fixes: [A](https://github.com/risk-of-thunder/R2API/pull/229) [B](https://github.com/risk-of-thunder/R2API/pull/244) -* [Added ILLine](https://github.com/risk-of-thunder/R2API/pull/230) -* [Fix for networked achievements](https://github.com/risk-of-thunder/R2API/pull/208) -* [Added SceneAssetAPI](https://github.com/risk-of-thunder/R2API/pull/210) -* [Added InteractablesAPI](https://github.com/risk-of-thunder/R2API/pull/216) +* [Allow character mods to opt out from default item display rules](https://github.com/risk-of-thunder/R2API/pull/330) \ No newline at end of file