diff --git a/.gitignore b/.gitignore old mode 100644 new mode 100755 index 019b9b031..21c7eba24 --- a/.gitignore +++ b/.gitignore @@ -9,11 +9,12 @@ /[Bb]uilds/ /[Ll]ogs/ /[Mm]emoryCaptures/ -*/bin/ -*/obj/ +/bin/ +/**/bin/ +/obj/ +/**/obj/ /packages/ - # Never ignore Asset meta data !/[Aa]ssets/**/*.meta @@ -25,6 +26,7 @@ # Visual Studio cache directory .vs/ +.vscode/ # Rider cache directory .idea/ @@ -76,3 +78,17 @@ crashlytics-build.properties /valheim_Data/BepInEx-out/plugins/EpicLoot/enchantcosts.json /valheim_Data/BepInEx-out/plugins/EpicLoot/adventuredata.json /valheim_Data/BepInEx-out/plugins/EpicLoot/abilities.json + +# Unity +ValheimUnity/Assets/ExternalLibraries/* +ValheimUnity/AssetBundles/*.manifest +ValheimUnity/Assets/ExternalLibraries/*.dll +ValheimUnity/Assets/ExternalLibraries/valheim/*.dll +ValheimUnity/UserSettings +ValheimUnity/Assets/ExternalLibraries/EpicLoot-UnityLib.dll +ValheimUnity/Packages/packages-lock.json + +# Manual adds +Common +ValheimMods.sln +*.env diff --git a/AdvancedPortals/AddPortals.cs b/AdvancedPortals/AddPortals.cs deleted file mode 100644 index ae342186e..000000000 --- a/AdvancedPortals/AddPortals.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System.Collections.Generic; -using System.Reflection; -using HarmonyLib; -using JetBrains.Annotations; - -namespace AdvancedPortals -{ - - [HarmonyPatch] - public static class AddPortal - { - public static readonly HashSet Hashes = new HashSet(); - - [UsedImplicitly] - private static IEnumerable TargetMethods() => new[] - { - AccessTools.DeclaredMethod(typeof(Game), nameof(Game.Awake)), - }; - - [UsedImplicitly] - private static void Prefix(Game __instance) - { - __instance.PortalPrefabHash.AddRange(Hashes); - } - } -} \ No newline at end of file diff --git a/AdvancedPortals/AdvancedPortal.cs b/AdvancedPortals/AdvancedPortal.cs deleted file mode 100644 index 519ad8925..000000000 --- a/AdvancedPortals/AdvancedPortal.cs +++ /dev/null @@ -1,29 +0,0 @@ -using System; -using System.Collections.Generic; -using UnityEngine; - -namespace AdvancedPortals -{ - public class AdvancedPortal : MonoBehaviour - { - public List AllowedItems = new List(); - public bool AllowEverything; - private ZNetView _nview; - public string DefaultName; - private void Awake() - { - _nview = GetComponent(); - - if (_nview == null || _nview.GetZDO() == null) - return; - - ZDOMan.instance.AddPortal(_nview.GetZDO()); - } - - private void Start() - { - ZDOMan.instance.ConvertPortals(); - ZDOMan.instance.ConnectPortals(); - } - } -} diff --git a/AdvancedPortals/AdvancedPortals-1.0.1.zip b/AdvancedPortals/AdvancedPortals-1.0.1.zip deleted file mode 100644 index 06cf69365..000000000 Binary files a/AdvancedPortals/AdvancedPortals-1.0.1.zip and /dev/null differ diff --git a/AdvancedPortals/AdvancedPortals-1.0.2.zip b/AdvancedPortals/AdvancedPortals-1.0.2.zip deleted file mode 100644 index 427099ecf..000000000 Binary files a/AdvancedPortals/AdvancedPortals-1.0.2.zip and /dev/null differ diff --git a/AdvancedPortals/AdvancedPortals-1.0.3.zip b/AdvancedPortals/AdvancedPortals-1.0.3.zip deleted file mode 100644 index 52449c4af..000000000 Binary files a/AdvancedPortals/AdvancedPortals-1.0.3.zip and /dev/null differ diff --git a/AdvancedPortals/AdvancedPortals-1.0.zip b/AdvancedPortals/AdvancedPortals-1.0.zip deleted file mode 100644 index affb23aef..000000000 Binary files a/AdvancedPortals/AdvancedPortals-1.0.zip and /dev/null differ diff --git a/AdvancedPortals/AdvancedPortals.cs b/AdvancedPortals/AdvancedPortals.cs index dfd82c296..d1e028966 100644 --- a/AdvancedPortals/AdvancedPortals.cs +++ b/AdvancedPortals/AdvancedPortals.cs @@ -1,27 +1,22 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using BepInEx; +using BepInEx; using BepInEx.Configuration; -using Common; +using BepInEx.Logging; using HarmonyLib; using JetBrains.Annotations; -using ServerSync; +using Jotunn.Configs; +using Jotunn.Entities; +using Jotunn.Managers; +using Jotunn.Utils; +using System; +using System.Collections.Generic; +using System.IO; +using System.Reflection; using UnityEngine; namespace AdvancedPortals { - public class PieceDef - { - public delegate void OnPrefabAddedDelegate(GameObject prefab); - - public string Table; - public string CraftingStation; - public OnPrefabAddedDelegate OnPrefabAdded; - public List Resources = new List(); - } - + [BepInDependency(Jotunn.Main.ModGuid)] + [NetworkCompatibility(CompatibilityLevel.EveryoneMustHaveMod, VersionStrictness.Patch)] [BepInPlugin(PluginId, DisplayName, Version)] [BepInIncompatibility("com.github.xafflict.UnrestrictedPortals")] [BepInDependency("org.bepinex.plugins.targetportal", BepInDependency.DependencyFlags.SoftDependency)] @@ -29,421 +24,232 @@ public class AdvancedPortals : BaseUnityPlugin { public const string PluginId = "randyknapp.mods.advancedportals"; public const string DisplayName = "Advanced Portals"; - public const string Version = "1.0.8"; - public static readonly string[] _portalPrefabs = { "portal_ancient", "portal_obsidian", "portal_blackmarble" }; - + public const string Version = "1.1.3"; + + private static string ConfigFileName = PluginId + ".cfg"; + private static string ConfigFileFullPath = BepInEx.Paths.ConfigPath + Path.DirectorySeparatorChar + ConfigFileName; + + public static readonly string[] PortalPrefabs = { "portal_ancient", "portal_obsidian", "portal_blackmarble" }; + public static readonly List RegisteredPrefabs = new List(); - public static readonly Dictionary RegisteredPieces = new Dictionary(); - - private readonly ConfigSync _configSync = new ConfigSync(PluginId) { DisplayName = DisplayName, CurrentVersion = Version, MinimumRequiredVersion = Version }; - private static ConfigEntry _serverConfigLocked; - private static ConfigEntry _ancientPortalEnabled; - private static ConfigEntry _ancientPortalRecipe; - private static ConfigEntry _ancientPortalAllowedItems; - private static ConfigEntry _ancientPortalAllowEverything; - private static ConfigEntry _obsidianPortalEnabled; - private static ConfigEntry _obsidianPortalRecipe; - private static ConfigEntry _obsidianPortalAllowedItems; - private static ConfigEntry _obsidianPortalAllowEverything; - private static ConfigEntry _obsidianPortalAllowPreviousPortalItems; - private static ConfigEntry _blackMarblePortalEnabled; - private static ConfigEntry _blackMarblePortalRecipe; - private static ConfigEntry _blackMarblePortalAllowedItems; - private static ConfigEntry _blackMarblePortalAllowEverything; - private static ConfigEntry _blackMarblePortalAllowPreviousPortalItems; - - private static AdvancedPortals _instance; + + public static ConfigEntry AncientPortalEnabled; + public static string AncientPortalRecipeDefault = "ElderBark:20,Iron:5,SurtlingCore:2"; + public static ConfigEntry AncientPortalRecipe = null!; + public static ConfigEntry AncientPortalAllowedItems = null!; + public static ConfigEntry AncientPortalAllowEverything = null!; + + public static ConfigEntry ObsidianPortalEnabled; + public static string ObsidianPortalRecipeDefault = "Obsidian:20,Silver:5,SurtlingCore:2"; + public static ConfigEntry ObsidianPortalRecipe; + public static ConfigEntry ObsidianPortalAllowedItems = null!; + public static ConfigEntry ObsidianPortalAllowEverything = null!; + public static ConfigEntry ObsidianPortalAllowPreviousPortalItems = null!; + + public static ConfigEntry BlackMarblePortalEnabled; + public static string BlackMarblePortalRecipeDefault = "BlackMarble:20,BlackMetal:5,Eitr:2"; + public static ConfigEntry BlackMarblePortalRecipe; + public static ConfigEntry BlackMarblePortalAllowedItems = null!; + public static ConfigEntry BlackMarblePortalAllowEverything = null!; + public static ConfigEntry BlackMarblePortalAllowPreviousPortalItems = null!; + + public static readonly ManualLogSource APLogger = BepInEx.Logging.Logger.CreateLogSource(DisplayName); private Harmony _harmony; [UsedImplicitly] private void Awake() { - _instance = this; - - _serverConfigLocked = SyncedConfig("Config Sync", "Lock Config", false, "[Server Only] The configuration is locked and may not be changed by clients once it has been synced from the server. Only valid for server config, will have no effect on clients."); - - _ancientPortalEnabled = SyncedConfig("Portal 1 - Ancient", "Ancient Portal Enabled", true, "Enable the Ancient Portal"); - _ancientPortalRecipe = SyncedConfig("Portal 1 - Ancient", "Ancient Portal Recipe", "ElderBark:20,Iron:5,SurtlingCore:2", "The items needed to build the Ancient Portal. A comma separated list of ITEM:QUANTITY pairs separated by a colon."); - _ancientPortalAllowedItems = SyncedConfig("Portal 1 - Ancient", "Ancient Portal Allowed Items", "Copper, CopperOre, CopperScrap, Tin, TinOre, Bronze", "A comma separated list of the item types allowed through the Ancient Portal"); - _ancientPortalAllowEverything = SyncedConfig("Portal 1 - Ancient", "Ancient Portal Allow Everything", false, "Allow all items through the Ancient Portal (overrides Allowed Items)"); - - _obsidianPortalEnabled = SyncedConfig("Portal 2 - Obsidian", "Obsidian Portal Enabled", true, "Enable the Obsidian Portal"); - _obsidianPortalRecipe = SyncedConfig("Portal 2 - Obsidian", "Obsidian Portal Recipe", "Obsidian:20,Silver:5,SurtlingCore:2", "The items needed to build the Obsidian Portal. A comma separated list of ITEM:QUANTITY pairs separated by a colon."); - _obsidianPortalAllowedItems = SyncedConfig("Portal 2 - Obsidian", "Obsidian Portal Allowed Items", "Iron, IronScrap", "A comma separated list of the item types allowed through the Obsidian Portal"); - _obsidianPortalAllowEverything = SyncedConfig("Portal 2 - Obsidian", "Obsidian Portal Allow Everything", false, "Allow all items through the Obsidian Portal (overrides Allowed Items)"); - _obsidianPortalAllowPreviousPortalItems = SyncedConfig("Portal 2 - Obsidian", "Obsidian Portal Use All Previous", true, "Additionally allow all items from the Ancient Portal"); - - _blackMarblePortalEnabled = SyncedConfig("Portal 3 - Black Marble", "Black Marble Portal Enabled", true, "Enable the Black Marble Portal"); - _blackMarblePortalRecipe = SyncedConfig("Portal 3 - Black Marble", "Black Marble Portal Recipe", "BlackMarble:20,BlackMetal:5,Eitr:2", "The items needed to build the Black Marble Portal. A comma separated list of ITEM:QUANTITY pairs separated by a colon."); - _blackMarblePortalAllowedItems = SyncedConfig("Portal 3 - Black Marble", "Black Marble Portal Allowed Items", "Silver, SilverOre", "A comma separated list of the item types allowed through the Black Marble Portal"); - _blackMarblePortalAllowEverything = SyncedConfig("Portal 3 - Black Marble", "Black Marble Portal Allow Everything", true, "Allow all items through the Black Marble Portal (overrides Allowed Items)"); - _blackMarblePortalAllowPreviousPortalItems = SyncedConfig("Portal 3 - Black Marble", "Black Marble Portal Use All Previous", true, "Additionally allow all items from the Obsidian and Ancient Portal"); - - _configSync.AddLockingConfigEntry(_serverConfigLocked); - - var assetBundle = LoadAssetBundle("advancedportals"); - - var allowedTypesAncient = _ancientPortalAllowedItems.Value.Replace(" ", "").Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).ToList(); - var allowedTypesObsidian = _obsidianPortalAllowedItems.Value.Replace(" ", "").Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).ToList(); - var allowedTypesBlackMarble = _blackMarblePortalAllowedItems.Value.Replace(" ", "").Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).ToList(); - - if (_ancientPortalEnabled.Value) + AddConfig("Portal 1 - Ancient", "Ancient Portal Enabled", + "Enable the Ancient Portal", + true, true, ref AncientPortalEnabled); + AddConfig("Portal 1 - Ancient", "Ancient Portal Recipe", + "The items needed to build the Ancient Portal. A comma separated list of ITEM:QUANTITY pairs separated by a colon.", + true, AncientPortalRecipeDefault, ref AncientPortalRecipe); + AddConfig("Portal 1 - Ancient", "Ancient Portal Allowed Items", + "A comma separated list of the item types allowed through the Ancient Portal", + true, "Copper, CopperOre, CopperScrap, Tin, TinOre, Bronze, BronzeScrap", ref AncientPortalAllowedItems); + AddConfig("Portal 1 - Ancient", "Ancient Portal Allow Everything", + "Allow all items through the Ancient Portal (overrides Allowed Items)", + true, false, ref AncientPortalAllowEverything); + + AddConfig("Portal 2 - Obsidian", "Obsidian Portal Enabled", + "Enable the Obsidian Portal", + true, true, ref ObsidianPortalEnabled); + AddConfig("Portal 2 - Obsidian", "Obsidian Portal Recipe", + "The items needed to build the Obsidian Portal. A comma separated list of ITEM:QUANTITY pairs separated by a colon.", + true, ObsidianPortalRecipeDefault, ref ObsidianPortalRecipe); + AddConfig("Portal 2 - Obsidian", "Obsidian Portal Allowed Items", + "A comma separated list of the item types allowed through the Obsidian Portal", + true, "Iron, IronScrap, IronOre", ref ObsidianPortalAllowedItems); + AddConfig("Portal 2 - Obsidian", "Obsidian Portal Allow Everything", + "Allow all items through the Obsidian Portal (overrides Allowed Items)", + true, false, ref ObsidianPortalAllowEverything); + AddConfig("Portal 2 - Obsidian", "Obsidian Portal Use All Previous", + "Additionally allow all items from the Ancient Portal", + true, true, ref ObsidianPortalAllowPreviousPortalItems); + + AddConfig("Portal 3 - Black Marble", "Black Marble Portal Enabled", + "Enable the Black Marble Portal", + true, true, ref BlackMarblePortalEnabled); + AddConfig("Portal 3 - Black Marble", "Black Marble Portal Recipe", + "The items needed to build the Black Marble Portal. A comma separated list of ITEM:QUANTITY pairs separated by a colon.", + true, BlackMarblePortalRecipeDefault, ref BlackMarblePortalRecipe); + AddConfig("Portal 3 - Black Marble", "Black Marble Portal Allowed Items", + "A comma separated list of the item types allowed through the Black Marble Portal", + true, "Silver, SilverOre, BlackMetal, BlackMetalScrap", ref BlackMarblePortalAllowedItems); + AddConfig("Portal 3 - Black Marble", "Black Marble Portal Allow Everything", + "Allow all items through the Black Marble Portal (overrides Allowed Items)", + true, true, ref BlackMarblePortalAllowEverything); + AddConfig("Portal 3 - Black Marble", "Black Marble Portal Use All Previous", + "Additionally allow all items from the Obsidian and Ancient Portal", + true, true, ref BlackMarblePortalAllowPreviousPortalItems); + + AssetBundle assetBundle = LoadAssetBundle("advancedportals"); + + LoadBuildPiece(assetBundle, "portal_ancient", new PieceConfig() { - LoadBuildPiece(assetBundle, "portal_ancient", new PieceDef() - { - Table = "_HammerPieceTable", - CraftingStation = "piece_workbench", - Resources = MakeRecipeFromConfig("Ancient Portal", _ancientPortalRecipe.Value), - OnPrefabAdded = (prefab => - { - if (!prefab.TryGetComponent(out var teleport)) - { - teleport = prefab.AddComponent(); - } - var advPortal = prefab.AddComponent(); - advPortal.DefaultName = "ancient"; - advPortal.AllowedItems = allowedTypesAncient; - advPortal.AllowEverything = _ancientPortalAllowEverything.Value; - - var itemDrop = prefab.GetComponent(); - itemDrop.m_description += $" Can Teleport: ({(advPortal.AllowEverything ? "Anything" : string.Join(", ", advPortal.AllowedItems))})"; - }) - }); - } - - if (_obsidianPortalEnabled.Value) + Name = "$item_elderbark $piece_portal", + Description = "$piece_portal_description", + PieceTable = "Hammer", + Category = "Misc", + CraftingStation = "piece_workbench", + Requirements = UpdatePortals.MakeRecipeFromConfig("Ancient Portal", AncientPortalRecipe.Value).ToArray() + }); + + LoadBuildPiece(assetBundle, "portal_obsidian", new PieceConfig() { - LoadBuildPiece(assetBundle, "portal_obsidian", new PieceDef() { - Table = "_HammerPieceTable", - CraftingStation = "piece_workbench", - Resources = MakeRecipeFromConfig("Obsidian Portal", _obsidianPortalRecipe.Value), - OnPrefabAdded = (prefab => { - if (!prefab.TryGetComponent(out var teleport)) - { - teleport = prefab.AddComponent(); - } - - var advPortal = prefab.AddComponent(); - advPortal.DefaultName = "obsidian"; - advPortal.AllowedItems = allowedTypesObsidian.ToList(); - if (_obsidianPortalAllowPreviousPortalItems.Value) - advPortal.AllowedItems.AddRange(allowedTypesAncient); - advPortal.AllowEverything = _obsidianPortalAllowEverything.Value; - - var itemDrop = prefab.GetComponent(); - itemDrop.m_description += $" Can Teleport: ({(advPortal.AllowEverything ? "Anything" : string.Join(", ", advPortal.AllowedItems))})"; - }) - }); - } - - if (_blackMarblePortalEnabled.Value) + Name = "$item_obsidian $piece_portal", + Description = "$piece_portal_description", + PieceTable = "Hammer", + Category = "Misc", + CraftingStation = "piece_workbench", + Requirements = UpdatePortals.MakeRecipeFromConfig("Obsidian Portal", ObsidianPortalRecipe.Value).ToArray() + }); + + LoadBuildPiece(assetBundle, "portal_blackmarble", new PieceConfig() { - LoadBuildPiece(assetBundle, "portal_blackmarble", new PieceDef() { - Table = "_HammerPieceTable", - CraftingStation = "piece_workbench", - Resources = MakeRecipeFromConfig("Black Marble Portal", _blackMarblePortalRecipe.Value), - OnPrefabAdded = (prefab => { - if (!prefab.TryGetComponent(out var teleport)) - { - teleport = prefab.AddComponent(); - } - var advPortal = prefab.AddComponent(); - advPortal.DefaultName = "blackmarble"; - advPortal.AllowedItems = allowedTypesBlackMarble.ToList(); - if (_blackMarblePortalAllowPreviousPortalItems.Value) - { - advPortal.AllowedItems.AddRange(allowedTypesAncient); - advPortal.AllowedItems.AddRange(allowedTypesObsidian); - } - advPortal.AllowEverything = _blackMarblePortalAllowEverything.Value; - - var itemDrop = prefab.GetComponent(); - itemDrop.m_description += $" Can Teleport: ({(advPortal.AllowEverything ? "Anything" : string.Join(", ", advPortal.AllowedItems))})"; - }) - }); - } + Name = "$item_blackmarble $piece_portal", + Description = "$piece_portal_description", + PieceTable = "Hammer", + Category = "Misc", + CraftingStation = "piece_workbench", + Requirements = UpdatePortals.MakeRecipeFromConfig("Black Marble Portal", BlackMarblePortalRecipe.Value).ToArray() + }); + + // Fix up mocked portal connect effects + GameObject fxAncient = assetBundle.LoadAsset("fx_portal_connected_ancient"); + PrefabManager.Instance.AddPrefab(new CustomPrefab(fxAncient, true)); + GameObject fxBlackMarble = assetBundle.LoadAsset("fx_portal_connected_blackmarble"); + PrefabManager.Instance.AddPrefab(new CustomPrefab(fxBlackMarble, true)); + GameObject fxObsidian = assetBundle.LoadAsset("fx_portal_connected_obsidian"); + PrefabManager.Instance.AddPrefab(new CustomPrefab(fxObsidian, true)); _harmony = Harmony.CreateAndPatchAll(Assembly.GetExecutingAssembly(), PluginId); + SetupWatcher(); + + // Ensure configurations apply in singleplayer games + PieceManager.OnPiecesRegistered += UpdatePortalsOnGameStart; - //Add Prefabs to Portal Connections - foreach (var portalPrefab in _portalPrefabs) + // Add Prefabs to Portal Connections + foreach (string portalPrefab in PortalPrefabs) { - AddPortal.Hashes.Add(portalPrefab.GetStableHashCode()); + AddPortal.Hashes.Add(portalPrefab.GetStableHashCode()); } - + // Patch TargetPortal's handle click method, since it does not directly call TeleportWorld.Teleport - var targetPortal = gameObject.GetComponent("TargetPortal.TargetPortal"); + Component targetPortal = gameObject.GetComponent("TargetPortal.TargetPortal"); if (targetPortal) { - var pluginType = targetPortal.GetType(); - var mapType = pluginType.Assembly.GetType("TargetPortal.Map"); - var handlePortalClickMethod = AccessTools.DeclaredMethod(mapType, "HandlePortalClick"); + Type pluginType = targetPortal.GetType(); + Type mapType = pluginType.Assembly.GetType("TargetPortal.Map"); + MethodInfo handlePortalClickMethod = AccessTools.DeclaredMethod(mapType, "HandlePortalClick"); if (handlePortalClickMethod != null) { - _harmony.Patch(handlePortalClickMethod, new HarmonyMethod(typeof(AdvancedPortals), nameof(TargetPortal_HandlePortalClick_Prefix))); - _harmony.Patch(handlePortalClickMethod, null, new HarmonyMethod(typeof(Teleport_Patch), nameof(Teleport_Patch.Generic_Postfix))); - } - } - } - - private static void TargetPortal_HandlePortalClick_Prefix() - { - var playerPos = Player.m_localPlayer.transform.position; - const float searchRadius = 2.0f; - var colliders = Physics.OverlapSphere(playerPos, searchRadius); - TeleportWorld closestTeleport = null; - var minDistSquared = searchRadius * searchRadius + 1; - foreach (var collider in colliders) - { - var twt = collider.gameObject.GetComponent(); - if (twt == null) - continue; - - var tw = twt.GetComponentInParent(); - if (tw == null) - continue; - - var d = collider.transform.position - playerPos; - var distSquared = d.x * d.x + d.y * d.y + d.z * d.z; - if (distSquared < minDistSquared) - { - closestTeleport = tw; - minDistSquared = distSquared; - } - } - - if (closestTeleport != null) - Teleport_Patch.Generic_Prefix(closestTeleport); - } - - private static List MakeRecipeFromConfig(string portalName, string configString) - { - var recipe = new List(); - - var entries = configString.Replace(" ", "").Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); - foreach (var entry in entries) - { - var parts = entry.Split(new[] { ':' }, StringSplitOptions.RemoveEmptyEntries); - if (parts.Length != 2) - { - _instance.Logger.LogError($"Incorrectly formatted recipe for {portalName}! Should be 'ITEM:QUANITY,ITEM2:QUANTITY' etc."); - return new List(); - } - var item = parts[0]; - var amountString = parts[1]; - if (!int.TryParse(amountString, out var amount)) - { - _instance.Logger.LogError($"Incorrectly formatted recipe for {portalName}! Should be 'ITEM:QUANITY,ITEM2:QUANTITY' etc."); - return new List(); + _harmony.Patch(handlePortalClickMethod, new HarmonyMethod( + typeof(Teleport_Patch), nameof(Teleport_Patch.TargetPortal_HandlePortalClick_Prefix))); + _harmony.Patch(handlePortalClickMethod, null, new HarmonyMethod( + typeof(Teleport_Patch), nameof(Teleport_Patch.Generic_Postfix))); } - recipe.Add(new RecipeRequirementConfig() {item = item, amount = amount}); - } - - if (recipe.Count == 0) - { - _instance.Logger.LogError($"Incorrectly formatted recipe for {portalName}! Must have at least one entry, should be 'ITEM:QUANITY,ITEM2:QUANTITY' etc."); } - return recipe; } [UsedImplicitly] private void OnDestroy() { - _instance = null; - _harmony?.UnpatchSelf(); + Config.Save(); } - private ConfigEntry SyncedConfig(string group, string configName, T value, string description, bool synchronizedSetting = true) => SyncedConfig(group, configName, value, new ConfigDescription(description), synchronizedSetting); - - private ConfigEntry SyncedConfig(string group, string configName, T value, ConfigDescription description, bool synchronizedSetting = true) + private void SetupWatcher() { - var configEntry = Config.Bind(group, configName, value, description); - - var syncedConfigEntry = _configSync.AddConfigEntry(configEntry); - syncedConfigEntry.SynchronizedConfig = synchronizedSetting; - - return configEntry; + FileSystemWatcher watcher = new(BepInEx.Paths.ConfigPath, ConfigFileName); + watcher.Changed += ReadConfigValues; + watcher.Created += ReadConfigValues; + watcher.Renamed += ReadConfigValues; + watcher.IncludeSubdirectories = true; + watcher.SynchronizingObject = ThreadingHelper.SynchronizingObject; + watcher.EnableRaisingEvents = true; } - public static AssetBundle LoadAssetBundle(string filename) - { - var assembly = Assembly.GetCallingAssembly(); - var assetBundle = AssetBundle.LoadFromStream(assembly.GetManifestResourceStream($"{assembly.GetName().Name}.{filename}")); + private DateTime _lastReloadTime; + private const long RELOAD_DELAY = 10000000; // One second - return assetBundle; - } - - private static void LoadBuildPiece(AssetBundle assetBundle, string assetName, PieceDef pieceDef) + private void ReadConfigValues(object sender, FileSystemEventArgs e) { - var prefab = assetBundle.LoadAsset(assetName); - RegisteredPieces.Add(prefab, pieceDef); - RegisteredPrefabs.Add(prefab); - } + DateTime now = DateTime.Now; + long time = now.Ticks - _lastReloadTime.Ticks; + if (!File.Exists(ConfigFileFullPath) || time < RELOAD_DELAY) return; - public static void TryRegisterPrefabs(ZNetScene zNetScene) - { - if (zNetScene == null || zNetScene.m_prefabs == null || zNetScene.m_prefabs.Count <= 0) + try { - return; + APLogger.LogInfo("Attempting to reload configuration..."); + Config.Reload(); } - - foreach (var prefab in RegisteredPrefabs) + catch { - if (!zNetScene.m_prefabs.Contains(prefab)) - { - zNetScene.m_prefabs.Add(prefab); - } + APLogger.LogWarning($"There was an issue loading {ConfigFileName}"); + return; } - } - public static void TryRegisterPieces(List pieceTables, List craftingStations) - { - foreach (var entry in RegisteredPieces) + if (ZNet.instance != null && !ZNet.instance.IsDedicated()) { - var prefab = entry.Key; - if (prefab == null) - { - _instance.Logger.LogError($"Tried to register piece but prefab was null!"); - continue; - } - - var pieceDef = entry.Value; - if (pieceDef == null) - { - _instance.Logger.LogError($"Tried to register piece ({prefab}) but pieceDef was null!"); - continue; - } - - var piece = prefab.GetComponent(); - if (piece == null) - { - _instance.Logger.LogError($"Tried to register piece ({prefab}) but Piece component was missing!"); - continue; - } - - var pieceTable = pieceTables.Find(x => x.name == pieceDef.Table); - if (pieceTable == null) - { - _instance.Logger.LogError($"Tried to register piece ({prefab}) but could not find piece table ({pieceDef.Table}) (pieceTables({pieceTables.Count})= {string.Join(", ", pieceTables.Select(x => x.name))})!"); - continue; - } - - if (pieceTable.m_pieces.Contains(prefab)) - { - continue; - } - - pieceTable.m_pieces.Add(prefab); - - var pieceStation = craftingStations.Find(x => x.name == pieceDef.CraftingStation); - piece.m_craftingStation = pieceStation; - - var resources = new List(); - foreach (var resource in pieceDef.Resources) - { - var resourcePrefab = ObjectDB.instance.GetItemPrefab(resource.item); - resources.Add(new Piece.Requirement() { - m_resItem = resourcePrefab.GetComponent(), - m_amount = resource.amount - }); - } - piece.m_resources = resources.ToArray(); - - var portalPrefab = pieceTables.SelectMany(x => x.m_pieces).FirstOrDefault(x => x.name == "portal_wood"); - if (portalPrefab != null) - { - if (portalPrefab.GetComponent() is Piece otherPiece) - { - piece.m_placeEffect.m_effectPrefabs = otherPiece.m_placeEffect.m_effectPrefabs.ToArray(); - } - - if (portalPrefab.GetComponent() is WearNTear otherWearTear) - { - var wearTear = prefab.GetComponent(); - if (wearTear) - { - wearTear.m_destroyedEffect.m_effectPrefabs = otherWearTear.m_destroyedEffect.m_effectPrefabs.ToArray(); - wearTear.m_hitEffect.m_effectPrefabs = otherWearTear.m_hitEffect.m_effectPrefabs.ToArray(); - } - } - } - else - { - _instance.Logger.LogError("No portal_wood prefab!"); - } - - pieceDef.OnPrefabAdded?.Invoke(prefab); + UpdatePortals.UpdatePortalConfigurations(); } + + _lastReloadTime = now; } - public static bool IsObjectDBReady() + private readonly ConfigurationManagerAttributes AdminConfig = new ConfigurationManagerAttributes { IsAdminOnly = true }; + private readonly ConfigurationManagerAttributes ClientConfig = new ConfigurationManagerAttributes { IsAdminOnly = false }; + + private void AddConfig(string section, string key, string description, bool synced, T value, ref ConfigEntry configEntry) { - // Hack, just making sure the built-in items and prefabs have loaded - return ObjectDB.instance != null && ObjectDB.instance.m_items.Count != 0 && ObjectDB.instance.GetItemPrefab("Amber") != null; + string extendedDescription = GetExtendedDescription(description, synced); + configEntry = Config.Bind(section, key, value, + new ConfigDescription(extendedDescription, null, synced ? AdminConfig : ClientConfig)); } - public static void TryRegisterObjects() + public string GetExtendedDescription(string description, bool synchronizedSetting) { - if (!IsObjectDBReady()) - { - return; - } - - ObjectDB.instance.UpdateItemHashes(); - - var pieceTables = new List(); - foreach (var itemPrefab in ObjectDB.instance.m_items) - { - var itemDrop = itemPrefab.GetComponent(); - if (itemDrop == null) - { - _instance.Logger.LogError($"An item without an ItemDrop ({itemPrefab}) exists in ObjectDB.instance.m_items! Don't do this!"); - continue; - } - var item = itemDrop.m_itemData; - if (item != null && item.m_shared.m_buildPieces != null && !pieceTables.Contains(item.m_shared.m_buildPieces)) - { - pieceTables.Add(item.m_shared.m_buildPieces); - } - } - - var craftingStations = new List(); - foreach (var pieceTable in pieceTables) - { - craftingStations.AddRange(pieceTable.m_pieces - .Where(x => x.GetComponent() != null) - .Select(x => x.GetComponent())); - } - - TryRegisterPieces(pieceTables, craftingStations); + return description + (synchronizedSetting ? " [Synced with Server]" : " [Not Synced with Server]"); } - } - [HarmonyPatch(typeof(ZNetScene), nameof(ZNetScene.Awake))] - public static class ZNetScene_Awake_Patch - { - public static bool Prefix(ZNetScene __instance) + public static AssetBundle LoadAssetBundle(string filename) { - AdvancedPortals.TryRegisterPrefabs(__instance); - return true; + Assembly assembly = Assembly.GetCallingAssembly(); + AssetBundle assetBundle = AssetBundle.LoadFromStream(assembly.GetManifestResourceStream($"{assembly.GetName().Name}.{filename}")); + + return assetBundle; } - } - [HarmonyPatch(typeof(ObjectDB), nameof(ObjectDB.CopyOtherDB))] - public static class ObjectDB_CopyOtherDB_Patch - { - public static void Postfix() + private static void LoadBuildPiece(AssetBundle assetBundle, string assetName, PieceConfig piececonfig) { - AdvancedPortals.TryRegisterObjects(); + GameObject prefab = assetBundle.LoadAsset(assetName); + PieceManager.Instance.AddPiece(new CustomPiece(prefab, true, piececonfig)); } - } - [HarmonyPatch(typeof(ObjectDB), nameof(ObjectDB.Awake))] - public static class ObjectDB_Awake_Patch - { - public static void Postfix() + private static void UpdatePortalsOnGameStart() { - AdvancedPortals.TryRegisterObjects(); + UpdatePortals.UpdatePortalConfigurations(); } } } diff --git a/AdvancedPortals/AdvancedPortals.csproj b/AdvancedPortals/AdvancedPortals.csproj index f85d1274e..3d8dc0b87 100644 --- a/AdvancedPortals/AdvancedPortals.csproj +++ b/AdvancedPortals/AdvancedPortals.csproj @@ -1,6 +1,7 @@  + Debug AnyCPU @@ -9,7 +10,8 @@ Properties AdvancedPortals AdvancedPortals - v4.6.2 + v4.7.2 + 10.0 512 true @@ -33,68 +35,9 @@ true - - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Valheim\BepInEx\core\0Harmony.dll - - - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Valheim\valheim_Data\Managed\publicized_assemblies\assembly_utils_publicized.dll - - - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Valheim\valheim_Data\Managed\publicized_assemblies\assembly_valheim_publicized.dll - - - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Valheim\BepInEx\core\BepInEx.dll - - - ..\Libs\ServerSync.dll - - - - - - - - - - - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Valheim\unstripped_corlib\UnityEngine.dll - - - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Valheim\unstripped_corlib\UnityEngine.AssetBundleModule.dll - - - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Valheim\unstripped_corlib\UnityEngine.AudioModule.dll - - - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Valheim\unstripped_corlib\UnityEngine.CoreModule.dll - - - False - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Valheim\unstripped_corlib\UnityEngine.ImageConversionModule.dll - - - False - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Valheim\unstripped_corlib\UnityEngine.PhysicsModule.dll - - - False - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Valheim\unstripped_corlib\UnityEngine.TextRenderingModule.dll - - - - - + - - - - - ServerSync.dll - - - Designer - @@ -102,25 +45,13 @@ - + + - - - - - - - - This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105.The missing file is {0}. - - - - - - - - + + + \ No newline at end of file diff --git a/AdvancedPortals/CHANGELOG.md b/AdvancedPortals/CHANGELOG.md new file mode 100644 index 000000000..924474ba5 --- /dev/null +++ b/AdvancedPortals/CHANGELOG.md @@ -0,0 +1,52 @@ +## 1.1.3 +* Fixed an issue with the AllowEverything config option no longer being used in the teleport check. + +## 1.1.2 +* Changed portals metal check patch to better respect other mods including World Advancement & Progression. + +## 1.1.1 +* Fixed a small typo issue that only manifested when TargetPortal was installed. + +## 1.1.0 +* Overhauled portal appearances to better match vanilla styles! +* Now requires Jotunn to run, please install this new dependency! + +## 1.0.11 +* Update for Valheim version 0.219.13 Bog Witch. + +## 1.0.10 +* ServerSync Update fixing a multiplayer issue + +## 1.0.9 +* Update for 0.217.24 - Hildr's Request + +## 1.0.8 +* Fixing the Portal Saving issue with the changes that were presented in a recent update. +* This now adds the Custom Portal Prefabs to the PortalPrefab list during Game.Awake reducing the need to patch any ZDOMan stuff. + +## 1.0.7 +* Hildir's Request Update 0.217.14 + +## 1.0.6 +* Restored Portal Connections on Dedicated Servers. + +## 1.0.5 +* Updated Portal Connection logic which was preventing Advanced Portals from connecting + +## 1.0.4 +* Updates for 0.216.9 Valheim + +## 1.0.3 +* Vapok fixed a bug that makes Adventure Backpacks work with Advanced Portals + +## 1.0.2 +* Added bronze to Ancient portal transport list (how could I forget?) +* Updated to support other mods that extend the inventory (Thanks Vapok) + +## 1.0.1 +* Added compatibility with AnyPortal and TargetPortal +* Fixed a bug with Obsidian and Black Marble portal recipes +* Added 5 BlackMetal to the default recipe for Black Marble portals (delete your config to automatically use the new recipe) + +## 1.0.0 +* Initial Release \ No newline at end of file diff --git a/AdvancedPortals/ILRepack.targets b/AdvancedPortals/ILRepack.targets deleted file mode 100644 index 4c94bb572..000000000 --- a/AdvancedPortals/ILRepack.targets +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - \ No newline at end of file diff --git a/AdvancedPortals/Properties/AssemblyInfo.cs b/AdvancedPortals/Properties/AssemblyInfo.cs index c62fdc1a0..f16ab6e0e 100644 --- a/AdvancedPortals/Properties/AssemblyInfo.cs +++ b/AdvancedPortals/Properties/AssemblyInfo.cs @@ -31,5 +31,5 @@ // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.8")] -[assembly: AssemblyFileVersion("1.0.8")] +[assembly: AssemblyVersion("1.1.3")] +[assembly: AssemblyFileVersion("1.1.3")] diff --git a/AdvancedPortals/README.md b/AdvancedPortals/README.md index c7ad672eb..339288a68 100644 --- a/AdvancedPortals/README.md +++ b/AdvancedPortals/README.md @@ -1,8 +1,9 @@ -# AdvancedPortals -Author: RandyKnapp -Source: [Github](https://github.com/RandyKnapp/ValheimMods/tree/main/AdvancedPortals) -Discord: [RandyKnapp's Mod Community](https://discord.gg/randyknappmods) -Patreon: [Randy's Patreon](https://www.patreon.com/randyknapp) +# Advanced Portals + +Author: [RandyKnapp](https://discord.gg/ZNhYeavv3C) +Source: [Github](https://github.com/OrianaVenture/Randy_Vapok_ValheimMods/tree/main/AdvancedPortals) +Patreon: [patreon.com/randyknapp](https://www.patreon.com/randyknapp) +Discord: [RandyKnapp's Mod Community](https://discord.gg/ZNhYeavv3C) Adds three new portals to provide a lore-friendly and balanced way to reduce the item-transport slog! @@ -13,53 +14,23 @@ Adds three new portals to provide a lore-friendly and balanced way to reduce the * **Black Marble Portal:** Allows teleporting anything * *Requires:* 20 Black Marble, BlackMetal 5, 2 Refined Eitr -Includes ServerSync. - -## Configuration: - -### Ancient Portal +## Version 1.1.0! - * Ancient Portal Enabled: Enable this portal. If false, the portal will not be buildable. - * Ancient Portal Recipe: The items needed to build the Ancient portal. Must be in the following format: "ITEM1:QUANTITY,ITEM2:QUANTITY,..." where each ITEM is the item ID ([found here](https://valheim-modding.github.io/Jotunn/data/objects/item-list.html)), and QUANTITY is an integer. - * Ancient Portal Allowed Items: The items that will be allowed to teleport through the Ancient Portal. Must be in the following format: "ITEM1,ITEM2,ITEM3,..." where ITEM is the item ID. - * Ancient Portal Allow Everything: If set to true, the Allowed Items will be ignored and all items will be teleportable through this portal. Default=false. +As of the 1.1.0 update Jotunn is required to run this mod. A version check will be performed on server connection to ensure all players have the mod installed properly. -### Obsidian Portal +Configurations should sync on servers and live update on changing the randyknapp.mods.advancedportals.cfg file. - * Obsidian Portal Enabled: Enable this portal. If false, the portal will not be buildable. - * Obsidian Portal Recipe: The items needed to build the Obsidian portal. (same format as Ancient Portal Recipe) - * Obsidian Portal Allowed Items: The items that will be allowed to teleport through the Obsidian Portal. (same format as Ancient Portal Allowed Items) - * Obsidian Portal Allow Everything: If set to true, the Allowed Items will be ignored and all items will be teleportable through this portal. Default=false. - * Obsidian Portal Use All Previous: If set to true, automatically allow all the Allowed Items from the Ancient Portal through this portal too. Default=true. +## Configuration: -### Black Marble Portal +Each portal can be configured: - * Black Marble Portal Enabled: Enable this portal. If false, the portal will not be buildable. - * Black Marble Portal Recipe: The items needed to build the Black Marble portal. (same format as Ancient Portal Recipe) - * Black Marble Portal Allowed Items: The items that will be allowed to teleport through the Black Marble Portal. (same format as Ancient Portal Allowed Items) - * Black Marble Portal Allow Everything: If set to true, the Allowed Items will be ignored and all items will be teleportable through this portal. Default=true; - * Black Marble Portal Use All Previous: If set to true, automatically allow all the Allowed Items from the Ancient Portal and Obsidian Portal through this portal too. Default=true. + * Enabled: Enable building the portal. Existing portals of this type will not be removed. + * Recipe: Items needed to build the portal in the format "ITEM1:QUANTITY,ITEM2:QUANTITY,..." where each ITEM is the item ID ([found here](https://valheim-modding.github.io/Jotunn/data/objects/item-list.html)), and QUANTITY is an integer. + * Allowed Items: Items allowed to teleport through the portal in the format: "ITEM1,ITEM2,ITEM3,..." where ITEM is the item ID. + * Allow Everything: Allow all items through the portal. + * Use All Previous: For the Obsidian portal also include the Allowed Items from the Ancient portal. For the Black Marble portal also include the Allowed Items from both the Ancient and Obsidian portals. ## Installation: - * Nexus: Drop the AdvancedPortals.dll right into your BepInEx/plugins folder - * ThunderStore: Use r2modman to install, or manually drop the dll into your BepInEx/plugins folder - -### Changelist: -#### 1.0.6 -* Restored Portal Connections on Dedicated Servers. -#### 1.0.5 - * Updated Portal Connection logic which was preventing Advanced Portals from connecting -#### 1.0.4 - * Updates for Valheim 0.217.5 -#### 1.0.3 - * Vapok fixed a bug that makes Adventure Backpacks work with Advanced Portals -#### 1.0.2 - * Added bronze to Ancient portal transport list (how could I forget?) - * Updated to support other mods that extend the inventory (Thanks Vapok) -#### 1.0.1 - * Added compatibility with AnyPortal and TargetPortal - * Fixed a bug with Obsidian and Black Marble portal recipes - * Added 5 BlackMetal to the default recipe for Black Marble portals (delete your config to automatically use the new recipe) -#### 1.0.0 - * Initial Release \ No newline at end of file + * Manual: Drop the AdvancedPortals.dll into your BepInEx/plugins folder. Download Jotunn and install similarly. + * ThunderStore: When using a thunderstore mod manager the files should be placed in the correct directory for you. Dependencies should install automatically. diff --git a/AdvancedPortals/Teleport_Patch.cs b/AdvancedPortals/Teleport_Patch.cs deleted file mode 100644 index 00b1c1137..000000000 --- a/AdvancedPortals/Teleport_Patch.cs +++ /dev/null @@ -1,82 +0,0 @@ -using HarmonyLib; - -namespace AdvancedPortals -{ - [HarmonyPatch] - public static class Teleport_Patch - { - public static AdvancedPortal CurrentAdvancedPortal; - - public static void Generic_Prefix(TeleportWorld __instance) - { - CurrentAdvancedPortal = __instance.GetComponent(); - } - - public static void Generic_Postfix() - { - CurrentAdvancedPortal = null; - } - - [HarmonyPatch(typeof(TeleportWorld), nameof(TeleportWorld.UpdatePortal))] - [HarmonyPrefix] - public static void TeleportWorld_UpdatePortal_Prefix(TeleportWorld __instance) - { - CurrentAdvancedPortal = __instance.GetComponent(); - } - - [HarmonyPatch(typeof(TeleportWorld), nameof(TeleportWorld.SetText))] - [HarmonyPostfix] - public static void TeleportWorld_SetText_Postfix() - { - Game.instance.ConnectPortals(); - } - - - [HarmonyPatch(typeof(TeleportWorld), nameof(TeleportWorld.UpdatePortal))] - [HarmonyPostfix] - public static void TeleportWorld_UpdatePortal_Postfix() - { - CurrentAdvancedPortal = null; - } - - [HarmonyPatch(typeof(TeleportWorld), nameof(TeleportWorld.Teleport))] - [HarmonyPrefix] - public static void TeleportWorld_Teleport_Prefix(TeleportWorld __instance) - { - CurrentAdvancedPortal = __instance.GetComponent(); - } - - [HarmonyPatch(typeof(TeleportWorld), nameof(TeleportWorld.Teleport))] - [HarmonyPostfix] - public static void TeleportWorld_Teleport_Postfix() - { - CurrentAdvancedPortal = null; - } - - [HarmonyPatch(typeof(Inventory), nameof(Inventory.IsTeleportable))] - [HarmonyPrefix] - public static bool Inventory_IsTeleportable_Pretfix(Inventory __instance, ref bool __result) - { - if (CurrentAdvancedPortal == null) - return true; - - if (CurrentAdvancedPortal.AllowEverything) - { - __result = true; - return false; - } - - foreach (var itemData in __instance.GetAllItems()) - { - if (!itemData.m_shared.m_teleportable && itemData.m_dropPrefab != null && !CurrentAdvancedPortal.AllowedItems.Contains(itemData.m_dropPrefab.name)) - { - __result = false; - return false; - } - } - - __result = true; - return false; - } - } -} diff --git a/AdvancedPortals/AdvancedPortals-Header.png b/AdvancedPortals/image/AdvancedPortals-Header.png similarity index 100% rename from AdvancedPortals/AdvancedPortals-Header.png rename to AdvancedPortals/image/AdvancedPortals-Header.png diff --git a/AdvancedPortals/AdvancedPortals-Tile.png b/AdvancedPortals/image/AdvancedPortals-Tile.png similarity index 100% rename from AdvancedPortals/AdvancedPortals-Tile.png rename to AdvancedPortals/image/AdvancedPortals-Tile.png diff --git a/AdvancedPortals/Screenshots/Screenshot 2023-01-11 145724.png b/AdvancedPortals/image/Screenshot 2023-01-11 145724.png similarity index 100% rename from AdvancedPortals/Screenshots/Screenshot 2023-01-11 145724.png rename to AdvancedPortals/image/Screenshot 2023-01-11 145724.png diff --git a/AdvancedPortals/Screenshots/Screenshot 2023-01-11 15.02.48.png b/AdvancedPortals/image/Screenshot 2023-01-11 15.02.48.png similarity index 100% rename from AdvancedPortals/Screenshots/Screenshot 2023-01-11 15.02.48.png rename to AdvancedPortals/image/Screenshot 2023-01-11 15.02.48.png diff --git a/AdvancedPortals/Screenshots/Screenshot 2023-01-11 150221.png b/AdvancedPortals/image/Screenshot 2023-01-11 150221.png similarity index 100% rename from AdvancedPortals/Screenshots/Screenshot 2023-01-11 150221.png rename to AdvancedPortals/image/Screenshot 2023-01-11 150221.png diff --git a/AdvancedPortals/manifest.json b/AdvancedPortals/manifest.json deleted file mode 100644 index ef4320dcf..000000000 --- a/AdvancedPortals/manifest.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "name": "AdvancedPortals", - "version_number": "1.0.8", - "website_url": "https://github.com/RandyKnapp/ValheimMods/tree/main/AdvancedPortals", - "description": "Add new, lore-friendly and balanced portals to allow some items to be teleported.", - "dependencies": [] -} \ No newline at end of file diff --git a/AdvancedPortals/src/AddPortals.cs b/AdvancedPortals/src/AddPortals.cs new file mode 100644 index 000000000..4b29a555c --- /dev/null +++ b/AdvancedPortals/src/AddPortals.cs @@ -0,0 +1,25 @@ +using System.Collections.Generic; +using System.Reflection; +using HarmonyLib; +using JetBrains.Annotations; + +namespace AdvancedPortals +{ + [HarmonyPatch] + public static class AddPortal + { + public static readonly HashSet Hashes = new HashSet(); + + [UsedImplicitly] + private static IEnumerable TargetMethods() => new[] + { + AccessTools.DeclaredMethod(typeof(Game), nameof(Game.Awake)), + }; + + [UsedImplicitly] + private static void Prefix(Game __instance) + { + __instance.PortalPrefabHash.AddRange(Hashes); + } + } +} \ No newline at end of file diff --git a/AdvancedPortals/src/AdvancedPortal.cs b/AdvancedPortals/src/AdvancedPortal.cs new file mode 100644 index 000000000..17a33b4c3 --- /dev/null +++ b/AdvancedPortals/src/AdvancedPortal.cs @@ -0,0 +1,15 @@ +using System.Collections.Generic; +using UnityEngine; + +namespace AdvancedPortals +{ + public class AdvancedPortal : MonoBehaviour + { + public List AllowedItems = new List(); + public bool AllowEverything; + + private void Awake() + { + } + } +} diff --git a/AdvancedPortals/src/Teleport_Patch.cs b/AdvancedPortals/src/Teleport_Patch.cs new file mode 100644 index 000000000..c77f82589 --- /dev/null +++ b/AdvancedPortals/src/Teleport_Patch.cs @@ -0,0 +1,120 @@ +using HarmonyLib; +using UnityEngine; + +namespace AdvancedPortals +{ + [HarmonyPatch] + public static class Teleport_Patch + { + public static AdvancedPortal CurrentAdvancedPortal; + + public static void TargetPortal_HandlePortalClick_Prefix() + { + Vector3 playerPos = Player.m_localPlayer.transform.position; + const float searchRadius = 2.0f; + Collider[] colliders = Physics.OverlapSphere(playerPos, searchRadius); + TeleportWorld closestTeleport = null; + float minDistSquared = searchRadius * searchRadius + 1; + foreach (Collider collider in colliders) + { + TeleportWorldTrigger twt = collider.gameObject.GetComponent(); + if (twt == null) + { + continue; + } + + TeleportWorld tw = twt.GetComponentInParent(); + if (tw == null) + { + continue; + } + + Vector3 d = collider.transform.position - playerPos; + float distSquared = d.x * d.x + d.y * d.y + d.z * d.z; + if (distSquared < minDistSquared) + { + closestTeleport = tw; + minDistSquared = distSquared; + } + } + + if (closestTeleport != null) + { + Generic_Prefix(closestTeleport); + } + } + + public static void Generic_Prefix(TeleportWorld __instance) + { + CurrentAdvancedPortal = __instance.GetComponent(); + } + + public static void Generic_Postfix() + { + CurrentAdvancedPortal = null; + } + + [HarmonyPatch(typeof(TeleportWorld), nameof(TeleportWorld.UpdatePortal))] + [HarmonyPrefix] + public static void TeleportWorld_UpdatePortal_Prefix(TeleportWorld __instance) + { + CurrentAdvancedPortal = __instance.GetComponent(); + } + + [HarmonyPatch(typeof(TeleportWorld), nameof(TeleportWorld.UpdatePortal))] + [HarmonyPostfix] + public static void TeleportWorld_UpdatePortal_Postfix() + { + CurrentAdvancedPortal = null; + } + + [HarmonyPatch(typeof(TeleportWorld), nameof(TeleportWorld.Teleport))] + [HarmonyPrefix] + public static void TeleportWorld_Teleport_Prefix(TeleportWorld __instance) + { + CurrentAdvancedPortal = __instance.GetComponent(); + } + + [HarmonyPatch(typeof(TeleportWorld), nameof(TeleportWorld.Teleport))] + [HarmonyPostfix] + public static void TeleportWorld_Teleport_Postfix() + { + CurrentAdvancedPortal = null; + } + + // High priority to run before other mods + [HarmonyPatch(typeof(Inventory), nameof(Inventory.IsTeleportable))] + [HarmonyPostfix] + [HarmonyPriority(Priority.High)] + public static void Inventory_IsTeleportable_Pretfix(Inventory __instance, ref bool __result) + { + if (CurrentAdvancedPortal == null || __result == true) + { + // Do not change result for non-advanced portals, or if it already allowed to teleport + return; + } + + if (CurrentAdvancedPortal.AllowEverything) + { + __result = true; + return; + } + + foreach (ItemDrop.ItemData itemData in __instance.GetAllItems()) + { + if (itemData.m_dropPrefab == null) + { + continue; + } + + if (!itemData.m_shared.m_teleportable && + !CurrentAdvancedPortal.AllowedItems.Contains(itemData.m_dropPrefab.name)) + { + return; + } + } + + __result = true; + } + } +} diff --git a/AdvancedPortals/src/UpdatePortals.cs b/AdvancedPortals/src/UpdatePortals.cs new file mode 100644 index 000000000..c2c5e48e5 --- /dev/null +++ b/AdvancedPortals/src/UpdatePortals.cs @@ -0,0 +1,189 @@ +using Jotunn.Configs; +using Jotunn.Managers; +using System; +using System.Collections.Generic; +using System.Linq; +using UnityEngine; + +namespace AdvancedPortals +{ + internal class UpdatePortals + { + public static List MakeRecipeFromConfig(string portalName, string configString) + { + List recipe = new List(); + + string[] entries = configString.Replace(" ", "").Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); + foreach (string entry in entries) + { + string[] parts = entry.Split(new[] { ':' }, StringSplitOptions.RemoveEmptyEntries); + if (parts.Length != 2) + { + AdvancedPortals.APLogger.LogError($"Incorrectly formatted recipe for {portalName}! " + + $"Should be 'ITEM:QUANITY,ITEM2:QUANTITY' etc."); + continue; + } + + string item = parts[0]; + string amountString = parts[1]; + if (!int.TryParse(amountString, out int amount)) + { + AdvancedPortals.APLogger.LogError($"Incorrectly formatted recipe for {portalName}! " + + $"Should be 'ITEM:QUANITY,ITEM2:QUANTITY' etc."); + continue; + } + + recipe.Add(new RequirementConfig + { + Item = item, + Amount = amount, + Recover = true + }); + } + + return recipe; + } + + public static void UpdatePortalConfigurations() + { + GameObject hammerPrefab = PrefabManager.Instance.GetPrefab("Hammer"); + + if (hammerPrefab == null) + { + AdvancedPortals.APLogger.LogError($"Hammer not found, could not update portal configurations."); + return; + } + + if (!hammerPrefab.TryGetComponent(out ItemDrop itemdrop)) + { + AdvancedPortals.APLogger.LogError($"Hammer not found, could not update portal configurations."); + return; + } + + var pieceTable = itemdrop.m_itemData.m_shared.m_buildPieces; + + foreach (string portal in AdvancedPortals.PortalPrefabs) + { + GameObject portalPrefab = PrefabManager.Instance.GetPrefab(portal); + + if (portalPrefab == null) + { + AdvancedPortals.APLogger.LogError($"{portal} not found, could not update configurations."); + continue; + } + + if (!portalPrefab.TryGetComponent(out AdvancedPortal component)) + { + AdvancedPortals.APLogger.LogError($"AdvancedPortal not found, could not update {portal} configurations."); + continue; + } + + string name = string.Empty; + string recipeString = string.Empty; + bool enabled = true; + + switch (portal) + { + case "portal_ancient": + enabled = AdvancedPortals.AncientPortalEnabled.Value; + name = "Ancient Portal"; + recipeString = AdvancedPortals.AncientPortalRecipe.Value; + component.AllowEverything = AdvancedPortals.AncientPortalAllowEverything.Value; + component.AllowedItems = GetListFromString(AdvancedPortals.AncientPortalAllowedItems.Value); + break; + case "portal_obsidian": + enabled = AdvancedPortals.ObsidianPortalEnabled.Value; + name = "Obsidian Portal"; + recipeString = AdvancedPortals.ObsidianPortalRecipe.Value; + component.AllowEverything = AdvancedPortals.ObsidianPortalAllowEverything.Value; + component.AllowedItems = GetListFromString(AdvancedPortals.ObsidianPortalAllowedItems.Value); + if (AdvancedPortals.ObsidianPortalAllowPreviousPortalItems.Value) + { + component.AllowedItems.AddRange(GetListFromString(AdvancedPortals.AncientPortalAllowedItems.Value)); + } + break; + case "portal_blackmarble": + enabled = AdvancedPortals.BlackMarblePortalEnabled.Value; + name = "Black Marble Portal"; + recipeString = AdvancedPortals.BlackMarblePortalRecipe.Value; + component.AllowEverything = AdvancedPortals.BlackMarblePortalAllowEverything.Value; + component.AllowedItems = GetListFromString(AdvancedPortals.BlackMarblePortalAllowedItems.Value); + if (AdvancedPortals.BlackMarblePortalAllowPreviousPortalItems.Value) + { + component.AllowedItems.AddRange(GetListFromString(AdvancedPortals.AncientPortalAllowedItems.Value)); + component.AllowedItems.AddRange(GetListFromString(AdvancedPortals.ObsidianPortalAllowedItems.Value)); + } + break; + default: + AdvancedPortals.APLogger.LogError($"Could not update {portal} configurations. Not a configurable advance portal."); + continue; + } + + Piece piece = portalPrefab.GetComponent(); + if (piece == null) + { + AdvancedPortals.APLogger.LogError($"Could not update portal configurations for {portal}. Piece not found."); + } + + GameObject pieceTablePiece = pieceTable.m_pieces.Find(x => x.name == Utils.GetPrefabName(portalPrefab.name)); + if (!enabled) + { + if (pieceTablePiece != null) + { + // Remove existing + pieceTable.m_pieces.Remove(pieceTablePiece); + } + } + else + { + List config = MakeRecipeFromConfig(name, recipeString); + List reqs = new List(); + foreach (RequirementConfig req in config) + { + var newReq = req.GetRequirement(); + GameObject resource = PrefabManager.Instance.GetPrefab(req.Item); + ItemDrop resourceItemDrop = resource.GetComponent(); + if (resourceItemDrop != null) + { + newReq.m_resItem = resourceItemDrop; + reqs.Add(newReq); + } + else + { + AdvancedPortals.APLogger.LogError($"Could not add requirement {req.Item}, for {portal}"); + } + } + + piece.m_resources = reqs.ToArray(); + piece.m_description = GetAdvancedPortalDescription(component.AllowEverything, component.AllowedItems); + + if (pieceTablePiece != null) + { + // Update existing + var tablePiece = pieceTablePiece.GetComponent(); + tablePiece.m_resources = reqs.ToArray(); + } + else + { + // Add new + pieceTable.m_pieces.Add(portalPrefab); + } + } + } + } + + /// + /// Returns a UI description of the portal with the allowed teleportation rules. + /// + private static string GetAdvancedPortalDescription(bool allowEverything, List items) + { + return $"$piece_portal_description Can Teleport: ({(allowEverything ? "Anything" : string.Join(", ", items))})"; + } + + private static List GetListFromString(string items) + { + return items.Replace(" ", "") + .Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).ToList(); + } + } +} diff --git a/AdvancedPortals/icon.png b/AdvancedPortals/thunderstore/icon.png similarity index 100% rename from AdvancedPortals/icon.png rename to AdvancedPortals/thunderstore/icon.png diff --git a/AdvancedPortals/thunderstore/manifest.json b/AdvancedPortals/thunderstore/manifest.json new file mode 100644 index 000000000..23109c053 --- /dev/null +++ b/AdvancedPortals/thunderstore/manifest.json @@ -0,0 +1,10 @@ +{ + "name": "AdvancedPortals", + "version_number": "1.1.1", + "website_url": "https://discord.gg/ZNhYeavv3C", + "description": "Add new, lore-friendly and balanced portals to allow some items to be teleported.", + "dependencies": [ + "denikson-BepInExPack_Valheim-5.4.2333", + "ValheimModding-Jotunn-2.27.0" + ] +} \ No newline at end of file diff --git a/BuildTasks.props b/BuildTasks.props new file mode 100644 index 000000000..6eb505bb9 --- /dev/null +++ b/BuildTasks.props @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Common/Common.projitems b/Common/Common.projitems index bd2b60c14..c0d416dd4 100644 --- a/Common/Common.projitems +++ b/Common/Common.projitems @@ -9,15 +9,6 @@ Common - - - - - - - - - - + \ No newline at end of file diff --git a/Common/ConfigPositionedElement.cs b/Common/src/ConfigPositionedElement.cs similarity index 100% rename from Common/ConfigPositionedElement.cs rename to Common/src/ConfigPositionedElement.cs diff --git a/Common/Consumables.cs b/Common/src/Consumables.cs similarity index 100% rename from Common/Consumables.cs rename to Common/src/Consumables.cs diff --git a/Common/GameObjectExtensions.cs b/Common/src/GameObjectExtensions.cs similarity index 100% rename from Common/GameObjectExtensions.cs rename to Common/src/GameObjectExtensions.cs diff --git a/Common/GotDestroyed.cs b/Common/src/GotDestroyed.cs similarity index 100% rename from Common/GotDestroyed.cs rename to Common/src/GotDestroyed.cs diff --git a/Common/MultiValueDictionary.cs b/Common/src/MultiValueDictionary.cs similarity index 100% rename from Common/MultiValueDictionary.cs rename to Common/src/MultiValueDictionary.cs diff --git a/Common/PlayerExtensions.cs b/Common/src/PlayerExtensions.cs similarity index 100% rename from Common/PlayerExtensions.cs rename to Common/src/PlayerExtensions.cs diff --git a/Common/PrefabCreator.cs b/Common/src/PrefabCreator.cs similarity index 98% rename from Common/PrefabCreator.cs rename to Common/src/PrefabCreator.cs index b78d4aee4..d25427f3d 100644 --- a/Common/PrefabCreator.cs +++ b/Common/src/PrefabCreator.cs @@ -1,5 +1,5 @@ -using System.Collections.Generic; -using BepInEx.Logging; +using BepInEx.Logging; +using System.Collections.Generic; using UnityEngine; namespace Common diff --git a/Common/SerializableVector3.cs b/Common/src/SerializableVector3.cs similarity index 98% rename from Common/SerializableVector3.cs rename to Common/src/SerializableVector3.cs index fa00c0130..894dc3768 100644 --- a/Common/SerializableVector3.cs +++ b/Common/src/SerializableVector3.cs @@ -1,5 +1,5 @@ -using UnityEngine; -using System; +using System; +using UnityEngine; // ReSharper disable InconsistentNaming namespace Common diff --git a/Common/Utils.cs b/Common/src/Utils.cs similarity index 94% rename from Common/Utils.cs rename to Common/src/Utils.cs index 29dd4c539..1a2c31324 100644 --- a/Common/Utils.cs +++ b/Common/src/Utils.cs @@ -1,9 +1,7 @@ using System; using System.Collections.Generic; -using System.IO; using System.Linq; using System.Reflection; -using BepInEx; using UnityEngine; using UnityEngine.Rendering; @@ -38,7 +36,8 @@ public static string GetObjectString(object obj, string indent) return output; } - public static Sprite LoadSpriteFromFile(string spritePath) + // TODO: Fix or remove + /*public static Sprite LoadSpriteFromFile(string spritePath) { spritePath = Path.Combine(Paths.PluginPath, spritePath); if (File.Exists(spritePath)) @@ -52,13 +51,13 @@ public static Sprite LoadSpriteFromFile(string spritePath) } return null; - } + }*/ - public static Sprite LoadSpriteFromFile(string modFolder, string iconName) + /*public static Sprite LoadSpriteFromFile(string modFolder, string iconName) { var spritePath = Path.Combine(modFolder, iconName); return LoadSpriteFromFile(spritePath); - } + }*/ public static string RemoveBetween(string s, string from, string to) { diff --git a/ConsoleHelper/ConsoleHelper.csproj b/ConsoleHelper/ConsoleHelper.csproj index e1f2160a9..759c0dd00 100644 --- a/ConsoleHelper/ConsoleHelper.csproj +++ b/ConsoleHelper/ConsoleHelper.csproj @@ -1,6 +1,7 @@  + Debug AnyCPU @@ -9,7 +10,8 @@ Properties ConsoleHelper ConsoleHelper - v4.6.1 + v4.7.2 + 10.0 512 true @@ -31,31 +33,9 @@ prompt 4 - - - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Valheim\BepInEx\core\0Harmony.dll - - - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Valheim\valheim_Data\Managed\publicized_assemblies\assembly_valheim_publicized.dll - - - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Valheim\BepInEx\core\BepInEx.dll - - - - - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Valheim\unstripped_corlib\UnityEngine.dll - - - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Valheim\unstripped_corlib\UnityEngine.CoreModule.dll - - - - xcopy "$(TargetDir)$(TargetFileName)" "C:\Program Files (x86)\Steam\steamapps\common\Valheim\BepInEx\plugins\" /q /y /i - \ No newline at end of file diff --git a/CreatureLevelAndLootControl/API.cs b/CreatureLevelAndLootControl/API.cs index bc4fe7b82..155832040 100644 --- a/CreatureLevelAndLootControl/API.cs +++ b/CreatureLevelAndLootControl/API.cs @@ -1,55 +1,42 @@ using HarmonyLib; using System.Reflection; using System.Reflection.Emit; +using JetBrains.Annotations; #nullable enable namespace CreatureLevelControl { + [PublicAPI] public static class API { - private static readonly Assembly? targetAssembly; - static API() + public static bool IsLoaded() { - if (!((API.targetAssembly = API.LoadAssembly()) != (Assembly) null)) - return; - Harmony harmony = new Harmony("org.bepinex.plugins.creaturelevelcontrol.API"); - foreach (MethodInfo original in ((IEnumerable) typeof (API).GetMethods(BindingFlags.DeclaredOnly | BindingFlags.Static | BindingFlags.Public)).Where((Func) (m => m.Name != "IsLoaded" && m.Name != "LoadAssembly"))) - harmony.Patch((MethodBase) original, transpiler: new HarmonyMethod(AccessTools.DeclaredMethod(typeof (API), "transpiler"))); + return false; } - private static IEnumerable transpiler( - IEnumerable _, - MethodBase original) + public static bool IsEnabled() { - System.Type[] parameters = ((IEnumerable) original.GetParameters()).Select((Func) (p => - { - System.Type type = p.ParameterType; - if (type.Assembly == Assembly.GetExecutingAssembly()) - type = API.targetAssembly.GetType(type.FullName); - return type; - })).ToArray(); - MethodBase originalMethod = (MethodBase) API.targetAssembly.GetType("CreatureLevelControl.API").GetMethod(original.Name, parameters); - for (int i = 0; i < parameters.Length; ++i) - yield return new CodeInstruction(OpCodes.Ldarg, (object) i); - yield return new CodeInstruction(OpCodes.Call, (object) originalMethod); - yield return new CodeInstruction(OpCodes.Ret); + return false; } - public static Assembly? LoadAssembly() => ((IEnumerable) AppDomain.CurrentDomain.GetAssemblies()).SingleOrDefault((Func) (a => a.GetName().Name == "CreatureLevelControl")); - - public static bool IsLoaded() => API.LoadAssembly() != (Assembly) null; - - public static bool IsEnabled() => false; - - public static bool IsInfusionEnabled() => false; - - public static bool IsExtraEffectEnabled() => false; - - public static bool IsAffixEnabled() => false; - - public static int GetWorldLevel() => 0; + public static bool IsInfusionEnabled() + { + return false; + } + public static bool IsExtraEffectEnabled() + { + return false; + } + public static bool IsAffixEnabled() + { + return false; + } + public static int GetWorldLevel() + { + return 0; + } public static float[] LevelProbabilities( Character? character, @@ -66,16 +53,20 @@ public static int LevelRand(Character character) return UnityEngine.Random.Range(0, 10) != 0 ? 2 : 3; } - public static bool HasAffixBoss(Character character) => false; - + public static bool HasAffixBoss(Character character) + { + return false; + } public static BossAffix GetAffixBoss(Character character) => BossAffix.None; public static void SetAffixBoss(Character character, BossAffix affix) { } - public static bool HasExtraEffectCreature(Character character) => false; - + public static bool HasExtraEffectCreature(Character character) + { + return false; + } public static CreatureExtraEffect GetExtraEffectCreature(Character character) => CreatureExtraEffect.None; public static void SetExtraEffectCreature(Character character) @@ -86,8 +77,10 @@ public static void SetExtraEffectCreature(Character character, CreatureExtraEffe { } - public static bool HasInfusionCreature(Character character) => false; - + public static bool HasInfusionCreature(Character character) + { + return false; + } public static CreatureInfusion GetInfusionCreature(Character character) => CreatureInfusion.None; public static void SetInfusionCreature(Character character) @@ -98,7 +91,7 @@ public static void SetInfusionCreature(Character character, CreatureInfusion inf { } - public static Character? GetTwinBoss(Character boss) => (Character) null; + public static Character? GetTwinBoss(Character boss) => null; public static bool DropItemOnDeath(ItemDrop.ItemData item) => !item.m_shared.m_questItem && !item.m_equipped; } diff --git a/CreatureLevelAndLootControl/CreatureLevelAndLootControl.csproj b/CreatureLevelAndLootControl/CreatureLevelAndLootControl.csproj index 43fdb8049..a60952ca5 100644 --- a/CreatureLevelAndLootControl/CreatureLevelAndLootControl.csproj +++ b/CreatureLevelAndLootControl/CreatureLevelAndLootControl.csproj @@ -1,36 +1,11 @@ - enable disable CreatureLevelControlAPI CreatureLevelControl latestmajor - net461;net462 + net472 - - - M:\Code\VapokModBase\References\BepInEx\5.4.2101\BepInEx\core\0Harmony.dll - - - M:\Code\VapokModBase\References\Valheim\0.216.8\assembly_guiutils_publicized.dll - - - M:\Code\VapokModBase\References\Valheim\0.216.8\assembly_utils_publicized.dll - - - M:\Code\VapokModBase\References\Valheim\0.216.8\assembly_valheim_publicized.dll - - - ..\..\VapokModBase\References\CLLC\CreatureLevelControl.dll - - - - - M:\Code\VapokModBase\References\BepInEx\5.4.2101\unstripped_corlib\UnityEngine.dll - - - M:\Code\VapokModBase\References\BepInEx\5.4.2101\unstripped_corlib\UnityEngine.CoreModule.dll - - + diff --git a/DvergerColor/DvergerColor.cs b/DvergerColor/DvergerColor.cs index b8e03ee08..295327c37 100644 --- a/DvergerColor/DvergerColor.cs +++ b/DvergerColor/DvergerColor.cs @@ -6,7 +6,7 @@ namespace DvergerColor { - [BepInPlugin(PluginId, "Dverger Color", "1.0.3")] + [BepInPlugin(PluginId, "Dverger Color", "1.0.6")] [BepInProcess("valheim.exe")] public class DvergerColor : BaseUnityPlugin { @@ -51,10 +51,5 @@ private void Awake() _harmony = Harmony.CreateAndPatchAll(Assembly.GetExecutingAssembly(), PluginId); } - - private void OnDestroy() - { - _harmony.UnpatchSelf(); - } } } diff --git a/DvergerColor/ImprovedDvergerCirclet-1.0.2.zip b/DvergerColor/ImprovedDvergerCirclet-1.0.2.zip deleted file mode 100644 index 3d80f9bf7..000000000 Binary files a/DvergerColor/ImprovedDvergerCirclet-1.0.2.zip and /dev/null differ diff --git a/DvergerColor/ImprovedDvergerCirclet.csproj b/DvergerColor/ImprovedDvergerCirclet.csproj index 9a778cfbc..55b29ef9a 100644 --- a/DvergerColor/ImprovedDvergerCirclet.csproj +++ b/DvergerColor/ImprovedDvergerCirclet.csproj @@ -1,6 +1,7 @@  + Debug AnyCPU @@ -9,7 +10,8 @@ Properties DvergerColor ImprovedDvergerCirclet - v4.6.1 + v4.7.2 + 10.0 512 true @@ -34,54 +36,13 @@ - + - + - - - False - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Valheim\BepInEx\core\0Harmony.dll - - - False - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Valheim\valheim_Data\Managed\publicized_assemblies\assembly_utils_publicized.dll - - - False - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Valheim\valheim_Data\Managed\publicized_assemblies\assembly_valheim_publicized.dll - - - False - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Valheim\BepInEx\core\BepInEx.dll - - - False - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Valheim\unstripped_corlib\UnityEngine.dll - - - False - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Valheim\unstripped_corlib\UnityEngine.CoreModule.dll - - - False - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Valheim\unstripped_corlib\UnityEngine.ImageConversionModule.dll - - - False - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Valheim\unstripped_corlib\UnityEngine.InputLegacyModule.dll - - - False - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Valheim\unstripped_corlib\UnityEngine.TextRenderingModule.dll - - - - xcopy "$(TargetDir)\$(TargetFileName)" "C:\Program Files (x86)\Steam\steamapps\common\Valheim\BepInEx\plugins\" /q /y /i - \ No newline at end of file diff --git a/DvergerColor/ImprovedDvergerCirclet.zip b/DvergerColor/ImprovedDvergerCirclet.zip deleted file mode 100644 index f839a13a1..000000000 Binary files a/DvergerColor/ImprovedDvergerCirclet.zip and /dev/null differ diff --git a/DvergerColor/Properties/AssemblyInfo.cs b/DvergerColor/Properties/AssemblyInfo.cs index c24de2c73..d4d572415 100644 --- a/DvergerColor/Properties/AssemblyInfo.cs +++ b/DvergerColor/Properties/AssemblyInfo.cs @@ -31,5 +31,5 @@ // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] +[assembly: AssemblyVersion("1.0.7.0")] +[assembly: AssemblyFileVersion("1.0.7.0")] diff --git a/DvergerColor/ImprovedDvergerCirclet-Header.png b/DvergerColor/image/ImprovedDvergerCirclet-Header.png similarity index 100% rename from DvergerColor/ImprovedDvergerCirclet-Header.png rename to DvergerColor/image/ImprovedDvergerCirclet-Header.png diff --git a/DvergerColor/ImprovedDvergerCirclet-Tile.png b/DvergerColor/image/ImprovedDvergerCirclet-Tile.png similarity index 100% rename from DvergerColor/ImprovedDvergerCirclet-Tile.png rename to DvergerColor/image/ImprovedDvergerCirclet-Tile.png diff --git a/DvergerColor/Screenshot 2021-02-21 21.51.25.png b/DvergerColor/image/Screenshot 2021-02-21 21.51.25.png similarity index 100% rename from DvergerColor/Screenshot 2021-02-21 21.51.25.png rename to DvergerColor/image/Screenshot 2021-02-21 21.51.25.png diff --git a/DvergerColor/Screenshot 2021-02-21 21.51.33.png b/DvergerColor/image/Screenshot 2021-02-21 21.51.33.png similarity index 100% rename from DvergerColor/Screenshot 2021-02-21 21.51.33.png rename to DvergerColor/image/Screenshot 2021-02-21 21.51.33.png diff --git a/DvergerColor/Screenshot 2021-02-21 21.51.37.png b/DvergerColor/image/Screenshot 2021-02-21 21.51.37.png similarity index 100% rename from DvergerColor/Screenshot 2021-02-21 21.51.37.png rename to DvergerColor/image/Screenshot 2021-02-21 21.51.37.png diff --git a/DvergerColor/Screenshot 2021-02-21 21.51.41.png b/DvergerColor/image/Screenshot 2021-02-21 21.51.41.png similarity index 100% rename from DvergerColor/Screenshot 2021-02-21 21.51.41.png rename to DvergerColor/image/Screenshot 2021-02-21 21.51.41.png diff --git a/DvergerColor/manifest.json b/DvergerColor/manifest.json deleted file mode 100644 index 0b027ee86..000000000 --- a/DvergerColor/manifest.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "name": "ImprovedDvergerCirclet", - "version_number": "1.0.4", - "website_url": "https://github.com/RandyKnapp/ValheimMods/tree/main/DvergerColor", - "description": "Changes the color of the light emitted by the Dverger circlet to white. Adds several beam width options, and a pool of radiance (point light) option.", - "dependencies": [] -} diff --git a/DvergerColor/VisEquipment_Patch.cs b/DvergerColor/src/VisEquipment_Patch.cs similarity index 100% rename from DvergerColor/VisEquipment_Patch.cs rename to DvergerColor/src/VisEquipment_Patch.cs diff --git a/DvergerColor/icon.png b/DvergerColor/thunderstore/icon.png similarity index 100% rename from DvergerColor/icon.png rename to DvergerColor/thunderstore/icon.png diff --git a/DvergerColor/thunderstore/manifest.json b/DvergerColor/thunderstore/manifest.json new file mode 100644 index 000000000..86d181321 --- /dev/null +++ b/DvergerColor/thunderstore/manifest.json @@ -0,0 +1,9 @@ +{ + "name": "ImprovedDvergerCirclet", + "version_number": "1.0.7", + "website_url": "https://github.com/RandyKnapp/ValheimMods/tree/main/DvergerColor", + "description": "Changes the color of the light emitted by the Dverger circlet to white. Adds several beam width options, and a pool of radiance (point light) option.", + "dependencies": [ + "denikson-BepInExPack_Valheim-5.4.2200" + ] +} diff --git a/EpicLoot-Addon-Helheim/Console_Patch.cs b/EpicLoot-Addon-Helheim/Console_Patch.cs deleted file mode 100644 index 8d775353a..000000000 --- a/EpicLoot-Addon-Helheim/Console_Patch.cs +++ /dev/null @@ -1,34 +0,0 @@ -using System.Linq; -using HarmonyLib; - -namespace EpicLoot_Addon_Helheim -{ - [HarmonyPatch(typeof(Console), "InputText")] - public static class Console_Patch - { - public static bool Prefix(Console __instance) - { - var input = __instance.m_input.text; - var args = input.Split(' '); - if (args.Length == 0) - { - return true; - } - - var player = Player.m_localPlayer; - - if (Command("helheim", args)) - { - var level = args.Length >= 2 ? int.Parse(args[1]) : 0; - Helheim.SetLevel(level); - } - - return true; - } - - private static bool Command(string command, params string[] args) - { - return args.Contains(command); - } - } -} diff --git a/EpicLoot-Addon-Helheim/EnvMan_Patch.cs b/EpicLoot-Addon-Helheim/EnvMan_Patch.cs deleted file mode 100644 index dea9ee27e..000000000 --- a/EpicLoot-Addon-Helheim/EnvMan_Patch.cs +++ /dev/null @@ -1,159 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using HarmonyLib; -using Random = UnityEngine.Random; - -namespace EpicLoot_Addon_Helheim -{ - [HarmonyPatch] - public static class EnvMan_Patch - { - public static int PreviousHelheimLevel; - public static Heightmap.Biome PreviousBiome; - public static string PreviousEnvName; - - //Awake - [HarmonyPatch(typeof(EnvMan), nameof(EnvMan.Awake))] - public static class EnvMan_Awake_Patch - { - public static void Prefix(EnvMan __instance) - { - var eikthyr = __instance.m_environments.Find(x => x.m_name == "Eikthyr"); - AddHelheimEnvironments(__instance, eikthyr, 1); - - var ashrain = __instance.m_environments.Find(x => x.m_name == "Ashrain"); - AddHelheimEnvironments(__instance, ashrain, 2); - - var moder = __instance.m_environments.Find(x => x.m_name == "Moder"); - AddHelheimEnvironments(__instance, moder, 3); - } - } - - [HarmonyPatch(typeof(EnvMan), nameof(EnvMan.UpdateEnvironment))] - public static class EnvMan_UpdateEnvironment_Patch - { - public static bool Prefix(EnvMan __instance, long sec, Heightmap.Biome biome) - { - if (HasEnvChanged(Helheim.HelheimLevel, biome, __instance.GetCurrentEnvironment().m_name)) - { - Helheim.Log($"Helheim: {Helheim.HelheimLevel}, Biome: {biome}, Current: {__instance.GetCurrentEnvironment().m_name}, Wet: {__instance.IsWet()}, Freezing: {__instance.IsFreezing()}"); - } - - var allowBaseMethod = true; - var forceSwitch = Helheim.HelheimLevel != PreviousHelheimLevel; - __instance.m_firstEnv = forceSwitch; - - if (Helheim.HelheimLevel > 0) - { - var num = sec / __instance.m_environmentDuration; - if (!forceSwitch && __instance.m_currentEnv.m_name.StartsWith("Helheim") && __instance.m_environmentPeriod == num && __instance.m_currentBiome == biome) - { - return false; - } - - __instance.m_environmentPeriod = num; - __instance.m_currentBiome = biome; - var state = Random.state; - Random.InitState((int)num); - var availableEnvironments = __instance.GetAvailableEnvironments(biome); - if (availableEnvironments != null && availableEnvironments.Count > 0) - { - var biomeEnv = __instance.SelectWeightedEnvironment(availableEnvironments); - var helheimEnv = GetHelheimEnvironment(__instance, biomeEnv, Helheim.HelheimLevel); - __instance.QueueEnvironment(helheimEnv); - Helheim.LogWarning($"Changing Environment: {helheimEnv.m_name}"); - } - Random.state = state; - allowBaseMethod = false; - } - else - { - if (forceSwitch) - { - __instance.m_currentBiome = Heightmap.Biome.None; - } - } - - PreviousHelheimLevel = Helheim.HelheimLevel; - PreviousBiome = biome; - PreviousEnvName = __instance.GetCurrentEnvironment().m_name; - return allowBaseMethod; - } - } - - public static bool HasEnvChanged(int helheimLevel, Heightmap.Biome biome, string envName) - { - return PreviousHelheimLevel != helheimLevel || - PreviousBiome != biome || - PreviousEnvName != envName; - } - - public static EnvSetup GetHelheimEnvironment(EnvMan envMan, EnvSetup biomeEnv, int level) - { - var name = GetHelheimEnvName(level, biomeEnv.m_isWet, biomeEnv.m_isFreezing); - return envMan.GetEnv(name); - } - - public static void AddHelheimEnvironments(EnvMan envMan, EnvSetup baseEnv, int level) - { - var helheimEnvBase = baseEnv.Clone(); - helheimEnvBase.m_name = GetHelheimEnvName(level, false, false); - //FindAndCopyEnvObjectByName(envMan, "Thunder", helheimEnvBase); - envMan.m_environments.Add(helheimEnvBase); - - var helheimEnvWet = helheimEnvBase.Clone(); - helheimEnvWet.m_name = GetHelheimEnvName(level, true, false); - helheimEnvWet.m_isWet = true; - FindAndCopyPsystemByName(envMan, level == 1 ? "LightRain" : "Rain", helheimEnvWet); - envMan.m_environments.Add(helheimEnvWet); - - var helheimEnvFreezing = helheimEnvBase.Clone(); - helheimEnvFreezing.m_name = GetHelheimEnvName(level, false, true); - helheimEnvFreezing.m_isFreezing = true; - FindAndCopyPsystemByName(envMan, level == 1 ? "Snow" : "SnowStorm", helheimEnvFreezing); - envMan.m_environments.Add(helheimEnvFreezing); - } - - public static void FindAndCopyEnvObjectByName(EnvMan envMan, string name, EnvSetup targetEnv) - { - if (targetEnv.m_envObject.name == name) - { - return; - } - - foreach (var env in envMan.m_environments) - { - if (env.m_envObject.name.Equals(name)) - { - targetEnv.m_envObject = env.m_envObject; - return; - } - } - } - - public static void FindAndCopyPsystemByName(EnvMan envMan, string name, EnvSetup targetEnv) - { - if (targetEnv.m_psystems.ToList().Exists(x => x.name == name)) - { - return; - } - - foreach (var env in envMan.m_environments) - { - foreach (var psystem in env.m_psystems) - { - if (psystem.name.Equals(name)) - { - targetEnv.m_psystems = targetEnv.m_psystems.AddToArray(psystem); - return; - } - } - } - } - - public static string GetHelheimEnvName(int level, bool wet, bool freezing) - { - return $"Helheim{level}{(wet ? "_Wet" : (freezing ? "_Freezing" : ""))}"; - } - } -} diff --git a/EpicLoot-Addon-Helheim/EpicLoot-Addon-Helheim.csproj b/EpicLoot-Addon-Helheim/EpicLoot-Addon-Helheim.csproj deleted file mode 100644 index b78b7b44e..000000000 --- a/EpicLoot-Addon-Helheim/EpicLoot-Addon-Helheim.csproj +++ /dev/null @@ -1,95 +0,0 @@ - - - - - Debug - AnyCPU - {FB508E8D-0C64-4EC6-B746-F668F510295F} - Library - Properties - EpicLoot_Addon_Helheim - EpicLoot-Addon-Helheim - v4.6.2 - 512 - true - - - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - true - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Valheim\BepInEx\core\0Harmony.dll - - - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Valheim\valheim_Data\Managed\publicized_assemblies\assembly_valheim_publicized.dll - - - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Valheim\BepInEx\core\BepInEx.dll - - - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Valheim\BepInEx\plugins\CreatureLevelControl.dll - - - ..\Libs\fastJSON.dll - - - - - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Valheim\unstripped_corlib\UnityEngine.dll - - - False - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Valheim\unstripped_corlib\UnityEngine.AssetBundleModule.dll - - - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Valheim\unstripped_corlib\UnityEngine.CoreModule.dll - - - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Valheim\unstripped_corlib\UnityEngine.UI.dll - - - - - - - - - - - - - helheim - - - - - fastJSON.dll - - - - - - - - - xcopy "$(TargetDir)$(TargetFileName)" "C:\Program Files (x86)\Steam\steamapps\common\Valheim\BepInEx\plugins\$(ProjectName)\" /q /y /i - -xcopy "$(ProjectDir)helheim.json" "C:\Program Files (x86)\Steam\steamapps\common\Valheim\BepInEx\plugins\$(ProjectName)\" /q /y /i - - \ No newline at end of file diff --git a/EpicLoot-Addon-Helheim/Helheim.cs b/EpicLoot-Addon-Helheim/Helheim.cs deleted file mode 100644 index 3a6a66712..000000000 --- a/EpicLoot-Addon-Helheim/Helheim.cs +++ /dev/null @@ -1,165 +0,0 @@ -using System; -using System.IO; -using System.Linq; -using System.Reflection; -using BepInEx; -using BepInEx.Configuration; -using fastJSON; -using HarmonyLib; -using UnityEngine; - -namespace EpicLoot_Addon_Helheim -{ - public class Assets - { - public GameObject HelheimTextPrefab; - } - - [BepInPlugin(PluginId, "Epic Loot Addon - Helheim", Version)] - [BepInDependency("randyknapp.mods.epicloot")] - [BepInDependency("org.bepinex.plugins.creaturelevelcontrol")] - public class Helheim : BaseUnityPlugin - { - private const string PluginId = "randyknapp.mods.epicloot.addon.helheim"; - private const string Version = "1.0.0"; - - private static ConfigEntry _loggingEnabled; - - private static Helheim _instance; - - public const int HelheimLevelCount = 5; - public static int HelheimLevel { get; private set; } - public static readonly Assets Assets = new Assets(); - - public static event Action HelheimLevelChanged; - - public HelheimConfig HelheimConfig; - - public Helheim() - { - LoadEmbeddedAssembly("fastJSON.dll"); - } - - public void Awake() - { - _instance = this; - _loggingEnabled = Config.Bind("Logging", "Logging Enabled", false, "Enable logging"); - - var assetBundle = LoadAssetBundle("helheim"); - Assets.HelheimTextPrefab = assetBundle.LoadAsset("HelheimText"); - - HelheimConfig = LoadJsonFile("helheim.json"); - - Harmony.CreateAndPatchAll(Assembly.GetExecutingAssembly(), PluginId); - - CreatureLevelControl.CreatureLevelControl.difficulty.Value = CreatureLevelControl.CreatureLevelControl.Difficulty.Custom; - } - - private static void LoadEmbeddedAssembly(string assemblyName) - { - var stream = GetManifestResourceStream(assemblyName); - if (stream == null) - { - LogError($"Could not load embedded assembly ({assemblyName})!"); - return; - } - - using (stream) - { - var data = new byte[stream.Length]; - stream.Read(data, 0, data.Length); - Assembly.Load(data); - } - } - - public static AssetBundle LoadAssetBundle(string filename) - { - using (var stream = GetManifestResourceStream(filename)) - { - return AssetBundle.LoadFromStream(stream); - } - } - - public static Stream GetManifestResourceStream(string filename) - { - var assembly = Assembly.GetCallingAssembly(); - var fullname = assembly.GetManifestResourceNames().SingleOrDefault(x => x.EndsWith(filename)); - if (!string.IsNullOrEmpty(fullname)) - { - return assembly.GetManifestResourceStream(fullname); - } - - return null; - } - - public static T LoadJsonFile(string filename) where T : class - { - var jsonFile = LoadJsonText(filename); - T result; - try - { - result = string.IsNullOrEmpty(jsonFile) ? null : JSON.ToObject(jsonFile); - } - catch (Exception) - { - LogError($"Could not parse file '{filename}'! Errors in JSON!"); - throw; - } - - return result; - } - - public static string LoadJsonText(string filename) - { - var jsonFileName = GetAssetPath(filename); - return !string.IsNullOrEmpty(jsonFileName) ? File.ReadAllText(jsonFileName) : null; - } - - public static string GetAssetPath(string assetName) - { - var assembly = typeof(Helheim).Assembly; - var assetFileName = Path.Combine(Path.GetDirectoryName(assembly.Location) ?? string.Empty, assetName); - if (!File.Exists(assetFileName)) - { - LogError($"Could not find asset ({assetName})"); - return null; - } - - return assetFileName; - } - - public static void SetLevel(int level) - { - if (HelheimLevel != level) - { - HelheimLevel = Mathf.Clamp(level, 0, HelheimLevelCount); - HelheimLevelChanged?.Invoke(HelheimLevel); - LogWarning($"Helheim level: {HelheimLevel}"); - } - } - - public static void Log(string message) - { - if (_loggingEnabled.Value) - { - _instance.Logger.LogInfo(message); - } - } - - public static void LogWarning(string message) - { - if (_loggingEnabled.Value) - { - _instance.Logger.LogWarning(message); - } - } - - public static void LogError(string message) - { - if (_loggingEnabled.Value) - { - _instance.Logger.LogError(message); - } - } - } -} diff --git a/EpicLoot-Addon-Helheim/HelheimConfig.cs b/EpicLoot-Addon-Helheim/HelheimConfig.cs deleted file mode 100644 index c23b8322e..000000000 --- a/EpicLoot-Addon-Helheim/HelheimConfig.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace EpicLoot_Addon_Helheim -{ - [Serializable] - public class HelheimLevelConfig - { - public int Level; - } - - [Serializable] - public class HelheimConfig - { - public List HelheimLevels = new List(); - } -} diff --git a/EpicLoot-Addon-Helheim/HelheimText.cs b/EpicLoot-Addon-Helheim/HelheimText.cs deleted file mode 100644 index 19ef51f31..000000000 --- a/EpicLoot-Addon-Helheim/HelheimText.cs +++ /dev/null @@ -1,49 +0,0 @@ -using HarmonyLib; -using UnityEngine; -using UnityEngine.UI; - -namespace EpicLoot_Addon_Helheim -{ - [HarmonyPatch(typeof(Hud), nameof(Hud.Awake))] - public static class Hud_Awake_Patch - { - public static void Postfix(Hud __instance) - { - var helheimTextObject = Object.Instantiate(Helheim.Assets.HelheimTextPrefab, __instance.m_rootObject.transform, false); - helheimTextObject.AddComponent(); - } - } - - public class HelheimText : MonoBehaviour - { - public Text Label; - - public void Awake() - { - Label = GetComponentInChildren(); - } - - public void Update() - { - Label.text = Helheim.HelheimLevel == 0 ? "Valheim" : $"Helheim: Level {Helheim.HelheimLevel} - {GetHelheimName()}"; - } - - // Niflhel - the edge of Helheim - // Baldyrborg - the funeral pyre of Baldyr - // Myrkstaðr - the place of darkness - // Frostsalr - the house of frost - // Skaðihaugar - the mounds of death - private static string GetHelheimName() - { - switch (Helheim.HelheimLevel) - { - case 1: return "Niflhel"; - case 2: return "Baldyrborg"; - case 3: return "Myrkstaðr"; - case 4: return "Frostsalr"; - case 5: return "Skaðihaugar"; - default: return ""; - } - } - } -} diff --git a/EpicLoot-Addon-Helheim/Notes.md b/EpicLoot-Addon-Helheim/Notes.md deleted file mode 100644 index 126d7d271..000000000 --- a/EpicLoot-Addon-Helheim/Notes.md +++ /dev/null @@ -1,6 +0,0 @@ -## Helheim Notes - -- Create world at Helheim level, can't change existing world -- Store "Helheim" and "HelheimLevel{X}" in the GlobalKeys of the world -- If you go to a Valheim world with a character that's been in Helheim, you get Helheim Sickness for 30 minutes (?) that doesn't go away automatically, even if you come back to Helheim -- Helheim sickness gives you -80% health regen and stamina regen, and -100 carry weight \ No newline at end of file diff --git a/EpicLoot-Addon-Helheim/Properties/AssemblyInfo.cs b/EpicLoot-Addon-Helheim/Properties/AssemblyInfo.cs deleted file mode 100644 index c659a3033..000000000 --- a/EpicLoot-Addon-Helheim/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System.Reflection; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("EpicLoot-Addon-Helheim")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("EpicLoot-Addon-Helheim")] -[assembly: AssemblyCopyright("Copyright © 2021")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("fb508e8d-0c64-4ec6-b746-f668f510295f")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0")] -[assembly: AssemblyFileVersion("1.0.0")] diff --git a/EpicLoot-Addon-Helheim/helheim.json b/EpicLoot-Addon-Helheim/helheim.json deleted file mode 100644 index 704fbe499..000000000 --- a/EpicLoot-Addon-Helheim/helheim.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "HelheimLevels" : [ - { - "Level" : 1 - }, - { - "Level" : 2 - } - ] -} diff --git a/EpicLoot-Addon-MCE/EpicLoot-Addon-MCE.csproj b/EpicLoot-Addon-MCE/EpicLoot-Addon-MCE.csproj deleted file mode 100644 index c690a24e8..000000000 --- a/EpicLoot-Addon-MCE/EpicLoot-Addon-MCE.csproj +++ /dev/null @@ -1,77 +0,0 @@ - - - - - Debug - AnyCPU - {8B9A7665-F688-4E13-B62A-F62DB7E065C4} - Library - Properties - EpicLoot_Addon_MCE - EpicLoot-Addon-MCE - v4.6.1 - 512 - true - - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - true - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Valheim\BepInEx\core\0Harmony.dll - - - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Valheim\valheim_Data\Managed\publicized_assemblies\assembly_utils_publicized.dll - - - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Valheim\valheim_Data\Managed\publicized_assemblies\assembly_valheim_publicized.dll - - - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Valheim\BepInEx\core\BepInEx.dll - - - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Valheim\BepInEx\plugins\ModConfigEnforcer.dll - - - - - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Valheim\unstripped_corlib\UnityEngine.dll - - - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Valheim\unstripped_corlib\UnityEngine.CoreModule.dll - - - - - - - - - - {963cf4f8-ba02-49fc-8884-97e781e6bc18} - EpicLoot - - - - - - - - xcopy "$(TargetDir)$(TargetFileName)" "C:\Program Files (x86)\Steam\steamapps\common\Valheim\BepInEx\plugins\$(ProjectName)\" /q /y /i - - \ No newline at end of file diff --git a/EpicLoot-Addon-MCE/EpicLoot-Addon-MCE.zip b/EpicLoot-Addon-MCE/EpicLoot-Addon-MCE.zip deleted file mode 100644 index 4c7a19a78..000000000 Binary files a/EpicLoot-Addon-MCE/EpicLoot-Addon-MCE.zip and /dev/null differ diff --git a/EpicLoot-Addon-MCE/EpicLoot-Header.png b/EpicLoot-Addon-MCE/EpicLoot-Header.png deleted file mode 100644 index 2308f932f..000000000 Binary files a/EpicLoot-Addon-MCE/EpicLoot-Header.png and /dev/null differ diff --git a/EpicLoot-Addon-MCE/EpicLoot-Tile.png b/EpicLoot-Addon-MCE/EpicLoot-Tile.png deleted file mode 100644 index d99558046..000000000 Binary files a/EpicLoot-Addon-MCE/EpicLoot-Tile.png and /dev/null differ diff --git a/EpicLoot-Addon-MCE/EpicLoot_Addon_MCE.cs b/EpicLoot-Addon-MCE/EpicLoot_Addon_MCE.cs deleted file mode 100644 index b1ae95171..000000000 --- a/EpicLoot-Addon-MCE/EpicLoot_Addon_MCE.cs +++ /dev/null @@ -1,138 +0,0 @@ -using System.Reflection; -using BepInEx; -using BepInEx.Configuration; -using Common; -using EpicLoot; -using EpicLoot.Adventure; -using EpicLoot.Crafting; -using EpicLoot.GatedItemType; -using HarmonyLib; -using ModConfigEnforcer; - -namespace EpicLoot_Addon_MCE -{ - [BepInPlugin(PluginId, "Epic Loot Addon - MCE", Version)] - [BepInDependency("randyknapp.mods.epicloot")] - [BepInDependency("pfhoenix.modconfigenforcer")] - public class EpicLoot_Addon_MCE : BaseUnityPlugin - { - private const string PluginId = "randyknapp.mods.epicloot.addon.mce"; - private const string Version = "1.0.4"; - - private static ConfigVariable _gatedItemTypeModeConfig; - private static ConfigVariable _bossTrophyDropMode; - private static ConfigVariable _bossTrophyDropPlayerRange; - private static ConfigVariable _adventureModeEnabled; - - private static readonly JsonFileConfigVariable _lootConfigFile = new JsonFileConfigVariable("loottables.json"); - private static readonly JsonFileConfigVariable _magicEffectsConfigFile = new JsonFileConfigVariable("magiceffects.json"); - private static readonly JsonFileConfigVariable _itemInfoConfigFile = new JsonFileConfigVariable("iteminfo.json"); - private static readonly JsonFileConfigVariable _recipesConfigFile = new JsonFileConfigVariable("recipes.json"); - private static readonly JsonFileConfigVariable _enchantCostsConfigFile = new JsonFileConfigVariable("enchantcosts.json"); - private static readonly JsonFileConfigVariable _itemNamesConfigFile = new JsonFileConfigVariable("itemnames.json"); - private static readonly JsonFileConfigVariable _adventureDataConfigFile = new JsonFileConfigVariable("adventuredata.json"); - - public void Awake() - { - var epicLootConfig = EpicLoot.EpicLoot.GetConfigObject(); - ConfigManager.RegisterMod(EpicLoot.EpicLoot.PluginId, epicLootConfig); - - ConfigManager.ServerConfigReceived += InitializeConfig; - - _gatedItemTypeModeConfig = ReplaceConfigVar(epicLootConfig, "Balance", "Item Drop Limits"); - _bossTrophyDropMode = ReplaceConfigVar(epicLootConfig, "Balance", "Boss Trophy Drop Mode"); - _bossTrophyDropPlayerRange = ReplaceConfigVar(epicLootConfig, "Balance", "Boss Trophy Drop Player Range"); - _adventureModeEnabled = ReplaceConfigVar(epicLootConfig, "Balance", "Adventure Mode Enabled"); - - ConfigManager.RegisterModConfigVariable(EpicLoot.EpicLoot.PluginId, _lootConfigFile); - ConfigManager.RegisterModConfigVariable(EpicLoot.EpicLoot.PluginId, _magicEffectsConfigFile); - ConfigManager.RegisterModConfigVariable(EpicLoot.EpicLoot.PluginId, _itemInfoConfigFile); - ConfigManager.RegisterModConfigVariable(EpicLoot.EpicLoot.PluginId, _recipesConfigFile); - ConfigManager.RegisterModConfigVariable(EpicLoot.EpicLoot.PluginId, _enchantCostsConfigFile); - ConfigManager.RegisterModConfigVariable(EpicLoot.EpicLoot.PluginId, _itemNamesConfigFile); - ConfigManager.RegisterModConfigVariable(EpicLoot.EpicLoot.PluginId, _adventureDataConfigFile); - - Harmony.CreateAndPatchAll(Assembly.GetExecutingAssembly(), PluginId); - } - - public static ConfigVariable ReplaceConfigVar(ConfigFile epicLootConfig, string section, string key) - { - if (epicLootConfig.TryGetEntry(section, key, out var gatedItemTypeConfigEntry)) - { - return ConfigManager.RegisterModConfigVariable( - EpicLoot.EpicLoot.PluginId, - gatedItemTypeConfigEntry.Definition.Key, - (T)gatedItemTypeConfigEntry.DefaultValue, - gatedItemTypeConfigEntry.Definition.Section, - gatedItemTypeConfigEntry.Description.Description, - false); - } - - return null; - } - - public static void InitializeConfig() - { - LootRoller.Initialize(_lootConfigFile.Value, _itemInfoConfigFile.Value); - MagicItemEffectDefinitions.Initialize(_magicEffectsConfigFile.Value); - GatedItemTypeHelper.Initialize(_itemInfoConfigFile.Value); - RecipesHelper.Initialize(_recipesConfigFile.Value); - EnchantCostsHelper.Initialize(_enchantCostsConfigFile.Value); - MagicItemNames.Initialize(_itemNamesConfigFile.Value); - AdventureDataManager.Initialize(_adventureDataConfigFile.Value); - } - - [HarmonyPatch(typeof(EpicLoot.EpicLoot), "GetGatedItemTypeMode")] - public static class EpicLoot_GetGatedItemTypeMode_Patch - { - public static bool Prefix(ref GatedItemTypeMode __result) - { - __result = _gatedItemTypeModeConfig.Value; - return false; - } - } - - [HarmonyPatch(typeof(EpicLoot.EpicLoot), nameof(EpicLoot.EpicLoot.GetBossTrophyDropMode))] - public static class EpicLoot_GetBossTrophyDropMode_Patch - { - public static bool Prefix(ref BossDropMode __result) - { - __result = _bossTrophyDropMode.Value; - return false; - } - } - - [HarmonyPatch(typeof(EpicLoot.EpicLoot), nameof(EpicLoot.EpicLoot.GetBossTrophyDropPlayerRange))] - public static class EpicLoot_GetBossTrophyDropPlayerRange_Patch - { - public static bool Prefix(ref float __result) - { - __result = _bossTrophyDropPlayerRange.Value; - return false; - } - } - - [HarmonyPatch(typeof(EpicLoot.EpicLoot), nameof(EpicLoot.EpicLoot.IsAdventureModeEnabled))] - public static class EpicLoot_IsAdventureModeEnabled_Patch - { - public static bool Prefix(ref bool __result) - { - __result = _adventureModeEnabled.Value; - return false; - } - } - - [HarmonyPatch(typeof(ZNet), "Start")] - public static class ZNet_Start_Patch - { - public static void Postfix() - { - // This resets EpicLoot to using its local config when starting a local game - if (ConfigManager.ShouldUseLocalConfig) - { - EpicLoot.EpicLoot.InitializeConfig(); - } - } - } - } -} diff --git a/EpicLoot-Addon-MCE/JsonFileConfigVariable.cs b/EpicLoot-Addon-MCE/JsonFileConfigVariable.cs deleted file mode 100644 index 3cf7d5c89..000000000 --- a/EpicLoot-Addon-MCE/JsonFileConfigVariable.cs +++ /dev/null @@ -1,47 +0,0 @@ -using System; -using ModConfigEnforcer; -using UnityEngine; - -namespace EpicLoot_Addon_MCE -{ - public class JsonFileConfigVariable : IConfigVariable where T : class - { - private readonly string _sourceFile; - private T _config; - - public JsonFileConfigVariable(string sourceFile) - { - _sourceFile = sourceFile; - } - - public string GetName() => _sourceFile; - public bool LocalOnly() => false; - public Type GetValueType() => typeof(T); - public T Value => (T)GetValue(); - - public object GetValue() - { - return ConfigManager.ShouldUseLocalConfig ? EpicLoot.EpicLoot.LoadJsonFile(_sourceFile) : _config; - } - - public void SetValue(object o) - { - // Will never be called - } - - public void Serialize(ZPackage zpg) - { - Debug.LogWarning($"Serialized config ({_sourceFile})"); - var configJson = EpicLoot.EpicLoot.LoadJsonText(_sourceFile); - zpg.Write(configJson); - } - - public bool Deserialize(ZPackage zpg) - { - Debug.LogWarning($"Deserialized config ({_sourceFile})"); - var configJson = zpg.ReadString(); - _config = EpicLoot.EpicLoot.JsonToObject(configJson); - return _config != null; - } - } -} diff --git a/EpicLoot-Addon-MCE/Properties/AssemblyInfo.cs b/EpicLoot-Addon-MCE/Properties/AssemblyInfo.cs deleted file mode 100644 index 593536c98..000000000 --- a/EpicLoot-Addon-MCE/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("EpicLoot-Addon-MCE")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("EpicLoot-Addon-MCE")] -[assembly: AssemblyCopyright("Copyright © 2021")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("8b9a7665-f688-4e13-b62a-f62db7e065c4")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.3")] -[assembly: AssemblyFileVersion("1.0.3")] diff --git a/EpicLoot-Addon-MCE/README.md b/EpicLoot-Addon-MCE/README.md deleted file mode 100644 index 5027bc4bb..000000000 --- a/EpicLoot-Addon-MCE/README.md +++ /dev/null @@ -1,36 +0,0 @@ -# Epic Loot Addon - MCE v1.0.3 -Author: RandyKnapp -Source: [Github](https://github.com/RandyKnapp/ValheimMods/tree/main/EpicLoot-Addon-MCE/) - -## What does it do? - -This mod addon allows EpicLoot config files to be enforced by the server using ModConfigEnforcer. All loot tables, recipes, even item names will be controlled by the server for all connected clients. Both clients and server are required to have this addon installed to get server enforced configs. - -#### Currently Enforced by MCE: - - * Config Value: Balance / Item Drop Limits - * Config File: enchantcosts.json - * Config File: iteminfo.json - * Config File: itemnames.json - * Config File: loottables.json - * Config File: magiceffects.json - * Config File: recipes.json - -## How to Install - -For both the server and the client: - -1. Install [EpicLoot](https://www.nexusmods.com/valheim/mods/387) (requires version 0.6.4 or later) -2. Install [ModConfigEnforcer](https://www.nexusmods.com/valheim/mods/460) (requires version 1.4.1 or later) -3. Copy EpicLoot-Addon-MCE.dll to your BepInEx plugins folder - -## Patch Notes -#### Version 1.0.0 - * Initial release, allows MCE to control config values -#### Version 1.0.1 - * Added config for adventure stuff update -#### Version 1.0.2 - * Added config for boss trophy drops -#### Version 1.0.3 - * Added additional config for boss trophy drops - * Added adventure mode toggle diff --git a/EpicLoot-UnityLib/AugmentUI.cs b/EpicLoot-UnityLib/AugmentUI.cs deleted file mode 100644 index 324292943..000000000 --- a/EpicLoot-UnityLib/AugmentUI.cs +++ /dev/null @@ -1,335 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using JetBrains.Annotations; -using UnityEngine; -using UnityEngine.UI; - -namespace EpicLoot_UnityLib -{ - public class AugmentUI : EnchantingTableUIPanelBase - { - public Text AvailableEffectsText; - public Text AvailableEffectsHeader; - public Scrollbar AvailableEffectsScrollbar; - public List AugmentSelectors; - - [Header("Cost")] - public Text CostLabel; - public MultiSelectItemList CostList; - - public delegate List GetAugmentableItemsDelegate(); - public delegate List> GetAugmentableEffectsDelegate(ItemDrop.ItemData item); - public delegate string GetAvailableEffectsDelegate(ItemDrop.ItemData item, int augmentIndex); - public delegate List GetAugmentCostDelegate(ItemDrop.ItemData item, int augmentIndex); - // Returns the augment choice dialog - public delegate GameObject AugmentItemDelegate(ItemDrop.ItemData item, int augmentIndex); - - public static GetAugmentableItemsDelegate GetAugmentableItems; - public static GetAugmentableEffectsDelegate GetAugmentableEffects; - public static GetAvailableEffectsDelegate GetAvailableEffects; - public static GetAugmentCostDelegate GetAugmentCost; - public static AugmentItemDelegate AugmentItem; - - private int _augmentIndex; - private GameObject _choiceDialog; - - public override void Awake() - { - base.Awake(); - - for (var index = 0; index < AugmentSelectors.Count; index++) - { - var augmentSelector = AugmentSelectors[index]; - augmentSelector.onValueChanged.AddListener(OnAugmentSelectorToggled); - } - } - - [UsedImplicitly] - public void OnEnable() - { - _augmentIndex = -1; - foreach (var augmentSelector in AugmentSelectors) - { - augmentSelector.isOn = false; - } - - if (AvailableEffectsHeader != null) - { - var augmentChoices = 2; - var featureValues = EnchantingTableUI.instance.SourceTable.GetFeatureCurrentValue(EnchantingFeature.Augment); - if (!float.IsNaN(featureValues.Item1)) - augmentChoices = (int)featureValues.Item1; - - var colorPre = augmentChoices > 2 ? "" : ""; - var colorPost = augmentChoices > 2 ? "" : ""; - AvailableEffectsHeader.text = Localization.instance.Localize($"$mod_epicloot_augment_availableeffects {colorPre}($mod_epicloot_augment_choices){colorPost}", augmentChoices.ToString()); - } - - OnAugmentIndexChanged(); - - var items = GetAugmentableItems(); - AvailableItems.SetItems(items.Cast().ToList()); - DeselectAll(); - } - - public override void Update() - { - base.Update(); - - if (!_locked && ZInput.IsGamepadActive()) - { - if (ZInput.GetButtonDown("JoyButtonY")) - { - var activeAugmentCount = AugmentSelectors.Count(x => x.isActiveAndEnabled); - var nextAugmentIndex = (_augmentIndex + 1) % activeAugmentCount; - AugmentSelectors[nextAugmentIndex].isOn = true; - ZInput.ResetButtonStatus("JoyButtonY"); - } - - if (AvailableEffectsScrollbar != null) - { - var rightStickAxis = ZInput.GetJoyRightStickY(); - if (Mathf.Abs(rightStickAxis) > 0.5f) - AvailableEffectsScrollbar.value = Mathf.Clamp01(AvailableEffectsScrollbar.value + rightStickAxis * -0.1f); - } - } - - if (_choiceDialog != null && !_choiceDialog.activeSelf) - { - Unlock(); - Destroy(_choiceDialog); - _choiceDialog = null; - Cancel(); - - AvailableItems.ForeachElement((i, e) => - { - if (!e.IsSelected()) - return; - e.SetItem(e.GetListElement()); - e.Refresh(); - }); - RefreshAugmentSelectors(); - OnAugmentIndexChanged(); - } - } - - public void OnAugmentSelectorToggled(bool isOn) - { - if (isOn) - { - for (var index = 0; index < AugmentSelectors.Count; index++) - { - var selector = AugmentSelectors[index]; - if (selector.isOn) - { - SelectAugmentIndex(index); - return; - } - } - } - else - { - if (!AugmentSelectors.Any(x => x.isOn)) - SelectAugmentIndex(-1); - } - } - - public void SelectAugmentIndex(int index) - { - if (index != _augmentIndex) - { - _augmentIndex = index; - OnAugmentIndexChanged(); - } - } - - public void OnAugmentIndexChanged() - { - var selectedItem = AvailableItems.GetSingleSelectedItem(); - if (selectedItem?.Item1.GetItem() == null) - { - MainButton.interactable = false; - AvailableEffectsText.text = ""; - CostLabel.enabled = false; - CostList.SetItems(new List()); - _augmentIndex = -1; - foreach (var augmentSelector in AugmentSelectors) - { - augmentSelector.isOn = false; - } - return; - } - - if (_augmentIndex < 0) - { - AvailableEffectsText.text = string.Empty; - CostLabel.enabled = false; - CostList.SetItems(new List()); - MainButton.interactable = false; - } - else - { - var item = selectedItem.Item1.GetItem(); - var info = GetAvailableEffects(item, _augmentIndex); - - AvailableEffectsText.text = info; - ScrollEnchantInfoToTop(); - - CostLabel.enabled = true; - var cost = GetAugmentCost(item, _augmentIndex); - CostList.SetItems(cost.Cast().ToList()); - - var featureValues = EnchantingTableUI.instance.SourceTable.GetFeatureCurrentValue(EnchantingFeature.Augment); - var reenchantCostReduction = float.IsNaN(featureValues.Item2) ? 0 : featureValues.Item2; - if (reenchantCostReduction > 0) - CostLabel.text = Localization.instance.Localize($"$mod_epicloot_augmentcost (-{reenchantCostReduction}% $item_coins!)"); - else - CostLabel.text = Localization.instance.Localize("$mod_epicloot_augmentcost"); - - var canAfford = LocalPlayerCanAffordCost(cost); - var featureUnlocked = EnchantingTableUI.instance.SourceTable.IsFeatureUnlocked(EnchantingFeature.Augment); - MainButton.interactable = featureUnlocked && canAfford && _augmentIndex >= 0; - } - } - - private void ScrollEnchantInfoToTop() - { - AvailableEffectsScrollbar.value = 1; - } - - protected override void DoMainAction() - { - var selectedItem = AvailableItems.GetSingleSelectedItem(); - if (selectedItem?.Item1.GetItem() == null) - { - Cancel(); - return; - } - - var item = selectedItem.Item1.GetItem(); - var cost = GetAugmentCost(item, _augmentIndex); - - var player = Player.m_localPlayer; - if (!player.NoCostCheat()) - { - if (!LocalPlayerCanAffordCost(cost)) - { - Debug.LogError("[Augment Item] ERROR: Tried to augment item but could not afford the cost. This should not happen!"); - return; - } - - var inventory = player.GetInventory(); - foreach (var costElement in cost) - { - var costItem = costElement.GetItem(); - inventory.RemoveItem(costItem.m_shared.m_name, costItem.m_stack); - } - } - - if (_choiceDialog != null) - Destroy(_choiceDialog); - - _choiceDialog = AugmentItem(item, _augmentIndex); - - Lock(); - } - - protected override void OnSelectedItemsChanged() - { - var entry = AvailableItems.GetSingleSelectedItem(); - var item = entry?.Item1.GetItem(); - var augmentableEffects = GetAugmentableEffects(item); - - if (augmentableEffects.Count > AugmentSelectors.Count) - { - Debug.LogError($"[Epic Loot] ERROR: Too many magic effects to show! (Max: {AugmentSelectors.Count})"); - } - - for (var index = 0; index < AugmentSelectors.Count; index++) - { - var selector = AugmentSelectors[index]; - if (selector == null) - continue; - - selector.SetIsOnWithoutNotify(index == 0); - selector.gameObject.SetActive(item != null && index < augmentableEffects.Count); - if (!selector.gameObject.activeSelf) - continue; - - var selectorText = selector.GetComponentInChildren(); - if (selectorText != null) - { - selectorText.text = augmentableEffects[index].Item1; - selector.interactable = augmentableEffects[index].Item2; - } - } - - if (item == null) - AvailableEffectsText.text = string.Empty; - - _augmentIndex = 0; - OnAugmentIndexChanged(); - } - - private void RefreshAugmentSelectors() - { - var entry = AvailableItems.GetSingleSelectedItem(); - var item = entry?.Item1.GetItem(); - var augmentableEffects = GetAugmentableEffects(item); - - for (var index = 0; index < AugmentSelectors.Count; index++) - { - var selector = AugmentSelectors[index]; - if (selector == null) - continue; - - selector.SetIsOnWithoutNotify(index == _augmentIndex); - selector.gameObject.SetActive(item != null && index < augmentableEffects.Count); - if (!selector.gameObject.activeSelf) - continue; - - var selectorText = selector.GetComponentInChildren(); - if (selectorText != null) - { - selectorText.text = augmentableEffects[index].Item1; - selector.interactable = augmentableEffects[index].Item2; - } - } - } - - public override bool CanCancel() - { - return base.CanCancel() || (_choiceDialog != null && _choiceDialog.activeSelf); - } - - public override void Cancel() - { - base.Cancel(); - OnAugmentIndexChanged(); - } - - public override void Lock() - { - base.Lock(); - foreach (var selector in AugmentSelectors) - { - selector.interactable = false; - } - } - - public override void Unlock() - { - base.Unlock(); - foreach (var selector in AugmentSelectors) - { - selector.interactable = true; - } - } - - public override void DeselectAll() - { - AvailableItems.DeselectAll(); - } - } -} diff --git a/EpicLoot-UnityLib/ConvertUI.cs b/EpicLoot-UnityLib/ConvertUI.cs deleted file mode 100644 index 29e7e1490..000000000 --- a/EpicLoot-UnityLib/ConvertUI.cs +++ /dev/null @@ -1,297 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using JetBrains.Annotations; -using UnityEngine; -using UnityEngine.UI; - -namespace EpicLoot_UnityLib -{ - public enum MaterialConversionMode - { - Upgrade, - Convert, - Junk - } - - public class ConversionRecipeCostUnity - { - public ItemDrop.ItemData Item; - public int Amount; - } - - public class ConversionRecipeUnity : IListElement - { - public ItemDrop.ItemData Product; - public int Amount; - public List Cost; - - public ItemDrop.ItemData GetItem() => Product; - public string GetDisplayNameSuffix() => Amount > 1 ? $" x{Amount}" : string.Empty; - - public int GetMax() - { - var player = Player.m_localPlayer; - if (player == null) - return 0; - - var inventory = player.GetInventory(); - var min = int.MaxValue; - foreach (var cost in Cost) - { - var count = inventory.CountItems(cost.Item.m_shared.m_name); - var canMake = Mathf.FloorToInt(count / (float)cost.Amount); - min = Mathf.Min(min, canMake); - } - - return min; - } - } - - public class ConvertUI : EnchantingTableUIPanelBase - { - public MultiSelectItemList Products; - public List ModeButtons; - - [Header("Cost")] - public Text CostLabel; - public MultiSelectItemList CostList; - - public delegate List GetConversionRecipesDelegate(int mode); - - public static GetConversionRecipesDelegate GetConversionRecipes; - - private Text _progressLabel; - private ToggleGroup _toggleGroup; - private MaterialConversionMode _mode; - - public override void Awake() - { - base.Awake(); - - _progressLabel = ProgressBar.gameObject.GetComponentInChildren(); - - if (ModeButtons.Count > 0) - { - _toggleGroup = ModeButtons[0].group; - _toggleGroup.EnsureValidState(); - } - - for (var index = 0; index < ModeButtons.Count; index++) - { - var modeButton = ModeButtons[index]; - modeButton.onValueChanged.AddListener((isOn) => - { - if (isOn) - RefreshMode(); - }); - } - } - - [UsedImplicitly] - public void OnEnable() - { - _mode = 0; - RefreshMode(); - var items = GetConversionRecipes((int)_mode); - AvailableItems.SetItems(items.Cast().ToList()); - } - - public override void Update() - { - base.Update(); - - if (!_locked && ZInput.IsGamepadActive()) - { - if (ZInput.GetButtonDown("JoyButtonY")) - { - var nextModeIndex = ((int)_mode + 1) % ModeButtons.Count; - ModeButtons[nextModeIndex].isOn = true; - ZInput.ResetButtonStatus("JoyButtonY"); - } - } - } - - public void RefreshMode() - { - var prevMode = _mode; - for (var index = 0; index < ModeButtons.Count; index++) - { - var button = ModeButtons[index]; - if (button.isOn) - { - _mode = (MaterialConversionMode)index; - } - } - - if (prevMode != _mode) - OnModeChanged(); - } - - public void OnModeChanged() - { - DeselectAll(); - RefreshAvailableItems(); - - switch (_mode) - { - case MaterialConversionMode.Upgrade: - CostLabel.text = Localization.instance.Localize("$mod_epicloot_upgradecost"); - _progressLabel.text = Localization.instance.Localize("$mod_epicloot_upgradeprogress"); - _buttonLabel.text = Localization.instance.Localize("$mod_epicloot_upgrade"); - break; - - case MaterialConversionMode.Convert: - CostLabel.text = Localization.instance.Localize("$mod_epicloot_convertcost"); - _progressLabel.text = Localization.instance.Localize("$mod_epicloot_convertprogress"); - _buttonLabel.text = Localization.instance.Localize("$mod_epicloot_convert"); - break; - - case MaterialConversionMode.Junk: - CostLabel.text = Localization.instance.Localize("$mod_epicloot_junkcost"); - _progressLabel.text = Localization.instance.Localize("$mod_epicloot_junkprogress"); - _buttonLabel.text = Localization.instance.Localize("$mod_epicloot_junk"); - break; - - default: - throw new ArgumentOutOfRangeException(); - } - } - - protected override void DoMainAction() - { - var selectedRecipes = AvailableItems.GetSelectedItems(); - var allProducts = GetConversionProducts(selectedRecipes); - var cost = GetConversionCost(selectedRecipes); - - Cancel(); - - var player = Player.m_localPlayer; - var inventory = player.GetInventory(); - foreach (var costElement in cost) - { - var costItem = costElement.GetItem(); - inventory.RemoveItem(costItem.m_shared.m_name, costItem.m_stack); - } - - foreach (var productElement in allProducts) - { - var product = productElement.GetItem(); - if (inventory.CanAddItem(product)) - { - inventory.AddItem(product); - player.Message(MessageHud.MessageType.TopLeft, $"$msg_added {product.m_shared.m_name}", product.m_stack, product.GetIcon()); - } - else - { - var itemDrop = ItemDrop.DropItem(product, product.m_stack, player.transform.position + player.transform.forward + player.transform.up, player.transform.rotation); - itemDrop.GetComponent().velocity = Vector3.up * 5f; - player.Message(MessageHud.MessageType.TopLeft, $"$msg_dropped {itemDrop.m_itemData.m_shared.m_name} $mod_epicloot_sacrifice_inventoryfullexplanation", itemDrop.m_itemData.m_stack, itemDrop.m_itemData.GetIcon()); - } - } - - DeselectAll(); - RefreshAvailableItems(); - } - - public static List GetConversionProducts(List> selectedRecipes) - { - var products = new Dictionary(); - - foreach (var entry in selectedRecipes) - { - var recipe = entry.Item1; - var multiple = entry.Item2; - - if (products.TryGetValue(recipe.Product.m_shared.m_name, out var item)) - { - item.m_stack += recipe.Amount * multiple; - } - else - { - item = recipe.Product.Clone(); - item.m_stack = recipe.Amount * multiple; - products.Add(item.m_shared.m_name, item); - } - } - - return products.Values.OrderBy(x => Localization.instance.Localize(x.m_shared.m_name)).Select(x => new InventoryItemListElement() { Item = x }).ToList(); - } - - public static List GetConversionCost(List> selectedRecipes) - { - var costs = new Dictionary(); - - foreach (var entry in selectedRecipes) - { - var recipe = entry.Item1; - var multiple = entry.Item2; - - foreach (var recipeCost in recipe.Cost) - { - if (costs.TryGetValue(recipeCost.Item.m_shared.m_name, out var item)) - { - item.m_stack += recipeCost.Amount * multiple; - } - else - { - item = recipeCost.Item.Clone(); - item.m_stack = recipeCost.Amount * multiple; - costs.Add(item.m_shared.m_name, item); - } - } - } - - return costs.Values.OrderBy(x => Localization.instance.Localize(x.m_shared.m_name)).Select(x => new InventoryItemListElement() { Item = x }).ToList(); - } - - public void RefreshAvailableItems() - { - var items = GetConversionRecipes((int)_mode); - AvailableItems.SetItems(items.Cast().ToList()); - AvailableItems.DeselectAll(); - OnSelectedItemsChanged(); - } - - protected override void OnSelectedItemsChanged() - { - var selectedRecipes = AvailableItems.GetSelectedItems(); - var allProducts = GetConversionProducts(selectedRecipes); - Products.SetItems(allProducts.Cast().ToList()); - - var cost = GetConversionCost(selectedRecipes); - CostList.SetItems(cost.Cast().ToList()); - - var baseFeatureValues = EnchantingTableUI.instance.SourceTable.GetFeatureValue(EnchantingFeature.ConvertMaterials, 0); - var currentFeatureValues = EnchantingTableUI.instance.SourceTable.GetFeatureCurrentValue(EnchantingFeature.ConvertMaterials); - var isBonusCost = false; - if (_mode == MaterialConversionMode.Upgrade) - { - if (currentFeatureValues.Item1 < baseFeatureValues.Item1 && allProducts.Any(x => x.Item.m_shared.m_ammoType.EndsWith("MagicCraftingMaterial"))) - isBonusCost = true; - if (currentFeatureValues.Item2 < baseFeatureValues.Item2 && allProducts.Any(x => x.Item.m_shared.m_ammoType.EndsWith("Runestone"))) - isBonusCost = true; - - if (isBonusCost && cost.Count > 0) - CostLabel.text = Localization.instance.Localize("($mod_epicloot_bonus) $mod_epicloot_upgradecost"); - else - CostLabel.text = Localization.instance.Localize("$mod_epicloot_upgradecost"); - } - - var canAfford = LocalPlayerCanAffordCost(cost); - var featureUnlocked = EnchantingTableUI.instance.SourceTable.IsFeatureUnlocked(EnchantingFeature.ConvertMaterials); - MainButton.interactable = featureUnlocked && canAfford && selectedRecipes.Count > 0; - } - - public override void Cancel() - { - base.Cancel(); - OnModeChanged(); - } - - public override void DeselectAll() - { - AvailableItems.DeselectAll(); - } - } -} diff --git a/EpicLoot-UnityLib/DisenchantUI.cs b/EpicLoot-UnityLib/DisenchantUI.cs deleted file mode 100644 index 1e6ab9a95..000000000 --- a/EpicLoot-UnityLib/DisenchantUI.cs +++ /dev/null @@ -1,107 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using JetBrains.Annotations; -using UnityEngine.UI; - -namespace EpicLoot_UnityLib -{ - public class DisenchantUI : EnchantingTableUIPanelBase - { - public Text CostLabel; - public MultiSelectItemList CostList; - public EnchantBonus BonusPanel; - - public delegate List GetDisenchantItemsDelegate(); - public delegate List GetDisenchantCostDelegate(ItemDrop.ItemData item); - public delegate List DisenchantItemDelegate(ItemDrop.ItemData item); - - public static GetDisenchantItemsDelegate GetDisenchantItems; - public static GetDisenchantCostDelegate GetDisenchantCost; - public static DisenchantItemDelegate DisenchantItem; - - [UsedImplicitly] - public void OnEnable() - { - var items = GetDisenchantItems(); - AvailableItems.SetItems(items.Cast().ToList()); - AvailableItems.DeselectAll(); - } - - protected override void DoMainAction() - { - Cancel(); - - var selectedItem = AvailableItems.GetSingleSelectedItem(); - if (selectedItem?.Item1.GetItem() == null) - return; - - var item = selectedItem.Item1.GetItem(); - var cost = GetDisenchantCost(item); - if (!LocalPlayerCanAffordCost(cost)) - return; - - var player = Player.m_localPlayer; - var inventory = player.GetInventory(); - foreach (var costElement in cost) - { - var costItem = costElement.GetItem(); - inventory.RemoveItem(costItem.m_shared.m_name, costItem.m_stack); - } - - var bonusItems = DisenchantItem(item); - - if (bonusItems.Count > 0) - { - EnchantingTableUI.instance.PlayEnchantBonusSFX(); - BonusPanel.Show(); - - GiveItemsToPlayer(bonusItems); - } - - RefreshAvailableItems(); - } - - public void RefreshAvailableItems() - { - var items = GetDisenchantItems(); - AvailableItems.SetItems(items.Cast().ToList()); - AvailableItems.DeselectAll(); - OnSelectedItemsChanged(); - } - - protected override void OnSelectedItemsChanged() - { - var selectedItem = AvailableItems.GetSingleSelectedItem(); - - if (selectedItem != null) - { - CostLabel.enabled = true; - var cost = GetDisenchantCost(selectedItem.Item1.GetItem()); - CostList.SetItems(cost.Cast().ToList()); - - var featureValues = EnchantingTableUI.instance.SourceTable.GetFeatureCurrentValue(EnchantingFeature.Disenchant); - var costReduction = float.IsNaN(featureValues.Item2) ? 0 : (int)featureValues.Item2; - - if (costReduction > 0 && cost.Count > 0) - CostLabel.text = Localization.instance.Localize("$mod_epicloot_disenchantcost ($mod_epicloot_disenchantcostreduction)", costReduction.ToString()); - else - CostLabel.text = Localization.instance.Localize("$mod_epicloot_disenchantcost"); - - var canAfford = LocalPlayerCanAffordCost(cost); - var featureUnlocked = EnchantingTableUI.instance.SourceTable.IsFeatureUnlocked(EnchantingFeature.Disenchant); - MainButton.interactable = featureUnlocked && canAfford; - } - else - { - CostList.SetItems(new List()); - CostLabel.enabled = false; - MainButton.interactable = false; - } - } - - public override void DeselectAll() - { - AvailableItems.DeselectAll(); - } - } -} diff --git a/EpicLoot-UnityLib/EnchantUI.cs b/EpicLoot-UnityLib/EnchantUI.cs deleted file mode 100644 index 962d6c653..000000000 --- a/EpicLoot-UnityLib/EnchantUI.cs +++ /dev/null @@ -1,244 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using JetBrains.Annotations; -using UnityEngine; -using UnityEngine.UI; - -namespace EpicLoot_UnityLib -{ - public class EnchantUI : EnchantingTableUIPanelBase - { - public Text EnchantInfo; - public Scrollbar EnchantInfoScrollbar; - public List RarityButtons; - - [Header("Cost")] - public Text CostLabel; - public MultiSelectItemList CostList; - - public AudioClip[] EnchantCompleteSFX; - - public delegate List GetEnchantableItemsDelegate(); - public delegate string GetEnchantInfoDelegate(ItemDrop.ItemData item, MagicRarityUnity rarity); - public delegate List GetEnchantCostDelegate(ItemDrop.ItemData item, MagicRarityUnity rarity); - // Returns the success dialog - public delegate GameObject EnchantItemDelegate(ItemDrop.ItemData item, MagicRarityUnity rarity); - - public static GetEnchantableItemsDelegate GetEnchantableItems; - public static GetEnchantInfoDelegate GetEnchantInfo; - public static GetEnchantCostDelegate GetEnchantCost; - public static EnchantItemDelegate EnchantItem; - - private ToggleGroup _toggleGroup; - private MagicRarityUnity _rarity; - private GameObject _successDialog; - - public override void Awake() - { - base.Awake(); - - if (RarityButtons.Count > 0) - { - _toggleGroup = RarityButtons[0].group; - _toggleGroup.EnsureValidState(); - } - - for (var index = 0; index < RarityButtons.Count; index++) - { - var rarityButton = RarityButtons[index]; - rarityButton.onValueChanged.AddListener((isOn) => { - if (isOn) - RefreshRarity(); - }); - } - } - - [UsedImplicitly] - public void OnEnable() - { - _rarity = MagicRarityUnity.Magic; - OnRarityChanged(); - RarityButtons[0].isOn = true; - var items = GetEnchantableItems(); - AvailableItems.SetItems(items.Cast().ToList()); - } - - public override void Update() - { - base.Update(); - - if (!_locked && ZInput.IsGamepadActive()) - { - if (ZInput.GetButtonDown("JoyButtonY")) - { - var nextModeIndex = ((int)_rarity + 1) % RarityButtons.Count; - RarityButtons[nextModeIndex].isOn = true; - ZInput.ResetButtonStatus("JoyButtonY"); - } - - if (EnchantInfoScrollbar != null) - { - var rightStickAxis = ZInput.GetJoyRightStickY(); - if (Mathf.Abs(rightStickAxis) > 0.5f) - EnchantInfoScrollbar.value = Mathf.Clamp01(EnchantInfoScrollbar.value + rightStickAxis * -0.1f); - } - } - - if (_successDialog != null && !_successDialog.activeSelf) - { - Unlock(); - Destroy(_successDialog); - _successDialog = null; - } - } - - public void RefreshRarity() - { - var prevRarity = _rarity; - for (var index = 0; index < RarityButtons.Count; index++) - { - var button = RarityButtons[index]; - if (button.isOn) - { - _rarity = (MagicRarityUnity)index; - } - } - - if (prevRarity != _rarity) - OnRarityChanged(); - } - - public void OnRarityChanged() - { - var selectedItem = AvailableItems.GetSingleSelectedItem(); - if (selectedItem?.Item1.GetItem() == null) - { - MainButton.interactable = false; - EnchantInfo.text = ""; - CostLabel.enabled = false; - CostList.SetItems(new List()); - return; - } - - var item = selectedItem.Item1.GetItem(); - var info = GetEnchantInfo(item, _rarity); - - EnchantInfo.text = info; - ScrollEnchantInfoToTop(); - - CostLabel.enabled = true; - var cost = GetEnchantCost(item, _rarity); - CostList.SetItems(cost.Cast().ToList()); - - var canAfford = LocalPlayerCanAffordCost(cost); - var featureUnlocked = EnchantingTableUI.instance.SourceTable.IsFeatureUnlocked(EnchantingFeature.Enchant); - MainButton.interactable = featureUnlocked && canAfford; - } - - private void ScrollEnchantInfoToTop() - { - EnchantInfoScrollbar.value = 1; - } - - protected override void DoMainAction() - { - var selectedItem = AvailableItems.GetSelectedItems().FirstOrDefault(); - - Cancel(); - - if (selectedItem?.Item1.GetItem() == null) - return; - - var item = selectedItem.Item1.GetItem(); - var cost = GetEnchantCost(item, _rarity); - - var player = Player.m_localPlayer; - if (!player.NoCostCheat()) - { - if (!LocalPlayerCanAffordCost(cost)) - { - Debug.LogError("[Enchant Item] ERROR: Tried to enchant item but could not afford the cost. This should not happen!"); - return; - } - - var inventory = player.GetInventory(); - foreach (var costElement in cost) - { - var costItem = costElement.GetItem(); - inventory.RemoveItem(costItem.m_shared.m_name, costItem.m_stack); - } - } - - if (_successDialog != null) - Destroy(_successDialog); - - DeselectAll(); - Lock(); - - _successDialog = EnchantItem(item, _rarity); - - RefreshAvailableItems(); - } - - protected override AudioClip GetCompleteAudioClip() - { - return EnchantCompleteSFX[(int)_rarity]; - } - - public void RefreshAvailableItems() - { - var items = GetEnchantableItems(); - AvailableItems.SetItems(items.Cast().ToList()); - AvailableItems.DeselectAll(); - OnSelectedItemsChanged(); - } - - protected override void OnSelectedItemsChanged() - { - OnRarityChanged(); - } - - public override bool CanCancel() - { - return base.CanCancel() || (_successDialog != null && _successDialog.activeSelf); - } - - public override void Cancel() - { - base.Cancel(); - - if (_successDialog != null && _successDialog.activeSelf) - { - Destroy(_successDialog); - _successDialog = null; - } - - OnRarityChanged(); - } - - public override void Lock() - { - base.Lock(); - - foreach (var modeButton in RarityButtons) - { - modeButton.interactable = false; - } - } - - public override void Unlock() - { - base.Unlock(); - - foreach (var modeButton in RarityButtons) - { - modeButton.interactable = true; - } - } - - public override void DeselectAll() - { - AvailableItems.DeselectAll(); - } - } -} diff --git a/EpicLoot-UnityLib/EnchantingTable.cs b/EpicLoot-UnityLib/EnchantingTable.cs deleted file mode 100644 index 76cb773ca..000000000 --- a/EpicLoot-UnityLib/EnchantingTable.cs +++ /dev/null @@ -1,322 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using UnityEngine; - -namespace EpicLoot_UnityLib -{ - public class EnchantingTable : MonoBehaviour, Hoverable, Interactable - { - public const float UseDistance = 2.7f; - public const string DisplayNameLocID = "mod_epicloot_assets_enchantingtable"; - public const int FeatureUnavailableSentinel = -2; - public const int FeatureLockedSentinel = -1; - public const int FeatureLevelOne = 1; - - public GameObject EnchantingUIPrefab; - - public event Action OnFeatureLevelChanged; - public event Action OnAnyFeatureLevelChanged; - - public delegate bool UpgradesActiveDelegate(EnchantingFeature feature, out bool featureActive); - public static UpgradesActiveDelegate UpgradesActive; - - private static readonly List _upgradeRequests = new(); - private ZNetView _nview; - private Player _interactingPlayer; - - public bool Interact(Humanoid user, bool repeat, bool alt) - { - if (repeat || user != Player.m_localPlayer || !InUseDistance(user)) - return false; - - EnchantingTableUI.Show(EnchantingUIPrefab, this); - _interactingPlayer = Player.m_localPlayer; - return false; - } - - public void Awake() - { - _nview = GetComponent(); - - if (_nview == null || _nview.GetZDO() == null) - return; - - var wearTear = GetComponent(); - if (wearTear != null) - { - wearTear.m_destroyedEffect.m_effectPrefabs = new EffectList.EffectData[] - { - new() { m_prefab = ZNetScene.instance.GetPrefab("vfx_SawDust") }, - new() { m_prefab = ZNetScene.instance.GetPrefab("sfx_wood_destroyed") } - }; - wearTear.m_hitEffect.m_effectPrefabs = new EffectList.EffectData[1] - { - new() { m_prefab = ZNetScene.instance.GetPrefab("vfx_SawDust") } - }; - } - - _nview.Register("el.TableUpgradeRequest", RPC_TableUpgradeRequest); - _nview.Register("el.TableUpgradeResponse", RPC_TableUpgradeResponse); - - InitFeatureLevels(); - } - - //Function RequestTableUpgrade - public void RequestTableUpgrade(EnchantingFeature feature, int toLevel, Action responseCallback) - { - var tableZDO = _nview.GetZDO().m_uid; - _upgradeRequests.Add(new EnchantingFeatureUpgradeRequest() - { - TableZDO = tableZDO, - Feature = feature, - ToLevel = toLevel, - ResponseCallback = responseCallback - }); - _nview.InvokeRPC("el.TableUpgradeRequest",tableZDO, (int)feature, toLevel); - - } - - //Function for RPC_TableUpgradeRequest - private void RPC_TableUpgradeRequest(long sender, ZDOID tableZDO, int featureI, int toLevel) - { - if (!_nview.IsOwner()) - return; - - var instance = ZNetScene.instance.FindInstance(tableZDO); - if (instance == null) - return; - - var table = instance.GetComponent(); - if (table == null) - return; - - var feature = (EnchantingFeature)featureI; - if (table.IsFeatureAvailable(feature) && toLevel == table.GetFeatureLevel(feature) + 1) - { - table.SetFeatureLevel(feature, toLevel); - _nview.InvokeRPC(sender,"el.TableUpgradeResponse",tableZDO, featureI, toLevel, true); - OnFeatureLevelChanged?.Invoke(feature, toLevel); - OnAnyFeatureLevelChanged?.Invoke(); - } - else - { - _nview.InvokeRPC(sender,"el.TableUpgradeResponse",tableZDO, featureI, toLevel, false); - } - } - - //FUnction for RPC_TableUpgradeResponse - private void RPC_TableUpgradeResponse(long sender, ZDOID tableZDO, int featureI, int toLevel, bool success) - { - //Only sent to Sender of Request. - //Performs checks, - //Calls OnTableUpgradeResponse - var feature = (EnchantingFeature)featureI; - var listCopy = _upgradeRequests.ToList(); - foreach (var request in listCopy) - { - if (request.TableZDO == tableZDO && request.Feature == feature && request.ToLevel == toLevel) - { - request.ResponseCallback.Invoke(success); - _upgradeRequests.Remove(request); - if (Player.m_localPlayer != null) - { - if (toLevel == 0) - Player.m_localPlayer.Message(MessageHud.MessageType.Center, Localization.instance.Localize("$mod_epicloot_unlockmessage", EnchantingTableUpgrades.GetFeatureName(feature))); - else - Player.m_localPlayer.Message(MessageHud.MessageType.Center, Localization.instance.Localize("$mod_epicloot_upgrademessage", EnchantingTableUpgrades.GetFeatureName(feature), toLevel.ToString())); - } - } - } - - } - - public void Update() - { - if (_interactingPlayer != null && EnchantingTableUI.instance != null && EnchantingTableUI.instance.isActiveAndEnabled && !InUseDistance(_interactingPlayer)) - { - EnchantingTableUI.Hide(); - _interactingPlayer = null; - } - } - - public void Refresh() - { - OnAnyFeatureLevelChanged?.Invoke(); - } - public bool UseItem(Humanoid user, ItemDrop.ItemData item) - { - return false; - } - - public bool InUseDistance(Humanoid human) - { - return Vector3.Distance(human.transform.position, transform.position) < UseDistance; - } - - public string GetHoverText() - { - return !InUseDistance(Player.m_localPlayer) - ? Localization.instance.Localize("$piece_toofar") - : Localization.instance.Localize($"${DisplayNameLocID}\n[$KEY_Use] $piece_use"); - } - - public string GetHoverName() - { - return DisplayNameLocID; - } - - private string FormatFeatureName(string featureName) - { - return string.Format($"el.et.v1.{featureName}"); - } - - private void InitFeatureLevels() - { - const int uninitializedSentinel = -888; - foreach (EnchantingFeature feature in Enum.GetValues(typeof(EnchantingFeature))) - { - var featureName = feature.ToString(); - if (_nview.GetZDO().GetInt(FormatFeatureName(featureName), uninitializedSentinel) == uninitializedSentinel) - _nview.GetZDO().Set(FormatFeatureName(featureName), GetDefaultFeatureLevel(feature)+1); - //For those that travel here from afar, you might be asking yourself why I'm adding and subtracting 1 to the level. - //It's because Iron Gate decided that 0 value ZDO's should be removed when world save occurs........ - } - } - - private static int GetDefaultFeatureLevel(EnchantingFeature feature) - { - if (!UpgradesActive(feature, out var featureActive)) - return FeatureLevelOne; - - if (!featureActive) - return FeatureUnavailableSentinel; - - return EnchantingTableUpgrades.Config.DefaultFeatureLevels.TryGetValue(feature, out var level) ? level : FeatureUnavailableSentinel; - } - - public void Reset() - { - foreach (EnchantingFeature feature in Enum.GetValues(typeof(EnchantingFeature))) - { - SetFeatureLevel(feature, GetDefaultFeatureLevel(feature)); - } - } - - public int GetFeatureLevel(EnchantingFeature feature) - { - if (_nview == null || _nview.GetZDO() == null) - return FeatureUnavailableSentinel; - - - if (!UpgradesActive(feature, out var featureActive)) - { - return FeatureLevelOne; - } - - if (!featureActive) - return FeatureUnavailableSentinel; - - var featureName = feature.ToString(); - var level = _nview.GetZDO().GetInt(FormatFeatureName(featureName), FeatureUnavailableSentinel); - //For those that travel here from afar, you might be asking yourself why I'm adding and subtracting 1 to the level. - //It's because Iron Gate decided that 0 value ZDO's should be removed when world save occurs........ - var returncode = level == FeatureUnavailableSentinel ? FeatureUnavailableSentinel : level - 1; - return level == FeatureUnavailableSentinel ? FeatureUnavailableSentinel : level - 1; - } - - public void SetFeatureLevel(EnchantingFeature feature, int level) - { - if (_nview == null) - return; - - if (!UpgradesActive(feature, out var featureActive)) - { - level = FeatureLevelOne; - } - else - { - if (level > (EnchantingTableUpgrades.Config.MaximumFeatureLevels.TryGetValue(feature, out var maxLevel) ? maxLevel : 1)) - { - return; - } - } - var featureName = feature.ToString(); - _nview.GetZDO().Set(FormatFeatureName(featureName), level+1); - OnFeatureLevelChanged?.Invoke(feature, level); - OnAnyFeatureLevelChanged?.Invoke(); - } - - public bool IsFeatureAvailable(EnchantingFeature feature) - { - return GetFeatureLevel(feature) > FeatureUnavailableSentinel; - } - - public bool IsFeatureLocked(EnchantingFeature feature) - { - return GetFeatureLevel(feature) == FeatureLockedSentinel; - } - - public bool IsFeatureUnlocked(EnchantingFeature feature) - { - return GetFeatureLevel(feature) > FeatureLockedSentinel; - } - - public bool IsFeatureMaxLevel(EnchantingFeature feature) - { - return GetFeatureLevel(feature) == EnchantingTableUpgrades.GetFeatureMaxLevel(feature); - } - - public List GetFeatureUnlockCost(EnchantingFeature feature) - { - if (IsFeatureUnlocked(feature)) - Debug.LogWarning($"[EpicLoot] Warning: tried to get unlock cost for a feature that is already unlocked! ({feature})"); - - return EnchantingTableUpgrades.GetUpgradeCost(feature, 0); - } - - public List GetFeatureUpgradeCost(EnchantingFeature feature) - { - if (IsFeatureLocked(feature) || !IsFeatureAvailable(feature)) - Debug.LogWarning($"[EpicLoot] Warning: tried to get enchanting feature unlock cost for a feature that is locked or unavailable! ({feature})"); - - var currentLevel = GetFeatureLevel(feature); - return EnchantingTableUpgrades.GetUpgradeCost(feature, currentLevel + 1); - } - - public Tuple GetFeatureValue(EnchantingFeature feature, int level) - { - if (!IsFeatureAvailable(feature)) - return new Tuple(float.NaN, float.NaN); - - if (level < 0 || level > EnchantingTableUpgrades.GetFeatureMaxLevel(feature)) - return new Tuple(float.NaN, float.NaN); - - var values = feature switch - { - EnchantingFeature.Sacrifice => EnchantingTableUpgrades.Config.UpgradeValues.Sacrifice, - EnchantingFeature.ConvertMaterials => EnchantingTableUpgrades.Config.UpgradeValues.ConvertMaterials, - EnchantingFeature.Enchant => EnchantingTableUpgrades.Config.UpgradeValues.Enchant, - EnchantingFeature.Augment => EnchantingTableUpgrades.Config.UpgradeValues.Augment, - EnchantingFeature.Disenchant => EnchantingTableUpgrades.Config.UpgradeValues.Disenchant, - EnchantingFeature.Helheim => EnchantingTableUpgrades.Config.UpgradeValues.Helheim, - _ => throw new ArgumentOutOfRangeException(nameof(feature), feature, null) - }; - - if (level >= values.Count) - return new Tuple(float.NaN, float.NaN); - - var levelValues = values[level]; - if (levelValues.Length == 1) - return new Tuple(levelValues[0], float.NaN); - if (levelValues.Length >= 2) - return new Tuple(levelValues[0], levelValues[1]); - return new Tuple(float.NaN, float.NaN); - } - - public Tuple GetFeatureCurrentValue(EnchantingFeature feature) - { - return GetFeatureValue(feature, GetFeatureLevel(feature)); - } - } -} \ No newline at end of file diff --git a/EpicLoot-UnityLib/EnchantingTableUI.cs b/EpicLoot-UnityLib/EnchantingTableUI.cs deleted file mode 100644 index 3d714c2b3..000000000 --- a/EpicLoot-UnityLib/EnchantingTableUI.cs +++ /dev/null @@ -1,180 +0,0 @@ -using UnityEngine; -using UnityEngine.UI; - -namespace EpicLoot_UnityLib -{ - public class EnchantingTableUI : MonoBehaviour - { - public GameObject Root; - public GameObject Scrim; - public TabHandler TabHandler; - public GameObject TabScrim; - - [Header("Content")] - public EnchantingTableUIPanelBase[] Panels; - - [Header("Audio")] - public AudioSource Audio; - public AudioClip TabClickSFX; - public AudioClip EnchantBonusSFX; - - public EnchantingTable SourceTable { get; private set; } - - public static EnchantingTableUI instance { get; set; } - - public delegate void AugaFixupDelegate(EnchantingTableUI ui); - public static AugaFixupDelegate AugaFixup; - public delegate void TabActivationDelegate(EnchantingTableUI ui); - public static TabActivationDelegate TabActivation; - - private int _hiddenFrames; - - public void Awake() - { - instance = this; - Localization.instance.Localize(transform); - - var uiSFX = GameObject.Find("sfx_gui_button"); - if (uiSFX) - Audio.outputAudioMixerGroup = uiSFX.GetComponent().outputAudioMixerGroup; - - for (var index = 0; index < TabHandler.m_tabs.Count; index++) - { - var tabData = TabHandler.m_tabs[index]; - tabData.m_onClick.AddListener(PlayTabSelectSFX); - var featureStatus = tabData.m_button.gameObject.GetComponent(); - if (featureStatus != null) - featureStatus.SetFeature((EnchantingFeature)index); - } - - AugaFixup(this); - TabActivation(this); - } - - public static void Show(GameObject enchantingUiPrefab, EnchantingTable source) - { - if (instance == null) - { - if (StoreGui.instance != null) - { - var inGameGui = StoreGui.instance.transform.parent; - var siblingIndex = StoreGui.instance.transform.GetSiblingIndex() + 1; - var enchantingUI = Instantiate(enchantingUiPrefab, inGameGui); - enchantingUI.transform.SetSiblingIndex(siblingIndex); - } - } - - if (instance == null) - return; - - instance.SourceTable = source; - instance.Root.SetActive(true); - instance.Scrim.SetActive(true); - instance.SourceTable.Refresh(); - - foreach (var panel in instance.Panels) - { - panel.DeselectAll(); - } - } - - public static void Hide() - { - if (instance == null) - return; - - instance.Root.SetActive(false); - instance.Scrim.SetActive(false); - instance.SourceTable = null; - } - - public static bool IsVisible() - { - return instance != null && ((instance._hiddenFrames <= 2) || (instance.Root != null && instance.Root.activeSelf)); - } - - public static bool IsInTextInput() - { - if (!IsVisible()) - return false; - - var textFields = instance.Root.GetComponentsInChildren(false); - foreach (var inputField in textFields) - { - if (inputField.isFocused) - return true; - } - - return false; - } - - public void Update() - { - if (Root == null) - return; - - if (!Root.activeSelf) - { - _hiddenFrames++; - return; - } - - _hiddenFrames = 0; - - var disallowClose = (Chat.instance != null && Chat.instance.HasFocus()) || Console.IsVisible() || Menu.IsVisible() || (TextViewer.instance != null && TextViewer.instance.IsVisible()) || Player.m_localPlayer.InCutscene(); - if (disallowClose) - return; - - var gotCloseInput = ZInput.GetButtonDown("JoyButtonB") || Input.GetKeyDown(KeyCode.Escape) || Input.GetKeyDown(KeyCode.Tab); - if (gotCloseInput) - { - ZInput.ResetButtonStatus("JoyButtonB"); - ZInput.ResetButtonStatus("JoyJump"); - - var panelCapturedInput = false; - foreach (var panel in Panels) - { - if (panel.isActiveAndEnabled && panel.CanCancel()) - { - panel.Cancel(); - panelCapturedInput = true; - break; - } - } - - if (!panelCapturedInput) - Hide(); - } - } - - public static void UpdateTabActivation() - { - TabActivation(instance); - } - - public static void UpdateUpgradeActivation() - { - TabActivation(instance); - } - - public void LockTabs() - { - TabScrim.SetActive(true); - } - - public void UnlockTabs() - { - TabScrim.SetActive(false); - } - - public void PlayTabSelectSFX() - { - Audio.PlayOneShot(TabClickSFX); - } - - public void PlayEnchantBonusSFX() - { - Audio.PlayOneShot(EnchantBonusSFX); - } - } -} diff --git a/EpicLoot-UnityLib/EnchantingTableUIPanelBase.cs b/EpicLoot-UnityLib/EnchantingTableUIPanelBase.cs deleted file mode 100644 index daef17050..000000000 --- a/EpicLoot-UnityLib/EnchantingTableUIPanelBase.cs +++ /dev/null @@ -1,211 +0,0 @@ -using System.Collections.Generic; -using UnityEngine; -using UnityEngine.UI; - -namespace EpicLoot_UnityLib -{ - public abstract class EnchantingTableUIPanelBase : MonoBehaviour - { - public const float CountdownTime = 0.8f; - - public MultiSelectItemList AvailableItems; - public Button MainButton; - public GameObject LevelDisplay; - public GuiBar ProgressBar; - public AudioSource Audio; - public AudioClip ProgressLoopSFX; - public AudioClip CompleteSFX; - - protected bool _inProgress; - protected float _countdown; - protected Text _buttonLabel; - protected string _defaultButtonLabelText; - protected bool _locked; - - protected abstract void DoMainAction(); - protected abstract void OnSelectedItemsChanged(); - - public virtual void Awake() - { - if (AvailableItems != null) - { - AvailableItems.OnSelectedItemsChanged += OnSelectedItemsChanged; - AvailableItems.GiveFocus(true, 0); - } - - if (MainButton != null) - { - MainButton.onClick.AddListener(OnMainButtonClicked); - _buttonLabel = MainButton.GetComponentInChildren(); - _defaultButtonLabelText = _buttonLabel.text; - } - - var uiSFX = GameObject.Find("sfx_gui_button"); - if (uiSFX && Audio != null) - Audio.outputAudioMixerGroup = uiSFX.GetComponent().outputAudioMixerGroup; - } - - protected virtual void OnMainButtonClicked() - { - if (_inProgress) - Cancel(); - else - StartProgress(); - } - - public virtual void DeselectAll() - { - } - - public virtual void Update() - { - if (ProgressBar != null) - ProgressBar.gameObject.SetActive(_inProgress); - if (LevelDisplay != null) - LevelDisplay.gameObject.SetActive(!_inProgress); - - if (_inProgress) - { - if (ProgressBar != null) - ProgressBar.SetValue(CountdownTime - _countdown); - - _countdown -= Time.deltaTime; - if (_countdown < 0) - { - _inProgress = false; - _countdown = 0; - - if (Audio != null) - { - Audio.loop = false; - Audio.Stop(); - } - - DoMainAction(); - PlayCompleteSFX(); - } - } - } - - private void PlayCompleteSFX() - { - var clip = GetCompleteAudioClip(); - if (Audio != null && clip != null) - Audio.PlayOneShot(clip); - } - - protected virtual AudioClip GetCompleteAudioClip() - { - return CompleteSFX; - } - - public virtual void StartProgress() - { - _buttonLabel.text = Localization.instance.Localize("$menu_cancel"); - _inProgress = true; - _countdown = CountdownTime; - - if (ProgressBar != null) - ProgressBar.SetMaxValue(CountdownTime); - - if (Audio != null) - { - Audio.loop = true; - Audio.clip = ProgressLoopSFX; - Audio.Play(); - } - - Lock(); - } - - public virtual bool CanCancel() - { - return _inProgress; - } - - public virtual void Cancel() - { - _buttonLabel.text = Localization.instance.Localize(_defaultButtonLabelText); - _inProgress = false; - _countdown = 0; - - if (Audio != null) - { - Audio.loop = false; - Audio.Stop(); - } - - Unlock(); - } - - public virtual void Lock() - { - _locked = true; - var lists = GetComponentsInChildren(); - foreach (var list in lists) - { - list.Lock(); - } - - EnchantingTableUI.instance.LockTabs(); - } - - public virtual void Unlock() - { - _locked = false; - var lists = GetComponentsInChildren(); - foreach (var list in lists) - { - list.Unlock(); - } - - EnchantingTableUI.instance.UnlockTabs(); - } - - protected static bool LocalPlayerCanAffordCost(List cost) - { - var player = Player.m_localPlayer; - if (player.NoCostCheat()) - return true; - - var inventory = player.GetInventory(); - foreach (var element in cost) - { - var item = element.GetItem(); - if (inventory.CountItems(item.m_shared.m_name) < item.m_stack) - return false; - } - - return true; - } - - protected static void GiveItemsToPlayer(List sacrificeProducts) - { - var player = Player.m_localPlayer; - var inventory = player.GetInventory(); - - foreach (var sacrificeProduct in sacrificeProducts) - { - var item = sacrificeProduct.GetItem(); - do - { - var itemToAdd = item.Clone(); - itemToAdd.m_stack = Mathf.Min(item.m_stack, item.m_shared.m_maxStackSize); - item.m_stack -= itemToAdd.m_stack; - //Debug.LogWarning($"Adding item: {itemToAdd.m_shared.m_name} x{itemToAdd.m_stack} (remaining:{item.m_stack})"); - if (inventory.CanAddItem(itemToAdd)) - { - inventory.AddItem(itemToAdd); - player.Message(MessageHud.MessageType.TopLeft, $"$msg_added {itemToAdd.m_shared.m_name}", itemToAdd.m_stack, itemToAdd.GetIcon()); - } - else - { - var itemDrop = ItemDrop.DropItem(itemToAdd, itemToAdd.m_stack, player.transform.position + player.transform.forward + player.transform.up, player.transform.rotation); - itemDrop.GetComponent().velocity = Vector3.up * 5f; - player.Message(MessageHud.MessageType.TopLeft, $"$msg_dropped {itemDrop.m_itemData.m_shared.m_name} $mod_epicloot_sacrifice_inventoryfullexplanation", itemDrop.m_itemData.m_stack, itemDrop.m_itemData.GetIcon()); - } - } while (item.m_stack > 0); - } - } - } -} diff --git a/EpicLoot-UnityLib/EnchantingTableUpgrades.cs b/EpicLoot-UnityLib/EnchantingTableUpgrades.cs deleted file mode 100644 index 2e08ca9f4..000000000 --- a/EpicLoot-UnityLib/EnchantingTableUpgrades.cs +++ /dev/null @@ -1,177 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Runtime.CompilerServices; -using UnityEngine; - -namespace EpicLoot_UnityLib -{ - public enum EnchantingFeature - { - Sacrifice, - ConvertMaterials, - Enchant, - Augment, - Disenchant, - Helheim - } - - [Serializable] - public class ItemAmount - { - public string Item = ""; - public int Amount = 1; - } - - [Serializable] - public class EnchantingUpgradeCosts - { - public List> Sacrifice; - public List> ConvertMaterials; - public List> Enchant; - public List> Augment; - public List> Disenchant; - public List> Helheim; - } - - [Serializable] - public class EnchantingFeatureValues - { - public List Sacrifice; - public List ConvertMaterials; - public List Enchant; - public List Augment; - public List Disenchant; - public List Helheim; - } - - [Serializable] - public class EnchantingUpgradesConfig - { - public Dictionary DefaultFeatureLevels; - public Dictionary MaximumFeatureLevels; - public EnchantingUpgradeCosts UpgradeCosts; - public EnchantingFeatureValues UpgradeValues; - } - - public class EnchantingFeatureUpgradeRequest - { - public ZDOID TableZDO; - public EnchantingFeature Feature; - public int ToLevel; - public Action ResponseCallback; - } - - public static class EnchantingTableUpgrades - { - public static EnchantingUpgradesConfig Config; - - - public static void InitializeConfig(EnchantingUpgradesConfig config) - { - Config = config; - } - - public static string GetFeatureName(EnchantingFeature feature) - { - var featureNames = new [] - { - "$mod_epicloot_sacrifice", - "$mod_epicloot_convertmaterials", - "$mod_epicloot_enchant", - "$mod_epicloot_augment", - "$mod_epicloot_disenchant", - "$mod_epicloot_helheim", - }; - return featureNames[(int)feature]; - } - - public static string GetFeatureDescription(EnchantingFeature feature) - { - var featureDescriptions = new [] - { - "$mod_epicloot_featureinfo_sacrifice", - "$mod_epicloot_featureinfo_convertmaterials", - "$mod_epicloot_featureinfo_enchant", - "$mod_epicloot_featureinfo_augment", - "$mod_epicloot_featureinfo_disenchant", - "$mod_epicloot_featureinfo_helheim", - }; - return featureDescriptions[(int)feature]; - } - - public static string GetFeatureUpgradeLevelDescription(EnchantingTable table, EnchantingFeature feature, int level) - { - var featureUpgradeDescriptions = new [] - { - "$mod_epicloot_featureupgrade_sacrifice", - "$mod_epicloot_featureupgrade_convertmaterials", - "$mod_epicloot_featureupgrade_enchant", - "$mod_epicloot_featureupgrade_augment", - "$mod_epicloot_featureupgrade_disenchant", - "$mod_epicloot_featureupgrade_helheim", - }; - - var values = table.GetFeatureValue(feature, level); - return Localization.instance.Localize(featureUpgradeDescriptions[(int)feature], values.Item1.ToString("0.#"), values.Item2.ToString("0.#")); - } - - public static int GetFeatureMaxLevel(EnchantingFeature feature) - { - return Config.MaximumFeatureLevels.TryGetValue(feature, out var maxLevel) ? maxLevel : 1; - } - - public static List GetUpgradeCost(EnchantingFeature feature, int level) - { - var result = new List(); - - var upgradeCosts = feature switch - { - EnchantingFeature.Sacrifice => Config.UpgradeCosts.Sacrifice, - EnchantingFeature.ConvertMaterials => Config.UpgradeCosts.ConvertMaterials, - EnchantingFeature.Enchant => Config.UpgradeCosts.Enchant, - EnchantingFeature.Augment => Config.UpgradeCosts.Augment, - EnchantingFeature.Disenchant => Config.UpgradeCosts.Disenchant, - EnchantingFeature.Helheim => Config.UpgradeCosts.Helheim, - _ => throw new ArgumentOutOfRangeException(nameof(feature), feature, null) - }; - - if (upgradeCosts == null) - return result; - - if (level < 0 || level >= upgradeCosts.Count) - { - Debug.LogWarning($"[EpicLoot] Warning: tried to get enchanting feature upgrade cost for level that does not exist ({feature}, {level})"); - return result; - } - - var costList = upgradeCosts[level]; - if (costList == null) - return result; - - foreach (var itemAmountConfig in costList) - { - var prefab = ObjectDB.instance.GetItemPrefab(itemAmountConfig.Item); - if (prefab == null) - { - Debug.LogWarning($"[EpicLoot] Tried to add unknown item ({itemAmountConfig.Item}) to upgrade cost for feature ({feature}, {level})"); - continue; - } - - var itemDrop = prefab.GetComponent(); - if (itemDrop == null) - { - Debug.LogWarning($"[EpicLoot] Tried to add item without ItemDrop ({itemAmountConfig.Item}) to upgrade cost for feature ({feature}, {level})"); - continue; - } - - var costItem = itemDrop.m_itemData.Clone(); - costItem.m_dropPrefab = prefab; - costItem.m_stack = itemAmountConfig.Amount; - result.Add(new InventoryItemListElement() { Item = costItem }); - } - - return result; - } - } -} diff --git a/EpicLoot-UnityLib/EpicLoot-UnityLib.csproj b/EpicLoot-UnityLib/EpicLoot-UnityLib.csproj index 578ac689e..6c1d698b4 100644 --- a/EpicLoot-UnityLib/EpicLoot-UnityLib.csproj +++ b/EpicLoot-UnityLib/EpicLoot-UnityLib.csproj @@ -2,6 +2,8 @@ + + Debug AnyCPU @@ -10,7 +12,8 @@ Properties EpicLoot_UnityLib EpicLoot-UnityLib - v4.6.1 + v4.8 + 10.0 512 true @@ -38,104 +41,23 @@ latest - - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Valheim\BepInEx\core\0Harmony.dll - - - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Valheim\valheim_Data\Managed\publicized_assemblies\assembly_guiutils_publicized.dll - - - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Valheim\valheim_Data\Managed\publicized_assemblies\assembly_utils_publicized.dll - - - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Valheim\valheim_Data\Managed\publicized_assemblies\assembly_valheim_publicized.dll - - - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Valheim\BepInEx\core\BepInEx.dll - - - False - ..\Libs\Newtonsoft.Json.dll - - - False - ..\Libs\Newtonsoft.Json.dll - - - - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Valheim\unstripped_corlib\UnityEngine.dll - - - False - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Valheim\unstripped_corlib\UnityEngine.AssetBundleModule.dll - - - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Valheim\unstripped_corlib\UnityEngine.AudioModule.dll - - - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Valheim\unstripped_corlib\UnityEngine.CoreModule.dll - - - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Valheim\unstripped_corlib\UnityEngine.ImageConversionModule.dll - - - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Valheim\unstripped_corlib\UnityEngine.InputLegacyModule.dll - - - False - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Valheim\unstripped_corlib\UnityEngine.ParticleSystemModule.dll - - - False - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Valheim\unstripped_corlib\UnityEngine.PhysicsModule.dll - - - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Valheim\unstripped_corlib\UnityEngine.TextCoreModule.dll - - - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Valheim\unstripped_corlib\UnityEngine.TextRenderingModule.dll - - - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Valheim\unstripped_corlib\UnityEngine.UI.dll - - - ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Valheim\unstripped_corlib\UnityEngine.UIModule.dll - - - - - - - - - - - - - - - - - + - - - - - - + + + - - xcopy "$(TargetDir)$(TargetFileName)" "$(SolutionDir)ValheimUnity\Assets\ExternalLibraries\" /q /y /i - This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + \ No newline at end of file diff --git a/EpicLoot-UnityLib/FeatureStatus.cs b/EpicLoot-UnityLib/FeatureStatus.cs deleted file mode 100644 index 14e30af32..000000000 --- a/EpicLoot-UnityLib/FeatureStatus.cs +++ /dev/null @@ -1,143 +0,0 @@ -using System.Text; -using UnityEngine; -using UnityEngine.UI; - -namespace EpicLoot_UnityLib -{ - public class FeatureStatus : MonoBehaviour - { - public EnchantingFeature Feature; - public Transform UnlockedContainer; - public Transform LockedContainer; - public GameObject UnlockedLabel; - public Image[] Stars; - public Text ManyStarsLabel; - public UITooltip Tooltip; - - public delegate void MakeFeatureUnlockTooltipDelegate(GameObject obj); - public static MakeFeatureUnlockTooltipDelegate MakeFeatureUnlockTooltip; - - public delegate bool UpgradesActiveDelegate(EnchantingFeature feature, out bool featureActive); - public static UpgradesActiveDelegate UpgradesActive; - - - public void Awake() - { - if (Tooltip != null) - MakeFeatureUnlockTooltip(Tooltip.gameObject); - } - - public void OnEnable() - { - EnchantingTableUI.instance.SourceTable.OnFeatureLevelChanged += OnFeatureLevelChanged; - Refresh(); - } - - public void OnDisable() - { - EnchantingTableUI.instance.SourceTable.OnFeatureLevelChanged -= OnFeatureLevelChanged; - } - - public void SetFeature(EnchantingFeature feature) - { - if (Feature != feature) - { - Feature = feature; - Refresh(); - } - } - - public void Refresh() - { - if (EnchantingTableUI.instance == null || EnchantingTableUI.instance.SourceTable == null) - return; - - if (!EnchantingTableUI.instance.SourceTable.IsFeatureAvailable(Feature)) - { - if (UnlockedContainer != null) - UnlockedContainer.gameObject.SetActive(false); - if (LockedContainer != null) - LockedContainer.gameObject.SetActive(false); - return; - } - - if (EnchantingTableUI.instance.SourceTable.IsFeatureLocked(Feature)) - { - if (UnlockedContainer != null) - UnlockedContainer.gameObject.SetActive(false); - if (LockedContainer != null) - LockedContainer.gameObject.SetActive(true); - } - else - { - if (UnlockedContainer != null) - UnlockedContainer.gameObject.SetActive(true); - if (LockedContainer != null) - LockedContainer.gameObject.SetActive(false); - - var level = EnchantingTableUI.instance.SourceTable.GetFeatureLevel(Feature); - if (level > Stars.Length) - { - for (var index = 0; index < Stars.Length; index++) - { - var star = Stars[index]; - star.enabled = index == 0; - } - - if (ManyStarsLabel != null) - { - ManyStarsLabel.enabled = true; - ManyStarsLabel.text = $"×{level}"; - } - } - else - { - for (var index = 0; index < Stars.Length; index++) - { - var star = Stars[index]; - star.gameObject.SetActive(level > index && UpgradesActive(Feature,out _)); - } - - if (ManyStarsLabel != null) - ManyStarsLabel.enabled = false; - } - - if (UnlockedLabel != null) - UnlockedLabel.SetActive(level == 0); - } - - if (Tooltip != null && UpgradesActive(Feature,out _)) - { - Tooltip.m_topic = Localization.instance.Localize(EnchantingTableUpgrades.GetFeatureName(Feature)); - - var sb = new StringBuilder(); - var locked = EnchantingTableUI.instance.SourceTable.IsFeatureLocked(Feature); - var currentLevel = EnchantingTableUI.instance.SourceTable.GetFeatureLevel(Feature); - var maxLevel = EnchantingTableUpgrades.GetFeatureMaxLevel(Feature); - if (locked) - sb.AppendLine(Localization.instance.Localize("$mod_epicloot_currentlevel: $mod_epicloot_featurelocked")); - else if (currentLevel == 0) - sb.AppendLine(Localization.instance.Localize($"$mod_epicloot_currentlevel: $mod_epicloot_featureunlocked / {maxLevel}")); - else - sb.AppendLine(Localization.instance.Localize($"$mod_epicloot_currentlevel: {currentLevel} / {maxLevel}")); - - if (!locked && currentLevel > 0) - { - var text = EnchantingTableUpgrades.GetFeatureUpgradeLevelDescription(EnchantingTableUI.instance.SourceTable, Feature, currentLevel); - sb.AppendLine($"{text}"); - } - - sb.AppendLine(); - sb.AppendLine(Localization.instance.Localize(EnchantingTableUpgrades.GetFeatureDescription(Feature))); - - Tooltip.m_text = Localization.instance.Localize(sb.ToString()); - } - } - - private void OnFeatureLevelChanged(EnchantingFeature feature, int _) - { - if (isActiveAndEnabled && feature == Feature) - Refresh(); - } - } -} diff --git a/EpicLoot-UnityLib/FeatureStatus3D.cs b/EpicLoot-UnityLib/FeatureStatus3D.cs deleted file mode 100644 index 07ca5d4a1..000000000 --- a/EpicLoot-UnityLib/FeatureStatus3D.cs +++ /dev/null @@ -1,42 +0,0 @@ -using UnityEngine; - -namespace EpicLoot_UnityLib -{ - public class FeatureStatus3D : MonoBehaviour - { - public EnchantingTable SourceTable; - public EnchantingFeature Feature; - public GameObject UnlockedObject; - public GameObject[] LevelObjects; - - public void OnEnable() - { - SourceTable.OnAnyFeatureLevelChanged += Refresh; - Refresh(); - } - - public void OnDisable() - { - SourceTable.OnAnyFeatureLevelChanged -= Refresh; - } - - public void Refresh() - { - var featureIsUnlocked = SourceTable.IsFeatureAvailable(Feature) && SourceTable.IsFeatureUnlocked(Feature); - if (UnlockedObject != null) - UnlockedObject.SetActive(featureIsUnlocked); - - var currentLevel = SourceTable.GetFeatureLevel(Feature); - for (var index = 0; index < LevelObjects.Length; index++) - { - var levelObject = LevelObjects[index]; - if (levelObject == null) - continue; - levelObject.SetActive(featureIsUnlocked && currentLevel == index); - } - - if (featureIsUnlocked && currentLevel >= LevelObjects.Length && LevelObjects[LevelObjects.Length - 1] != null) - LevelObjects[LevelObjects.Length - 1].SetActive(true); - } - } -} diff --git a/EpicLoot-UnityLib/MultiSelectItemList.cs b/EpicLoot-UnityLib/MultiSelectItemList.cs deleted file mode 100644 index e1d6e5513..000000000 --- a/EpicLoot-UnityLib/MultiSelectItemList.cs +++ /dev/null @@ -1,612 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text.RegularExpressions; -using UnityEngine; -using UnityEngine.UI; - -namespace EpicLoot_UnityLib -{ - public interface IListElement - { - ItemDrop.ItemData GetItem(); - int GetMax(); - string GetDisplayNameSuffix(); - } - - public class InventoryItemListElement : IListElement - { - public ItemDrop.ItemData Item; - - public ItemDrop.ItemData GetItem() => Item; - public int GetMax() => Item?.m_stack ?? 0; - public string GetDisplayNameSuffix() => string.Empty; - } - - public class MultiSelectItemList : MonoBehaviour - { - public enum SortMode { Rarity, Name, Quantity } - - public bool Multiselect = true; - public bool Filterable = true; - public bool Sortable = true; - public bool ReadOnly = false; - public Transform ListContainer; - public MultiSelectItemListElement ElementPrefab; - public Dropdown SortByDropdown; - public InputField FilterByText; - public Toggle SelectAllToggle; - - public event Action OnSelectedItemsChanged; - public event Action OnItemsChanged; - - public delegate List SortByRarityDelegate(List items); - public delegate List SortByNameDelegate(List items); - - public static SortByRarityDelegate SortByRarity; - public static SortByNameDelegate SortByName; - - private bool _locked; - private bool _hasGamepadFocus; - private ScrollRectEnsureVisible _scrollRectEnsureVisible; - - public void Awake() - { - var scrollRect = GetComponentInChildren(); - _scrollRectEnsureVisible = scrollRect != null ? scrollRect.GetComponent() : null; - - if (SelectAllToggle != null) - SelectAllToggle.onValueChanged.AddListener(OnSelectAllToggled); - - if (SortByDropdown != null) - { - foreach (var optionData in SortByDropdown.options) - { - optionData.text = Localization.instance.Localize(optionData.text); - } - - SortByDropdown.onValueChanged.AddListener(OnSortModeChanged); - } - - if (FilterByText != null) - { - FilterByText.onValueChanged.AddListener(OnFilterChanged); - } - - Refresh(); - } - - public void Update() - { - if (_locked || !HasGamepadFocus() || !ZInput.IsGamepadActive() || ListContainer == null) - return; - - var elementCount = ListContainer.childCount; - var focusedElement = GetFocusedElement(); - if (focusedElement == null) - return; - - var focusedElementIndex = focusedElement.transform.GetSiblingIndex(); - var grid = ListContainer.GetComponent(); - if (ListContainer.GetComponent() != null) - { - if (focusedElementIndex > 0 && ZInput.GetButtonDown("JoyLStickUp")) - { - focusedElement.GiveFocus(false); - var newElement = GetElement(focusedElementIndex - 1); - newElement.GiveFocus(true); - CenterOnItem(newElement); - ZInput.ResetButtonStatus("JoyLStickUp"); - } - else if (focusedElementIndex < elementCount - 1 && ZInput.GetButtonDown("JoyLStickDown")) - { - focusedElement.GiveFocus(false); - var newElement = GetElement(focusedElementIndex + 1); - newElement.GiveFocus(true); - CenterOnItem(newElement); - ZInput.ResetButtonStatus("JoyLStickDown"); - } - else if (ZInput.GetButtonDown("JoyLStickLeft")) - { - ZInput.ResetButtonStatus("JoyLStickLeft"); - } - else if (ZInput.GetButtonDown("JoyLStickRight")) - { - ZInput.ResetButtonStatus("JoyLStickRight"); - } - } - else if (grid != null) - { - var columnCount = grid.constraintCount; - - if (focusedElementIndex >= columnCount && ZInput.GetButtonDown("JoyLStickUp")) - { - focusedElement.GiveFocus(false); - var newElement = GetElement(focusedElementIndex - columnCount); - newElement.GiveFocus(true); - CenterOnItem(newElement); - ZInput.ResetButtonStatus("JoyLStickUp"); - } - else if (focusedElementIndex < elementCount - columnCount && ZInput.GetButtonDown("JoyLStickDown")) - { - focusedElement.GiveFocus(false); - var newElement = GetElement(focusedElementIndex + columnCount); - newElement.GiveFocus(true); - CenterOnItem(newElement); - ZInput.ResetButtonStatus("JoyLStickDown"); - } - else if ((focusedElementIndex % columnCount) > 0 && ZInput.GetButtonDown("JoyLStickLeft")) - { - focusedElement.GiveFocus(false); - var newElement = GetElement(focusedElementIndex - 1); - newElement.GiveFocus(true); - CenterOnItem(newElement); - ZInput.ResetButtonStatus("JoyLStickLeft"); - } - else if ((focusedElementIndex % columnCount) < columnCount - 1 && focusedElementIndex < elementCount - 1 && ZInput.GetButtonDown("JoyLStickRight")) - { - focusedElement.GiveFocus(false); - var newElement = GetElement(focusedElementIndex + 1); - newElement.GiveFocus(true); - CenterOnItem(newElement); - ZInput.ResetButtonStatus("JoyLStickRight"); - } - } - - if (Multiselect && SelectAllToggle != null) - { - if (ZInput.GetButtonDown("JoyLStick")) - { - SelectAllToggle.isOn = !SelectAllToggle.isOn; - ZInput.ResetButtonStatus("JoyLStick"); - } - } - - if (Sortable && SortByDropdown != null) - { - if (ZInput.GetButtonDown("JoyRStick")) - { - var currentSortMode = SortByDropdown.value; - var sortModeCount = SortByDropdown.options.Count; - currentSortMode = ((currentSortMode + 1) % sortModeCount); - SortByDropdown.value = currentSortMode; - ZInput.ResetButtonStatus("JoyRStick"); - } - } - } - - private void CenterOnItem(MultiSelectItemListElement element) - { - if (_scrollRectEnsureVisible != null) - _scrollRectEnsureVisible.CenterOnItem((RectTransform)element.transform); - } - - private void OnFilterChanged(string _) - { - Refresh(); - } - - public void Refresh() - { - RefreshFilter(); - RefreshSelectAllToggle(); - } - - private void RefreshFilter() - { - if (FilterByText != null && !Filterable && FilterByText.gameObject.activeSelf) - { - FilterByText.gameObject.SetActive(false); - } - - if (!Filterable || FilterByText == null) - return; - - var filterText = FilterByText.text; - var filterIsEmpty = string.IsNullOrEmpty(filterText) || string.IsNullOrWhiteSpace(filterText); - - var filterParts = filterIsEmpty ? Array.Empty() : filterText.Split(new []{' '}, StringSplitOptions.RemoveEmptyEntries); - var elementCount = ListContainer.childCount; - for (var i = 0; i < elementCount; ++i) - { - var childToCache = ListContainer.GetChild(i); - var element = childToCache.GetComponent(); - - // Strip rich text tags from item name - var itemName = element.ItemName.text; - var richTextRegex = new Regex(@"<[^>]*>"); - itemName = richTextRegex.Replace(itemName, string.Empty); - - var nameMatches = filterIsEmpty; - foreach (var part in filterParts) - { - if (itemName.IndexOf(part, StringComparison.OrdinalIgnoreCase) >= 0) - { - nameMatches = true; - break; - } - } - - element.gameObject.SetActive(nameMatches); - } - } - - public void RefreshSelectAllToggle() - { - if (SelectAllToggle != null) - { - if (!Multiselect && SelectAllToggle.gameObject.activeSelf) - { - SelectAllToggle.gameObject.SetActive(false); - return; - } - - var allAreSelected = true; - var elementCount = ListContainer.childCount; - for (var i = 0; i < elementCount; ++i) - { - var childToCache = ListContainer.GetChild(i); - var element = childToCache.GetComponent(); - if (!element.IsMaxSelected()) - allAreSelected = false; - } - - SelectAllToggle.SetIsOnWithoutNotify(allAreSelected); - } - } - - private void OnSelectAllToggled(bool _ = true) - { - if (SelectAllToggle == null) - return; - - if (SelectAllToggle.isOn) - ForeachElement((_, x) => x.SelectMaxQuantity(true)); - else - ForeachElement((_, x) => x.Deselect(true)); - RefreshSelectAllToggle(); - } - - private void OnSortModeChanged(int sortModeValue) - { - if (!Sortable || SortByDropdown == null) - return; - - var previousSelectionAmounts = GetCurrentSelectionAmounts(); - - var items = previousSelectionAmounts.Keys.ToList(); - var sortMode = (SortMode)SortByDropdown.value; - var sortedItems = SortItems(sortMode, items); - - for (var i = 0; i < sortedItems.Count; ++i) - { - var childToSet = ListContainer.GetChild(i); - var itemToSet = sortedItems[i]; - var element = childToSet.GetComponent(); - element.SuppressEvents = true; - element.SetItem(itemToSet); - if (previousSelectionAmounts.TryGetValue(itemToSet, out var previousQuantity)) - element.SelectQuantity(previousQuantity, true); - element.SuppressEvents = false; - } - - RefreshSelectAllToggle(); - } - - public Dictionary GetCurrentSelectionAmounts() - { - var selectionAmounts = new Dictionary(); - var elementCount = ListContainer.childCount; - for (var i = 0; i < elementCount; ++i) - { - var childToCache = ListContainer.GetChild(i); - var element = childToCache.GetComponent(); - if (element != null && element.GetItem() != null) - selectionAmounts.Add(element.GetListElement(), element.GetSelectedQuantity()); - } - - return selectionAmounts; - } - - private void MakeEnoughElements(int itemCount) - { - var elementCount = ListContainer.childCount; - if (elementCount > itemCount) - { - for (var i = elementCount - 1; i >= itemCount; --i) - { - var childToDestroy = ListContainer.GetChild(i); - var element = childToDestroy.GetComponent(); - element.OnSelectionChanged -= OnElementSelectionChanged; - DestroyImmediate(childToDestroy.gameObject); - } - } - else if (elementCount < itemCount) - { - for (var i = elementCount; i < itemCount; ++i) - { - var newElement = Instantiate(ElementPrefab, ListContainer); - newElement.SuppressEvents = true; - newElement.OnSelectionChanged += OnElementSelectionChanged; - } - } - } - - public void SetItems(List items) - { - var itemCount = items.Count; - - var previousSelectionAmounts = GetCurrentSelectionAmounts(); - var focusedElement = GetFocusedElement(); - - MakeEnoughElements(itemCount); - - var sortedItems = items; - if (Sortable && SortByDropdown != null) - { - var sortMode = (SortMode)SortByDropdown.value; - sortedItems = SortItems(sortMode, items); - } - - var didFocus = false; - for (var i = 0; i < itemCount; ++i) - { - var childToSet = ListContainer.GetChild(i); - var itemToSet = sortedItems[i]; - var element = childToSet.GetComponent(); - element.SuppressEvents = true; - element.SetItem(itemToSet); - if (previousSelectionAmounts.TryGetValue(itemToSet, out var previousQuantity)) - element.SelectQuantity(previousQuantity, true); - element.SuppressEvents = false; - var shouldFocus = HasGamepadFocus() && ((focusedElement == null && i == 0) || element == focusedElement); - element.GiveFocus(shouldFocus); - if (shouldFocus) - { - didFocus = true; - CenterOnItem(element); - } - } - - if (HasGamepadFocus() && !didFocus && ListContainer.childCount > 0) - { - // Force GiveFocus to fire - _hasGamepadFocus = false; - GiveFocus(true, 0); - CenterOnItem(GetElement(0)); - } - - OnItemsChanged?.Invoke(); - OnSelectedItemsChanged?.Invoke(); - RefreshSelectAllToggle(); - } - - private void OnElementSelectionChanged(MultiSelectItemListElement element, bool isSelected, int selectedQuantity) - { - if (!Multiselect) - { - ForeachElement((_, x) => - { - if (x != element) - { - x.SuppressEvents = true; - x.Deselect(true); - x.SuppressEvents = false; - } - }); - } - - OnSelectedItemsChanged?.Invoke(); - RefreshSelectAllToggle(); - } - - public List SortItems(SortMode mode, List items) - { - switch (mode) - { - case SortMode.Rarity: - if (SortByRarity != null) - return SortByRarity(items); - break; - - case SortMode.Name: - if (SortByName != null) - return SortByName(items); - else - return items.OrderBy(x => Localization.instance.Localize(x.GetItem().m_shared.m_name)).ThenByDescending(x => x.GetItem().m_stack).ToList(); - - case SortMode.Quantity: - return items.OrderByDescending(x => x.GetItem().m_stack).ThenBy(x => Localization.instance.Localize(x.GetItem().m_shared.m_name)).ToList(); - - default: - throw new ArgumentOutOfRangeException(nameof(mode), mode, null); - } - - return items.ToList(); - } - - public List> GetSelectedItems() - { - var result = new List>(); - var elementCount = ListContainer.childCount; - for (var i = 0; i < elementCount; ++i) - { - var childToCache = ListContainer.GetChild(i); - var element = childToCache.GetComponent(); - var quantity = element.GetSelectedQuantity(); - if (quantity > 0) - result.Add(new Tuple((T)element.GetListElement(), quantity)); - } - - return result; - } - - public Tuple GetSingleSelectedItem() - { - var elementCount = ListContainer.childCount; - for (var i = 0; i < elementCount; ++i) - { - var childToCache = ListContainer.GetChild(i); - var element = childToCache.GetComponent(); - var quantity = element.GetSelectedQuantity(); - if (quantity > 0) - return new Tuple((T)element.GetListElement(), quantity); - } - - return null; - } - - public int GetFirstSelectedIndex() - { - var elementCount = ListContainer.childCount; - for (var i = 0; i < elementCount; ++i) - { - var childToCache = ListContainer.GetChild(i); - var element = childToCache.GetComponent(); - var quantity = element.GetSelectedQuantity(); - if (quantity > 0) - return i; - } - - return -1; - } - - public void Lock() - { - _locked = true; - if (SortByDropdown != null) - SortByDropdown.interactable = false; - if (FilterByText != null) - FilterByText.interactable = false; - if (SelectAllToggle != null) - SelectAllToggle.interactable = false; - ForeachElement((_, e) => e.Lock()); - } - - public void Unlock() - { - _locked = false; - if (SortByDropdown != null) - SortByDropdown.interactable = Sortable && !ReadOnly; - if (FilterByText != null) - FilterByText.interactable = Filterable && !ReadOnly; - if (SelectAllToggle != null) - SelectAllToggle.interactable = Multiselect && !ReadOnly; - ForeachElement((_, e) => e.Unlock()); - } - - private MultiSelectItemListElement GetElement(int index) - { - var child = ListContainer.GetChild(index); - return child == null ? null : child.GetComponent(); - } - - public void ForeachElement(Action func) - { - if (ListContainer == null) - return; - - var elementCount = ListContainer.childCount; - for (var i = 0; i < elementCount; ++i) - { - var element = GetElement(i); - if (element != null) - func(i, element); - } - } - - public void DeselectAll() - { - SuppressEvents(true); - ForeachElement((_, e) => e.Deselect(true)); - SuppressEvents(false); - - OnSelectedItemsChanged?.Invoke(); - RefreshSelectAllToggle(); - } - - public void SuppressEvents(bool suppress) - { - ForeachElement((_, e) => e.SuppressEvents = suppress); - } - - public void GiveFocus(bool focused, int tryFocusIndex) - { - if (_hasGamepadFocus != focused) - { - _hasGamepadFocus = focused; - - var focusIndex = focused ? Mathf.Clamp(tryFocusIndex, 0, ListContainer.childCount - 1) : -1; - ForeachElement((i, e) => - { - var shouldFocus = i == focusIndex; - e.GiveFocus(shouldFocus); - if (shouldFocus) - CenterOnItem(e); - }); - } - } - - public bool HasGamepadFocus() - { - return _hasGamepadFocus; - } - - public MultiSelectItemListElement GetFocusedElement() - { - if (ListContainer == null || !ZInput.IsGamepadActive()) - return null; - - var elementCount = ListContainer.childCount; - for (var i = 0; i < elementCount; ++i) - { - var child = ListContainer.GetChild(i); - if (child == null) - continue; - - var element = child.GetComponent(); - if (element != null && element.HasGamepadFocus()) - return element; - } - - return null; - } - - public int GetItemCount() - { - if (ListContainer == null) - return 0; - - var elementCount = ListContainer.childCount; - var activeChildCount = 0; - for (var i = 0; i < elementCount; ++i) - { - var child = ListContainer.GetChild(i); - if (child != null && child.gameObject.activeSelf) - activeChildCount++; - } - - return activeChildCount; - } - - public bool IsGrid() - { - return ListContainer != null && ListContainer.GetComponent() != null; - } - - public void InitWithExistingItems() - { - for (var i = 0; i < ListContainer.childCount; ++i) - { - var childToSet = ListContainer.GetChild(i); - var element = childToSet.GetComponent(); - element.OnSelectionChanged += OnElementSelectionChanged; - } - DeselectAll(); - - OnItemsChanged?.Invoke(); - OnSelectedItemsChanged?.Invoke(); - RefreshSelectAllToggle(); - } - } -} diff --git a/EpicLoot-UnityLib/MultiSelectListFocusController.cs b/EpicLoot-UnityLib/MultiSelectListFocusController.cs deleted file mode 100644 index 051f33798..000000000 --- a/EpicLoot-UnityLib/MultiSelectListFocusController.cs +++ /dev/null @@ -1,123 +0,0 @@ -using System.Collections.Generic; -using UnityEngine; - -namespace EpicLoot_UnityLib -{ - public class MultiSelectListFocusController : MonoBehaviour - { - public List Lists = new List(); - public GameObject[] SortHints; - public GameObject[] SelectAllHints; - public GameObject[] SelectHints; - - private int _focusedListIndex; - private bool _gamepadWasEnabled; - - - public void OnEnable() - { - _focusedListIndex = 0; - for (var index = 0; index < Lists.Count; index++) - { - Lists[index].GiveFocus(index == _focusedListIndex, 0); - } - RefreshHints(); - } - - public void Update() - { - if (Lists.Count == 0) - return; - - var currentList = Lists[_focusedListIndex]; - var loopCount = 0; - var itemCount = currentList.GetItemCount(); - while (itemCount == 0) - { - currentList.GiveFocus(false, 0); - - _focusedListIndex = (_focusedListIndex + 1) % Lists.Count; - currentList = Lists[_focusedListIndex]; - itemCount = currentList.GetItemCount(); - if (currentList.GetItemCount() > 0) - { - currentList.GiveFocus(true, 0); - RefreshHints(); - break; - } - loopCount++; - if (loopCount >= Lists.Count) - return; - } - - if (ZInput.IsGamepadActive()) - { - var newFocusedIndex = _focusedListIndex; - if (ZInput.GetButtonDown("JoyTabLeft")) - { - newFocusedIndex = Mathf.Max(_focusedListIndex - 1, 0); - ZInput.ResetButtonStatus("JoyTabLeft"); - } - else if (ZInput.GetButtonDown("JoyTabRight")) - { - newFocusedIndex = Mathf.Min(_focusedListIndex + 1, Lists.Count - 1); - ZInput.ResetButtonStatus("JoyTabRight"); - } - - if (newFocusedIndex != _focusedListIndex) - { - var offset = newFocusedIndex - _focusedListIndex; - if (Lists[newFocusedIndex].GetItemCount() == 0) - newFocusedIndex = (newFocusedIndex + offset + Lists.Count) % Lists.Count; - if (Lists[newFocusedIndex].GetItemCount() == 0) - newFocusedIndex = _focusedListIndex; - } - - FocusList(newFocusedIndex); - } - - if (_gamepadWasEnabled != ZInput.IsGamepadActive()) - RefreshHints(); - - _gamepadWasEnabled = ZInput.IsGamepadActive(); - } - - public void FocusList(int newFocusedIndex) - { - var list = Lists[_focusedListIndex]; - var currentFocusElement = list.GetFocusedElement(); - var currentFocusIndex = currentFocusElement != null ? currentFocusElement.transform.GetSiblingIndex() : -1; - if (newFocusedIndex != _focusedListIndex && newFocusedIndex >= 0 && newFocusedIndex < Lists.Count) - { - _focusedListIndex = newFocusedIndex; - for (var index = 0; index < Lists.Count; index++) - { - var isGrid = Lists[index].IsGrid(); - Lists[index].GiveFocus(index == _focusedListIndex, isGrid ? 0 : currentFocusIndex); - } - - RefreshHints(); - } - } - - private void RefreshHints() - { - if (!isActiveAndEnabled || !ZInput.IsGamepadActive() || Lists.Count == 0) - return; - - var focusedList = Lists[_focusedListIndex]; - foreach (var hint in SortHints) - { - hint.SetActive(focusedList.Sortable && focusedList.SortByDropdown != null && focusedList.SortByDropdown.isActiveAndEnabled); - } - foreach (var hint in SelectAllHints) - { - hint.SetActive(focusedList.Multiselect && focusedList.SelectAllToggle != null && focusedList.SelectAllToggle.isActiveAndEnabled); - } - foreach (var hint in SelectHints) - { - hint.SetActive(!focusedList.ReadOnly && focusedList.GetFocusedElement() != null); - } - } - } -} diff --git a/EpicLoot-UnityLib/PlaySoundOnChecked.cs b/EpicLoot-UnityLib/PlaySoundOnChecked.cs deleted file mode 100644 index c91c2b63b..000000000 --- a/EpicLoot-UnityLib/PlaySoundOnChecked.cs +++ /dev/null @@ -1,31 +0,0 @@ -using UnityEngine; -using UnityEngine.UI; - -namespace EpicLoot_UnityLib -{ - [RequireComponent(typeof(Toggle))] - public class PlaySoundOnChecked : MonoBehaviour - { - public AudioSource Audio; - public AudioClip SFX; - - private Toggle _toggle; - - public void Awake() - { - _toggle = GetComponent(); - _toggle.onValueChanged.AddListener(OnToggleChanged); - } - - public void OnDestroy() - { - _toggle.onValueChanged.RemoveListener(OnToggleChanged); - } - - private void OnToggleChanged(bool _) - { - if (Audio != null && SFX != null && _toggle.isOn) - Audio.PlayOneShot(SFX); - } - } -} diff --git a/EpicLoot-UnityLib/SacrificeUI.cs b/EpicLoot-UnityLib/SacrificeUI.cs deleted file mode 100644 index b419f85bb..000000000 --- a/EpicLoot-UnityLib/SacrificeUI.cs +++ /dev/null @@ -1,92 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using JetBrains.Annotations; -using UnityEngine; -using Random = UnityEngine.Random; - -namespace EpicLoot_UnityLib -{ - public class SacrificeUI : EnchantingTableUIPanelBase - { - public MultiSelectItemList SacrificeProducts; - public EnchantBonus BonusPanel; - - public delegate List GetSacrificeItemsDelegate(); - public delegate List GetSacrificeProductsDelegate(List> items); - - public static GetSacrificeItemsDelegate GetSacrificeItems; - public static GetSacrificeProductsDelegate GetSacrificeProducts; - - [UsedImplicitly] - public void OnEnable() - { - var items = GetSacrificeItems(); - AvailableItems.SetItems(items.Cast().ToList()); - AvailableItems.DeselectAll(); - var random = new System.Random(); - } - - protected override void DoMainAction() - { - var selectedItems = AvailableItems.GetSelectedItems(); - var sacrificeProducts = GetSacrificeProducts(selectedItems.Select(x => new Tuple(x.Item1.GetItem(), x.Item2)).ToList()); - - Cancel(); - - var chanceToDoubleEntry = EnchantingTableUI.instance.SourceTable.GetFeatureCurrentValue(EnchantingFeature.Sacrifice); - var chanceToDouble = float.IsNaN(chanceToDoubleEntry.Item1) ? 0.0f : chanceToDoubleEntry.Item1 / 100.0f; - - if (Random.Range(0.0f, 1.0f) < chanceToDouble) - { - EnchantingTableUI.instance.PlayEnchantBonusSFX(); - BonusPanel.Show(); - - foreach (var sacrificeProduct in sacrificeProducts) - { - sacrificeProduct.Item.m_stack *= 2; - } - } - - var player = Player.m_localPlayer; - var inventory = player.GetInventory(); - foreach (var selectedItem in selectedItems) - { - inventory.RemoveItem(selectedItem.Item1.GetItem(), selectedItem.Item2); - } - - GiveItemsToPlayer(sacrificeProducts); - - RefreshAvailableItems(); - AvailableItems.GiveFocus(true, 0); - } - - private void RefreshAvailableItems() - { - var items = GetSacrificeItems(); - AvailableItems.SetItems(items.Cast().ToList()); - AvailableItems.DeselectAll(); - OnSelectedItemsChanged(); - } - - protected override void OnSelectedItemsChanged() - { - var selectedItems = AvailableItems.GetSelectedItems(); - var sacrificeProducts = GetSacrificeProducts(selectedItems.Select(x => new Tuple(x.Item1.GetItem(), x.Item2)).ToList()); - SacrificeProducts.SetItems(sacrificeProducts.Cast().ToList()); - var featureUnlocked = EnchantingTableUI.instance.SourceTable.IsFeatureUnlocked(EnchantingFeature.Sacrifice); - MainButton.interactable = featureUnlocked && selectedItems.Count > 0; - } - - public override void Cancel() - { - _buttonLabel.text = Localization.instance.Localize("$mod_epicloot_sacrifice"); - base.Cancel(); - } - - public override void DeselectAll() - { - AvailableItems.DeselectAll(); - } - } -} diff --git a/EpicLoot-UnityLib/SelectableTextColor.cs b/EpicLoot-UnityLib/SelectableTextColor.cs deleted file mode 100644 index 2db83e32f..000000000 --- a/EpicLoot-UnityLib/SelectableTextColor.cs +++ /dev/null @@ -1,28 +0,0 @@ -using UnityEngine; -using UnityEngine.UI; - -namespace EpicLoot_UnityLib -{ - public class SelectableTextColor : MonoBehaviour - { - private Color _defaultColor = Color.white; - public Color DisabledColor = Color.grey; - private Selectable _selectable; - private Text _text; - - public void Awake() - { - _selectable = GetComponent(); - _text = GetComponentInChildren(); - _defaultColor = _text.color; - } - - public void Update() - { - if (_selectable.IsInteractable()) - _text.color = _defaultColor; - else - _text.color = DisabledColor; - } - } -} diff --git a/EpicLoot-UnityLib/UpgradeTableUI.cs b/EpicLoot-UnityLib/UpgradeTableUI.cs deleted file mode 100644 index 9e04b7e47..000000000 --- a/EpicLoot-UnityLib/UpgradeTableUI.cs +++ /dev/null @@ -1,250 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using System.Text; -using UnityEngine; -using UnityEngine.UI; - -namespace EpicLoot_UnityLib -{ - public class UpgradeTableUI : EnchantingTableUIPanelBase - { - public Transform ListContainer; - public Text SelectedFeatureText; - public Image SelectedFeatureImage; - public FeatureStatus SelectedFeatureStatus; - public Text SelectedFeatureInfoText; - public Text CostLabel; - public MultiSelectItemList CostList; - - private readonly List _featureButtons = new List(); - - private int _selectedFeature = -1; - - protected override void OnSelectedItemsChanged() {} - - public override void Awake() - { - base.Awake(); - _featureButtons.Clear(); - for (var i = 0; i < ListContainer.childCount; ++ i) - { - var child = ListContainer.GetChild(i); - var button = child.GetComponentInChildren(); - if (button != null) - { - _featureButtons.Add(button); - button.OnSelectionChanged += OnButtonSelected; - if (i == 0) - button.SelectMaxQuantity(true); - } - } - - EnchantingTableUI.instance.SourceTable.OnAnyFeatureLevelChanged += Refresh; - Refresh(); - } - - private void OnButtonSelected(MultiSelectItemListElement selectedButton, bool selected, int _) - { - if (_inProgress) - return; - - var noneSelected = !_featureButtons.Any(x => x.IsSelected()); - if (noneSelected) - { - _selectedFeature = -1; - Refresh(); - return; - } - - _selectedFeature = -1; - for (var index = 0; index < _featureButtons.Count; index++) - { - var button = _featureButtons[index]; - if (button == selectedButton) - { - _selectedFeature = index; - } - else - { button.SuppressEvents = true; - button.Deselect(true); - button.SuppressEvents = false; - } - } - - Refresh(); - } - - public void Refresh() - { - for (var index = 0; index < _featureButtons.Count; index++) - { - var button = _featureButtons[index]; - var featureIsEnabled = EnchantingTableUI.instance.SourceTable.IsFeatureAvailable((EnchantingFeature)index); - button.gameObject.SetActive(featureIsEnabled); - } - - if (_selectedFeature >= 0) - { - var selectedButton = _featureButtons[_selectedFeature]; - - SelectedFeatureText.enabled = true; - SelectedFeatureText.text = selectedButton.ItemName.text; - SelectedFeatureImage.enabled = true; - SelectedFeatureImage.sprite = selectedButton.ItemIcon.sprite; - - var selectedFeature = (EnchantingFeature)_selectedFeature; - SelectedFeatureStatus.gameObject.SetActive(true); - SelectedFeatureStatus.SetFeature(selectedFeature); - } - else - { - SelectedFeatureText.enabled = false; - SelectedFeatureImage.enabled = false; - SelectedFeatureStatus.gameObject.SetActive(false); - } - - SelectedFeatureInfoText.text = GenerateFeatureInfoText(); - - if (_selectedFeature < 0) - { - CostLabel.enabled = false; - CostList.gameObject.SetActive(false); - MainButton.interactable = false; - return; - } - - var feature = (EnchantingFeature)_selectedFeature; - CostLabel.enabled = true; - var maxLevel = EnchantingTableUI.instance.SourceTable.IsFeatureMaxLevel(feature); - var canAfford = true; - if (maxLevel) - { - CostLabel.text = Localization.instance.Localize("$mod_epicloot_featuremaxlevel"); - CostList.SetItems(new List()); - } - else - { - if (EnchantingTableUI.instance.SourceTable.IsFeatureLocked(feature)) - { - var cost = EnchantingTableUI.instance.SourceTable.GetFeatureUnlockCost(feature); - CostLabel.text = Localization.instance.Localize("$mod_epicloot_unlockcost"); - CostList.SetItems(cost.Cast().ToList()); - canAfford = LocalPlayerCanAffordCost(cost); - _buttonLabel.text = Localization.instance.Localize("$mod_epicloot_featureunlock"); - } - else - { - var cost = EnchantingTableUI.instance.SourceTable.GetFeatureUpgradeCost(feature); - CostLabel.text = Localization.instance.Localize("$mod_epicloot_upgradecost"); - CostList.SetItems(cost.Cast().ToList()); - canAfford = LocalPlayerCanAffordCost(cost); - _buttonLabel.text = Localization.instance.Localize("$mod_epicloot_upgrade"); - } - } - - CostList.gameObject.SetActive(!maxLevel && _selectedFeature >= 0); - MainButton.interactable = !maxLevel && canAfford; - } - - private string GenerateFeatureInfoText() - { - if (_selectedFeature < 0) - return Localization.instance.Localize("$mod_epicloot_featureinfo_none"); - - var sb = new StringBuilder(); - - var feature = (EnchantingFeature)_selectedFeature; - var locked = EnchantingTableUI.instance.SourceTable.IsFeatureLocked(feature); - var currentLevel = EnchantingTableUI.instance.SourceTable.GetFeatureLevel(feature); - var maxLevel = EnchantingTableUpgrades.GetFeatureMaxLevel(feature); - sb.AppendLine(Localization.instance.Localize($"{EnchantingTableUpgrades.GetFeatureName(feature)}")); - sb.AppendLine(); - if (locked) - sb.AppendLine(Localization.instance.Localize("$mod_epicloot_currentlevel: $mod_epicloot_featurelocked")); - else if (currentLevel == 0) - sb.AppendLine(Localization.instance.Localize($"$mod_epicloot_currentlevel: $mod_epicloot_featureunlocked / {maxLevel}")); - else - sb.AppendLine(Localization.instance.Localize($"$mod_epicloot_currentlevel: {currentLevel} / {maxLevel}")); - sb.AppendLine(); - - sb.AppendLine(Localization.instance.Localize(EnchantingTableUpgrades.GetFeatureDescription(feature))); - sb.AppendLine(); - sb.AppendLine(Localization.instance.Localize("$mod_epicloot_effectsperlevel")); - - for (var i = 1; i <= maxLevel; ++i) - { - var text = EnchantingTableUpgrades.GetFeatureUpgradeLevelDescription(EnchantingTableUI.instance.SourceTable, feature, i); - sb.AppendLine($"{i}: " + (i == currentLevel ? $"{text}" : text)); - } - - return sb.ToString(); - } - - protected override void DoMainAction() - { - Cancel(); - if (_selectedFeature < 0) - return; - - var feature = (EnchantingFeature)_selectedFeature; - var maxLevel = EnchantingTableUI.instance.SourceTable.IsFeatureMaxLevel(feature); - if (maxLevel) - return; - - var cost = EnchantingTableUI.instance.SourceTable.IsFeatureLocked(feature) - ? EnchantingTableUI.instance.SourceTable.GetFeatureUnlockCost(feature) - : EnchantingTableUI.instance.SourceTable.GetFeatureUpgradeCost(feature); - - var canAfford = LocalPlayerCanAffordCost(cost); - if (canAfford) - { - var currentLevel = EnchantingTableUI.instance.SourceTable.GetFeatureLevel(feature); - EnchantingTableUI.instance.SourceTable.RequestTableUpgrade(feature, currentLevel +1, (success)=> - { - if (!success) - { - Debug.LogError($"[Enchanting Upgrade] ERROR: Tried to upgrade ({feature}) to level ({currentLevel + 1}) but it failed!"); - return; - } - - var player = Player.m_localPlayer; - if (!player.NoCostCheat()) - { - if (!LocalPlayerCanAffordCost(cost)) - { - Debug.LogError("[Augment Item] ERROR: Tried to augment item but could not afford the cost. This should not happen!"); - return; - } - - var inventory = player.GetInventory(); - foreach (var costElement in cost) - { - var costItem = costElement.GetItem(); - inventory.RemoveItem(costItem.m_shared.m_name, costItem.m_stack); - } - } - - Refresh(); - }); - } - } - - public override void Lock() - { - base.Lock(); - foreach (var button in _featureButtons) - { - button.Lock(); - } - } - - public override void Unlock() - { - base.Unlock(); - foreach (var button in _featureButtons) - { - button.Unlock(); - } - } - } -} diff --git a/EpicLoot-UnityLib/packages.config b/EpicLoot-UnityLib/packages.config index ded7cfb3a..1063f24dc 100644 --- a/EpicLoot-UnityLib/packages.config +++ b/EpicLoot-UnityLib/packages.config @@ -1,4 +1,4 @@  - + \ No newline at end of file diff --git a/EpicLoot-UnityLib/src/AugmentUI.cs b/EpicLoot-UnityLib/src/AugmentUI.cs new file mode 100644 index 000000000..8d32be043 --- /dev/null +++ b/EpicLoot-UnityLib/src/AugmentUI.cs @@ -0,0 +1,336 @@ +using JetBrains.Annotations; +using System; +using System.Collections.Generic; +using System.Linq; +using UnityEngine; +using UnityEngine.UI; + +namespace EpicLoot_UnityLib +{ + public class AugmentUI : EnchantingTableUIPanelBase + { + public Text AvailableEffectsText; + public Text AvailableEffectsHeader; + public Scrollbar AvailableEffectsScrollbar; + + public RectTransform EnchantList; + public GameObject EnchantmentListPrefab; + + [Header("Cost")] + public Text CostLabel; + public MultiSelectItemList CostList; + + public delegate List GetAugmentableItemsDelegate(); + public delegate List> GetAugmentableEffectsDelegate(ItemDrop.ItemData item, bool runemode); + public delegate string GetAvailableEffectsDelegate(ItemDrop.ItemData item, int augmentIndex); + public delegate List GetAugmentCostDelegate(ItemDrop.ItemData item, int augmentIndex); + // Returns the augment choice dialog + public delegate GameObject AugmentItemDelegate(ItemDrop.ItemData item, int augmentIndex); + + public static GetAugmentableItemsDelegate GetAugmentableItems; + public static GetAugmentableEffectsDelegate GetAugmentableEffects; + public static GetAvailableEffectsDelegate GetAvailableEffects; + public static GetAugmentCostDelegate GetAugmentCost; + public static AugmentItemDelegate AugmentItem; + + private int _augmentIndex; + private GameObject _choiceDialog; + private List _AugmentSelectors = new List(); + + public override void Awake() + { + base.Awake(); + } + + [UsedImplicitly] + public void OnEnable() + { + if (EnchantList.childCount > 0) + { + foreach (Transform child in EnchantList) + { + Destroy(child.gameObject); + } + } + _AugmentSelectors.Clear(); + _augmentIndex = -1; + + if (AvailableEffectsHeader != null) + { + int augmentChoices = 2; + Tuple featureValues = EnchantingTableUI.instance.SourceTable.GetFeatureCurrentValue(EnchantingFeature.Augment); + if (!float.IsNaN(featureValues.Item1)) + { + augmentChoices = (int)featureValues.Item1; + } + + string colorPre = augmentChoices > 2 ? "" : ""; + string colorPost = augmentChoices > 2 ? "" : ""; + AvailableEffectsHeader.text = Localization.instance.Localize( + $"$mod_epicloot_augment_availableeffects {colorPre}($mod_epicloot_augment_choices){colorPost}", augmentChoices.ToString()); + } + + OnAugmentIndexChanged(); + + List items = GetAugmentableItems(); + AvailableItems.SetItems(items.Cast().ToList()); + DeselectAll(); + } + + public override void Update() + { + base.Update(); + + if (!_locked && ZInput.IsGamepadActive()) + { + if (ZInput.GetButtonDown("JoyButtonY")) + { + int activeAugmentCount = _AugmentSelectors.Count(); + int nextAugmentIndex = (_augmentIndex + 1) % activeAugmentCount; + _AugmentSelectors[nextAugmentIndex].isOn = true; + ZInput.ResetButtonStatus("JoyButtonY"); + } + + if (AvailableEffectsScrollbar != null) + { + float rightStickAxis = ZInput.GetJoyRightStickY(); + if (Mathf.Abs(rightStickAxis) > 0.5f) + { + AvailableEffectsScrollbar.value = Mathf.Clamp01(AvailableEffectsScrollbar.value + rightStickAxis * -0.1f); + } + } + } + + if (_choiceDialog != null && !_choiceDialog.activeSelf) + { + Unlock(); + Destroy(_choiceDialog); + _choiceDialog = null; + Cancel(); + + AvailableItems.ForeachElement((i, e) => + { + if (!e.IsSelected()) + { + return; + } + e.SetItem(e.GetListElement()); + e.Refresh(); + }); + RefreshAugmentSelectors(); + OnAugmentIndexChanged(); + } + } + + public void SelectAugmentIndex(int index) + { + if (index != _augmentIndex) + { + _augmentIndex = index; + OnAugmentIndexChanged(); + } + } + + public void OnAugmentIndexChanged() + { + Tuple selectedItem = AvailableItems.GetSingleSelectedItem(); + if (selectedItem?.Item1.GetItem() == null) + { + MainButton.interactable = false; + AvailableEffectsText.text = ""; + CostLabel.enabled = false; + CostList.SetItems(new List()); + _augmentIndex = -1; + return; + } + + if (_augmentIndex < 0) + { + AvailableEffectsText.text = string.Empty; + CostLabel.enabled = false; + CostList.SetItems(new List()); + MainButton.interactable = false; + } + else + { + ItemDrop.ItemData item = selectedItem.Item1.GetItem(); + string info = GetAvailableEffects(item, _augmentIndex); + + AvailableEffectsText.text = info; + ScrollEnchantInfoToTop(); + + CostLabel.enabled = true; + List cost = GetAugmentCost(item, _augmentIndex); + CostList.SetItems(cost.Cast().ToList()); + + Tuple featureValues = EnchantingTableUI.instance.SourceTable.GetFeatureCurrentValue(EnchantingFeature.Augment); + float reenchantCostReduction = float.IsNaN(featureValues.Item2) ? 0 : featureValues.Item2; + if (reenchantCostReduction > 0) + { + CostLabel.text = Localization.instance.Localize($"$mod_epicloot_augmentcost " + + $"(-{reenchantCostReduction}% $item_coins!)"); + } + else + { + CostLabel.text = Localization.instance.Localize("$mod_epicloot_augmentcost"); + } + + bool canAfford = LocalPlayerCanAffordCost(cost); + bool featureUnlocked = EnchantingTableUI.instance.SourceTable.IsFeatureUnlocked(EnchantingFeature.Augment); + MainButton.interactable = featureUnlocked && canAfford && _augmentIndex >= 0; + } + } + + private void ScrollEnchantInfoToTop() + { + AvailableEffectsScrollbar.value = 1; + } + + protected override void DoMainAction() + { + Tuple selectedItem = AvailableItems.GetSingleSelectedItem(); + if (selectedItem?.Item1.GetItem() == null) + { + Cancel(); + return; + } + + ItemDrop.ItemData item = selectedItem.Item1.GetItem(); + List cost = GetAugmentCost(item, _augmentIndex); + + Player player = Player.m_localPlayer; + if (!player.NoCostCheat()) + { + if (!LocalPlayerCanAffordCost(cost)) + { + Debug.LogError("[Augment Item] ERROR: Tried to augment item but could not afford the cost. This should not happen!"); + return; + } + + foreach (InventoryItemListElement costElement in cost) + { + InventoryManagement.Instance.RemoveItem(costElement.GetItem()); + } + } + + if (_choiceDialog != null) + { + Destroy(_choiceDialog); + } + + _choiceDialog = AugmentItem(item, _augmentIndex); + + foreach (AudioSource audioSource in _choiceDialog.GetComponentsInChildren()) + { + audioSource.volume = AudioVolumeLevel(); + } + + Lock(); + } + + protected override void OnSelectedItemsChanged() + { + Tuple entry = AvailableItems.GetSingleSelectedItem(); + ItemDrop.ItemData item = entry?.Item1.GetItem(); + + RefreshAugmentSelectors(); + + if (item == null) + { + AvailableEffectsText.text = string.Empty; + } + + _augmentIndex = 0; + OnAugmentIndexChanged(); + } + + private void RefreshAugmentSelectors() + { + Tuple entry = AvailableItems.GetSingleSelectedItem(); + if (EnchantList.childCount > 0) + { + foreach (Transform child in EnchantList) + { + Destroy(child.gameObject); + } + } + + _AugmentSelectors.Clear(); + if (entry == null || entry.Item1 == null) + { + return; + } + + ItemDrop.ItemData item = entry?.Item1.GetItem(); + List> augmentableEffects = GetAugmentableEffects(item, false); + + int enchantIndex = 0; + foreach (Tuple effect in augmentableEffects) + { + GameObject enchantmentListElement = Instantiate(EnchantmentListPrefab, EnchantList); + Text enchantmentElement = enchantmentListElement.GetComponentInChildren(); + Toggle enchantmentbutton = enchantmentListElement.GetComponent(); + foreach (AudioSource audioSource in enchantmentListElement.GetComponentsInChildren()) + { + audioSource.volume = AudioVolumeLevel(); + } + + _AugmentSelectors.Add(enchantmentbutton); + enchantmentbutton.onValueChanged.AddListener((isOn) => + { + if (isOn) + { + SelectAugmentIndex(_AugmentSelectors.IndexOf(enchantmentbutton)); + } + }); + + if (enchantmentElement != null) + { + enchantmentElement.text = effect.Item1; + } + + if (effect.Item2 == false) + { + enchantmentbutton.interactable = false; + } + enchantmentListElement.SetActive(true); + + enchantIndex++; + } + } + + public override bool CanCancel() + { + return base.CanCancel() || (_choiceDialog != null && _choiceDialog.activeSelf); + } + + public override void Cancel() + { + base.Cancel(); + OnAugmentIndexChanged(); + } + + public override void Lock() + { + base.Lock(); + foreach (Toggle selector in _AugmentSelectors) + { + selector.interactable = false; + } + } + + public override void Unlock() + { + base.Unlock(); + foreach (Toggle selector in _AugmentSelectors) + { + selector.interactable = true; + } + } + + public override void DeselectAll() + { + AvailableItems.DeselectAll(); + } + } +} diff --git a/EpicLoot-UnityLib/src/ConvertUI.cs b/EpicLoot-UnityLib/src/ConvertUI.cs new file mode 100644 index 000000000..2f64dd313 --- /dev/null +++ b/EpicLoot-UnityLib/src/ConvertUI.cs @@ -0,0 +1,305 @@ +using JetBrains.Annotations; +using System; +using System.Collections.Generic; +using System.Linq; +using UnityEngine; +using UnityEngine.UI; + +namespace EpicLoot_UnityLib +{ + public enum MaterialConversionMode + { + Upgrade, + Convert, + Junk + } + + public class ConversionRecipeCostUnity + { + public ItemDrop.ItemData Item; + public int Amount; + } + + public class ConversionRecipeUnity : IListElement + { + public ItemDrop.ItemData Product; + public int Amount; + public List Cost; + + public List GetEffectNames() => new List(); + public string GetEnchantName() => string.Empty; + public ItemDrop.ItemData GetItem() => Product; + public string GetDisplayNameSuffix() => Amount > 1 ? $" x{Amount}" : string.Empty; + + public int GetMax() + { + int min = int.MaxValue; + foreach (ConversionRecipeCostUnity cost in Cost) + { + int count = InventoryManagement.Instance.CountItem(cost.Item); + int canMake = Mathf.FloorToInt(count / (float)cost.Amount); + min = Mathf.Min(min, canMake); + } + + return min; + } + } + + public class ConvertUI : EnchantingTableUIPanelBase + { + public MultiSelectItemList Products; + public List ModeButtons; + + [Header("Cost")] + public Text CostLabel; + public MultiSelectItemList CostList; + + public delegate List GetConversionRecipesDelegate(int mode); + + public static GetConversionRecipesDelegate GetConversionRecipes; + + private Text _progressLabel; + private ToggleGroup _toggleGroup; + private MaterialConversionMode _mode; + + public override void Awake() + { + base.Awake(); + + _progressLabel = ProgressBar.gameObject.GetComponentInChildren(); + + if (ModeButtons.Count > 0) + { + _toggleGroup = ModeButtons[0].group; + _toggleGroup.EnsureValidState(); + } + + for (int index = 0; index < ModeButtons.Count; index++) + { + Toggle modeButton = ModeButtons[index]; + modeButton.onValueChanged.AddListener((isOn) => + { + if (isOn) + { + RefreshMode(); + } + }); + } + } + + [UsedImplicitly] + public void OnEnable() + { + _mode = 0; + RefreshMode(); + List items = GetConversionRecipes((int)_mode); + AvailableItems.SetItems(items.Cast().ToList()); + } + + public override void Update() + { + base.Update(); + + if (!_locked && ZInput.IsGamepadActive()) + { + if (ZInput.GetButtonDown("JoyButtonY")) + { + int nextModeIndex = ((int)_mode + 1) % ModeButtons.Count; + ModeButtons[nextModeIndex].isOn = true; + ZInput.ResetButtonStatus("JoyButtonY"); + } + } + } + + public void RefreshMode() + { + MaterialConversionMode prevMode = _mode; + for (int index = 0; index < ModeButtons.Count; index++) + { + Toggle button = ModeButtons[index]; + if (button.isOn) + { + _mode = (MaterialConversionMode)index; + } + } + + if (prevMode != _mode) + { + OnModeChanged(); + } + } + + public void OnModeChanged() + { + DeselectAll(); + RefreshAvailableItems(); + + switch (_mode) + { + case MaterialConversionMode.Upgrade: + CostLabel.text = Localization.instance.Localize("$mod_epicloot_upgradecost"); + _progressLabel.text = Localization.instance.Localize("$mod_epicloot_upgradeprogress"); + if (_useTMP) + _tmpButtonLabel.text = Localization.instance.Localize("$mod_epicloot_upgrade"); + else + _buttonLabel.text = Localization.instance.Localize("$mod_epicloot_upgrade"); + break; + + case MaterialConversionMode.Convert: + CostLabel.text = Localization.instance.Localize("$mod_epicloot_convertcost"); + _progressLabel.text = Localization.instance.Localize("$mod_epicloot_convertprogress"); + if (_useTMP) + _tmpButtonLabel.text = Localization.instance.Localize("$mod_epicloot_convert"); + else + _buttonLabel.text = Localization.instance.Localize("$mod_epicloot_convert"); + break; + + case MaterialConversionMode.Junk: + CostLabel.text = Localization.instance.Localize("$mod_epicloot_junkcost"); + _progressLabel.text = Localization.instance.Localize("$mod_epicloot_junkprogress"); + if (_useTMP) + _tmpButtonLabel.text = Localization.instance.Localize("$mod_epicloot_junk"); + else + _buttonLabel.text = Localization.instance.Localize("$mod_epicloot_junk"); + break; + + default: + throw new ArgumentOutOfRangeException(); + } + } + + protected override void DoMainAction() + { + List> selectedRecipes = AvailableItems.GetSelectedItems(); + List allProducts = GetConversionProducts(selectedRecipes); + List cost = GetConversionCost(selectedRecipes); + + Cancel(); + + foreach (InventoryItemListElement costElement in cost) + { + InventoryManagement.Instance.RemoveItem(costElement.GetItem()); + } + + foreach (InventoryItemListElement productElement in allProducts) + { + InventoryManagement.Instance.GiveItem(productElement.GetItem()); + } + + DeselectAll(); + RefreshAvailableItems(); + } + + public static List GetConversionProducts(List> selectedRecipes) + { + Dictionary products = new Dictionary(); + + foreach (Tuple entry in selectedRecipes) + { + ConversionRecipeUnity recipe = entry.Item1; + int multiple = entry.Item2; + + if (products.TryGetValue(recipe.Product.m_shared.m_name, out ItemDrop.ItemData item)) + { + item.m_stack += recipe.Amount * multiple; + } + else + { + item = recipe.Product.Clone(); + item.m_stack = recipe.Amount * multiple; + products.Add(item.m_shared.m_name, item); + } + } + + return products.Values.OrderBy(x => Localization.instance.Localize(x.m_shared.m_name)).Select(x => new InventoryItemListElement() { Item = x }).ToList(); + } + + public static List GetConversionCost(List> selectedRecipes) + { + Dictionary costs = new Dictionary(); + + foreach (Tuple entry in selectedRecipes) + { + ConversionRecipeUnity recipe = entry.Item1; + int multiple = entry.Item2; + + foreach (ConversionRecipeCostUnity recipeCost in recipe.Cost) + { + if (costs.TryGetValue(recipeCost.Item.m_shared.m_name, out ItemDrop.ItemData item)) + { + item.m_stack += recipeCost.Amount * multiple; + } + else + { + item = recipeCost.Item.Clone(); + item.m_stack = recipeCost.Amount * multiple; + costs.Add(item.m_shared.m_name, item); + } + } + } + + return costs.Values.OrderBy(x => Localization.instance.Localize(x.m_shared.m_name)) + .Select(x => new InventoryItemListElement() { Item = x }).ToList(); + } + + public void RefreshAvailableItems() + { + List items = GetConversionRecipes((int)_mode); + AvailableItems.SetItems(items.Cast().ToList()); + AvailableItems.DeselectAll(); + OnSelectedItemsChanged(); + } + + protected override void OnSelectedItemsChanged() + { + List> selectedRecipes = AvailableItems.GetSelectedItems(); + List allProducts = GetConversionProducts(selectedRecipes); + Products.SetItems(allProducts.Cast().ToList()); + + List cost = GetConversionCost(selectedRecipes); + CostList.SetItems(cost.Cast().ToList()); + + Tuple baseFeatureValues = EnchantingTableUI.instance.SourceTable.GetFeatureValue(EnchantingFeature.ConvertMaterials, 0); + Tuple currentFeatureValues = EnchantingTableUI.instance.SourceTable.GetFeatureCurrentValue(EnchantingFeature.ConvertMaterials); + bool isBonusCost = false; + if (_mode == MaterialConversionMode.Upgrade) + { + if (currentFeatureValues.Item1 < baseFeatureValues.Item1 && + allProducts.Any(x => x.Item.m_shared.m_ammoType.EndsWith("MagicCraftingMaterial"))) + { + isBonusCost = true; + } + + if (currentFeatureValues.Item2 < baseFeatureValues.Item2 && + allProducts.Any(x => x.Item.m_shared.m_ammoType.EndsWith("Runestone"))) + { + isBonusCost = true; + } + + if (isBonusCost && cost.Count > 0) + { + CostLabel.text = Localization.instance.Localize("($mod_epicloot_bonus) $mod_epicloot_upgradecost"); + } + else + { + CostLabel.text = Localization.instance.Localize("$mod_epicloot_upgradecost"); + } + } + + bool canAfford = LocalPlayerCanAffordCost(cost); + bool featureUnlocked = EnchantingTableUI.instance.SourceTable.IsFeatureUnlocked(EnchantingFeature.ConvertMaterials); + MainButton.interactable = featureUnlocked && canAfford && selectedRecipes.Count > 0; + } + + public override void Cancel() + { + base.Cancel(); + OnModeChanged(); + } + + public override void DeselectAll() + { + AvailableItems.DeselectAll(); + } + } +} diff --git a/EpicLoot-UnityLib/src/DisenchantUI.cs b/EpicLoot-UnityLib/src/DisenchantUI.cs new file mode 100644 index 000000000..4aca293a3 --- /dev/null +++ b/EpicLoot-UnityLib/src/DisenchantUI.cs @@ -0,0 +1,108 @@ +using JetBrains.Annotations; +using System.Collections.Generic; +using System.Linq; +using UnityEngine.UI; + +namespace EpicLoot_UnityLib +{ + public class DisenchantUI : EnchantingTableUIPanelBase + { + public Text CostLabel; + public MultiSelectItemList CostList; + public EnchantBonus BonusPanel; + + public delegate List GetDisenchantItemsDelegate(); + public delegate List GetDisenchantCostDelegate(ItemDrop.ItemData item); + public delegate List DisenchantItemDelegate(ItemDrop.ItemData item); + + public static GetDisenchantItemsDelegate GetDisenchantItems; + public static GetDisenchantCostDelegate GetDisenchantCost; + public static DisenchantItemDelegate DisenchantItem; + + [UsedImplicitly] + public void OnEnable() + { + List items = GetDisenchantItems(); + AvailableItems.SetItems(items.Cast().ToList()); + AvailableItems.DeselectAll(); + } + + protected override void DoMainAction() + { + Cancel(); + + System.Tuple selectedItem = AvailableItems.GetSingleSelectedItem(); + if (selectedItem?.Item1.GetItem() == null) + { + return; + } + + ItemDrop.ItemData item = selectedItem.Item1.GetItem(); + List cost = GetDisenchantCost(item); + if (!LocalPlayerCanAffordCost(cost)) + { + return; + } + + foreach (InventoryItemListElement costElement in cost) + { + InventoryManagement.Instance.RemoveItem(costElement.GetItem()); + } + + List bonusItems = DisenchantItem(item); + + if (bonusItems.Count > 0) + { + EnchantingTableUI.instance.PlayEnchantBonusSFX(); + BonusPanel.Show(); + + GiveItemsToPlayer(bonusItems); + } + + RefreshAvailableItems(); + } + + public void RefreshAvailableItems() + { + List items = GetDisenchantItems(); + AvailableItems.SetItems(items.Cast().ToList()); + AvailableItems.DeselectAll(); + OnSelectedItemsChanged(); + } + + protected override void OnSelectedItemsChanged() + { + System.Tuple selectedItem = AvailableItems.GetSingleSelectedItem(); + + if (selectedItem != null) + { + CostLabel.enabled = true; + List cost = GetDisenchantCost(selectedItem.Item1.GetItem()); + CostList.SetItems(cost.Cast().ToList()); + + System.Tuple featureValues = EnchantingTableUI.instance.SourceTable.GetFeatureCurrentValue(EnchantingFeature.Disenchant); + int costReduction = float.IsNaN(featureValues.Item2) ? 0 : (int)featureValues.Item2; + + if (costReduction > 0 && cost.Count > 0) + CostLabel.text = Localization.instance.Localize("$mod_epicloot_disenchantcost ($mod_epicloot_disenchantcostreduction)", costReduction.ToString()); + else + CostLabel.text = Localization.instance.Localize("$mod_epicloot_disenchantcost"); + + bool canAfford = LocalPlayerCanAffordCost(cost); + bool featureUnlocked = EnchantingTableUI.instance.SourceTable.IsFeatureUnlocked(EnchantingFeature.Disenchant); + MainButton.interactable = featureUnlocked && canAfford; + } + else + { + CostList.SetItems(new List()); + CostLabel.enabled = false; + MainButton.interactable = false; + } + } + + public override void DeselectAll() + { + AvailableItems.DeselectAll(); + } + } +} diff --git a/EpicLoot-UnityLib/EnchantBonus.cs b/EpicLoot-UnityLib/src/EnchantBonus.cs similarity index 100% rename from EpicLoot-UnityLib/EnchantBonus.cs rename to EpicLoot-UnityLib/src/EnchantBonus.cs diff --git a/EpicLoot-UnityLib/src/EnchantUI.cs b/EpicLoot-UnityLib/src/EnchantUI.cs new file mode 100644 index 000000000..731b30931 --- /dev/null +++ b/EpicLoot-UnityLib/src/EnchantUI.cs @@ -0,0 +1,257 @@ +using JetBrains.Annotations; +using System.Collections.Generic; +using System.Linq; +using UnityEngine; +using UnityEngine.UI; + +namespace EpicLoot_UnityLib +{ + public class EnchantUI : EnchantingTableUIPanelBase + { + public Text EnchantInfo; + public Scrollbar EnchantInfoScrollbar; + public List RarityButtons; + + [Header("Cost")] + public Text CostLabel; + public MultiSelectItemList CostList; + + public AudioClip[] EnchantCompleteSFX; + + public delegate List GetEnchantableItemsDelegate(); + public delegate string GetEnchantInfoDelegate(ItemDrop.ItemData item, MagicRarityUnity rarity); + public delegate List GetEnchantCostDelegate(ItemDrop.ItemData item, MagicRarityUnity rarity); + // Returns the success dialog + public delegate GameObject EnchantItemDelegate(ItemDrop.ItemData item, MagicRarityUnity rarity); + + public static GetEnchantableItemsDelegate GetEnchantableItems; + public static GetEnchantInfoDelegate GetEnchantInfo; + public static GetEnchantCostDelegate GetEnchantCost; + public static EnchantItemDelegate EnchantItem; + + private ToggleGroup _toggleGroup; + private MagicRarityUnity _rarity; + private GameObject _successDialog; + + public override void Awake() + { + base.Awake(); + + if (RarityButtons.Count > 0) + { + _toggleGroup = RarityButtons[0].group; + _toggleGroup.EnsureValidState(); + } + + for (int index = 0; index < RarityButtons.Count; index++) + { + Toggle rarityButton = RarityButtons[index]; + rarityButton.onValueChanged.AddListener((isOn) => + { + if (isOn) + RefreshRarity(); + }); + } + } + + [UsedImplicitly] + public void OnEnable() + { + foreach(AudioSource audioSource in this.GetComponentsInChildren()) + { + audioSource.volume = AudioVolumeLevel(); + } + + _rarity = MagicRarityUnity.Magic; + OnRarityChanged(); + RarityButtons[0].isOn = true; + List items = GetEnchantableItems(); + AvailableItems.SetItems(items.Cast().ToList()); + } + + public override void Update() + { + base.Update(); + + if (!_locked && ZInput.IsGamepadActive()) + { + if (ZInput.GetButtonDown("JoyButtonY")) + { + int nextModeIndex = ((int)_rarity + 1) % RarityButtons.Count; + RarityButtons[nextModeIndex].isOn = true; + ZInput.ResetButtonStatus("JoyButtonY"); + } + + if (EnchantInfoScrollbar != null) + { + float rightStickAxis = ZInput.GetJoyRightStickY(); + if (Mathf.Abs(rightStickAxis) > 0.5f) + { + EnchantInfoScrollbar.value = Mathf.Clamp01(EnchantInfoScrollbar.value + rightStickAxis * -0.1f); + } + } + } + + if (_successDialog != null && !_successDialog.activeSelf) + { + Unlock(); + Destroy(_successDialog); + _successDialog = null; + } + } + + public void RefreshRarity() + { + MagicRarityUnity prevRarity = _rarity; + for (int index = 0; index < RarityButtons.Count; index++) + { + Toggle button = RarityButtons[index]; + if (button.isOn) + { + _rarity = (MagicRarityUnity)index; + } + } + + if (prevRarity != _rarity) + { + OnRarityChanged(); + } + } + + public void OnRarityChanged() + { + System.Tuple selectedItem = AvailableItems.GetSingleSelectedItem(); + if (selectedItem?.Item1.GetItem() == null) + { + MainButton.interactable = false; + EnchantInfo.text = ""; + CostLabel.enabled = false; + CostList.SetItems(new List()); + return; + } + + ItemDrop.ItemData item = selectedItem.Item1.GetItem(); + string info = GetEnchantInfo(item, _rarity); + + EnchantInfo.text = info; + ScrollEnchantInfoToTop(); + + CostLabel.enabled = true; + List cost = GetEnchantCost(item, _rarity); + CostList.SetItems(cost.Cast().ToList()); + + bool canAfford = LocalPlayerCanAffordCost(cost); + bool featureUnlocked = EnchantingTableUI.instance.SourceTable.IsFeatureUnlocked(EnchantingFeature.Enchant); + MainButton.interactable = featureUnlocked && canAfford; + } + + private void ScrollEnchantInfoToTop() + { + EnchantInfoScrollbar.value = 1; + } + + protected override void DoMainAction() + { + System.Tuple selectedItem = + AvailableItems.GetSelectedItems().FirstOrDefault(); + + Cancel(); + + if (selectedItem?.Item1.GetItem() == null) + { + return; + } + + ItemDrop.ItemData item = selectedItem.Item1.GetItem(); + List cost = GetEnchantCost(item, _rarity); + + Player player = Player.m_localPlayer; + if (!player.NoCostCheat()) + { + if (!LocalPlayerCanAffordCost(cost)) + { + Debug.LogError("[Enchant Item] ERROR: Tried to enchant item but could not afford the cost. This should not happen!"); + return; + } + + foreach (InventoryItemListElement costElement in cost) + { + InventoryManagement.Instance.RemoveItem(costElement.GetItem()); + } + } + + if (_successDialog != null) + { + Destroy(_successDialog); + } + + DeselectAll(); + Lock(); + + _successDialog = EnchantItem(item, _rarity); + + RefreshAvailableItems(); + } + + protected override AudioClip GetCompleteAudioClip() + { + return EnchantCompleteSFX[(int)_rarity]; + } + + public void RefreshAvailableItems() + { + List items = GetEnchantableItems(); + AvailableItems.SetItems(items.Cast().ToList()); + AvailableItems.DeselectAll(); + OnSelectedItemsChanged(); + } + + protected override void OnSelectedItemsChanged() + { + OnRarityChanged(); + } + + public override bool CanCancel() + { + return base.CanCancel() || (_successDialog != null && _successDialog.activeSelf); + } + + public override void Cancel() + { + base.Cancel(); + + if (_successDialog != null && _successDialog.activeSelf) + { + Destroy(_successDialog); + _successDialog = null; + } + + OnRarityChanged(); + } + + public override void Lock() + { + base.Lock(); + + foreach (Toggle modeButton in RarityButtons) + { + modeButton.interactable = false; + } + } + + public override void Unlock() + { + base.Unlock(); + + foreach (Toggle modeButton in RarityButtons) + { + modeButton.interactable = true; + } + } + + public override void DeselectAll() + { + AvailableItems.DeselectAll(); + } + } +} diff --git a/EpicLoot-UnityLib/src/EnchantingTable.cs b/EpicLoot-UnityLib/src/EnchantingTable.cs new file mode 100755 index 000000000..82b0a0ceb --- /dev/null +++ b/EpicLoot-UnityLib/src/EnchantingTable.cs @@ -0,0 +1,362 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using UnityEngine; + +namespace EpicLoot_UnityLib +{ + public class EnchantingTable : MonoBehaviour, Hoverable, Interactable + { + public const float UseDistance = 2.7f; + public const string DisplayNameLocID = "mod_epicloot_assets_enchantingtable"; + public const int FeatureUnavailableSentinel = -2; + public const int FeatureLockedSentinel = -1; + public const int FeatureLevelOne = 1; + + public GameObject EnchantingUIPrefab; + + public event Action OnFeatureLevelChanged; + public event Action OnAnyFeatureLevelChanged; + + public delegate bool UpgradesActiveDelegate(EnchantingFeature feature, out bool featureActive); + public static UpgradesActiveDelegate UpgradesActive; + + private static readonly List _upgradeRequests = new(); + private ZNetView _nview; + private Player _interactingPlayer; + + public bool Interact(Humanoid user, bool repeat, bool alt) + { + if (repeat || user != Player.m_localPlayer || !InUseDistance(user)) + { + return false; + } + + EnchantingTableUI.Show(this); + _interactingPlayer = Player.m_localPlayer; + return false; + } + + public void Awake() + { + } + + public void Start() + { + _nview = GetComponent(); + + if (_nview == null || !_nview.IsValid()) + { + return; + } + + _nview.Register("el.TableUpgradeRequest", RPC_TableUpgradeRequest); + _nview.Register("el.TableUpgradeResponse", RPC_TableUpgradeResponse); + + InitFeatureLevels(); + + Refresh(); + } + + public void RequestTableUpgrade(EnchantingFeature feature, int toLevel, Action responseCallback) + { + ZDOID tableZDO = _nview.GetZDO().m_uid; + _upgradeRequests.Add(new EnchantingFeatureUpgradeRequest() + { + TableZDO = tableZDO, + Feature = feature, + ToLevel = toLevel, + ResponseCallback = responseCallback + }); + + _nview.InvokeRPC("el.TableUpgradeRequest",tableZDO, (int)feature, toLevel); + } + + private void RPC_TableUpgradeRequest(long sender, ZDOID tableZDO, int featureI, int toLevel) + { + if (!_nview.IsOwner()) + { + return; + } + + GameObject instance = ZNetScene.instance.FindInstance(tableZDO); + if (instance == null) + { + return; + } + + EnchantingTable table = instance.GetComponent(); + if (table == null) + { + return; + } + + EnchantingFeature feature = (EnchantingFeature)featureI; + if (table.IsFeatureAvailable(feature) && toLevel == table.GetFeatureLevel(feature) + 1) + { + table.SetFeatureLevel(feature, toLevel); + _nview.InvokeRPC(sender,"el.TableUpgradeResponse",tableZDO, featureI, toLevel, true); + OnFeatureLevelChanged?.Invoke(feature, toLevel); + OnAnyFeatureLevelChanged?.Invoke(); + } + else + { + _nview.InvokeRPC(sender,"el.TableUpgradeResponse",tableZDO, featureI, toLevel, false); + } + } + + private void RPC_TableUpgradeResponse(long sender, ZDOID tableZDO, int featureI, int toLevel, bool success) + { + //Only sent to Sender of Request. + //Performs checks, + //Calls OnTableUpgradeResponse + EnchantingFeature feature = (EnchantingFeature)featureI; + List listCopy = _upgradeRequests.ToList(); + foreach (EnchantingFeatureUpgradeRequest request in listCopy) + { + if (request.TableZDO == tableZDO && request.Feature == feature && request.ToLevel == toLevel) + { + request.ResponseCallback.Invoke(success); + _upgradeRequests.Remove(request); + if (Player.m_localPlayer != null) + { + if (toLevel == 0) + { + Player.m_localPlayer.Message(MessageHud.MessageType.Center, + Localization.instance.Localize("$mod_epicloot_unlockmessage", + EnchantingTableUpgrades.GetFeatureName(feature))); + } + else + { + Player.m_localPlayer.Message(MessageHud.MessageType.Center, + Localization.instance.Localize("$mod_epicloot_upgrademessage", + EnchantingTableUpgrades.GetFeatureName(feature), toLevel.ToString())); + } + } + } + } + } + + public void Update() + { + if (_interactingPlayer != null && EnchantingTableUI.instance != null && + EnchantingTableUI.instance.isActiveAndEnabled && !InUseDistance(_interactingPlayer)) + { + EnchantingTableUI.Hide(); + _interactingPlayer = null; + } + } + + public void Refresh() + { + OnAnyFeatureLevelChanged?.Invoke(); + } + + public bool UseItem(Humanoid user, ItemDrop.ItemData item) + { + return false; + } + + public bool InUseDistance(Humanoid human) + { + return Vector3.Distance(human.transform.position, transform.position) < UseDistance; + } + + public string GetHoverText() + { + return !InUseDistance(Player.m_localPlayer) + ? Localization.instance.Localize("$piece_toofar") + : Localization.instance.Localize($"${DisplayNameLocID}\n[$KEY_Use] $piece_use"); + } + + public string GetHoverName() + { + return DisplayNameLocID; + } + + private string FormatFeatureName(string featureName) + { + return string.Format($"el.et.v1.{featureName}"); + } + + private void InitFeatureLevels() + { + const int uninitializedSentinel = -888; + foreach (EnchantingFeature feature in Enum.GetValues(typeof(EnchantingFeature))) + { + string featureName = feature.ToString(); + int featureValue = _nview.GetZDO().GetInt(FormatFeatureName(featureName), uninitializedSentinel); + if (featureValue < 0) + { + //For those that travel here from afar, you might be asking yourself why I'm adding and subtracting 1 to the level. + //It's because Iron Gate decided that 0 value ZDO's should be removed when world save occurs........ + _nview.GetZDO().Set(FormatFeatureName(featureName), GetDefaultFeatureLevel(feature) + 1); + } + } + } + + private static int GetDefaultFeatureLevel(EnchantingFeature feature) + { + if (!UpgradesActive(feature, out bool featureActive)) + { + return featureActive ? FeatureLevelOne : FeatureUnavailableSentinel; + } + + if (!featureActive) + { + return FeatureUnavailableSentinel; + } + + return EnchantingTableUpgrades.Config.DefaultFeatureLevels.TryGetValue(feature, out int level) ? + level : FeatureUnavailableSentinel; + } + + public void Reset() + { + foreach (EnchantingFeature feature in Enum.GetValues(typeof(EnchantingFeature))) + { + SetFeatureLevel(feature, GetDefaultFeatureLevel(feature)); + } + } + + public int GetFeatureLevel(EnchantingFeature feature) + { + if (_nview == null || _nview.GetZDO() == null) + { + return FeatureUnavailableSentinel; + } + + if (!UpgradesActive(feature, out bool featureActive)) + { + return featureActive ? FeatureLevelOne : FeatureUnavailableSentinel; + } + + if (!featureActive) + { + return FeatureUnavailableSentinel; + } + + string featureName = feature.ToString(); + int level = _nview.GetZDO().GetInt(FormatFeatureName(featureName), FeatureUnavailableSentinel); + //For those that travel here from afar, you might be asking yourself why I'm adding and subtracting 1 to the level. + //It's because Iron Gate decided that 0 value ZDO's should be removed when world save occurs........ + return level == FeatureUnavailableSentinel ? FeatureUnavailableSentinel : level - 1; + } + + public void SetFeatureLevel(EnchantingFeature feature, int level) + { + if (_nview == null) + { + return; + } + + if (!UpgradesActive(feature, out bool featureActive)) + { + level = featureActive ? FeatureLevelOne : FeatureUnavailableSentinel; + } + else + { + if (level > (EnchantingTableUpgrades.Config.MaximumFeatureLevels.TryGetValue( + feature, out int maxLevel) ? maxLevel : 1)) + { + return; + } + } + + string featureName = feature.ToString(); + _nview.GetZDO().Set(FormatFeatureName(featureName), level+1); + OnFeatureLevelChanged?.Invoke(feature, level); + OnAnyFeatureLevelChanged?.Invoke(); + } + + public bool IsFeatureAvailable(EnchantingFeature feature) + { + return GetFeatureLevel(feature) > FeatureUnavailableSentinel; + } + + public bool IsFeatureLocked(EnchantingFeature feature) + { + return GetFeatureLevel(feature) == FeatureLockedSentinel; + } + + public bool IsFeatureUnlocked(EnchantingFeature feature) + { + return GetFeatureLevel(feature) > FeatureLockedSentinel; + } + + public bool IsFeatureMaxLevel(EnchantingFeature feature) + { + return GetFeatureLevel(feature) == EnchantingTableUpgrades.GetFeatureMaxLevel(feature); + } + + public List GetFeatureUnlockCost(EnchantingFeature feature) + { + if (IsFeatureUnlocked(feature)) + { + Debug.LogWarning($"[EpicLoot] Warning: " + + $"tried to get unlock cost for a feature that is already unlocked! ({feature})"); + } + + return EnchantingTableUpgrades.GetUpgradeCost(feature, 0); + } + + public List GetFeatureUpgradeCost(EnchantingFeature feature) + { + if (IsFeatureLocked(feature) || !IsFeatureAvailable(feature)) + { + Debug.LogWarning($"[EpicLoot] Warning: " + + $"tried to get enchanting feature unlock cost for a feature that is locked or unavailable! ({feature})"); + } + + int currentLevel = GetFeatureLevel(feature); + return EnchantingTableUpgrades.GetUpgradeCost(feature, currentLevel + 1); + } + + public Tuple GetFeatureValue(EnchantingFeature feature, int level) + { + if (!IsFeatureAvailable(feature)) + { + return new Tuple(float.NaN, float.NaN); + } + + if (level < 0 || level > EnchantingTableUpgrades.GetFeatureMaxLevel(feature)) + { + return new Tuple(float.NaN, float.NaN); + } + + List values = feature switch + { + EnchantingFeature.Sacrifice => EnchantingTableUpgrades.Config.UpgradeValues.Sacrifice, + EnchantingFeature.ConvertMaterials => EnchantingTableUpgrades.Config.UpgradeValues.ConvertMaterials, + EnchantingFeature.Enchant => EnchantingTableUpgrades.Config.UpgradeValues.Enchant, + EnchantingFeature.Augment => EnchantingTableUpgrades.Config.UpgradeValues.Augment, + EnchantingFeature.Disenchant => EnchantingTableUpgrades.Config.UpgradeValues.Disenchant, + EnchantingFeature.Rune => EnchantingTableUpgrades.Config.UpgradeValues.Rune, + _ => throw new ArgumentOutOfRangeException(nameof(feature), feature, null) + }; + + if (level >= values.Count) + { + return new Tuple(float.NaN, float.NaN); + } + + float[] levelValues = values[level]; + if (levelValues.Length == 1) + { + return new Tuple(levelValues[0], float.NaN); + } + + if (levelValues.Length >= 2) + { + return new Tuple(levelValues[0], levelValues[1]); + } + + return new Tuple(float.NaN, float.NaN); + } + + public Tuple GetFeatureCurrentValue(EnchantingFeature feature) + { + return GetFeatureValue(feature, GetFeatureLevel(feature)); + } + } +} \ No newline at end of file diff --git a/EpicLoot-UnityLib/src/EnchantingTableUI.cs b/EpicLoot-UnityLib/src/EnchantingTableUI.cs new file mode 100644 index 000000000..f89f8d1e6 --- /dev/null +++ b/EpicLoot-UnityLib/src/EnchantingTableUI.cs @@ -0,0 +1,242 @@ +using UnityEngine; +using UnityEngine.UI; + +namespace EpicLoot_UnityLib +{ + public class EnchantingTableUI : MonoBehaviour + { + public GameObject Root; + public GameObject Scrim; + public TabHandler TabHandler; + public GameObject TabScrim; + + [Header("Content")] + public EnchantingTableUIPanelBase[] Panels; + + [Header("Audio")] + public AudioSource Audio; + public AudioClip TabClickSFX; + public AudioClip EnchantBonusSFX; + + public EnchantingTable SourceTable { get; private set; } + + public static EnchantingTableUI instance { get; set; } + + public delegate void AugaFixupDelegate(EnchantingTableUI ui); + public static AugaFixupDelegate AugaFixup; + public delegate void TabActivationDelegate(EnchantingTableUI ui); + public static TabActivationDelegate TabActivation; + public delegate float AudioVolumeLevelDelegate(); + public static AudioVolumeLevelDelegate AudioVolumeLevel; + + private int _hiddenFrames; + + public void Awake() + { + instance = this; + } + + public void Start() + { + Localization.instance.Localize(transform); + + GameObject uiSFX = GameObject.Find("sfx_gui_button"); + if (uiSFX) + { + Audio.outputAudioMixerGroup = + uiSFX.GetComponent().outputAudioMixerGroup; + Audio.volume = AudioVolumeLevel(); + } + + instance.SetupTabs(); + + AugaFixup(this); + } + + private static void CreateUI(EnchantingTable source) + { + if (StoreGui.instance == null) + { + return; + } + + Transform inGameGui = StoreGui.instance.transform.parent; + int siblingIndex = StoreGui.instance.transform.GetSiblingIndex() + 1; + + // Call to arms compatibility: increase scroll sensitivity + foreach (ScrollRect scrollRect in source.EnchantingUIPrefab.GetComponentsInChildren(true)) + { + scrollRect.scrollSensitivity = 800f; + } + + GameObject enchantingUI = Instantiate(source.EnchantingUIPrefab, inGameGui); + enchantingUI.transform.SetSiblingIndex(siblingIndex); + + // TODO: Reduce duplicate code, mock this inside unity in the future + Transform existingBackground = StoreGui.instance.m_rootPanel.transform.Find("border (1)"); + Transform panel = enchantingUI.transform.Find("Panel"); + if (existingBackground != null & panel != null) + { + Image image = existingBackground.GetComponent(); + panel.GetComponent().material = image.material; + } + } + + private void SetupTabs() + { + foreach(TabHandler.Tab tab in TabHandler.m_tabs) + { + tab.m_onClick.AddListener(PlayTabSelectSFX); + + FeatureStatus fs = tab.m_button.gameObject.GetComponent(); + if (fs != null) + { + fs.Refresh(); + } + } + + TabActivation(this); + } + + public static void Show(EnchantingTable source) + { + if (instance == null) + { + CreateUI(source); + } + + if (instance == null) + { + Debug.LogError("Enchanting Table UI not setup properly!"); + return; + } + + instance.SourceTable = source; + instance.Root.SetActive(true); + instance.Scrim.SetActive(true); + instance.SourceTable.Refresh(); + + foreach (EnchantingTableUIPanelBase panel in instance.Panels) + { + panel.DeselectAll(); + } + } + + public static void Hide() + { + if (instance == null) + { + return; + } + + instance.Root.SetActive(false); + instance.Scrim.SetActive(false); + instance.SourceTable = null; + } + + public static bool IsVisible() + { + return instance != null && ((instance._hiddenFrames <= 2) || + (instance.Root != null && instance.Root.activeSelf)); + } + + public static bool IsInTextInput() + { + if (!IsVisible()) + { + return false; + } + + InputField[] textFields = instance.Root.GetComponentsInChildren(false); + foreach (InputField inputField in textFields) + { + if (inputField.isFocused) + { + return true; + } + } + + return false; + } + + public void Update() + { + if (Root == null) + { + return; + } + + if (!Root.activeSelf) + { + _hiddenFrames++; + return; + } + + _hiddenFrames = 0; + + bool disallowClose = (Chat.instance != null && Chat.instance.HasFocus()) || + Console.IsVisible() || Menu.IsVisible() || (TextViewer.instance != null && + TextViewer.instance.IsVisible()) || Player.m_localPlayer.InCutscene(); + + if (disallowClose) + { + return; + } + + bool gotCloseInput = ZInput.GetButtonDown("JoyButtonB") || + ZInput.GetKeyDown(KeyCode.Escape) || ZInput.GetKeyDown(KeyCode.Tab); + + if (gotCloseInput) + { + ZInput.ResetButtonStatus("JoyButtonB"); + ZInput.ResetButtonStatus("JoyJump"); + + bool panelCapturedInput = false; + foreach (EnchantingTableUIPanelBase panel in Panels) + { + if (panel.isActiveAndEnabled && panel.CanCancel()) + { + panel.Cancel(); + panelCapturedInput = true; + break; + } + } + + if (!panelCapturedInput) + { + Hide(); + } + } + } + + public static void UpdateTabActivation() + { + TabActivation(instance); + } + + public static void UpdateUpgradeActivation() + { + TabActivation(instance); + } + + public void LockTabs() + { + TabScrim.SetActive(true); + } + + public void UnlockTabs() + { + TabScrim.SetActive(false); + } + + public void PlayTabSelectSFX() + { + Audio.PlayOneShot(TabClickSFX, Audio.volume); + } + + public void PlayEnchantBonusSFX() + { + Audio.PlayOneShot(EnchantBonusSFX, Audio.volume); + } + } +} diff --git a/EpicLoot-UnityLib/src/EnchantingTableUIPanelBase.cs b/EpicLoot-UnityLib/src/EnchantingTableUIPanelBase.cs new file mode 100644 index 000000000..1a8ea8fe8 --- /dev/null +++ b/EpicLoot-UnityLib/src/EnchantingTableUIPanelBase.cs @@ -0,0 +1,250 @@ +using System.Collections.Generic; +using TMPro; +using UnityEngine; +using UnityEngine.UI; + +namespace EpicLoot_UnityLib +{ + public abstract class EnchantingTableUIPanelBase : MonoBehaviour + { + public const float CountdownTime = 0.8f; + + public MultiSelectItemList AvailableItems; + public Button MainButton; + public GameObject LevelDisplay; + public GuiBar ProgressBar; + public AudioSource Audio; + public AudioClip ProgressLoopSFX; + public AudioClip CompleteSFX; + public AudioClip MainActionSFX; + + public delegate float AudioVolumeLevelDelegate(); + public static AudioVolumeLevelDelegate AudioVolumeLevel; + + protected bool _inProgress; + protected float _countdown; + protected Text _buttonLabel; + protected TMP_Text _tmpButtonLabel; + protected bool _useTMP = false; + protected string _defaultButtonLabelText; + protected bool _locked; + + protected abstract void DoMainAction(); + protected abstract void OnSelectedItemsChanged(); + + public virtual void Awake() + { + if (AvailableItems != null) + { + AvailableItems.OnSelectedItemsChanged += OnSelectedItemsChanged; + AvailableItems.GiveFocus(true, 0); + } + + if (MainButton != null) + { + MainButton.onClick.AddListener(OnMainButtonClicked); + _buttonLabel = MainButton.GetComponentInChildren(); + if (_buttonLabel == null) + { + _tmpButtonLabel = MainButton.GetComponentInChildren(); + _useTMP = true; + } + + _defaultButtonLabelText = _useTMP ? _tmpButtonLabel.text : _buttonLabel.text; + } + + GameObject uiSFX = GameObject.Find("sfx_gui_button"); + if (uiSFX && Audio != null) + { + Audio.outputAudioMixerGroup = uiSFX.GetComponent().outputAudioMixerGroup; + Audio.volume = AudioVolumeLevel(); + } + + foreach (AudioSource audioSource in this.GetComponentsInChildren()) + { + audioSource.volume = AudioVolumeLevel(); + } + } + + protected virtual void OnMainButtonClicked() + { + if (MainActionSFX != null) + { + Audio.PlayOneShot(MainActionSFX, Audio.volume); + } + + if (_inProgress) + { + Cancel(); + } + else + { + StartProgress(); + } + } + + public virtual void DeselectAll() + { + } + + public virtual void Update() + { + if (ProgressBar != null) + { + ProgressBar.gameObject.SetActive(_inProgress); + } + if (LevelDisplay != null) + { + LevelDisplay.gameObject.SetActive(!_inProgress); + } + + if (_inProgress) + { + if (ProgressBar != null) + { + ProgressBar.SetValue(CountdownTime - _countdown); + } + + _countdown -= Time.deltaTime; + if (_countdown < 0) + { + _inProgress = false; + _countdown = 0; + + if (Audio != null) + { + Audio.loop = false; + Audio.Stop(); + } + + DoMainAction(); + PlayCompleteSFX(); + } + } + } + + private void PlayCompleteSFX() + { + AudioClip clip = GetCompleteAudioClip(); + if (Audio != null && clip != null) + { + Audio.PlayOneShot(clip, Audio.volume); + } + } + + protected virtual AudioClip GetCompleteAudioClip() + { + return CompleteSFX; + } + + public virtual void StartProgress() + { + if (_useTMP) + { + _tmpButtonLabel.text = Localization.instance.Localize("$menu_cancel"); + } + else + { + _buttonLabel.text = Localization.instance.Localize("$menu_cancel"); + } + + _inProgress = true; + _countdown = CountdownTime; + + if (ProgressBar != null) + { + ProgressBar.SetMaxValue(CountdownTime); + } + + if (Audio != null) + { + Audio.loop = true; + Audio.clip = ProgressLoopSFX; + Audio.Play(); + } + + Lock(); + } + + public virtual bool CanCancel() + { + return _inProgress; + } + + public virtual void Cancel() + { + if (_useTMP) + { + _tmpButtonLabel.text = Localization.instance.Localize(_defaultButtonLabelText); + } + else + { + _buttonLabel.text = Localization.instance.Localize(_defaultButtonLabelText); + } + + _inProgress = false; + _countdown = 0; + + if (Audio != null) + { + Audio.loop = false; + Audio.Stop(); + } + + Unlock(); + } + + public virtual void Lock() + { + _locked = true; + MultiSelectItemList[] lists = GetComponentsInChildren(); + foreach (MultiSelectItemList list in lists) + { + list.Lock(); + } + + EnchantingTableUI.instance.LockTabs(); + } + + public virtual void Unlock() + { + _locked = false; + MultiSelectItemList[] lists = GetComponentsInChildren(); + foreach (MultiSelectItemList list in lists) + { + list.Unlock(); + } + + EnchantingTableUI.instance.UnlockTabs(); + } + + protected static bool LocalPlayerCanAffordCost(List cost) + { + if (Player.m_localPlayer.NoCostCheat()) + { + return true; + } + + foreach (InventoryItemListElement element in cost) + { + ItemDrop.ItemData item = element.GetItem(); + + if (!InventoryManagement.Instance.HasItem(item)) + { + return false; + } + } + + return true; + } + + protected static void GiveItemsToPlayer(List sacrificeProducts) + { + foreach (InventoryItemListElement sacrificeProduct in sacrificeProducts) + { + ItemDrop.ItemData item = sacrificeProduct.GetItem(); + InventoryManagement.Instance.GiveItem(item); + } + } + } +} diff --git a/EpicLoot-UnityLib/src/EnchantingTableUpgrades.cs b/EpicLoot-UnityLib/src/EnchantingTableUpgrades.cs new file mode 100755 index 000000000..4fd31208a --- /dev/null +++ b/EpicLoot-UnityLib/src/EnchantingTableUpgrades.cs @@ -0,0 +1,219 @@ +using System; +using System.Collections.Generic; +using UnityEngine; + +namespace EpicLoot_UnityLib +{ + public enum EnchantingFeature + { + Sacrifice, + ConvertMaterials, + Enchant, + Augment, + Disenchant, + Rune + } + + [Serializable] + public class ItemAmount + { + public string Item = ""; + public int Amount = 1; + } + + [Serializable] + public class EnchantingUpgradeCosts + { + public List> Sacrifice; + public List> ConvertMaterials; + public List> Enchant; + public List> Augment; + public List> Disenchant; + public List> Rune; + } + + [Serializable] + public class EnchantingFeatureValues + { + public List Sacrifice; + public List ConvertMaterials; + public List Enchant; + public List Augment; + public List Disenchant; + public List Rune; + } + + [Serializable] + public class EnchantingUpgradesConfig + { + public Dictionary DefaultFeatureLevels; + public Dictionary MaximumFeatureLevels; + public EnchantingUpgradeCosts UpgradeCosts; + public EnchantingFeatureValues UpgradeValues; + } + + public class EnchantingFeatureUpgradeRequest + { + public ZDOID TableZDO; + public EnchantingFeature Feature; + public int ToLevel; + public Action ResponseCallback; + } + + public static class EnchantingTableUpgrades + { + public static EnchantingUpgradesConfig Config; + + + public static void InitializeConfig(EnchantingUpgradesConfig config) + { + Config = config; + } + + public static EnchantingUpgradesConfig GetCFG() + { + return Config; + } + + public static string GetFeatureName(EnchantingFeature feature) + { + switch (feature) + { + case EnchantingFeature.Sacrifice: + return "$mod_epicloot_sacrifice"; + case EnchantingFeature.ConvertMaterials: + return "$mod_epicloot_convertmaterials"; + case EnchantingFeature.Enchant: + return "$mod_epicloot_enchant"; + case EnchantingFeature.Augment: + return "$mod_epicloot_augment"; + case EnchantingFeature.Disenchant: + return "$mod_epicloot_disenchant"; + case EnchantingFeature.Rune: + return "$mod_epicloot_runemanagement"; + default: + return ""; + } + } + + public static string GetFeatureDescription(EnchantingFeature feature) + { + switch (feature) + { + case EnchantingFeature.Sacrifice: + return "$mod_epicloot_featureinfo_sacrifice"; + case EnchantingFeature.ConvertMaterials: + return "$mod_epicloot_featureinfo_convertmaterials"; + case EnchantingFeature.Enchant: + return "$mod_epicloot_featureinfo_enchant"; + case EnchantingFeature.Augment: + return "$mod_epicloot_featureinfo_augment"; + case EnchantingFeature.Disenchant: + return "$mod_epicloot_featureinfo_disenchant"; + case EnchantingFeature.Rune: + return "$mod_epicloot_featureinfo_runes"; + default: + return ""; + } + } + + public static string GetFeatureUpgradeLevelDescription(EnchantingTable table, + EnchantingFeature feature, int level) + { + string description; + switch (feature) + { + case EnchantingFeature.Sacrifice: + description = "$mod_epicloot_featureupgrade_sacrifice"; + break; + case EnchantingFeature.ConvertMaterials: + description = "$mod_epicloot_featureupgrade_convertmaterials"; + break; + case EnchantingFeature.Enchant: + description = "$mod_epicloot_featureupgrade_enchant"; + break; + case EnchantingFeature.Augment: + description = "$mod_epicloot_featureupgrade_augment"; + break; + case EnchantingFeature.Disenchant: + description = "$mod_epicloot_featureupgrade_disenchant"; + break; + case EnchantingFeature.Rune: + description = "$mod_epicloot_featureupgrade_runes"; + break; + default: + description = ""; + break; + } + + Tuple values = table.GetFeatureValue(feature, level); + return Localization.instance.Localize(description, + values.Item1.ToString(), values.Item2.ToString()); + } + + public static int GetFeatureMaxLevel(EnchantingFeature feature) + { + return Config.MaximumFeatureLevels.TryGetValue(feature, out int maxLevel) ? maxLevel : 1; + } + + public static List GetUpgradeCost(EnchantingFeature feature, int level) + { + List result = new List(); + + List> upgradeCosts = feature switch + { + EnchantingFeature.Sacrifice => Config.UpgradeCosts.Sacrifice, + EnchantingFeature.ConvertMaterials => Config.UpgradeCosts.ConvertMaterials, + EnchantingFeature.Enchant => Config.UpgradeCosts.Enchant, + EnchantingFeature.Augment => Config.UpgradeCosts.Augment, + EnchantingFeature.Disenchant => Config.UpgradeCosts.Disenchant, + EnchantingFeature.Rune => Config.UpgradeCosts.Rune, + _ => throw new ArgumentOutOfRangeException(nameof(feature), feature, null) + }; + + if (upgradeCosts == null) + { + return result; + } + + if (level < 0 || level >= upgradeCosts.Count) + { + Debug.LogWarning($"[EpicLoot] Warning: tried to get enchanting feature upgrade cost for " + + $"level that does not exist ({feature}, {level})"); + return result; + } + + List costList = upgradeCosts[level]; + if (costList == null) + { + return result; + } + + foreach (ItemAmount itemAmountConfig in costList) + { + GameObject prefab = ObjectDB.instance.GetItemPrefab(itemAmountConfig.Item); + if (prefab == null) + { + Debug.LogWarning($"[EpicLoot] Tried to add unknown item ({itemAmountConfig.Item}) " + + $"to upgrade cost for feature ({feature}, {level})"); + continue; + } + + ItemDrop itemDrop = prefab.GetComponent(); + if (itemDrop == null) + { + Debug.LogWarning($"[EpicLoot] Tried to add item without ItemDrop ({itemAmountConfig.Item}) " + + $"to upgrade cost for feature ({feature}, {level})"); + continue; + } + + ItemDrop.ItemData costItem = itemDrop.m_itemData.Clone(); + costItem.m_dropPrefab = prefab; + costItem.m_stack = itemAmountConfig.Amount; + result.Add(new InventoryItemListElement() { Item = costItem }); + } + + return result; + } + } +} diff --git a/EpicLoot-UnityLib/src/FeatureStatus.cs b/EpicLoot-UnityLib/src/FeatureStatus.cs new file mode 100644 index 000000000..dd17628ad --- /dev/null +++ b/EpicLoot-UnityLib/src/FeatureStatus.cs @@ -0,0 +1,177 @@ +using System.Text; +using UnityEngine; +using UnityEngine.UI; + +namespace EpicLoot_UnityLib +{ + public class FeatureStatus : MonoBehaviour + { + public EnchantingFeature Feature; + public Transform UnlockedContainer; + public Transform LockedContainer; + public GameObject UnlockedLabel; + public Image[] Stars; + public Text ManyStarsLabel; + public UITooltip Tooltip; + + public delegate void MakeFeatureUnlockTooltipDelegate(GameObject obj); + public static MakeFeatureUnlockTooltipDelegate MakeFeatureUnlockTooltip; + + public delegate bool UpgradesActiveDelegate(EnchantingFeature feature, out bool featureActive); + public static UpgradesActiveDelegate UpgradesActive; + + public void Awake() + { + if (Tooltip != null) + { + MakeFeatureUnlockTooltip(Tooltip.gameObject); + } + } + + public void OnEnable() + { + if (EnchantingTableUI.instance != null && EnchantingTableUI.instance.SourceTable != null) + { + EnchantingTableUI.instance.SourceTable.OnFeatureLevelChanged += OnFeatureLevelChanged; + } + + Refresh(); + } + + public void OnDisable() + { + if (EnchantingTableUI.instance != null && EnchantingTableUI.instance.SourceTable != null) + { + EnchantingTableUI.instance.SourceTable.OnFeatureLevelChanged -= OnFeatureLevelChanged; + } + } + + public void SetFeature(EnchantingFeature feature) + { + if (Feature != feature) + { + Feature = feature; + Refresh(); + } + } + + public void Refresh() + { + if (EnchantingTableUI.instance == null || EnchantingTableUI.instance.SourceTable == null) + { + return; + } + + if (EnchantingTableUI.instance.SourceTable.IsFeatureAvailable(Feature) == false) + { + if (UnlockedContainer != null) + { + UnlockedContainer.gameObject.SetActive(false); + } + + if (LockedContainer != null) + { + LockedContainer.gameObject.SetActive(false); + } + + return; + } + + if (EnchantingTableUI.instance.SourceTable.IsFeatureLocked(Feature) == true) + { + if (UnlockedContainer != null) + { + UnlockedContainer.gameObject.SetActive(false); + } + if (LockedContainer != null) + { + LockedContainer.gameObject.SetActive(true); + } + } + else + { + if (UnlockedContainer != null) + { + UnlockedContainer.gameObject.SetActive(true); + } + if (LockedContainer != null) + { + LockedContainer.gameObject.SetActive(false); + } + + int level = EnchantingTableUI.instance.SourceTable.GetFeatureLevel(Feature); + if (level > Stars.Length) + { + for (int index = 0; index < Stars.Length; index++) + { + Stars[index].gameObject.SetActive(index == 0); + } + + if (ManyStarsLabel != null) + { + ManyStarsLabel.gameObject.SetActive(true); + ManyStarsLabel.text = $"×{level}"; + } + } + else + { + for (int index = 0; index < Stars.Length; index++) + { + bool active = level > index && UpgradesActive(Feature, out _); + Stars[index].gameObject.SetActive(active); + } + + ManyStarsLabel?.gameObject.SetActive(false); + } + + UnlockedLabel?.SetActive(level == 0); + } + + if (Tooltip != null && UpgradesActive(Feature, out _)) + { + Tooltip.m_topic = Localization.instance.Localize(EnchantingTableUpgrades.GetFeatureName(Feature)); + + StringBuilder sb = new StringBuilder(); + bool locked = EnchantingTableUI.instance.SourceTable.IsFeatureLocked(Feature); + int currentLevel = EnchantingTableUI.instance.SourceTable.GetFeatureLevel(Feature); + int maxLevel = EnchantingTableUpgrades.GetFeatureMaxLevel(Feature); + + if (locked) + { + sb.AppendLine(Localization.instance.Localize("$mod_epicloot_currentlevel: " + + "$mod_epicloot_featurelocked")); + } + else if (currentLevel == 0) + { + sb.AppendLine(Localization.instance.Localize($"$mod_epicloot_currentlevel: " + + $"$mod_epicloot_featureunlocked / {maxLevel}")); + } + else + { + sb.AppendLine(Localization.instance.Localize($"$mod_epicloot_currentlevel: " + + $"{currentLevel} / {maxLevel}")); + } + + if (!locked && currentLevel > 0) + { + string text = EnchantingTableUpgrades.GetFeatureUpgradeLevelDescription( + EnchantingTableUI.instance.SourceTable, Feature, currentLevel); + sb.AppendLine($"{text}"); + } + + sb.AppendLine(); + sb.AppendLine(Localization.instance.Localize(EnchantingTableUpgrades.GetFeatureDescription(Feature))); + + Tooltip.m_text = Localization.instance.Localize(sb.ToString()); + } + } + + private void OnFeatureLevelChanged(EnchantingFeature feature, int _) + { + if (isActiveAndEnabled && feature == Feature) + { + Refresh(); + } + } + } +} diff --git a/EpicLoot-UnityLib/src/FeatureStatus3D.cs b/EpicLoot-UnityLib/src/FeatureStatus3D.cs new file mode 100644 index 000000000..035a134ca --- /dev/null +++ b/EpicLoot-UnityLib/src/FeatureStatus3D.cs @@ -0,0 +1,49 @@ +using UnityEngine; + +namespace EpicLoot_UnityLib +{ + public class FeatureStatus3D : MonoBehaviour + { + public EnchantingTable SourceTable; + public EnchantingFeature Feature; + public GameObject UnlockedObject; + public GameObject[] LevelObjects; + + public void OnEnable() + { + SourceTable.OnAnyFeatureLevelChanged += Refresh; + Refresh(); + } + + public void OnDisable() + { + SourceTable.OnAnyFeatureLevelChanged -= Refresh; + } + + public void Refresh() + { + bool featureIsUnlocked = SourceTable.IsFeatureAvailable(Feature) && SourceTable.IsFeatureUnlocked(Feature); + if (UnlockedObject != null) + { + UnlockedObject.SetActive(featureIsUnlocked); + } + + int currentLevel = SourceTable.GetFeatureLevel(Feature); + for (int index = 0; index < LevelObjects.Length; index++) + { + GameObject levelObject = LevelObjects[index]; + if (levelObject == null) + { + continue; + } + + levelObject.SetActive(featureIsUnlocked && currentLevel == index); + } + + if (featureIsUnlocked && currentLevel >= LevelObjects.Length && LevelObjects[LevelObjects.Length - 1] != null) + { + LevelObjects[LevelObjects.Length - 1].SetActive(true); + } + } + } +} diff --git a/EpicLoot-UnityLib/src/InventoryManagement.cs b/EpicLoot-UnityLib/src/InventoryManagement.cs new file mode 100644 index 000000000..27844ab04 --- /dev/null +++ b/EpicLoot-UnityLib/src/InventoryManagement.cs @@ -0,0 +1,200 @@ +using System.Collections.Generic; +using UnityEngine; + +namespace EpicLoot_UnityLib; + +public class InventoryManagement +{ + static InventoryManagement() { } + private InventoryManagement() { } + private static readonly InventoryManagement _instance = new InventoryManagement(); + + public static InventoryManagement Instance + { + get => _instance; + } + + private void SendMessage(string message, int amount, Sprite icon) + { + Player.m_localPlayer.Message(MessageHud.MessageType.TopLeft, + message, amount, icon); + } + + private Inventory GetInventory() + { + Player player = Player.m_localPlayer; + + if (player != null) + { + return player.GetInventory(); + } + + return null; + } + + public List GetAllItems() + { + Inventory inventory = GetInventory(); + if (inventory != null) + { + return inventory.GetAllItems(); + } + + return null; + } + + public bool HasItem(ItemDrop.ItemData item) + { + Inventory inventory = GetInventory(); + + if (inventory == null || inventory.CountItems(item.m_shared.m_name) < item.m_stack) + { + return false; + } + + return true; + } + + public int CountItem(ItemDrop.ItemData item) + { + return CountItem(item.m_shared.m_name); + } + + public int CountItem(string item) + { + Inventory inventory = GetInventory(); + + if (inventory == null) + { + return 0; + } + + return inventory.CountItems(item); + } + + public void GiveItem(string item, int amount) + { + Debug.Log($"Attempting to give item {item} with amount {amount}"); + Inventory inventory = GetInventory(); + if (inventory != null) + { + AddItem(ref inventory, item, amount); + } + else + { + DropItem(item, amount); + } + } + + public bool GiveItem(ItemDrop.ItemData item) + { + Debug.Log($"Attempting to give itemdata {item.m_shared.m_name} with amount {item.m_stack}"); + Inventory inventory = GetInventory(); + + do + { + ItemDrop.ItemData itemToAdd = item.Clone(); + itemToAdd.m_stack = Mathf.Min(item.m_stack, item.m_shared.m_maxStackSize); + item.m_stack -= itemToAdd.m_stack; + + if (inventory != null) + { + AddItem(ref inventory, itemToAdd); + } + else + { + DropItem(itemToAdd); + } + } while (item.m_stack > 0); + + return true; + } + + private void AddItem(ref Inventory inventory, string item, int amount) + { + ItemDrop.ItemData result = inventory.AddItem(item, amount, 1, 0, 0, string.Empty); + + if (result == null) + { + DropItem(item, amount); + } + } + + private void AddItem(ref Inventory inventory, ItemDrop.ItemData item) + { + if (inventory.AddItem(item)) + { + SendMessage($"$msg_added {item.m_shared.m_name}", item.m_stack, item.GetIcon()); + } + else + { + DropItem(item); + } + } + + private void DropItem(string item, int amount) + { + Debug.Log($"Attempting to drop item {item} with amount {amount}"); + Player player = Player.m_localPlayer; + GameObject prefab = ObjectDB.instance.GetItemPrefab(item); + + if (prefab != null) + { + GameObject go = GameObject.Instantiate(prefab, + player.transform.position + player.transform.forward + player.transform.up, + player.transform.rotation); + + ItemDrop itemdrop = go.GetComponent(); + itemdrop.SetStack(amount); + itemdrop.GetComponent().linearVelocity = Vector3.up * 5f; + + SendMessage($"$msg_dropped {itemdrop.m_itemData.m_shared.m_name}", + itemdrop.m_itemData.m_stack, itemdrop.m_itemData.GetIcon()); + } + } + + private void DropItem(ItemDrop.ItemData item) + { + Debug.Log($"Attempting to drop itemdata {item.m_shared.m_name} with amount {item.m_stack}"); + Player player = Player.m_localPlayer; + ItemDrop itemDrop = ItemDrop.DropItem(item, item.m_stack, + player.transform.position + player.transform.forward + player.transform.up, + player.transform.rotation); + itemDrop.GetComponent().linearVelocity = Vector3.up * 5f; + + SendMessage($"$msg_dropped {itemDrop.m_itemData.m_shared.m_name}", + itemDrop.m_itemData.m_stack, itemDrop.m_itemData.GetIcon()); + } + + public void RemoveExactItem(ItemDrop.ItemData item, int amount) + { + Inventory inventory = GetInventory(); + + inventory.RemoveItem(item, amount); + } + + public void RemoveItem(ItemDrop.ItemData item) + { + RemoveItem(item.m_shared.m_name, item.m_stack); + } + + public void RemoveItem(string item, int amount) + { + Inventory inventory = GetInventory(); + + inventory.RemoveItem(item, amount); + } + + public List GetBoundItems() + { + List boundItems = new List(); + + if (Player.m_localPlayer != null) + { + Inventory inventory = Player.m_localPlayer.GetInventory(); + inventory.GetBoundItems(boundItems); + } + + return boundItems; + } +} diff --git a/EpicLoot-UnityLib/src/MultiSelectItemList.cs b/EpicLoot-UnityLib/src/MultiSelectItemList.cs new file mode 100644 index 000000000..fc884a1e7 --- /dev/null +++ b/EpicLoot-UnityLib/src/MultiSelectItemList.cs @@ -0,0 +1,685 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text.RegularExpressions; +using UnityEngine; +using UnityEngine.UI; + +namespace EpicLoot_UnityLib +{ + public interface IListElement + { + ItemDrop.ItemData GetItem(); + + public List GetEffectNames(); + string GetEnchantName(); + int GetMax(); + string GetDisplayNameSuffix(); + } + + public class InventoryItemListElement : IListElement + { + public ItemDrop.ItemData Item; + public List> Effects; + public string EnchantName; + + public List GetEffectNames() => Effects?.Select(x => x.Item1).ToList() ?? new List(); + public string GetEnchantName() => EnchantName ?? string.Empty; + public ItemDrop.ItemData GetItem() => Item; + public int GetMax() => Item?.m_stack ?? 0; + public string GetDisplayNameSuffix() => string.Empty; + } + + public class MultiSelectItemList : MonoBehaviour + { + public enum SortMode { Rarity, Name, Quantity } + + public bool Multiselect = true; + public bool Filterable = true; + public bool Sortable = true; + public bool ReadOnly = false; + public bool UseEnchantAsName = false; + public Transform ListContainer; + public MultiSelectItemListElement ElementPrefab; + public Dropdown SortByDropdown; + public InputField FilterByText; + public Toggle SelectAllToggle; + + public event Action OnSelectedItemsChanged; + public event Action OnItemsChanged; + + public delegate List SortByRarityDelegate(List items); + public delegate List SortByNameDelegate(List items); + + public static SortByRarityDelegate SortByRarity; + public static SortByNameDelegate SortByName; + + private bool _locked; + private bool _hasGamepadFocus; + private ScrollRectEnsureVisible _scrollRectEnsureVisible; + + public void Awake() + { + ScrollRect scrollRect = GetComponentInChildren(); + _scrollRectEnsureVisible = scrollRect != null ? scrollRect.GetComponent() : null; + + if (SelectAllToggle != null) + { + SelectAllToggle.onValueChanged.AddListener(OnSelectAllToggled); + } + + if (SortByDropdown != null) + { + foreach (Dropdown.OptionData optionData in SortByDropdown.options) + { + optionData.text = Localization.instance.Localize(optionData.text); + } + + SortByDropdown.onValueChanged.AddListener(OnSortModeChanged); + } + + if (FilterByText != null) + { + FilterByText.onValueChanged.AddListener(OnFilterChanged); + } + + Refresh(); + } + + public void Update() + { + if (_locked || !HasGamepadFocus() || !ZInput.IsGamepadActive() || ListContainer == null) + { + return; + } + + int elementCount = ListContainer.childCount; + MultiSelectItemListElement focusedElement = GetFocusedElement(); + if (focusedElement == null) + { + return; + } + + int focusedElementIndex = focusedElement.transform.GetSiblingIndex(); + GridLayoutGroup grid = ListContainer.GetComponent(); + if (ListContainer.GetComponent() != null) + { + if (focusedElementIndex > 0 && ZInput.GetButtonDown("JoyLStickUp")) + { + focusedElement.GiveFocus(false); + MultiSelectItemListElement newElement = GetElement(focusedElementIndex - 1); + newElement.GiveFocus(true); + CenterOnItem(newElement); + ZInput.ResetButtonStatus("JoyLStickUp"); + } + else if (focusedElementIndex < elementCount - 1 && ZInput.GetButtonDown("JoyLStickDown")) + { + focusedElement.GiveFocus(false); + MultiSelectItemListElement newElement = GetElement(focusedElementIndex + 1); + newElement.GiveFocus(true); + CenterOnItem(newElement); + ZInput.ResetButtonStatus("JoyLStickDown"); + } + else if (ZInput.GetButtonDown("JoyLStickLeft")) + { + ZInput.ResetButtonStatus("JoyLStickLeft"); + } + else if (ZInput.GetButtonDown("JoyLStickRight")) + { + ZInput.ResetButtonStatus("JoyLStickRight"); + } + } + else if (grid != null) + { + int columnCount = grid.constraintCount; + + if (focusedElementIndex >= columnCount && + ZInput.GetButtonDown("JoyLStickUp")) + { + focusedElement.GiveFocus(false); + MultiSelectItemListElement newElement = GetElement(focusedElementIndex - columnCount); + newElement.GiveFocus(true); + CenterOnItem(newElement); + ZInput.ResetButtonStatus("JoyLStickUp"); + } + else if (focusedElementIndex < elementCount - columnCount && + ZInput.GetButtonDown("JoyLStickDown")) + { + focusedElement.GiveFocus(false); + MultiSelectItemListElement newElement = GetElement(focusedElementIndex + columnCount); + newElement.GiveFocus(true); + CenterOnItem(newElement); + ZInput.ResetButtonStatus("JoyLStickDown"); + } + else if ((focusedElementIndex % columnCount) > 0 && + ZInput.GetButtonDown("JoyLStickLeft")) + { + focusedElement.GiveFocus(false); + MultiSelectItemListElement newElement = GetElement(focusedElementIndex - 1); + newElement.GiveFocus(true); + CenterOnItem(newElement); + ZInput.ResetButtonStatus("JoyLStickLeft"); + } + else if ((focusedElementIndex % columnCount) < columnCount - 1 && + focusedElementIndex < elementCount - 1 && + ZInput.GetButtonDown("JoyLStickRight")) + { + focusedElement.GiveFocus(false); + MultiSelectItemListElement newElement = GetElement(focusedElementIndex + 1); + newElement.GiveFocus(true); + CenterOnItem(newElement); + ZInput.ResetButtonStatus("JoyLStickRight"); + } + } + + if (Multiselect && SelectAllToggle != null) + { + if (ZInput.GetButtonDown("JoyLStick")) + { + SelectAllToggle.isOn = !SelectAllToggle.isOn; + ZInput.ResetButtonStatus("JoyLStick"); + } + } + + if (Sortable && SortByDropdown != null) + { + if (ZInput.GetButtonDown("JoyRStick")) + { + int currentSortMode = SortByDropdown.value; + int sortModeCount = SortByDropdown.options.Count; + currentSortMode = ((currentSortMode + 1) % sortModeCount); + SortByDropdown.value = currentSortMode; + ZInput.ResetButtonStatus("JoyRStick"); + } + } + } + + private void CenterOnItem(MultiSelectItemListElement element) + { + if (_scrollRectEnsureVisible != null) + { + _scrollRectEnsureVisible.CenterOnItem((RectTransform)element.transform); + } + } + + private void OnFilterChanged(string _) + { + Refresh(); + } + + public void Refresh() + { + RefreshFilter(); + RefreshSelectAllToggle(); + } + + private void RefreshFilter() + { + if (FilterByText != null && !Filterable && FilterByText.gameObject.activeSelf) + { + FilterByText.gameObject.SetActive(false); + } + + if (!Filterable || FilterByText == null) + return; + + string filterText = FilterByText.text; + bool filterIsEmpty = string.IsNullOrEmpty(filterText) || string.IsNullOrWhiteSpace(filterText); + + string[] filterParts = filterIsEmpty ? Array.Empty() : + filterText.Split(new []{' '}, StringSplitOptions.RemoveEmptyEntries); + int elementCount = ListContainer.childCount; + + for (int i = 0; i < elementCount; ++i) + { + Transform childToCache = ListContainer.GetChild(i); + MultiSelectItemListElement element = childToCache.GetComponent(); + + // Strip rich text tags from item name + string itemName = element.ItemName.text; + Regex richTextRegex = new Regex(@"<[^>]*>"); + itemName = richTextRegex.Replace(itemName, string.Empty); + + bool nameMatches = filterIsEmpty; + foreach (string part in filterParts) + { + if (itemName.IndexOf(part, StringComparison.OrdinalIgnoreCase) >= 0) + { + nameMatches = true; + break; + } + } + + element.gameObject.SetActive(nameMatches); + } + } + + public void RefreshSelectAllToggle() + { + if (SelectAllToggle != null) + { + if (!Multiselect && SelectAllToggle.gameObject.activeSelf) + { + SelectAllToggle.gameObject.SetActive(false); + return; + } + + bool allAreSelected = true; + int elementCount = ListContainer.childCount; + for (int i = 0; i < elementCount; ++i) + { + Transform childToCache = ListContainer.GetChild(i); + MultiSelectItemListElement element = childToCache.GetComponent(); + if (!element.IsMaxSelected()) + { + allAreSelected = false; + } + } + + SelectAllToggle.SetIsOnWithoutNotify(allAreSelected); + } + } + + private void OnSelectAllToggled(bool _ = true) + { + if (SelectAllToggle == null) + { + return; + } + + if (SelectAllToggle.isOn) + { + ForeachElement((_, x) => x.SelectMaxQuantity(true)); + } + else + { + ForeachElement((_, x) => x.Deselect(true)); + } + + RefreshSelectAllToggle(); + } + + private void OnSortModeChanged(int sortModeValue) + { + if (!Sortable || SortByDropdown == null) + { + return; + } + + Dictionary previousSelectionAmounts = GetCurrentSelectionAmounts(); + + List items = previousSelectionAmounts.Keys.ToList(); + SortMode sortMode = (SortMode)SortByDropdown.value; + List sortedItems = SortItems(sortMode, items); + + for (int i = 0; i < sortedItems.Count; ++i) + { + Transform childToSet = ListContainer.GetChild(i); + IListElement itemToSet = sortedItems[i]; + MultiSelectItemListElement element = childToSet.GetComponent(); + element.SuppressEvents = true; + element.SetItem(itemToSet); + if (previousSelectionAmounts.TryGetValue(itemToSet, out int previousQuantity)) + { + element.SelectQuantity(previousQuantity, true); + } + + element.SuppressEvents = false; + } + + RefreshSelectAllToggle(); + } + + public Dictionary GetCurrentSelectionAmounts() + { + Dictionary selectionAmounts = new Dictionary(); + int elementCount = ListContainer.childCount; + for (int i = 0; i < elementCount; ++i) + { + Transform childToCache = ListContainer.GetChild(i); + MultiSelectItemListElement element = childToCache.GetComponent(); + if (element != null && element.GetItem() != null) + { + selectionAmounts.Add(element.GetListElement(), element.GetSelectedQuantity()); + } + } + + return selectionAmounts; + } + + private void MakeEnoughElements(int itemCount) + { + int elementCount = ListContainer.childCount; + if (elementCount > itemCount) + { + for (int i = elementCount - 1; i >= itemCount; --i) + { + Transform childToDestroy = ListContainer.GetChild(i); + MultiSelectItemListElement element = childToDestroy.GetComponent(); + element.OnSelectionChanged -= OnElementSelectionChanged; + DestroyImmediate(childToDestroy.gameObject); + } + } + else if (elementCount < itemCount) + { + for (int i = elementCount; i < itemCount; ++i) + { + MultiSelectItemListElement newElement = Instantiate(ElementPrefab, ListContainer); + newElement.SuppressEvents = true; + newElement.OnSelectionChanged += OnElementSelectionChanged; + } + } + } + + public void SetItems(List items) + { + int itemCount = items.Count; + + Dictionary previousSelectionAmounts = GetCurrentSelectionAmounts(); + MultiSelectItemListElement focusedElement = GetFocusedElement(); + + MakeEnoughElements(itemCount); + + List sortedItems = items; + if (Sortable && SortByDropdown != null) + { + SortMode sortMode = (SortMode)SortByDropdown.value; + sortedItems = SortItems(sortMode, items); + } + + bool didFocus = false; + for (int i = 0; i < itemCount; ++i) + { + Transform childToSet = ListContainer.GetChild(i); + IListElement itemToSet = sortedItems[i]; + MultiSelectItemListElement element = childToSet.GetComponent(); + element.UseEnchantAsName = UseEnchantAsName; + element.SuppressEvents = true; + element.SetItem(itemToSet); + + if (previousSelectionAmounts.TryGetValue(itemToSet, out int previousQuantity)) + { + element.SelectQuantity(previousQuantity, true); + } + + element.SuppressEvents = false; + bool shouldFocus = HasGamepadFocus() && ((focusedElement == null && i == 0) || element == focusedElement); + element.GiveFocus(shouldFocus); + + if (shouldFocus) + { + didFocus = true; + CenterOnItem(element); + } + } + + if (HasGamepadFocus() && !didFocus && ListContainer.childCount > 0) + { + // Force GiveFocus to fire + _hasGamepadFocus = false; + GiveFocus(true, 0); + CenterOnItem(GetElement(0)); + } + + OnItemsChanged?.Invoke(); + OnSelectedItemsChanged?.Invoke(); + RefreshSelectAllToggle(); + } + + private void OnElementSelectionChanged(MultiSelectItemListElement element, bool isSelected, int selectedQuantity) + { + if (!Multiselect) + { + ForeachElement((_, x) => + { + if (x != element) + { + x.SuppressEvents = true; + x.Deselect(true); + x.SuppressEvents = false; + } + }); + } + + OnSelectedItemsChanged?.Invoke(); + RefreshSelectAllToggle(); + } + + public List SortItems(SortMode mode, List items) + { + switch (mode) + { + case SortMode.Rarity: + if (SortByRarity != null) + { + return SortByRarity(items); + } + break; + case SortMode.Name: + if (SortByName != null) + { + return SortByName(items); + } + else + { + return items.OrderBy(x => Localization.instance.Localize( + x.GetItem().m_shared.m_name)).ThenByDescending(x => x.GetItem().m_stack).ToList(); + } + case SortMode.Quantity: + return items.OrderByDescending(x => x.GetItem().m_stack) + .ThenBy(x => Localization.instance.Localize(x.GetItem().m_shared.m_name)).ToList(); + default: + throw new ArgumentOutOfRangeException(nameof(mode), mode, null); + } + + return items.ToList(); + } + + public List> GetSelectedItems() + { + List> result = new List>(); + int elementCount = ListContainer.childCount; + for (int i = 0; i < elementCount; ++i) + { + Transform childToCache = ListContainer.GetChild(i); + MultiSelectItemListElement element = childToCache.GetComponent(); + int quantity = element.GetSelectedQuantity(); + if (quantity > 0) + { + result.Add(new Tuple((T)element.GetListElement(), quantity)); + } + } + + return result; + } + + public Tuple GetSingleSelectedItem() + { + int elementCount = ListContainer.childCount; + for (int i = 0; i < elementCount; ++i) + { + Transform childToCache = ListContainer.GetChild(i); + MultiSelectItemListElement element = childToCache.GetComponent(); + int quantity = element.GetSelectedQuantity(); + if (quantity > 0) + { + return new Tuple((T)element.GetListElement(), quantity); + } + } + + return null; + } + + public int GetFirstSelectedIndex() + { + int elementCount = ListContainer.childCount; + for (int i = 0; i < elementCount; ++i) + { + Transform childToCache = ListContainer.GetChild(i); + MultiSelectItemListElement element = childToCache.GetComponent(); + int quantity = element.GetSelectedQuantity(); + if (quantity > 0) + { + return i; + } + } + + return -1; + } + + public void Lock() + { + _locked = true; + if (SortByDropdown != null) + SortByDropdown.interactable = false; + if (FilterByText != null) + FilterByText.interactable = false; + if (SelectAllToggle != null) + SelectAllToggle.interactable = false; + ForeachElement((_, e) => e.Lock()); + } + + public void Unlock() + { + _locked = false; + if (SortByDropdown != null) + SortByDropdown.interactable = Sortable && !ReadOnly; + if (FilterByText != null) + FilterByText.interactable = Filterable && !ReadOnly; + if (SelectAllToggle != null) + SelectAllToggle.interactable = Multiselect && !ReadOnly; + ForeachElement((_, e) => e.Unlock()); + } + + private MultiSelectItemListElement GetElement(int index) + { + Transform child = ListContainer.GetChild(index); + return child == null ? null : child.GetComponent(); + } + + public void ForeachElement(Action func) + { + if (ListContainer == null) + { + return; + } + + int elementCount = ListContainer.childCount; + for (int i = 0; i < elementCount; ++i) + { + MultiSelectItemListElement element = GetElement(i); + if (element != null) + { + func(i, element); + } + } + } + + public void DeselectAll() + { + SuppressEvents(true); + ForeachElement((_, e) => e.Deselect(true)); + SuppressEvents(false); + + OnSelectedItemsChanged?.Invoke(); + RefreshSelectAllToggle(); + } + + public void SuppressEvents(bool suppress) + { + ForeachElement((_, e) => e.SuppressEvents = suppress); + } + + public void GiveFocus(bool focused, int tryFocusIndex) + { + if (_hasGamepadFocus != focused) + { + _hasGamepadFocus = focused; + + int focusIndex = focused ? Mathf.Clamp(tryFocusIndex, 0, ListContainer.childCount - 1) : -1; + ForeachElement((i, e) => + { + bool shouldFocus = i == focusIndex; + e.GiveFocus(shouldFocus); + if (shouldFocus) + { + CenterOnItem(e); + } + }); + } + } + + public bool HasGamepadFocus() + { + return _hasGamepadFocus; + } + + public MultiSelectItemListElement GetFocusedElement() + { + if (ListContainer == null || !ZInput.IsGamepadActive()) + { + return null; + } + + int elementCount = ListContainer.childCount; + for (int i = 0; i < elementCount; ++i) + { + Transform child = ListContainer.GetChild(i); + if (child == null) + { + continue; + } + + MultiSelectItemListElement element = child.GetComponent(); + if (element != null && element.HasGamepadFocus()) + { + return element; + } + } + + return null; + } + + public int GetItemCount() + { + if (ListContainer == null) + { + return 0; + } + + int elementCount = ListContainer.childCount; + int activeChildCount = 0; + for (int i = 0; i < elementCount; ++i) + { + Transform child = ListContainer.GetChild(i); + if (child != null && child.gameObject.activeSelf) + { + activeChildCount++; + } + } + + return activeChildCount; + } + + public bool IsGrid() + { + return ListContainer != null && ListContainer.GetComponent() != null; + } + + public void InitWithExistingItems() + { + for (int i = 0; i < ListContainer.childCount; ++i) + { + Transform childToSet = ListContainer.GetChild(i); + MultiSelectItemListElement element = childToSet.GetComponent(); + element.OnSelectionChanged += OnElementSelectionChanged; + } + + DeselectAll(); + + OnItemsChanged?.Invoke(); + OnSelectedItemsChanged?.Invoke(); + RefreshSelectAllToggle(); + } + } +} diff --git a/EpicLoot-UnityLib/MultiSelectItemListElement.cs b/EpicLoot-UnityLib/src/MultiSelectItemListElement.cs similarity index 76% rename from EpicLoot-UnityLib/MultiSelectItemListElement.cs rename to EpicLoot-UnityLib/src/MultiSelectItemListElement.cs index 59f6b113b..3d0ba14f7 100644 --- a/EpicLoot-UnityLib/MultiSelectItemListElement.cs +++ b/EpicLoot-UnityLib/src/MultiSelectItemListElement.cs @@ -20,9 +20,15 @@ public class MultiSelectItemListElement : MonoBehaviour public Button QuantityUpButton; public Button QuantityDownButton; public UITooltip Tooltip; + public UITooltip ItemTooltip; public bool ReadOnly; public bool CheckPlayerInventory; public bool NoMax; + public bool UseEnchantAsName = false; + + public delegate float AudioVolumeLevelDelegate(); + public static AudioVolumeLevelDelegate AudioVolumeLevel; + public AudioSource Audio; public AudioClip OnClickSFX; public GameObject GamepadFocusIndicator; @@ -35,6 +41,10 @@ public class MultiSelectItemListElement : MonoBehaviour public delegate void SetMagicItemDelegate(MultiSelectItemListElement element, ItemDrop.ItemData item, UITooltip tooltip); public static SetMagicItemDelegate SetMagicItem; + + public delegate void SetItemTooltipDelegate(ItemDrop.ItemData item, UITooltip tooltip); + + public static SetItemTooltipDelegate SetItemTooltip; private IListElement _item; private int _selectedQuantity; @@ -45,7 +55,7 @@ public void Awake() { if (ItemIcon != null || MagicBG != null) { - var iconMaterial = InventoryGui.instance.m_dragItemPrefab.transform.Find("icon").GetComponent().material; + Material iconMaterial = InventoryGui.instance.m_dragItemPrefab.transform.Find("icon").GetComponent().material; if (iconMaterial != null) { if (ItemIcon != null) @@ -57,15 +67,24 @@ public void Awake() if (Tooltip != null) { - var storeItemTooltip = StoreGui.instance.m_listElement.GetComponent().m_tooltipPrefab; + GameObject storeItemTooltip = StoreGui.instance.m_listElement.GetComponent().m_tooltipPrefab; Tooltip.m_tooltipPrefab = storeItemTooltip; } + else + { + ItemTooltip = gameObject.AddComponent(); + ItemTooltip.m_tooltipPrefab = StoreGui.instance.m_listElement.GetComponent().m_tooltipPrefab; + } if (Audio != null) { - var uiSFX = GameObject.Find("sfx_gui_button"); + GameObject uiSFX = GameObject.Find("sfx_gui_button"); if (uiSFX != null) + { Audio.outputAudioMixerGroup = uiSFX.GetComponent().outputAudioMixerGroup; + } + + Audio.volume = AudioVolumeLevel(); } if (!ReadOnly) @@ -120,14 +139,18 @@ public void Update() private void OnClicked() { if (IsSelected()) + { Deselect(false); + } else + { SelectMaxQuantity(false); + } } public void SelectMaxQuantity(bool noSound) { - var maxSelectedAmount = NoMax || _item == null ? 1 : (_item?.GetItem()?.m_stack ?? 0); + int maxSelectedAmount = NoMax || _item == null ? 1 : (_item?.GetItem()?.m_stack ?? 0); SelectQuantity(maxSelectedAmount, noSound); } @@ -143,19 +166,27 @@ public bool IsMaxSelected() private void OnSelectedAmountChanged(string typedInAmount) { - var successParse = int.TryParse(typedInAmount, out var result); + bool successParse = int.TryParse(typedInAmount, out int result); if (!successParse) + { Deselect(false); + } else + { SelectQuantity(result, false); + } } private void OnSelectedToggleChanged(bool _) { if (SelectedToggle.isOn) + { SelectMaxQuantity(true); + } else + { Deselect(true); + } } private void OnQuantityUpButtonClicked() @@ -170,7 +201,7 @@ private void OnQuantityDownButtonClicked() public void SetItem(IListElement item) { - var sameItem = _item == item; + bool sameItem = _item == item; _item = item; if (_item?.GetItem() == null) @@ -187,12 +218,18 @@ public void SetItem(IListElement item) Tooltip.m_topic = string.Empty; Tooltip.m_text = string.Empty; } + + if (ItemTooltip != null) + { + ItemTooltip.Set("", ""); + } } else { if (SetMagicItem != null) { SetMagicItem(this, _item.GetItem(), Tooltip); + CheckAndSetNameToEnchantingEffects(); } else { @@ -201,7 +238,12 @@ public void SetItem(IListElement item) if (ItemIcon != null) ItemIcon.sprite = _item.GetItem().GetIcon(); if (ItemName != null) - ItemName.text = Localization.instance.Localize(_item.GetItem().m_shared.m_name); + { + if (CheckAndSetNameToEnchantingEffects() == false) + { + ItemName.text = Localization.instance.Localize(_item.GetItem().m_shared.m_name); + } + } if (Tooltip != null) { @@ -211,12 +253,41 @@ public void SetItem(IListElement item) } if (ItemName != null) + { ItemName.text += _item.GetDisplayNameSuffix(); + } + + if (ItemTooltip != null) + { + if (SetItemTooltip != null) + { + SetItemTooltip(_item.GetItem(), ItemTooltip); + } + } } if (!sameItem) + { Deselect(true); + } + RefreshGamepadFocusIndicator(); + + + } + + private bool CheckAndSetNameToEnchantingEffects() + { + if (UseEnchantAsName && _item.GetEnchantName() != string.Empty) + { + ItemName.text = _item.GetEnchantName(); + ItemName.alignment = TextAnchor.MiddleLeft; + // Adjust the text box container to give it the whole width, which is normally used for quantity + ItemName.GetComponent().offsetMax = new Vector2(y: 0f, x: -5f); + return true; + } + + return false; } public void Deselect(bool noSound) @@ -226,7 +297,7 @@ public void Deselect(bool noSound) public void SelectQuantity(int quantity, bool noSound) { - var prevQuantity = _selectedQuantity; + int prevQuantity = _selectedQuantity; if (_item == null) { _selectedQuantity = quantity; @@ -242,10 +313,14 @@ public void SelectQuantity(int quantity, bool noSound) } if (!SuppressEvents && prevQuantity != _selectedQuantity) + { OnSelectionChanged?.Invoke(this, IsSelected(), _selectedQuantity); + } if (Audio != null && !ReadOnly && !noSound && prevQuantity != _selectedQuantity) - Audio.PlayOneShot(OnClickSFX); + { + Audio.PlayOneShot(OnClickSFX, AudioVolumeLevel()); + } Refresh(); } @@ -254,7 +329,7 @@ public void Refresh() { RefreshGamepadFocusIndicator(); - var stackItem = _item != null && _item.GetItem().m_shared.m_maxStackSize > 1; + bool stackItem = _item != null && _item.GetItem().m_shared.m_maxStackSize > 1; if (MainButton != null) { @@ -278,13 +353,13 @@ public void Refresh() if (ItemTotalQuantity != null && _item != null) { ItemTotalQuantity.gameObject.SetActive(ReadOnly || stackItem); - var quantityText = string.Format(ReadOnly ? ReadOnlyQuantityFormat : TotalQuantityFormat, _item.GetMax()); + string quantityText = string.Format(ReadOnly ? ReadOnlyQuantityFormat : TotalQuantityFormat, _item.GetMax()); if (CheckPlayerInventory) { - var inventory = Player.m_localPlayer.GetInventory(); - var hasEnough = inventory.CountItems(_item.GetItem().m_shared.m_name) >= _item.GetItem().m_stack; - if (!hasEnough) + if (!InventoryManagement.Instance.HasItem(_item.GetItem())) + { quantityText = $"{quantityText}"; + } } ItemTotalQuantity.text = quantityText; } diff --git a/EpicLoot-UnityLib/src/MultiSelectListFocusController.cs b/EpicLoot-UnityLib/src/MultiSelectListFocusController.cs new file mode 100644 index 000000000..a192c5d7d --- /dev/null +++ b/EpicLoot-UnityLib/src/MultiSelectListFocusController.cs @@ -0,0 +1,146 @@ +using System.Collections.Generic; +using UnityEngine; + +namespace EpicLoot_UnityLib +{ + public class MultiSelectListFocusController : MonoBehaviour + { + public List Lists = new List(); + public GameObject[] SortHints; + public GameObject[] SelectAllHints; + public GameObject[] SelectHints; + + private int _focusedListIndex; + private bool _gamepadWasEnabled; + + + public void OnEnable() + { + _focusedListIndex = 0; + for (int index = 0; index < Lists.Count; index++) + { + if (Lists[index] == null) + { + continue; + } + Lists[index].GiveFocus(index == _focusedListIndex, 0); + } + + RefreshHints(); + } + + public void Update() + { + if (Lists.Count == 0) + { + return; + } + + MultiSelectItemList currentList = Lists[_focusedListIndex]; + int loopCount = 0; + int itemCount = currentList.GetItemCount(); + while (itemCount == 0) + { + currentList.GiveFocus(false, 0); + + _focusedListIndex = (_focusedListIndex + 1) % Lists.Count; + currentList = Lists[_focusedListIndex]; + if (currentList == null) + { + continue; + } + + itemCount = currentList.GetItemCount(); + if (currentList.GetItemCount() > 0) + { + currentList.GiveFocus(true, 0); + RefreshHints(); + break; + } + + loopCount++; + if (loopCount >= Lists.Count) + { + return; + } + } + + if (ZInput.IsGamepadActive()) + { + int newFocusedIndex = _focusedListIndex; + if (ZInput.GetButtonDown("JoyTabLeft")) + { + newFocusedIndex = Mathf.Max(_focusedListIndex - 1, 0); + ZInput.ResetButtonStatus("JoyTabLeft"); + } + else if (ZInput.GetButtonDown("JoyTabRight")) + { + newFocusedIndex = Mathf.Min(_focusedListIndex + 1, Lists.Count - 1); + ZInput.ResetButtonStatus("JoyTabRight"); + } + + if (newFocusedIndex != _focusedListIndex) + { + int offset = newFocusedIndex - _focusedListIndex; + if (Lists[newFocusedIndex].GetItemCount() == 0) + { + newFocusedIndex = (newFocusedIndex + offset + Lists.Count) % Lists.Count; + } + if (Lists[newFocusedIndex].GetItemCount() == 0) + { + newFocusedIndex = _focusedListIndex; + } + } + + FocusList(newFocusedIndex); + } + + if (_gamepadWasEnabled != ZInput.IsGamepadActive()) + { + RefreshHints(); + } + + _gamepadWasEnabled = ZInput.IsGamepadActive(); + } + + public void FocusList(int newFocusedIndex) + { + MultiSelectItemList list = Lists[_focusedListIndex]; + MultiSelectItemListElement currentFocusElement = list.GetFocusedElement(); + int currentFocusIndex = currentFocusElement != null ? currentFocusElement.transform.GetSiblingIndex() : -1; + if (newFocusedIndex != _focusedListIndex && newFocusedIndex >= 0 && newFocusedIndex < Lists.Count) + { + _focusedListIndex = newFocusedIndex; + for (int index = 0; index < Lists.Count; index++) + { + bool isGrid = Lists[index].IsGrid(); + Lists[index].GiveFocus(index == _focusedListIndex, isGrid ? 0 : currentFocusIndex); + } + + RefreshHints(); + } + } + + private void RefreshHints() + { + if (!isActiveAndEnabled || !ZInput.IsGamepadActive() || Lists.Count == 0) + return; + + MultiSelectItemList focusedList = Lists[_focusedListIndex]; + foreach (GameObject hint in SortHints) + { + hint.SetActive(focusedList.Sortable && focusedList.SortByDropdown != null && + focusedList.SortByDropdown.isActiveAndEnabled); + } + foreach (GameObject hint in SelectAllHints) + { + hint.SetActive(focusedList.Multiselect && focusedList.SelectAllToggle != null && + focusedList.SelectAllToggle.isActiveAndEnabled); + } + foreach (GameObject hint in SelectHints) + { + hint.SetActive(!focusedList.ReadOnly && focusedList.GetFocusedElement() != null); + } + } + } +} diff --git a/EpicLoot-UnityLib/src/PlaySoundOnChecked.cs b/EpicLoot-UnityLib/src/PlaySoundOnChecked.cs new file mode 100644 index 000000000..6a1e6d34f --- /dev/null +++ b/EpicLoot-UnityLib/src/PlaySoundOnChecked.cs @@ -0,0 +1,37 @@ +using UnityEngine; +using UnityEngine.UI; + +namespace EpicLoot_UnityLib +{ + [RequireComponent(typeof(Toggle))] + public class PlaySoundOnChecked : MonoBehaviour + { + public AudioSource Audio; + public AudioClip SFX; + + public delegate float AudioVolumeLevelDelegate(); + public static AudioVolumeLevelDelegate AudioVolumeLevel; + + private Toggle _toggle; + + public void Awake() + { + _toggle = GetComponent(); + _toggle.onValueChanged.AddListener(OnToggleChanged); + } + + public void OnDestroy() + { + _toggle.onValueChanged.RemoveListener(OnToggleChanged); + } + + private void OnToggleChanged(bool _) + { + if (Audio != null && SFX != null && _toggle.isOn) + { + Audio.volume = AudioVolumeLevel(); + Audio.PlayOneShot(SFX, Audio.volume); + } + } + } +} diff --git a/EpicLoot-UnityLib/src/RuneUI.cs b/EpicLoot-UnityLib/src/RuneUI.cs new file mode 100644 index 000000000..c83919392 --- /dev/null +++ b/EpicLoot-UnityLib/src/RuneUI.cs @@ -0,0 +1,547 @@ +using JetBrains.Annotations; +using System; +using System.Collections.Generic; +using System.Linq; +using UnityEngine; +using UnityEngine.UI; + +namespace EpicLoot_UnityLib +{ + public class RuneUI : EnchantingTableUIPanelBase + { + public Toggle RuneExtractButton; + public Toggle RuneEtchButton; + + [Header("Cost")] + public Text CostLabel; + public MultiSelectItemList CostList; + + [Header("Rune Selector")] + public RectTransform EnchantList; + public GameObject EnchantmentListPrefab; + public GameObject AvailableRunesWindow; + public MultiSelectItemList AvailableRunes; + + public Text Warning; + + public AudioClip RunicActionCompleted; + + // These use delegates which are connected at runtime from the non-unity side of EL + public delegate List GetRuneExtractItemsDelegate(); + public delegate List GetRuneEtchItemsDelegate(); + public delegate List GetApplyableRunesDelegate(ItemDrop.ItemData item, string selected_enchantment); + public delegate List> GetItemEnchantsDelegate(ItemDrop.ItemData item, bool runecheck); + public delegate List GetRuneExtractCostDelegate(ItemDrop.ItemData item, MagicRarityUnity rarity, float costModifier); + public delegate List GetRuneEtchCostDelegate(ItemDrop.ItemData item, MagicRarityUnity rarity, float costModifier); + public delegate bool RuneItemDestructionEnabledDelegate(); + public delegate MagicRarityUnity GetItemRarityDelegate(ItemDrop.ItemData item); + public delegate ItemDrop.ItemData GetItemEnchantedByRuneDelegate(ItemDrop.ItemData item, int enchantment, float powerModifier); + public delegate string GetSelectedEnchantmentByIndexDelegate(ItemDrop.ItemData item, int enchantment); + public delegate GameObject ApplyRuneToItemAndReturnSuccess(ItemDrop.ItemData item, ItemDrop.ItemData rune, int enchantment); + + public static GetApplyableRunesDelegate GetApplyableRunes; + public static GetRuneExtractItemsDelegate GetRuneExtractItems; + public static GetRuneEtchItemsDelegate GetRuneEtchItems; + public static GetItemEnchantsDelegate GetItemEnchants; + public static GetRuneExtractCostDelegate GetRuneExtractCost; + public static GetRuneEtchCostDelegate GetRuneEtchCost; + public static GetItemEnchantedByRuneDelegate ItemToBeRuned; + public static RuneItemDestructionEnabledDelegate ExtractItemsDestroyed; + public static GetItemRarityDelegate GetItemRarity; + public static ApplyRuneToItemAndReturnSuccess RuneEnchancedItem; + public static GetSelectedEnchantmentByIndexDelegate GetSelectedEnchantmentByIndex; + + private RuneAction _runeAction; + private GameObject _successDialog; + private ItemDrop.ItemData _selectedItem; + private ItemDrop.ItemData _selectedOverrideRune; + private MagicRarityUnity _selectedRarity = MagicRarityUnity.Magic; + private int _selectedEnchantmentIndex = -1; + + private enum RuneAction + { + Extract, + Etch + } + + public override void Awake() + { + base.Awake(); + + RuneExtractButton.onValueChanged.AddListener((isOn) => + { + ExtractModeSelected(isOn); + }); + + RuneEtchButton.onValueChanged.AddListener((isOn) => + { + EtchModeSelected(isOn); + }); + + AvailableRunes.OnSelectedItemsChanged += OnSelectedOverrideRuneChanged; + } + + [UsedImplicitly] + public void OnEnable() + { + RuneExtractButton.isOn = false; + RuneEtchButton.Select(); + RuneEtchButton.isOn = true; + EtchModeSelected(true); + } + + public override void Update() + { + base.Update(); + + bool featureUnlocked = EnchantingTableUI.instance.SourceTable.IsFeatureUnlocked(EnchantingFeature.Rune); + if (!featureUnlocked && !Player.m_localPlayer.NoCostCheat()) + { + return; + } + + // Check if the action is completed, and unlock the UI + if (_successDialog != null && !_successDialog.activeSelf) + { + Unlock(); + Destroy(_successDialog); + _successDialog = null; + } + } + + public void UpdateDisplaySelectedItemEnchantments() + { + if (_selectedItem == null) + { + MainButton.interactable = false; + return; + } + + // Set the enchantments to be selected based on the enchantments on this item + List> info = GetItemEnchants(_selectedItem, true); + RefreshSelectableEnchantments(); + UpdateDisplayAvailableOverwriteEnchantments(); //TODO remove? + + // Set enchantment list to the enchantments of the selected item + Tuple featureValues = + EnchantingTableUI.instance.SourceTable.GetFeatureCurrentValue(EnchantingFeature.Rune); + + float costReduction = GetCostReduction(featureValues.Item1); + + CostLabel.enabled = true; + List cost; + + if (_runeAction == RuneAction.Extract) + { + cost = GetRuneExtractCost(_selectedItem, _selectedRarity, costReduction); + } + else if (_runeAction == RuneAction.Etch) + { + cost = GetRuneEtchCost(_selectedItem, _selectedRarity, costReduction); + } + else + { + cost = new List(); + } + + CostList.SetItems(cost.Cast().ToList()); + + CheckIfActionDoable(); + } + + public void UpdateDisplayAvailableOverwriteEnchantments() + { + if (_selectedItem == null || _runeAction == RuneAction.Extract || _selectedEnchantmentIndex <= -1) + { + AvailableRunes.SetItems(new List()); + MainButton.interactable = false; + return; + } + + List availableEnchantRunes = + GetApplyableRunes(_selectedItem, GetSelectedEnchantmentByIndex(_selectedItem, _selectedEnchantmentIndex)); + AvailableRunes.SetItems(availableEnchantRunes.Cast().ToList()); + } + + private void ClearEnchantmentList() + { + // Clear the enchantment list + if (EnchantList.childCount > 0) + { + foreach (Transform child in EnchantList) + { + Destroy(child.gameObject); + } + } + _selectedEnchantmentIndex = -1; + } + + private void RefreshSelectableEnchantments() + { + Tuple entry = AvailableItems.GetSingleSelectedItem(); + ItemDrop.ItemData item = entry?.Item1.GetItem(); + List> augmentableEffects = GetItemEnchants(item, true); + + ClearEnchantmentList(); + + int enchantIndex = 0; + foreach (Tuple effect in augmentableEffects) + { + GameObject enchantmentListElement = Instantiate(EnchantmentListPrefab, EnchantList); + Text enchantmentElement = enchantmentListElement.GetComponentInChildren(); + Toggle enchantmentbutton = enchantmentListElement.GetComponent(); + enchantmentbutton.onValueChanged.AddListener((isOn) => + { + SetSelectedEnchantIndex(); + UpdateDisplayAvailableOverwriteEnchantments(); + CheckIfActionDoable(); + }); + + if (enchantmentElement != null) + { + enchantmentElement.text = effect.Item1; + } + + enchantmentListElement.SetActive(true); + enchantIndex++; + } + } + + private void SetSelectedEnchantIndex() + { + if (EnchantList.childCount > 0) + { + int index = 0; + foreach (Transform child in EnchantList) + { + if (child.GetComponent().isOn == true) + { + _selectedEnchantmentIndex = index; + return; + } + index++; + } + } + + _selectedEnchantmentIndex = -1; + } + + public bool LocalPlayerCanAffordRuneCost(List cost) + { + if (cost == null || cost.Count == 0) + { + return true; + } + + if (Player.m_localPlayer == null) + { + return false; + } + + if (Player.m_localPlayer.NoCostCheat()) + { + return true; + } + + foreach (InventoryItemListElement element in cost) + { + ItemDrop.ItemData item = element.GetItem(); + if (!InventoryManagement.Instance.HasItem(item)) + { + return false; + } + } + + return true; + } + + public void ExtractModeSelected(bool enabled) + { + _runeAction = RuneAction.Extract; + MainButton.GetComponentInChildren().text = Localization.instance.Localize("$mod_epicloot_rune_extract"); + Warning.text = Localization.instance.Localize("$mod_epicloot_rune_extract_warning"); + + // Deselect runes and clear them + AvailableRunesWindow.SetActive(false); + if (AvailableRunes.GetItemCount() > 0) + { + AvailableRunes.SetItems(new List()); + } + + NewModeSelected(enabled); + } + + public void EtchModeSelected(bool enabled) + { + _runeAction = RuneAction.Etch; + MainButton.GetComponentInChildren().text = Localization.instance.Localize("$mod_epicloot_rune_etch"); + Warning.text = Localization.instance.Localize("$mod_epicloot_rune_etch_warning"); + + AvailableRunesWindow.SetActive(true); + + NewModeSelected(enabled); + } + + private void NewModeSelected(bool enabled) + { + RefreshAvailableItems(); + _selectedEnchantmentIndex = -1; + + if (!enabled) + { + MainButton.interactable = false; + return; + } + + Tuple selectedItem = AvailableItems.GetSingleSelectedItem(); + + // Clears the list of enchantments if no item is selected + if (selectedItem?.Item1.GetItem() == null) + { + CostLabel.enabled = false; + CostList.SetItems(new List()); + AvailableRunes.SetItems(new List()); + MainButton.interactable = false; + return; + } + else + { + // Check the currently selected item + if (selectedItem?.Item1.GetItem() != _selectedItem) + { + _selectedItem = selectedItem.Item1.GetItem(); + _selectedRarity = GetItemRarity(_selectedItem); + } + + UpdateDisplaySelectedItemEnchantments(); + } + + bool featureUnlocked = EnchantingTableUI.instance.SourceTable.IsFeatureUnlocked(EnchantingFeature.Rune); + + if (!featureUnlocked) + { + MainButton.interactable = featureUnlocked; + } + } + + protected override void DoMainAction() + { + Tuple selectedItem = AvailableItems.GetSelectedItems().FirstOrDefault(); + + // Clear any currently existing success dialog + Cancel(); + + if (selectedItem?.Item1.GetItem() == null) + { + return; + } + + Tuple featureValues = EnchantingTableUI.instance.SourceTable.GetFeatureCurrentValue(EnchantingFeature.Rune); + float costReduction = GetCostReduction(featureValues.Item1); + float powerModifier = GetPowerModifier(featureValues.Item2); + ItemDrop.ItemData item = selectedItem.Item1.GetItem(); + + if (_runeAction == RuneAction.Extract) + { + List cost = GetRuneExtractCost(item, _selectedRarity, costReduction); + ItemDrop.ItemData RuneWithEnchant = ItemToBeRuned(item, _selectedEnchantmentIndex, powerModifier); + + if (RuneWithEnchant == null) + { + return; + } + + Player player = Player.m_localPlayer; + if (!player.NoCostCheat()) + { + if (!LocalPlayerCanAffordCost(cost)) + { + return; + } + + foreach (InventoryItemListElement costElement in cost) + { + InventoryManagement.Instance.RemoveItem(costElement.GetItem()); + } + } + + bool destroyExtractedItem = ExtractItemsDestroyed(); + + if (destroyExtractedItem) + { + // Destroy the item + InventoryManagement.Instance.RemoveItem(item); + } + + InventoryManagement.Instance.GiveItem(RuneWithEnchant); + CostList.SetItems(new List()); + } + else if (_runeAction == RuneAction.Etch) + { + // Modify an existing item and destroy the selected Rune + ItemDrop.ItemData rune = AvailableRunes.GetSingleSelectedItem().Item1.GetItem(); + ItemDrop.ItemData itemToEtch = selectedItem?.Item1.GetItem(); + + if (_successDialog != null) + { + Destroy(_successDialog); + } + + _successDialog = RuneEnchancedItem(itemToEtch, rune, _selectedEnchantmentIndex); + _successDialog.SetActive(true); + // Remove the rune from the inventory + InventoryManagement.Instance.RemoveExactItem(rune, 1); + CostList.SetItems(new List()); + } + + DeselectAll(); + + RefreshAvailableItems(); + _selectedEnchantmentIndex = -1; + CostList.SetItems(new List()); + AvailableRunes.SetItems(new List()); + } + + protected override AudioClip GetCompleteAudioClip() + { + return RunicActionCompleted; + } + + public void RefreshAvailableItems() + { + List items; + if (_runeAction == RuneAction.Extract) + { + items = GetRuneExtractItems(); + } + else if (_runeAction == RuneAction.Etch) + { + items = GetRuneEtchItems(); + } + else + { + items = new List(); + } + + AvailableItems.SetItems(items.Cast().ToList()); + RefreshSelectableEnchantments(); + AvailableItems.DeselectAll(); + OnSelectedItemsChanged(); + } + + protected override void OnSelectedItemsChanged() + { + Tuple selectedItem = AvailableItems.GetSingleSelectedItem(); + if (selectedItem?.Item1.GetItem() != null) + { + _selectedItem = selectedItem.Item1.GetItem(); + _selectedRarity = GetItemRarity(_selectedItem); + UpdateDisplaySelectedItemEnchantments(); + _selectedEnchantmentIndex = -1; + } + else + { + ClearEnchantmentList(); + } + } + + protected void OnSelectedOverrideRuneChanged() + { + Tuple rune = AvailableRunes.GetSingleSelectedItem(); + if (rune?.Item1.GetItem() != null) + { + _selectedOverrideRune = rune.Item1.GetItem(); + CheckIfActionDoable(); + } + else + { + _selectedOverrideRune = null; + } + } + + private void CheckIfActionDoable() + { + bool state = true; + + if (_selectedItem == null || _selectedEnchantmentIndex == -1) + { + state = false; + MainButton.interactable = false; + return; + } + + // Check costs, ignored if nocost mode + Tuple featureValues = EnchantingTableUI.instance.SourceTable.GetFeatureCurrentValue(EnchantingFeature.Rune); + float costReduction = GetCostReduction(featureValues.Item1); + + if (_runeAction == RuneAction.Etch) + { + List cost = GetRuneEtchCost(_selectedItem, _selectedRarity, costReduction); + CostList.SetItems(cost.Cast().ToList()); + state = LocalPlayerCanAffordRuneCost(cost); + + if (_selectedOverrideRune == null) + { + // Etching but does not have an override rune selected + state = false; + } + } + else if (_runeAction == RuneAction.Extract) + { + List cost = GetRuneExtractCost(_selectedItem, _selectedRarity, costReduction); + CostList.SetItems(cost.Cast().ToList()); + state = LocalPlayerCanAffordRuneCost(cost); + } + + MainButton.interactable = state; + } + + private float GetCostReduction(float value) + { + return value == 0f || value == float.NaN ? 1.0f : 1f - (value / 100f); + } + + private float GetPowerModifier(float value) + { + return value == float.NaN ? 1.0f : (value / 100f); + } + + public override bool CanCancel() + { + return base.CanCancel() || (_successDialog != null && _successDialog.activeSelf); + } + + public override void Cancel() + { + base.Cancel(); + + if (_successDialog != null && _successDialog.activeSelf) + { + Destroy(_successDialog); + _successDialog = null; + } + } + + public override void Lock() + { + base.Lock(); + + RuneExtractButton.interactable = false; + RuneEtchButton.interactable = false; + MainButton.interactable = false; + } + + public override void Unlock() + { + base.Unlock(); + + RuneExtractButton.interactable = true; + RuneEtchButton.interactable = true; + } + + public override void DeselectAll() + { + AvailableItems?.DeselectAll(); + } + } +} diff --git a/EpicLoot-UnityLib/src/SacrificeUI.cs b/EpicLoot-UnityLib/src/SacrificeUI.cs new file mode 100644 index 000000000..6db66c3a5 --- /dev/null +++ b/EpicLoot-UnityLib/src/SacrificeUI.cs @@ -0,0 +1,344 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using JetBrains.Annotations; +using UnityEngine; +using UnityEngine.UI; +using Random = UnityEngine.Random; + +namespace EpicLoot_UnityLib +{ + public class SacrificeUI : EnchantingTableUIPanelBase + { + enum SacrificeMode + { + Sacrifice, + Identify + } + + public Toggle SacrificeToggle; + public Toggle IdentifyToggle; + public GameObject IdentifyStylePanel; + public MultiSelectItemList CostList; + + public Dropdown IdentifyStyle; + + public MultiSelectItemList SacrificeProducts; + public EnchantBonus BonusPanel; + public Text Warning; + public Text Explainer; + + public delegate List GetSacrificeItemsDelegate(); + public delegate List GetSacrificeProductsDelegate( + List> items); + public delegate List GetIdentifyCostDelegate( + string filterType, List> unidentifiedItems, float cost_modifier); + public delegate List GetIdentifyItemsDelegate(); + public delegate List GetRandomFilteredLootRollDelegate( + string filterType, List> unidentifiedItems, float power_modifier); + public delegate List GetPotentialIdentificationsDelegate( + string filterType, List items_selected); + public delegate Dictionary GetIdentifyStylesDelegate(); + + public static GetSacrificeItemsDelegate GetSacrificeItems; + public static GetSacrificeProductsDelegate GetSacrificeProducts; + public static GetIdentifyItemsDelegate GetIdentifyItems; + public static GetIdentifyCostDelegate GetIdentifyCost; + public static GetRandomFilteredLootRollDelegate GetRandomFilteredLoot; + public static GetPotentialIdentificationsDelegate GetPotentialIdentifications; + public static GetIdentifyStylesDelegate GetIdentifyStyles; + + SacrificeMode _sacrificeMode = SacrificeMode.Sacrifice; + + public override void Awake() + { + base.Awake(); + + SacrificeToggle.onValueChanged.AddListener((isOn) => { + SacrificeModeSelected(isOn); + }); + + IdentifyToggle.onValueChanged.AddListener((isOn) => { + IdentifyModeSelected(isOn); + }); + + // Build the identify style dropdown options based on the configured styles + IdentifyStyle.ClearOptions(); + foreach (KeyValuePair entry in GetIdentifyStyles()) + { + IdentifyStyle.options.Add(new Dropdown.OptionData(Localization.instance.Localize(entry.Value))); + } + + // Trigger cost update when the identify style changes + IdentifyStyle.onValueChanged.AddListener((value) => + { + OnSelectedItemsChanged(); + }); + } + + [UsedImplicitly] + public void OnEnable() + { + List items = GetSacrificeItems(); + _sacrificeMode = SacrificeMode.Sacrifice; + IdentifyStylePanel.SetActive(false); + IdentifyToggle.isOn = false; + SacrificeToggle.isOn = true; + AvailableItems.SetItems(items.Cast().ToList()); + AvailableItems.DeselectAll(); + } + + protected override void DoMainAction() + { + if (_sacrificeMode == SacrificeMode.Identify) + { + IdentifyItems(); + } + else if (_sacrificeMode == SacrificeMode.Sacrifice) + { + SacrificeItems(); + } + + Unlock(); + } + + private void IdentifyItems() + { + List> selectedItems = AvailableItems.GetSelectedItems(); + List> unidentifiedItems = selectedItems + .Select(x => new Tuple(x.Item1.GetItem(), x.Item2)).ToList(); + string filterType = IdentifyStyle.options[IdentifyStyle.value].text; + Tuple featureValues = + EnchantingTableUI.instance.SourceTable.GetFeatureCurrentValue(EnchantingFeature.Sacrifice); + float cost_reduction = featureValues.Item1 == 0f || featureValues.Item1 == float.NaN ? + 1.0f : 1f - (featureValues.Item1 / 100f); + float power_modifier = featureValues.Item2 == float.NaN ? + 1.0f : (featureValues.Item2 / 100f) + 1f; + List cost = GetIdentifyCost(filterType, unidentifiedItems, cost_reduction); + + if (!LocalPlayerCanAffordCost(cost)) + { + return; + } + + if (!Player.m_localPlayer.NoCostCheat()) + { + foreach (InventoryItemListElement costElement in cost) + { + InventoryManagement.Instance.RemoveItem(costElement.GetItem()); + } + } + + List identifiedItems = GetRandomFilteredLoot(filterType, unidentifiedItems, power_modifier); + + Cancel(); + RefreshAvailableItems(); + AvailableItems.GiveFocus(true, 0); + } + + private void SacrificeItems() + { + List> selectedItems = AvailableItems.GetSelectedItems(); + List sacrificeProducts = GetSacrificeProducts(selectedItems + .Select(x => new Tuple(x.Item1.GetItem(), x.Item2)).ToList()); + + Cancel(); + + Tuple chanceToDoubleEntry = + EnchantingTableUI.instance.SourceTable.GetFeatureCurrentValue(EnchantingFeature.Sacrifice); + float chanceToDouble = float.IsNaN(chanceToDoubleEntry.Item1) ? 0.0f : chanceToDoubleEntry.Item1 / 100.0f; + + if (Random.Range(0.0f, 1.0f) < chanceToDouble) + { + EnchantingTableUI.instance.PlayEnchantBonusSFX(); + BonusPanel.Show(); + + foreach (InventoryItemListElement sacrificeProduct in sacrificeProducts) + { + sacrificeProduct.Item.m_stack *= 2; + } + } + + foreach (Tuple selectedItem in selectedItems) + { + InventoryManagement.Instance.RemoveExactItem(selectedItem.Item1.GetItem(), selectedItem.Item2); + } + + GiveItemsToPlayer(sacrificeProducts); + + RefreshAvailableItems(); + AvailableItems.GiveFocus(true, 0); + } + + private void SacrificeModeSelected(bool isOn) + { + if (!isOn) + { + return; + } + + _sacrificeMode = SacrificeMode.Sacrifice; + List items = GetSacrificeItems(); + AvailableItems.SetItems(items.Cast().ToList()); + AvailableItems.DeselectAll(); + Warning.text = Localization.instance.Localize("$mod_epicloot_sacrifice_warning"); + Warning.color = Color.red; + Explainer.text = Localization.instance.Localize("$mod_epicloot_sacrifice_productsexplainer"); + MainButton.GetComponentInChildren().text = Localization.instance.Localize("$mod_epicloot_sacrifice"); + OnSelectedItemsChanged(); + IdentifyStylePanel.SetActive(false); + CostList.gameObject.SetActive(false); + } + + private void IdentifyModeSelected(bool isOn) + { + if (!isOn) + { + return; + } + + _sacrificeMode = SacrificeMode.Identify; + List items = GetIdentifyItems(); + AvailableItems.SetItems(items.Cast().ToList()); + AvailableItems.DeselectAll(); + OnSelectedItemsChanged(); + Warning.text = Localization.instance.Localize("$mod_epicloot_identify_explain"); + Warning.color = new Color(1f, 0.631f, 0.235f); + Explainer.text = Localization.instance.Localize("$mod_epicloot_identify_productsexplainer"); + MainButton.GetComponentInChildren().text = Localization.instance.Localize("$mod_epicloot_identify"); + IdentifyStylePanel.SetActive(true); + CostList.gameObject.SetActive(true); + } + + private void RefreshAvailableItems() + { + if (_sacrificeMode == SacrificeMode.Identify) + { + List items = GetIdentifyItems(); + AvailableItems.SetItems(items.Cast().ToList()); + } + else if (_sacrificeMode == SacrificeMode.Sacrifice) + { + List items = GetSacrificeItems(); + AvailableItems.SetItems(items.Cast().ToList()); + } + + AvailableItems.DeselectAll(); + OnSelectedItemsChanged(); + } + + protected override void OnSelectedItemsChanged() + { + List> selectedItems = AvailableItems.GetSelectedItems(); + bool canAfford = true; + + if (_sacrificeMode == SacrificeMode.Sacrifice) + { + List sacrificeProducts = GetSacrificeProducts(selectedItems.Select( + x => new Tuple(x.Item1.GetItem(), x.Item2)).ToList()); + SacrificeProducts.SetItems(sacrificeProducts.Cast().ToList()); + } + else if (_sacrificeMode == SacrificeMode.Identify) + { + string identifyFilter = IdentifyStyle.options[IdentifyStyle.value].text; + List potentialIdentifyItems = + GetPotentialIdentifications(identifyFilter, selectedItems.Select(x => x.Item1.GetItem()).ToList()); + SacrificeProducts.SetItems(potentialIdentifyItems.Cast().ToList()); + List> unidentifiedItems = selectedItems.Select( + x => new Tuple(x.Item1.GetItem(), x.Item2)).ToList(); + Tuple featureValues = + EnchantingTableUI.instance.SourceTable.GetFeatureCurrentValue(EnchantingFeature.Sacrifice); + float costReduction = featureValues.Item1 == 0f || featureValues.Item1 == float.NaN ? + 1.0f : 1f - (featureValues.Item1 / 100f); + List cost = GetIdentifyCost(identifyFilter, unidentifiedItems, costReduction); + CostList.SetItems(cost.Cast().ToList()); + canAfford = LocalPlayerCanAffordIdentifyCost(cost); + + if (potentialIdentifyItems.Count() == 0) + { + canAfford = false; + } + } + + bool featureUnlocked = EnchantingTableUI.instance != null && + EnchantingTableUI.instance.SourceTable != null && + EnchantingTableUI.instance.SourceTable.IsFeatureUnlocked(EnchantingFeature.Sacrifice); + MainButton.interactable = featureUnlocked && selectedItems.Count > 0 && canAfford; + } + + public bool LocalPlayerCanAffordIdentifyCost(List cost) + { + if (cost == null || cost.Count == 0) + { + return true; + } + + if (Player.m_localPlayer == null) + { + return false; + } + + if (Player.m_localPlayer.NoCostCheat()) + { + return true; + } + + foreach (InventoryItemListElement element in cost) + { + ItemDrop.ItemData item = element.GetItem(); + if (!InventoryManagement.Instance.HasItem(item)) + { + Debug.Log($"Identify Cost failed, user does not have item {item.m_shared.m_name}."); + return false; + } + } + + return true; + } + + public override void Cancel() + { + if (_sacrificeMode == SacrificeMode.Sacrifice) + { + if (_useTMP) + { + _tmpButtonLabel.text = Localization.instance.Localize("$mod_epicloot_sacrifice"); + } + else + { + _buttonLabel.text = Localization.instance.Localize("$mod_epicloot_sacrifice"); + } + } + if (SacrificeMode.Identify == _sacrificeMode) + { + if (_buttonLabel != null) + { + _buttonLabel.text = Localization.instance.Localize("$mod_epicloot_identify"); + } + } + + Unlock(); + } + + public override void DeselectAll() + { + AvailableItems.DeselectAll(); + } + + public override void Lock() + { + base.Lock(); + + SacrificeToggle.interactable = false; + IdentifyToggle.interactable = false; + } + + public override void Unlock() + { + base.Unlock(); + + SacrificeToggle.interactable = true; + IdentifyToggle.interactable = true; + } + } +} diff --git a/EpicLoot-UnityLib/src/SelectableTextColor.cs b/EpicLoot-UnityLib/src/SelectableTextColor.cs new file mode 100644 index 000000000..a727e6fe4 --- /dev/null +++ b/EpicLoot-UnityLib/src/SelectableTextColor.cs @@ -0,0 +1,57 @@ +using TMPro; +using UnityEngine; +using UnityEngine.UI; + +namespace EpicLoot_UnityLib +{ + public class SelectableTextColor : MonoBehaviour + { + private Color _defaultColor = Color.white; + public Color DisabledColor = Color.grey; + private Selectable _selectable; + private Text _text; + private TMP_Text _tmpText; + private bool useTMP = false; + + public void Awake() + { + _selectable = GetComponent(); + _text = GetComponentInChildren(); + if (_text != null) + { + _defaultColor = _text.color; + return; + } + + _tmpText = GetComponentInChildren(); + _defaultColor = _tmpText.color; + useTMP = true; + } + + public void Update() + { + if (_selectable.IsInteractable()) + { + if (!useTMP) + { + _text.color = _defaultColor; + } + else + { + _tmpText.color = _defaultColor; + } + } + else + { + if (!useTMP) + { + _text.color = DisabledColor; + } + else + { + _tmpText.color = DisabledColor; + } + } + } + } +} diff --git a/EpicLoot-UnityLib/SetRarityColor.cs b/EpicLoot-UnityLib/src/SetRarityColor.cs similarity index 80% rename from EpicLoot-UnityLib/SetRarityColor.cs rename to EpicLoot-UnityLib/src/SetRarityColor.cs index 0962ac21e..5ddb0522f 100644 --- a/EpicLoot-UnityLib/SetRarityColor.cs +++ b/EpicLoot-UnityLib/src/SetRarityColor.cs @@ -27,7 +27,7 @@ public class SetRarityColor : MonoBehaviour public void Awake() { - foreach (var graphic in Graphics) + foreach (Graphic graphic in Graphics) { _defaultColors.Add(graphic, graphic.color); } @@ -45,8 +45,8 @@ public void Refresh() { if (Rarity > MagicRarityUnity.None && GetRarityColor != null) { - var color = GetRarityColor(Rarity); - foreach (var graphic in Graphics) + Color color = GetRarityColor(Rarity); + foreach (Graphic graphic in Graphics) { graphic.color = color; } @@ -55,7 +55,7 @@ public void Refresh() } else { - foreach (var graphic in Graphics) + foreach (Graphic graphic in Graphics) { graphic.color = _defaultColors[graphic]; } @@ -65,20 +65,20 @@ public void Refresh() // Copy of BeamColorSetter in EpicLoot public void SetColor(Color mid) { - var allBeams = GetComponentsInChildren(); - var allParticles = GetComponentsInChildren(); + LineRenderer[] allBeams = GetComponentsInChildren(); + ParticleSystem[] allParticles = GetComponentsInChildren(); - foreach (var lineRenderer in allBeams) + foreach (LineRenderer lineRenderer in allBeams) { - foreach (var mat in lineRenderer.sharedMaterials) + foreach (Material mat in lineRenderer.sharedMaterials) { mat.SetColor("_TintColor", SwapColorKeepLuminosity(mid, mat.GetColor("_TintColor"))); } } - foreach (var particleSystem in allParticles) + foreach (ParticleSystem particleSystem in allParticles) { - var main = particleSystem.main; + ParticleSystem.MainModule main = particleSystem.main; switch (main.startColor.mode) { case ParticleSystemGradientMode.Color: diff --git a/EpicLoot-UnityLib/TableUpgradeDecorations.cs b/EpicLoot-UnityLib/src/TableUpgradeDecorations.cs similarity index 93% rename from EpicLoot-UnityLib/TableUpgradeDecorations.cs rename to EpicLoot-UnityLib/src/TableUpgradeDecorations.cs index cb8c27475..1707ac1f4 100644 --- a/EpicLoot-UnityLib/TableUpgradeDecorations.cs +++ b/EpicLoot-UnityLib/src/TableUpgradeDecorations.cs @@ -24,11 +24,13 @@ public void OnDisable() public void Refresh() { - var unlockedCount = 0; + int unlockedCount = 0; foreach (EnchantingFeature feature in Enum.GetValues(typeof(EnchantingFeature))) { if (SourceTable.IsFeatureAvailable(feature) && SourceTable.IsFeatureUnlocked(feature)) + { ++unlockedCount; + } } BaseObjects.SetActive(true); diff --git a/EpicLoot-UnityLib/src/UpgradeTableUI.cs b/EpicLoot-UnityLib/src/UpgradeTableUI.cs new file mode 100644 index 000000000..3f1b7a4c3 --- /dev/null +++ b/EpicLoot-UnityLib/src/UpgradeTableUI.cs @@ -0,0 +1,284 @@ +using System.Collections.Generic; +using System.Linq; +using System.Text; +using UnityEngine; +using UnityEngine.UI; + +namespace EpicLoot_UnityLib +{ + public class UpgradeTableUI : EnchantingTableUIPanelBase + { + public Transform ListContainer; + public Text SelectedFeatureText; + public Image SelectedFeatureImage; + public FeatureStatus SelectedFeatureStatus; + public Text SelectedFeatureInfoText; + public Text CostLabel; + public MultiSelectItemList CostList; + + private readonly List _featureButtons = new List(); + + private int _selectedFeature = -1; + + protected override void OnSelectedItemsChanged() {} + + public override void Awake() + { + base.Awake(); + _featureButtons.Clear(); + + foreach(MultiSelectItemListElement child in ListContainer.GetComponentsInChildren(true)) + { + _featureButtons.Add(child); + child.OnSelectionChanged += OnButtonSelected; + child.SelectMaxQuantity(true); + } + } + + public void OnEnable() + { + if (EnchantingTableUI.instance.SourceTable != null) + { + EnchantingTableUI.instance.SourceTable.OnAnyFeatureLevelChanged -= Refresh; + EnchantingTableUI.instance.SourceTable.OnAnyFeatureLevelChanged += Refresh; + } + + Refresh(); + } + + private void OnButtonSelected(MultiSelectItemListElement selectedButton, bool selected, int _) + { + if (_inProgress) + { + return; + } + + bool noneSelected = !_featureButtons.Any(x => x.IsSelected()); + _selectedFeature = -1; + + if (!noneSelected) + { + for (int index = 0; index < _featureButtons.Count; index++) + { + MultiSelectItemListElement button = _featureButtons[index]; + if (button == selectedButton) + { + _selectedFeature = index; + } + else + { + button.SuppressEvents = true; + button.Deselect(true); + button.SuppressEvents = false; + } + } + } + + Refresh(); + } + + public void Refresh() + { + if (EnchantingTableUI.instance.SourceTable == null) + { + return; + } + + for (int index = 0; index < _featureButtons.Count; index++) + { + MultiSelectItemListElement button = _featureButtons[index]; + bool featureIsEnabled = EnchantingTableUI.instance.SourceTable.IsFeatureAvailable((EnchantingFeature)index); + button.gameObject.SetActive(featureIsEnabled); + } + + if (_selectedFeature >= 0) + { + MultiSelectItemListElement selectedButton = _featureButtons[_selectedFeature]; + + SelectedFeatureText.enabled = true; + SelectedFeatureText.text = selectedButton.ItemName.text; + SelectedFeatureImage.enabled = true; + SelectedFeatureImage.sprite = selectedButton.ItemIcon.sprite; + + EnchantingFeature selectedFeature = (EnchantingFeature)_selectedFeature; + SelectedFeatureStatus.gameObject.SetActive(true); + SelectedFeatureStatus.SetFeature(selectedFeature); + } + else + { + SelectedFeatureText.enabled = false; + SelectedFeatureImage.enabled = false; + SelectedFeatureStatus.gameObject.SetActive(false); + } + + SelectedFeatureInfoText.text = GenerateFeatureInfoText(); + + if (_selectedFeature < 0) + { + CostLabel.enabled = false; + CostList.gameObject.SetActive(false); + MainButton.interactable = false; + return; + } + + EnchantingFeature feature = (EnchantingFeature)_selectedFeature; + CostLabel.enabled = true; + bool maxLevel = EnchantingTableUI.instance.SourceTable.IsFeatureMaxLevel(feature); + bool canAfford = true; + + if (maxLevel) + { + CostLabel.text = Localization.instance.Localize("$mod_epicloot_featuremaxlevel"); + CostList.SetItems(new List()); + } + else + { + if (EnchantingTableUI.instance.SourceTable.IsFeatureLocked(feature)) + { + List cost = EnchantingTableUI.instance.SourceTable.GetFeatureUnlockCost(feature); + CostLabel.text = Localization.instance.Localize("$mod_epicloot_unlockcost"); + CostList.SetItems(cost.Cast().ToList()); + canAfford = LocalPlayerCanAffordCost(cost); + string buttonText = Localization.instance.Localize("$mod_epicloot_featureunlock"); + if (_useTMP) + _tmpButtonLabel.text = buttonText; + else + _buttonLabel.text = buttonText; + } + else + { + List cost = EnchantingTableUI.instance.SourceTable.GetFeatureUpgradeCost(feature); + CostLabel.text = Localization.instance.Localize("$mod_epicloot_upgradecost"); + CostList.SetItems(cost.Cast().ToList()); + canAfford = LocalPlayerCanAffordCost(cost); + string buttonText = Localization.instance.Localize("$mod_epicloot_upgrade"); + if (_useTMP) + _tmpButtonLabel.text = buttonText; + else + _buttonLabel.text = buttonText; + } + } + + CostList.gameObject.SetActive(!maxLevel && _selectedFeature >= 0); + MainButton.interactable = !maxLevel && canAfford; + } + + private string GenerateFeatureInfoText() + { + if (_selectedFeature < 0) + { + return Localization.instance.Localize("$mod_epicloot_featureinfo_none"); + } + + StringBuilder sb = new StringBuilder(); + + EnchantingFeature feature = (EnchantingFeature)_selectedFeature; + bool locked = EnchantingTableUI.instance.SourceTable.IsFeatureLocked(feature); + int currentLevel = EnchantingTableUI.instance.SourceTable.GetFeatureLevel(feature); + int maxLevel = EnchantingTableUpgrades.GetFeatureMaxLevel(feature); + sb.AppendLine(Localization.instance.Localize($"{EnchantingTableUpgrades.GetFeatureName(feature)}")); + sb.AppendLine(); + + if (locked) + { + sb.AppendLine(Localization.instance.Localize( + "$mod_epicloot_currentlevel: $mod_epicloot_featurelocked")); + } + else if (currentLevel == 0) + { + sb.AppendLine(Localization.instance.Localize( + $"$mod_epicloot_currentlevel: $mod_epicloot_featureunlocked / {maxLevel}")); + } + else + { + sb.AppendLine(Localization.instance.Localize( + $"$mod_epicloot_currentlevel: {currentLevel} / {maxLevel}")); + } + + sb.AppendLine(); + + sb.AppendLine(Localization.instance.Localize(EnchantingTableUpgrades.GetFeatureDescription(feature))); + sb.AppendLine(); + sb.AppendLine(Localization.instance.Localize("$mod_epicloot_effectsperlevel")); + + for (int i = 1; i <= maxLevel; ++i) + { + string text = EnchantingTableUpgrades.GetFeatureUpgradeLevelDescription(EnchantingTableUI.instance.SourceTable, feature, i); + sb.AppendLine($"{i}: " + (i == currentLevel ? $"{text}" : text)); + } + + return sb.ToString(); + } + + protected override void DoMainAction() + { + Cancel(); + if (_selectedFeature < 0) + { + return; + } + + EnchantingFeature feature = (EnchantingFeature)_selectedFeature; + bool maxLevel = EnchantingTableUI.instance.SourceTable.IsFeatureMaxLevel(feature); + if (maxLevel) + { + return; + } + + List cost = EnchantingTableUI.instance.SourceTable.IsFeatureLocked(feature) + ? EnchantingTableUI.instance.SourceTable.GetFeatureUnlockCost(feature) + : EnchantingTableUI.instance.SourceTable.GetFeatureUpgradeCost(feature); + + bool canAfford = LocalPlayerCanAffordCost(cost); + if (canAfford) + { + int currentLevel = EnchantingTableUI.instance.SourceTable.GetFeatureLevel(feature); + EnchantingTableUI.instance.SourceTable.RequestTableUpgrade(feature, currentLevel +1, (success)=> + { + if (!success) + { + Debug.LogError($"[Enchanting Upgrade] ERROR: " + + $"Tried to upgrade ({feature}) to level ({currentLevel + 1}) but it failed!"); + return; + } + + Player player = Player.m_localPlayer; + if (!player.NoCostCheat()) + { + if (!LocalPlayerCanAffordCost(cost)) + { + Debug.LogError("[Augment Item] ERROR: Tried to augment item but could not afford the cost. " + + "This should not happen!"); + return; + } + + foreach (InventoryItemListElement costElement in cost) + { + InventoryManagement.Instance.RemoveItem(costElement.GetItem()); + } + } + + Refresh(); + }); + } + } + + public override void Lock() + { + base.Lock(); + foreach (MultiSelectItemListElement button in _featureButtons) + { + button.Lock(); + } + } + + public override void Unlock() + { + base.Unlock(); + foreach (MultiSelectItemListElement button in _featureButtons) + { + button.Unlock(); + } + } + } +} diff --git a/EpicLoot/.editorconfig b/EpicLoot/.editorconfig new file mode 100644 index 000000000..c9cf0c4db --- /dev/null +++ b/EpicLoot/.editorconfig @@ -0,0 +1,24 @@ +[*] + +# https://marketplace.visualstudio.com/items?itemName=PaulHarrington.EditorGuidelinesPreview +guidelines = 120 + +[*.cs] + +# IDE0003: Remove qualification +dotnet_diagnostic.IDE0003.severity = none + +# IDE0051: Remove unused private members +dotnet_diagnostic.IDE0051.severity = none + +# IDE0060: Remove unused parameter +dotnet_diagnostic.IDE0060.severity = none + +# IDE0066: Convert switch statement to expression +dotnet_diagnostic.IDE0066.severity = none + +# IDE0090: 'new' expression can be simplified +dotnet_diagnostic.IDE0090.severity = none + +# IDE0130: Namespace does not match folder structure +dotnet_diagnostic.IDE0130.severity = none diff --git a/EpicLoot/API/API.cs b/EpicLoot/API/API.cs new file mode 100644 index 000000000..27f73aff6 --- /dev/null +++ b/EpicLoot/API/API.cs @@ -0,0 +1,252 @@ +using Common; +using EpicLoot.Abilities; +using EpicLoot.Adventure; +using EpicLoot.Crafting; +using EpicLoot.CraftingV2; +using EpicLoot.LegendarySystem; +using EpicLoot.MagicItemEffects; +using JetBrains.Annotations; +using Newtonsoft.Json; +using System; +using System.Collections.Generic; + +namespace EpicLoot; + +public static partial class API +{ + private static bool ShowLogs = false; + private static event Action OnReload; + private static event Action OnError; + private static event Action OnDebug; + + private static readonly Dictionary ExternalMagicItemEffectDefinitions = new(); + private static readonly Dictionary ExternalAbilities = new(); + private static readonly Dictionary> ExternalLegendaryItems = new(); + private static readonly Dictionary> ExternalLegendarySets = new(); + private static readonly Dictionary ExternalAssets = new(); + private static readonly List ExternalMaterialConversions = new(); + private static readonly List ExternalRecipes = new(); + private static readonly List ExternalSacrifices = new(); + private static readonly List ExternalBountyTargets = new(); + private static readonly Dictionary> ExternalSecretStashItems = new(); + private static readonly List ExternalTreasureMaps = new(); + private static readonly Dictionary> AbilityProxies = new(); + + /// + /// Static constructor, runs automatically once before the API class is first used. + /// + static API() + { + MagicItemEffectDefinitions.OnSetupMagicItemEffectDefinitions += ReloadExternalMagicEffects; + UniqueLegendaryHelper.OnSetupLegendaryItemConfig += ReloadExternalLegendary; + AbilityDefinitions.OnSetupAbilityDefinitions += ReloadExternalAbilities; + MaterialConversions.OnSetupMaterialConversions += ReloadExternalMaterialConversions; + RecipesHelper.OnSetupRecipeConfig += ReloadExternalRecipes; + EnchantCostsHelper.OnSetupEnchantingCosts += ReloadExternalSacrifices; + AdventureDataManager.OnSetupAdventureData += ReloadExternalAdventureData; + + OnReload += message => + { + if (!ShowLogs) return; + EpicLoot.Log(message); + }; + OnError += message => + { + if (!ShowLogs) return; + EpicLoot.LogWarning(message); + }; + OnDebug += message => + { + if (!ShowLogs) return; + EpicLoot.LogWarningForce(message); + }; + } + + /// + /// + /// True if added to + [PublicAPI] + public static bool RegisterAsset(string name, UnityEngine.Object asset) + { + if (EpicAssets.AssetCache.ContainsKey(name)) + { + OnError?.Invoke("Duplicate asset: " + name); + return false; + } + + EpicAssets.AssetCache[name] = asset; + ExternalAssets[name] = asset; + return true; + } + + /// + /// Can be useful for external plugins to know, so they can design features around it. + /// + /// + /// + /// true if player has item + [PublicAPI] + public static bool HasLegendaryItem(Player player, string legendaryItemID) + { + foreach (ItemDrop.ItemData item in player.GetEquipment()) + { + if (item.IsMagic(out var magicItem) && magicItem.LegendaryID == legendaryItemID) return true; + } + + return false; + } + + /// + /// Can be useful for external plugins to know, so they can design features around it. + /// + /// + /// + /// + /// true if player has full set + [PublicAPI] + public static bool HasLegendarySet(Player player, string legendarySetID, ref int count) + { + if (!UniqueLegendaryHelper.TryGetLegendarySetInfo(legendarySetID, out LegendarySetInfo legendarySetInfo, out ItemRarity _)) + { + return false; + } + + count = player.GetEquippedSetPieces(legendarySetID).Count; + return count >= legendarySetInfo.LegendaryIDs.Count; + } + /// + /// serialized object of magic effect definition if found + [PublicAPI] + public static string GetMagicItemEffectDefinition(string type) + { + if (!MagicItemEffectDefinitions.AllDefinitions.TryGetValue(type, out MagicItemEffectDefinition definition)) + { + return ""; + } + + return JsonConvert.SerializeObject(definition); + } + + /// can be null + /// can be null + /// + /// + /// + [PublicAPI] + public static float GetTotalActiveMagicEffectValue(Player player,ItemDrop.ItemData item, string effectType, float scale) + { + return MagicEffectsHelper.GetTotalActiveMagicEffectValue(player, item, effectType, scale); + } + + /// can be null + /// can be null + /// + /// + /// + [PublicAPI] + public static float GetTotalActiveMagicEffectValueForWeapon(Player player, ItemDrop.ItemData item, string effectType, float scale) + { + return MagicEffectsHelper.GetTotalActiveMagicEffectValueForWeapon(player, item, effectType, scale); + } + + /// can be null + /// + /// can be null + /// + /// True if player or item has magic effect + [PublicAPI] + public static bool HasActiveMagicEffect(Player player, string effectType, ItemDrop.ItemData item, ref float effectValue) + { + return MagicEffectsHelper.HasActiveMagicEffect(player, item, effectType, out effectValue); + } + + /// can be null + /// + /// + /// + /// True if magic effect is on item + [PublicAPI] + public static bool HasActiveMagicEffectOnWeapon(Player player, ItemDrop.ItemData item, string effectType, ref float effectValue) + { + return MagicEffectsHelper.HasActiveMagicEffectOnWeapon(player, item, effectType, out effectValue); + } + + /// + /// Currently hard coded to Modify Armor effect type ??? + /// + /// + /// + /// + /// + [PublicAPI] + public static float GetTotalActiveSetEffectValue(Player player, string effectType, float scale) + { + return MagicEffectsHelper.GetTotalActiveSetEffectValue(player, effectType, scale); + } + + /// + /// filter by + /// list of active magic effects on player + [PublicAPI] + public static List GetAllActiveMagicEffects(Player player, string effectType = null) + { + List list = player.GetAllActiveMagicEffects(effectType); + List output = new List(); + foreach (MagicItemEffect item in list) + { + output.Add(JsonConvert.SerializeObject(item)); + } + + return output; + } + + /// + /// filter by + /// list of active magic effects on set + [PublicAPI] + public static List GetAllActiveSetMagicEffects(Player player, string effectType = null) + { + List list = player.GetAllActiveSetMagicEffects(effectType); + List output = new List(); + foreach (MagicItemEffect item in list) + { + output.Add(JsonConvert.SerializeObject(item)); + } + + return output; + } + + /// + /// + /// + /// + /// total effect value found on player + [PublicAPI] + public static float GetTotalPlayerActiveMagicEffectValue(Player player, string effectType, float scale, + ItemDrop.ItemData ignoreThisItem = null) + { + return player.GetTotalActiveMagicEffectValue(effectType, scale, ignoreThisItem); + } + + /// + /// + /// + /// + /// + /// True if player has magic effect + [PublicAPI] + public static bool PlayerHasActiveMagicEffect(Player player, string effectType, ref float effectValue, + float scale = 1.0f, ItemDrop.ItemData ignoreThisItem = null) + { + return player.HasActiveMagicEffect(effectType, out effectValue, scale, ignoreThisItem); + } + + /// + /// + [PublicAPI] + public static string GetMagicItemJson(ItemDrop.ItemData itemData) + { + if (!itemData.IsMagic()) return null; + return JsonConvert.SerializeObject(itemData.GetMagicItem()); + } +} \ No newline at end of file diff --git a/EpicLoot/API/Ability.cs b/EpicLoot/API/Ability.cs new file mode 100644 index 000000000..32e155cde --- /dev/null +++ b/EpicLoot/API/Ability.cs @@ -0,0 +1,74 @@ +using EpicLoot.Abilities; +using JetBrains.Annotations; +using Newtonsoft.Json; + +namespace EpicLoot; + +public static partial class API +{ + /// JSON serialized + /// unique identifier if registered + [PublicAPI] + public static string AddAbility(string json) + { + try + { + AbilityDefinition def = JsonConvert.DeserializeObject(json); + if (def == null) + { + return null; + } + + if (AbilityDefinitions.Abilities.ContainsKey(def.ID)) + { + OnError?.Invoke($"Duplicate entry found for Abilities: {def.ID} when adding from external plugin."); + return null; + } + + ExternalAbilities[def.ID] = def; + AbilityDefinitions.Config.Abilities.Add(def); + AbilityDefinitions.Abilities[def.ID] = def; + return RuntimeRegistry.Register(def); + } + catch + { + OnError?.Invoke("Failed to parse ability definition passed in through external plugin."); + return null; + } + } + + /// unique identifier + /// JSON serialized + /// true if updated + [PublicAPI] + public static bool UpdateAbility(string key, string json) + { + if (!RuntimeRegistry.TryGetValue(key, out AbilityDefinition original)) + { + return false; + } + + AbilityDefinition def = JsonConvert.DeserializeObject(json); + if (def == null) + { + return false; + } + + original.CopyFieldsFrom(def); + return true; + } + + public static bool HasCurrentAbility(Player player, string key) + { + if (!RuntimeRegistry.TryGetValue(key, out AbilityDefinition definition)) + { + return false; + } + + if (!player.TryGetComponent(out AbilityController controller)) + { + return false; + } + return controller.GetCurrentAbility(definition.ID) is not null; + } +} \ No newline at end of file diff --git a/EpicLoot/API/AbilityProxy.cs b/EpicLoot/API/AbilityProxy.cs new file mode 100644 index 000000000..02d126d8b --- /dev/null +++ b/EpicLoot/API/AbilityProxy.cs @@ -0,0 +1,201 @@ +using EpicLoot.Abilities; +using JetBrains.Annotations; +using Newtonsoft.Json; +using System; +using System.Collections.Generic; + +namespace EpicLoot; + +public static partial class API +{ + /// + /// + /// true, if callback functions found using unique ability ID + private static bool TryGetProxyAbility(string abilityID, out Dictionary proxy) + { + return AbilityProxies.TryGetValue(abilityID, out proxy); + } + + /// JSON serialized + /// callback functions + /// unique identifier if registered + [PublicAPI] + public static string RegisterProxyAbility(string json, Dictionary delegates) + { + try + { + AbilityDefinition ability = JsonConvert.DeserializeObject(json); + if (ability == null) + { + return null; + } + + AbilityFactory.Register(ability.ID, typeof(AbilityProxy)); + AbilityProxies[ability.ID] = delegates; + AbilityProxyDefinition def = new AbilityProxyDefinition(ability, delegates); + AbilityDefinitions.Config.Abilities.Add(ability); + AbilityDefinitions.Abilities[ability.ID] = ability; + return RuntimeRegistry.Register(def); + } + catch + { + OnError?.Invoke("Failed to parse ability definition from external plugin"); + return null; + } + } + + /// unique identifier + /// JSON serialized + /// callback functions + /// + [PublicAPI] + public static bool UpdateProxyAbility(string key, string json, Dictionary proxy) + { + if (!RuntimeRegistry.TryGetValue(key, out AbilityProxyDefinition kvp)) + { + return false; + } + + AbilityDefinition def = JsonConvert.DeserializeObject(json); + kvp.Ability.CopyFieldsFrom(def); + kvp.Delegates.CopyFieldsFrom(proxy); + return true; + } + + /// + /// Custom class to contain proxy definition, to register as a unit + /// + private class AbilityProxyDefinition + { + public readonly AbilityDefinition Ability; + public readonly Dictionary Delegates; + public AbilityProxyDefinition(AbilityDefinition ability, Dictionary delegates) + { + Ability = ability; + Delegates = delegates; + } + } + + /// + /// unique ability ID + /// true if dictionary of callbacks injected into new instance of proxy + public static bool InjectCallbacks(this AbilityProxy proxy, string abilityID) + { + if (!TryGetProxyAbility(abilityID, out Dictionary delegates)) + { + return false; + } + + proxy._callbacks = delegates; + return true; + } + + /// + /// Ability wrapper to handle callbacks + /// + public class AbilityProxy : Ability + { + public Dictionary _callbacks = new Dictionary(); + private T GetCallback(string name) where T : Delegate + { + if (_callbacks.TryGetValue(name, out Delegate del) && del is T typed) + { + return typed; + } + + return null; + } + + public override void Initialize(AbilityDefinition abilityDef, Player player) + { + base.Initialize(abilityDef, player); + if (GetCallback>(nameof(Initialize)) is not { } callback) + { + return; + } + + callback(player, abilityDef.ID, abilityDef.Cooldown); + } + + public override void OnUpdate() + { + if (GetCallback(nameof(OnUpdate)) is not { } callback) + { + base.OnUpdate(); + } + else + { + callback(); + } + } + + protected override bool ShouldTrigger() + { + return GetCallback>(nameof(ShouldTrigger)) is not { } callback + ? base.ShouldTrigger() + : callback(); + } + + public override bool IsOnCooldown() => GetCallback>(nameof(IsOnCooldown)) is not { } callback + ? base.IsOnCooldown() + : callback(); + + public override float TimeUntilCooldownEnds() => + GetCallback>(nameof(TimeUntilCooldownEnds)) is not { } callback + ? base.TimeUntilCooldownEnds() + : callback(); + + public override float PercentCooldownComplete() => + GetCallback>(nameof(PercentCooldownComplete)) is not { } callback + ? base.PercentCooldownComplete() + : callback(); + + public override bool CanActivate() => GetCallback>(nameof(CanActivate)) is not { } callback + ? base.CanActivate() + : callback(); + + public override void TryActivate() + { + if (GetCallback(nameof(TryActivate)) is not { } callback) base.TryActivate(); + else callback(); + } + + protected override void Activate() + { + if (GetCallback(nameof(Activate)) is not { } callback) base.Activate(); + else callback(); + } + + protected override void ActivateCustomAction() + { + if (GetCallback(nameof(ActivateCustomAction)) is not { } callback) base.ActivateCustomAction(); + else callback(); + } + + protected override void ActivateStatusEffectAction() + { + if (GetCallback(nameof(ActivateStatusEffectAction)) is not { } callback) + base.ActivateStatusEffectAction(); + else callback(); + } + + protected override bool HasCooldown() => GetCallback>(nameof(HasCooldown)) is not { } callback + ? base.HasCooldown() + : callback(); + + protected override void SetCooldownEndTime(float cooldownEndTime) + { + if (GetCallback>(nameof(SetCooldownEndTime)) is not { } callback) + base.SetCooldownEndTime(cooldownEndTime); + else callback(cooldownEndTime); + } + public override float GetCooldownEndTime() => GetCallback>(nameof(GetCooldownEndTime)) is not { } callback + ? base.GetCooldownEndTime() : callback(); + + public override void OnRemoved() + { + if (GetCallback(nameof(OnRemoved)) is not { } callback) base.OnRemoved(); + else callback(); + } + } +} \ No newline at end of file diff --git a/EpicLoot/API/Bounty.cs b/EpicLoot/API/Bounty.cs new file mode 100644 index 000000000..766a587d6 --- /dev/null +++ b/EpicLoot/API/Bounty.cs @@ -0,0 +1,44 @@ +using EpicLoot.Adventure; +using JetBrains.Annotations; +using Newtonsoft.Json; + +namespace EpicLoot; + +public static partial class API +{ + [PublicAPI] + public static string AddBountyTarget(string json) + { + try + { + var bounty = JsonConvert.DeserializeObject(json); + + if (bounty == null) + { + return null; + } + + ExternalBountyTargets.Add(bounty); + AdventureDataManager.Config.Bounties.Targets.Add(bounty); + return RuntimeRegistry.Register(bounty); + } + catch + { + OnError?.Invoke("Failed to parse bounty target passed in through external plugin."); + return null; + } + } + + [PublicAPI] + public static bool UpdateBountyTarget(string key, string json) + { + if (!RuntimeRegistry.TryGetValue(key, out BountyTargetConfig bountyTarget)) + { + return false; + } + + BountyTargetConfig config = JsonConvert.DeserializeObject(json); + bountyTarget.CopyFieldsFrom(config); + return true; + } +} \ No newline at end of file diff --git a/EpicLoot/API/Helpers.cs b/EpicLoot/API/Helpers.cs new file mode 100644 index 000000000..dc828948d --- /dev/null +++ b/EpicLoot/API/Helpers.cs @@ -0,0 +1,142 @@ +using Common; +using EpicLoot.Crafting; +using JetBrains.Annotations; +using Newtonsoft.Json; +using System.Collections.Generic; +using System.Reflection; + +namespace EpicLoot; + +public static partial class API +{ + [PublicAPI] + public static string AddRecipe(string json) + { + try + { + var recipe = JsonConvert.DeserializeObject(json); + if (recipe == null) + { + return null; + } + + ExternalRecipes.Add(recipe); + RecipesHelper.Config.recipes.Add(recipe); + return RuntimeRegistry.Register(recipe); + } + catch + { + OnError?.Invoke("Failed to parse recipe passed in through external plugin."); + return null; + } + } + + /// JSON serialized List of + /// unique key if successfully added + [PublicAPI] + public static string AddRecipes(string json) + { + // TODO: Figure out why it looks like recipes are added twice + // PRIORITY: Low + // Some interesting logic about re-initializing recipes after item manager on items registered ?? + // Current fix, remove external recipes, then add again on reload + try + { + List recipes = JsonConvert.DeserializeObject>(json); + + if (recipes == null) + { + return null; + } + + ExternalRecipes.AddRange(recipes); + RecipesHelper.Config.recipes.AddRange(recipes); + return RuntimeRegistry.Register(recipes); + } + catch + { + OnError?.Invoke("Failed to parse recipe from external plugin"); + return null; + } + } + + /// unique identifier + /// JSON serialized List of + /// True if updated + [PublicAPI] + public static bool UpdateRecipes(string key, string json) + { + if (!RuntimeRegistry.TryGetValue(key, out List list)) + { + return false; + } + + List recipes = JsonConvert.DeserializeObject>(json); + + if (recipes == null) + { + return false; + } + + ExternalRecipes.ReplaceThenAdd(list, recipes); + RecipesHelper.Config.recipes.ReplaceThenAdd(list, recipes); + return true; + } + + /// + /// Helper function to add into dictionary of lists + /// + /// Dictionary T key, List V values + /// + /// + /// + /// + private static void AddOrSet(this Dictionary> dict, T key, V value) + { + if (!dict.ContainsKey(key)) + { + dict[key] = new List(); + } + + dict[key].Add(value); + } + /// + /// Helper function to copy all fields from one instance to the other + /// + /// + /// + /// + private static void CopyFieldsFrom(this T target, T source) + { + foreach (FieldInfo field in typeof(T).GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)) + { + object value = field.GetValue(source); + if (value == null) continue; + field.SetValue(target, value); + } + } + + /// + /// Helper function, removes all from list, then adds new items into list + /// + /// + /// + /// + /// + private static void ReplaceThenAdd(this List list, List original, List replacements) + { + list.RemoveAll(original); + list.AddRange(replacements); + } + + /// + /// Helper function, removes all instances from one list in the other list + /// + /// + /// + /// + private static void RemoveAll(this List list, List itemsToRemove) + { + foreach (var item in itemsToRemove) list.Remove(item); + } +} \ No newline at end of file diff --git a/EpicLoot/API/Legendary.cs b/EpicLoot/API/Legendary.cs new file mode 100644 index 000000000..53072c9ff --- /dev/null +++ b/EpicLoot/API/Legendary.cs @@ -0,0 +1,131 @@ +using EpicLoot.LegendarySystem; +using JetBrains.Annotations; +using Newtonsoft.Json; +using System; + +namespace EpicLoot; + +public static partial class API +{ + [PublicAPI] + public static string AddLegendaryItem(string type, string json) + { + try + { + if (!Enum.TryParse(type, true, out ItemRarity rarity)) + { + return null; + } + + LegendaryInfo config = JsonConvert.DeserializeObject(json); + + if (config == null) + { + return null; + } + + switch (rarity) + { + case ItemRarity.Legendary: + UniqueLegendaryHelper.Config.LegendaryItems.Add(config); + UniqueLegendaryHelper.LegendaryInfo[config.ID] = config; + break; + case ItemRarity.Mythic: + UniqueLegendaryHelper.Config.MythicItems.Add(config); + UniqueLegendaryHelper.MythicInfo[config.ID] = config; + break; + } + + ExternalLegendaryItems.AddOrSet(rarity, config); + return RuntimeRegistry.Register(config); + } + catch + { + OnError?.Invoke("Failed to parse legendary item from external plugin."); + return null; + } + } + + [PublicAPI] + public static bool UpdateLegendaryItem(string key, string json) + { + if (!RuntimeRegistry.TryGetValue(key, out LegendaryInfo legendaryInfo)) + { + return false; + } + + LegendaryInfo config = JsonConvert.DeserializeObject(json); + if (config == null) + { + return false; + } + + legendaryInfo.CopyFieldsFrom(config); + return true; + } + + [PublicAPI] + public static string AddLegendarySet(string type, string json) + { + try + { + if (!Enum.TryParse(type, true, out ItemRarity rarity)) + { + return null; + } + + LegendarySetInfo config = JsonConvert.DeserializeObject(json); + + if (config == null) + { + return null; + } + + switch (rarity) + { + case ItemRarity.Legendary: + UniqueLegendaryHelper.LegendarySets[config.ID] = config; + UniqueLegendaryHelper.Config.LegendarySets.Add(config); + foreach (var name in config.LegendaryIDs) + { + UniqueLegendaryHelper.LegendaryItemsToSetMap[name] = config; + } + break; + case ItemRarity.Mythic: + UniqueLegendaryHelper.MythicSets[config.ID] = config; + UniqueLegendaryHelper.Config.MythicSets.Add(config); + foreach (var name in config.LegendaryIDs) + { + UniqueLegendaryHelper.MythicItemsToSetMap[name] = config; + } + break; + } + + ExternalLegendarySets.AddOrSet(rarity, config); + return RuntimeRegistry.Register(config); + } + catch + { + OnError?.Invoke("Failed to parse legendary set from external plugin."); + return null; + } + } + + [PublicAPI] + public static bool UpdateLegendarySet(string key, string json) + { + if (!RuntimeRegistry.TryGetValue(key, out LegendarySetInfo legendarySetInfo)) + { + return false; + } + + LegendarySetInfo config = JsonConvert.DeserializeObject(json); + if (config == null) + { + return false; + } + + legendarySetInfo.CopyFieldsFrom(config); + return true; + } +} \ No newline at end of file diff --git a/EpicLoot/API/MagicItemEffect.cs b/EpicLoot/API/MagicItemEffect.cs new file mode 100644 index 000000000..225ec1114 --- /dev/null +++ b/EpicLoot/API/MagicItemEffect.cs @@ -0,0 +1,54 @@ +using JetBrains.Annotations; +using Newtonsoft.Json; + +namespace EpicLoot; + +public static partial class API +{ + /// JSON serialized + /// unique identifier if registered + [PublicAPI] + public static string AddMagicEffect(string json) + { + try + { + MagicItemEffectDefinition def = JsonConvert.DeserializeObject(json); + + if (def == null) + { + return null; + } + + MagicItemEffectDefinitions.Add(def); + ExternalMagicItemEffectDefinitions[def.Type] = def; + return RuntimeRegistry.Register(def); + } + catch + { + OnError?.Invoke("Failed to parse magic effect from external plugin"); + return null; + } + } + + /// unique identifier + /// JSON serialized + /// true if updated + + [PublicAPI] + public static bool UpdateMagicEffect(string key, string json) + { + if (!RuntimeRegistry.TryGetValue(key, out MagicItemEffectDefinition original)) + { + return false; + } + + MagicItemEffectDefinition def = JsonConvert.DeserializeObject(json); + if (def == null) + { + return false; + } + + original.CopyFieldsFrom(def); + return true; + } +} \ No newline at end of file diff --git a/EpicLoot/API/MaterialConversion.cs b/EpicLoot/API/MaterialConversion.cs new file mode 100644 index 000000000..bd32c4d9e --- /dev/null +++ b/EpicLoot/API/MaterialConversion.cs @@ -0,0 +1,54 @@ +using EpicLoot.CraftingV2; +using JetBrains.Annotations; +using Newtonsoft.Json; + +namespace EpicLoot; + +public static partial class API +{ + /// JSON serialized + /// unique key if added to + [PublicAPI] + public static string AddMaterialConversion(string json) + { + try + { + MaterialConversion conversion = JsonConvert.DeserializeObject(json); + if (conversion == null) + { + return null; + } + + ExternalMaterialConversions.Add(conversion); + MaterialConversions.Config.MaterialConversions.Add(conversion); + MaterialConversions.Conversions.Add(conversion.Type, conversion); + return RuntimeRegistry.Register(conversion); + } + catch + { + OnError?.Invoke("Failed to parse material conversion passed in through external plugin."); + return null; + } + } + + /// unique identifier + /// JSON serialized + /// True if updated + [PublicAPI] + public static bool UpdateMaterialConversion(string key, string json) + { + if (!RuntimeRegistry.TryGetValue(key, out MaterialConversion original)) + { + return false; + } + + MaterialConversion conversion = JsonConvert.DeserializeObject(json); + if (conversion == null) + { + return false; + } + + original.CopyFieldsFrom(conversion); + return true; + } +} \ No newline at end of file diff --git a/EpicLoot/API/Reload.cs b/EpicLoot/API/Reload.cs new file mode 100644 index 000000000..b648bdf3a --- /dev/null +++ b/EpicLoot/API/Reload.cs @@ -0,0 +1,175 @@ +using EpicLoot.Abilities; +using EpicLoot.Adventure; +using EpicLoot.Crafting; +using EpicLoot.CraftingV2; +using EpicLoot.LegendarySystem; +using System.Collections.Generic; + +namespace EpicLoot; + +public static partial class API +{ + /// + /// Reloads cached external adventure data into + /// + private static void ReloadExternalAdventureData() + { + ReloadExternalBounties(); + ReloadExternalSecretStashItems(); + ReloadExternalTreasures(); + } + + /// + /// Reloads cached secret stash items into + /// + private static void ReloadExternalSecretStashItems() + { + foreach (KeyValuePair> kvp in ExternalSecretStashItems) + { + switch (kvp.Key) + { + case SecretStashType.Materials: + AdventureDataManager.Config.SecretStash.Materials.AddRange(kvp.Value); + break; + case SecretStashType.OtherItems: + AdventureDataManager.Config.SecretStash.OtherItems.AddRange(kvp.Value); + break; + case SecretStashType.RandomItems: + AdventureDataManager.Config.SecretStash.RandomItems.AddRange(kvp.Value); + break; + case SecretStashType.Gamble: + AdventureDataManager.Config.Gamble.GambleCosts.AddRange(kvp.Value); + break; + case SecretStashType.Sale: + AdventureDataManager.Config.TreasureMap.SaleItems.AddRange(kvp.Value); + break; + } + } + } + + /// + /// Reloads cached external treasure maps into + /// + private static void ReloadExternalTreasures() + { + foreach (TreasureMapBiomeInfoConfig treasure in ExternalTreasureMaps) + { + AdventureDataManager.Config.TreasureMap.BiomeInfo.Add(treasure); + } + + OnReload?.Invoke("Reloaded external treasures"); + } + + /// + /// Reloads cached external bounties into + /// + private static void ReloadExternalBounties() + { + AdventureDataManager.Config.Bounties.Targets.AddRange(ExternalBountyTargets); + OnReload?.Invoke("Reloaded external bounties"); + } + + /// + /// Reloads cached external enchanting costs into + /// + private static void ReloadExternalSacrifices() + { + EnchantCostsHelper.Config.DisenchantProducts.AddRange(ExternalSacrifices); + OnReload?.Invoke("Reloaded external sacrifices"); + } + + /// + /// Reloads cached external recipes into + /// + private static void ReloadExternalRecipes() + { + RecipesHelper.Config.recipes.RemoveAll(ExternalRecipes); + RecipesHelper.Config.recipes.AddRange(ExternalRecipes); + OnReload?.Invoke("Reloaded external recipes"); + } + + /// + /// Reloads cached external material conversions into + /// + private static void ReloadExternalMaterialConversions() + { + foreach (MaterialConversion entry in ExternalMaterialConversions) + { + MaterialConversions.Config.MaterialConversions.Add(entry); + } + OnReload?.Invoke("Reloaded external material conversions"); + } + + /// + /// Reloads cached external magic effects into + /// + private static void ReloadExternalMagicEffects() + { + foreach (MagicItemEffectDefinition effect in ExternalMagicItemEffectDefinitions.Values) + { + MagicItemEffectDefinitions.Add(effect); + } + + OnReload?.Invoke("Reloaded external magic effects"); + } + + /// + /// Reloads cached external abilities into and + /// + private static void ReloadExternalAbilities() + { + foreach (KeyValuePair kvp in ExternalAbilities) + { + AbilityDefinitions.Config.Abilities.Add(kvp.Value); + } + + OnReload?.Invoke("Reloaded external abilities"); + } + + /// + /// Reloads cached legendary items and sets into + /// + private static void ReloadExternalLegendary() + { + foreach (KeyValuePair> kvp in ExternalLegendaryItems) + { + switch (kvp.Key) + { + case ItemRarity.Legendary: + UniqueLegendaryHelper.Config.LegendaryItems.AddRange(kvp.Value); + break; + case ItemRarity.Mythic: + UniqueLegendaryHelper.Config.MythicItems.AddRange(kvp.Value); + break; + } + } + + foreach (KeyValuePair> kvp in ExternalLegendarySets) + { + switch (kvp.Key) + { + case ItemRarity.Legendary: + UniqueLegendaryHelper.Config.LegendarySets.AddRange(kvp.Value); + break; + case ItemRarity.Mythic: + UniqueLegendaryHelper.Config.MythicSets.AddRange(kvp.Value); + break; + } + } + + OnReload?.Invoke("Reloaded external legendary abilities"); + } + + /// + /// Reloads cached assets into + /// + public static void ReloadExternalAssets() + { + foreach (KeyValuePair kvp in ExternalAssets) + { + EpicAssets.AssetCache[kvp.Key] = kvp.Value; + } + + OnReload?.Invoke("Reloaded external assets"); + } +} \ No newline at end of file diff --git a/EpicLoot/API/RunTimeRegistry.cs b/EpicLoot/API/RunTimeRegistry.cs new file mode 100644 index 000000000..2ec2a830e --- /dev/null +++ b/EpicLoot/API/RunTimeRegistry.cs @@ -0,0 +1,56 @@ +using JetBrains.Annotations; +using System.Collections.Generic; +using System.Linq; + +namespace EpicLoot; + +public static partial class API +{ + /// + /// Simple registry for external assets, useful to keep track of objects, + /// Keys are generated and returned to external API, + /// to store and use to update specific objects + /// + /// + /// 1. External plugin invokes to 'add', on success, returns unique key + /// 2. Key stored in registry with associated object + /// 3. External plugin invokes to 'update' using unique key to target object + /// + private static class RuntimeRegistry + { + private static readonly Dictionary registry = new(); + private static int counter; + + /// + /// unique identifier + public static string Register(object obj) + { + string typeName = obj.GetType().Name; + string key = $"{typeName}_obj_{++counter}"; + registry[key] = obj; + return key; + } + + /// unique key + /// object as class type + /// class type + /// True if object found matching key + public static bool TryGetValue(string key, out T value) where T : class + { + if (registry.TryGetValue(key, out object obj) && obj is T result) + { + value = result; + return true; + } + + value = null!; + return false; + } + + [PublicAPI] + public static int GetCount() => counter; + + [PublicAPI] + public static List GetRegisteredKeys() => registry.Keys.ToList(); + } +} \ No newline at end of file diff --git a/EpicLoot/API/Sacrifice.cs b/EpicLoot/API/Sacrifice.cs new file mode 100644 index 000000000..62d2184ed --- /dev/null +++ b/EpicLoot/API/Sacrifice.cs @@ -0,0 +1,49 @@ +using EpicLoot.Crafting; +using JetBrains.Annotations; +using Newtonsoft.Json; + +namespace EpicLoot; + +public static partial class API +{ + /// JSON serialized + /// True if added to + [PublicAPI] + public static string AddSacrifice(string json) + { + try + { + DisenchantProductsConfig sacrifice = JsonConvert.DeserializeObject(json); + + if (sacrifice == null) + { + return null; + } + + ExternalSacrifices.Add(sacrifice); + EnchantCostsHelper.Config.DisenchantProducts.Add(sacrifice); + return RuntimeRegistry.Register(sacrifice); + } + catch + { + OnError?.Invoke("Failed to parse sacrifice from external plugin"); + return null; + } + } + + /// unique identifier + /// JSON serialized + /// True if updated + [PublicAPI] + public static bool UpdateSacrifice(string key, string json) + { + if (!RuntimeRegistry.TryGetValue(key, out DisenchantProductsConfig disenchantProduct)) + { + return false; + } + + DisenchantProductsConfig sacrifice = JsonConvert.DeserializeObject(json); + disenchantProduct.CopyFieldsFrom(sacrifice); + return true; + } +} \ No newline at end of file diff --git a/EpicLoot/API/SecretStash.cs b/EpicLoot/API/SecretStash.cs new file mode 100644 index 000000000..111100865 --- /dev/null +++ b/EpicLoot/API/SecretStash.cs @@ -0,0 +1,88 @@ +using EpicLoot.Adventure; +using JetBrains.Annotations; +using Newtonsoft.Json; +using System; + +namespace EpicLoot; + +public static partial class API +{ + private enum SecretStashType + { + Materials, + RandomItems, + OtherItems, + Gamble, + Sale + } + + /// + /// JSON serialized + /// unique identifier if added + [PublicAPI] + public static string AddSecretStashItem(string type, string json) + { + try + { + if (!Enum.TryParse(type, true, out SecretStashType stashType)) + { + return null; + } + + SecretStashItemConfig secretStash = JsonConvert.DeserializeObject(json); + + if (secretStash == null) + { + return null; + } + + ExternalSecretStashItems.AddOrSet(stashType, secretStash); + switch (stashType) + { + case SecretStashType.Materials: + AdventureDataManager.Config.SecretStash.Materials.Add(secretStash); + break; + case SecretStashType.OtherItems: + AdventureDataManager.Config.SecretStash.OtherItems.Add(secretStash); + break; + case SecretStashType.RandomItems: + AdventureDataManager.Config.SecretStash.RandomItems.Add(secretStash); + break; + case SecretStashType.Gamble: + AdventureDataManager.Config.Gamble.GambleCosts.Add(secretStash); + break; + case SecretStashType.Sale: + AdventureDataManager.Config.TreasureMap.SaleItems.Add(secretStash); + break; + } + return RuntimeRegistry.Register(secretStash); + } + catch + { + OnError?.Invoke("Failed to parse secret stash from external plugin"); + return null; + } + } + + /// unique identifier + /// JSON serialized + /// True if fields copied + [PublicAPI] + public static bool UpdateSecretStashItem(string key, string json) + { + if (!RuntimeRegistry.TryGetValue(key, out SecretStashItemConfig original)) + { + return false; + } + + SecretStashItemConfig secretStash = JsonConvert.DeserializeObject(json); + + if (secretStash == null) + { + return false; + } + + original.CopyFieldsFrom(secretStash); + return true; + } +} \ No newline at end of file diff --git a/EpicLoot/API/Treasure.cs b/EpicLoot/API/Treasure.cs new file mode 100644 index 000000000..130a56946 --- /dev/null +++ b/EpicLoot/API/Treasure.cs @@ -0,0 +1,56 @@ +using EpicLoot.Adventure; +using JetBrains.Annotations; +using Newtonsoft.Json; + +namespace EpicLoot; + +public static partial class API +{ + /// JSON serialized + /// unique identifier + [PublicAPI] + public static string AddTreasureMap(string json) + { + try + { + var map = JsonConvert.DeserializeObject(json); + if (map == null) + { + return null; + } + + ExternalTreasureMaps.Add(map); + AdventureDataManager.Config.TreasureMap.BiomeInfo.Add(map); + return RuntimeRegistry.Register(map); + } + catch + { + OnError?.Invoke("Failed to parse treasure map from external plugin"); + return null; + } + } + + /// unique identifier + /// JSON serialized + /// + [PublicAPI] + public static bool UpdateTreasureMap(string key, string json) + { + if (!RuntimeRegistry.TryGetValue(key, out TreasureMapBiomeInfoConfig original)) + { + return false; + } + + try + { + var map = JsonConvert.DeserializeObject(json); + original.CopyFieldsFrom(map); + return true; + } + catch + { + OnError?.Invoke("Failed to parse treasure map from external plugin"); + return false; + } + } +} \ No newline at end of file diff --git a/EpicLoot/Abilities/Ability.cs b/EpicLoot/Abilities/Ability.cs deleted file mode 100644 index c92f44481..000000000 --- a/EpicLoot/Abilities/Ability.cs +++ /dev/null @@ -1,152 +0,0 @@ -using System.Linq; -using UnityEngine; - -namespace EpicLoot.Abilities -{ - public class Ability - { - public virtual string CooldownEndKey => $"EpicLoot.{AbilityDef.ID}.CooldownEnd"; - - public AbilityDefinition AbilityDef; - - protected Player _player; - protected ZNetView _netView; - - public virtual void Initialize(AbilityDefinition abilityDef, Player player) - { - AbilityDef = abilityDef; - _player = player; - _netView = _player.GetComponent(); - } - - public virtual void OnUpdate() - { - if (AbilityDef.ActivationMode == AbilityActivationMode.Triggerable && ShouldTrigger()) - { - TryActivate(); - } - } - - protected virtual bool ShouldTrigger() - { - return false; - } - - protected static float GetTime() - { - return (float)ZNet.instance.GetTimeSeconds(); - } - - public virtual bool IsOnCooldown() - { - if (HasCooldown()) - { - return GetTime() < GetCooldownEndTime(); - } - return false; - } - - public virtual float TimeUntilCooldownEnds() - { - var cooldownEndTime = GetCooldownEndTime(); - return Mathf.Max(0, cooldownEndTime - GetTime()); - } - - public virtual float PercentCooldownComplete() - { - if (HasCooldown() && IsOnCooldown()) - { - return 1.0f - (TimeUntilCooldownEnds() / AbilityDef.Cooldown); - } - - return 1.0f; - } - - public virtual bool CanActivate() - { - return !IsOnCooldown(); - } - - public virtual void TryActivate() - { - if (CanActivate()) - { - Activate(); - } - } - - protected virtual void Activate() - { - if (HasCooldown()) - { - var cooldownEndTime = GetTime() + AbilityDef.Cooldown; - SetCooldownEndTime(cooldownEndTime); - } - - switch (AbilityDef.Action) - { - case AbilityAction.Custom: - ActivateCustomAction(); - break; - - case AbilityAction.StatusEffect: - ActivateStatusEffectAction(); - break; - } - } - - protected virtual void ActivateCustomAction() - { - } - - protected virtual void ActivateStatusEffectAction() - { - if (AbilityDef.Action != AbilityAction.StatusEffect) - { - EpicLoot.LogError($"Tried to activate a status effect ability ({AbilityDef.ID}) that was not marked as Action=StatusEffect!"); - return; - } - - var statusEffectName = AbilityDef.ActionParams.FirstOrDefault(); - if (string.IsNullOrEmpty(statusEffectName)) - { - EpicLoot.LogError($"Tried to activate a status effect ability ({AbilityDef.ID}) but the status effect name param was missing!"); - return; - } - - var statusEffect = EpicLoot.LoadAsset(statusEffectName); - if (statusEffect == null) - { - EpicLoot.LogError($"Tried to activate a status effect ability ({AbilityDef.ID}) but the status effect asset could not be found ({statusEffectName})!"); - return; - } - - _player.GetSEMan().AddStatusEffect(statusEffect); - } - - protected virtual bool HasCooldown() - { - return AbilityDef.Cooldown > 0; - } - - protected virtual void SetCooldownEndTime(float cooldownEndTime) - { - _netView?.GetZDO()?.Set(CooldownEndKey, cooldownEndTime); - } - - public virtual float GetCooldownEndTime() - { - return _netView?.GetZDO()?.GetFloat(CooldownEndKey) ?? 0; - } - - public bool IsActivatedAbility() - { - return AbilityDef.ActivationMode == AbilityActivationMode.Activated; - } - - public void ResetCooldown() - { - SetCooldownEndTime(0); - } - } -} diff --git a/EpicLoot/Abilities/AbilityController.cs b/EpicLoot/Abilities/AbilityController.cs deleted file mode 100644 index 830314846..000000000 --- a/EpicLoot/Abilities/AbilityController.cs +++ /dev/null @@ -1,171 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using HarmonyLib; -using UnityEngine; - -namespace EpicLoot.Abilities -{ - [RequireComponent(typeof(Player))] - public class AbilityController : MonoBehaviour - { - public const int AbilitySlotCount = 3; - - private Player _player; - private readonly List _currentAbilities = new List(); - - public IReadOnlyList CurrentAbilities => _currentAbilities; - - public virtual void Awake() - { - _player = GetComponent(); - UpdatePlayerAbilities(); - } - - public virtual void Update() - { - if (_player.TakeInput()) - { - for (var i = 0; i < AbilitySlotCount; ++i) - { - CheckAbilityInput(i); - } - } - - foreach (var ability in _currentAbilities) - { - ability.OnUpdate(); - } - } - - public void CheckAbilityInput(int index) - { - if (!PlayerHasAbility(index)) - { - return; - } - - var keyCode = GetBindingKeycode(index); - if (keyCode != null && Input.GetKeyDown(keyCode)) - { - UseAbility(index); - } - } - - public static string GetBindingKeycode(int index) - { - index = Mathf.Clamp(index, 0, AbilitySlotCount - 1); - return EpicLoot.AbilityKeyCodes[index]?.Value.ToLowerInvariant(); - } - - private void UseAbility(int index) - { - var ability = _currentAbilities[index]; - if (!ability.IsActivatedAbility() || ability.IsOnCooldown()) - { - return; - } - - ability.TryActivate(); - } - - private bool PlayerHasAbility(int index) - { - return index >= 0 && index < _currentAbilities.Count; - } - - public void OnEquipItem() - { - UpdatePlayerAbilities(); - } - - public void OnUnequipItem() - { - UpdatePlayerAbilities(); - } - - public void UpdatePlayerAbilities() - { - var availableAbilities = GetAvailableAbilities(); - - _currentAbilities.RemoveAll(x => !availableAbilities.Exists(y => y.ID == x.AbilityDef.ID)); - - for (var i = 0; i < AbilitySlotCount && i < availableAbilities.Count; i++) - { - var abilityDef = availableAbilities[i]; - if (abilityDef.ActivationMode == AbilityActivationMode.Activated - || abilityDef.ActivationMode == AbilityActivationMode.Triggerable) - { - if (!_currentAbilities.Exists(x => x.AbilityDef.ID == abilityDef.ID)) - { - var ability = AbilityFactory.Create(abilityDef.ID); - ability.Initialize(abilityDef, _player); - _currentAbilities.Add(ability); - } - } - } - } - - private List GetAvailableAbilities() - { - var effectsWithAbilities = _player.GetAllActiveMagicEffects() - .Select(x => MagicItemEffectDefinitions.Get(x.EffectType)) - .Where(x => !string.IsNullOrEmpty(x.Ability)); - - var availableAbilities = new HashSet(); - foreach (var effectDef in effectsWithAbilities) - { - if (AbilityDefinitions.TryGetAbilityDef(effectDef.Ability, out var abilityDef) && !availableAbilities.Contains(abilityDef)) - { - availableAbilities.Add(abilityDef); - } - } - - return availableAbilities.ToList(); - } - - public virtual Ability GetCurrentAbility(int index) - { - return PlayerHasAbility(index) ? _currentAbilities[index] : null; - } - - public virtual Ability GetCurrentAbility(string abilityID) - { - return _currentAbilities.Find(x => x.AbilityDef.ID == abilityID); - } - } - - [HarmonyPatch(typeof(Player), nameof(Player.SetLocalPlayer))] - public static class Player_SetLocalPlayer_Patch - { - public static void Postfix(Player __instance) - { - __instance.gameObject.AddComponent(); - } - } - - [HarmonyPatch(typeof(Humanoid), nameof(Humanoid.EquipItem))] - public static class Player_EquipItem_Patch - { - public static void Postfix(Humanoid __instance) - { - var abilityController = __instance.GetComponent(); - if (abilityController != null) - { - abilityController.OnEquipItem(); - } - } - } - - [HarmonyPatch(typeof(Humanoid), nameof(Humanoid.UnequipItem))] - public static class Player_UnequipItem_Patch - { - public static void Postfix(Humanoid __instance) - { - var abilityController = __instance.GetComponent(); - if (abilityController != null) - { - abilityController.OnUnequipItem(); - } - } - } -} diff --git a/EpicLoot/Abilities/AbilityDefinitions.cs b/EpicLoot/Abilities/AbilityDefinitions.cs deleted file mode 100644 index 7cc977d8f..000000000 --- a/EpicLoot/Abilities/AbilityDefinitions.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System.Collections.Generic; - -namespace EpicLoot.Abilities -{ - public static class AbilityDefinitions - { - public static AbilityConfig Config; - public static readonly Dictionary Abilities = new Dictionary(); - - public static void Initialize(AbilityConfig config) - { - Config = config; - - Abilities.Clear(); - foreach (var def in Config.Abilities) - { - Abilities.Add(def.ID, def); - } - } - - public static bool TryGetAbilityDef(string abilityID, out AbilityDefinition abilityDef) - { - return Abilities.TryGetValue(abilityID, out abilityDef); - } - } -} diff --git a/EpicLoot/Abilities/AbilityFactory.cs b/EpicLoot/Abilities/AbilityFactory.cs deleted file mode 100644 index dde78b0ef..000000000 --- a/EpicLoot/Abilities/AbilityFactory.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace EpicLoot.Abilities -{ - public static class AbilityFactory - { - public static readonly Dictionary AbilityClassTypes = new Dictionary(); - - public static void Register(string abilityID, Type abilityClassType) - { - AbilityClassTypes.Add(abilityID, abilityClassType); - } - - public static Ability Create(string abilityID) - { - if (AbilityClassTypes.TryGetValue(abilityID, out var abilityClassType)) - { - return (Ability)Activator.CreateInstance(abilityClassType); - } - - return new Ability(); - } - } -} diff --git a/EpicLoot/Adventure/BountyManagmentSystem.cs b/EpicLoot/Adventure/BountyManagmentSystem.cs deleted file mode 100644 index af40f1c70..000000000 --- a/EpicLoot/Adventure/BountyManagmentSystem.cs +++ /dev/null @@ -1,127 +0,0 @@ -using System; -using System.IO; -using System.Linq; -using System.Runtime.Serialization.Formatters.Binary; -using BepInEx; -using EpicLoot.Adventure.Feature; -using Newtonsoft.Json; -using UnityEngine; - -namespace EpicLoot.Adventure; - -public class BountyManagmentSystem : MonoBehaviour -{ - public static BountyManagmentSystem Instance => _instance; - - public BountyLedger BountyLedger => _bountyLedger; - - private BountyLedger _bountyLedger; - private BountyLedger _tempBountyLedger; - private static BountyManagmentSystem _instance; - private const string LedgerIdentifier = "randyknapp.mods.epicloot.BountyLedger"; - private static string _ledgerSaveDirectory = Path.Combine(Paths.ConfigPath, "EpicLoot","BountySaves"); - private static string _ledgerSaveFile = Path.Combine(_ledgerSaveDirectory, $"{LedgerIdentifier}.{ZNet.m_world.m_uid}.dat"); - - private void Awake() - { - Directory.CreateDirectory(_ledgerSaveDirectory); - _instance = this; - } - - private void Start() - { - LoadBounties(); - InvokeRepeating(nameof(SaveBounties), 0f, 60f); - } - - private void SaveBounties() - { - if (!Common.Utils.IsServer() || ZoneSystem.instance == null || _bountyLedger == null) - { - return; - } - - SaveTempLedger(); - - var bf = new BinaryFormatter(); - var fs = File.Create(_ledgerSaveFile); - - bf.Serialize(fs,_tempBountyLedger); - fs.Close(); - } - - private void LoadBounties() - { - if (!Common.Utils.IsServer() || ZoneSystem.instance == null) - { - return; - } - - var globalKeys = ZoneSystem.instance.GetGlobalKeys(); - - if (File.Exists(_ledgerSaveFile)) - { - var bf = new BinaryFormatter(); - var fs = File.Open(_ledgerSaveFile, FileMode.Open); - var ledgerDataFile = bf.Deserialize(fs) as BountyLedger; - fs.Close(); - - if (ledgerDataFile != null) - { - _bountyLedger = ledgerDataFile; - } - } - else - { - var ledgerGlobalKey = globalKeys.Find(x => x.StartsWith(LedgerIdentifier,StringComparison.OrdinalIgnoreCase)); - var ledgerData = ledgerGlobalKey?.Substring(LedgerIdentifier.Length); - - if (string.IsNullOrEmpty(ledgerData)) - { - _bountyLedger = new BountyLedger { WorldID = ZNet.m_world.m_uid }; - } - else - { - try - { - _bountyLedger = JsonConvert.DeserializeObject(ledgerData); - } - catch (Exception) - { - Debug.LogWarning("[EpicLoot] WARNING! Could not load bounty kill ledger, kills made by other players may not have counted towards your bounties."); - _bountyLedger = new BountyLedger { WorldID = ZNet.m_world.m_uid }; - } - } - } - - foreach (var globalKey in globalKeys.Where(globalKey => globalKey.StartsWith(LedgerIdentifier,StringComparison.OrdinalIgnoreCase))) - { - ZoneSystem.instance.m_globalKeys.Remove(globalKey); - } - - } - - public void Shutdown(bool save = true) - { - if (!Common.Utils.IsServer() || ZoneSystem.instance == null || BountyLedger == null) - { - return; - } - - if (!save) - return; - - SaveTempLedger(); - - ZoneSystem.instance.m_globalKeys.RemoveWhere(x => x.StartsWith(LedgerIdentifier,StringComparison.OrdinalIgnoreCase)); - - var ledgerData = JsonConvert.SerializeObject(_tempBountyLedger, Formatting.None); - ledgerData = LedgerIdentifier + ledgerData; - ZoneSystem.instance.SetGlobalKey(ledgerData); - } - - private void SaveTempLedger() - { - _tempBountyLedger = _bountyLedger; - } -} \ No newline at end of file diff --git a/EpicLoot/Adventure/BountyTarget.cs b/EpicLoot/Adventure/BountyTarget.cs deleted file mode 100644 index d399dbcf7..000000000 --- a/EpicLoot/Adventure/BountyTarget.cs +++ /dev/null @@ -1,186 +0,0 @@ -using System; -using HarmonyLib; -using JetBrains.Annotations; -using UnityEngine; - -namespace EpicLoot.Adventure -{ - [RequireComponent(typeof(Character))] - public class BountyTarget : MonoBehaviour - { - public const string BountyIDKey = "BountyID"; - public const string BountyDataKey = "BountyData"; - public const string MonsterIDKey = "MonsterID"; - public const string IsAddKey = "IsAdd"; - public const string BountyTargetNameKey = "BountyTargetName"; - - private BountyInfo _bountyInfo; - private string _monsterID; - private bool _isAdd; - - private Character _character; - private ZDO _zdo; - - [UsedImplicitly] - public void Awake() - { - _character = GetComponent(); - _character.m_onDeath += OnDeath; - _zdo = _character.m_nview.GetZDO(); - - if (HasBeenSetup()) - { - Reinitialize(); - } - } - - [UsedImplicitly] - public void OnDestroy() - { - if (_character != null) - { - _character.m_onDeath -= OnDeath; - } - } - - private bool HasBeenSetup() - { - var bountyID = _zdo.GetString(BountyIDKey); - return !string.IsNullOrEmpty(bountyID); - } - - private void OnDeath() - { - if (ZNet.instance.IsServer() || !ZNet.instance.IsServer() && !ZNet.instance.IsDedicated()) - { - var pkg = new ZPackage(); - _bountyInfo.ToPackage(pkg); - - EpicLoot.LogWarning($"SENDING -> RPC_SlayBountyTarget: {_monsterID} ({(_isAdd ? "minion" : "target")})"); - ZRoutedRpc.instance.InvokeRoutedRPC(ZRoutedRpc.Everybody, "SlayBountyTarget", pkg, _monsterID, _isAdd); - } - else - { - var bountyID = _zdo.GetString(BountyIDKey); - EpicLoot.LogWarning($"SENDING -> RPC_SlayBountyTargetFromBountyId: (bountyID={bountyID}) {_monsterID} ({(_isAdd ? "minion" : "target")})"); - ZRoutedRpc.instance.InvokeRoutedRPC(ZRoutedRpc.Everybody, "SlayBountyIDTarget", _monsterID, _isAdd, bountyID); - } - } - - public void Initialize(BountyInfo bounty, string monsterID, bool isAdd) - { - _zdo.Set(BountyIDKey, bounty.ID); - if (ZNet.instance.IsServer() || !ZNet.instance.IsServer() && !ZNet.instance.IsDedicated()) - { - var pkg = new ZPackage(); - bounty.ToPackage(pkg); - pkg.SetPos(0); - _zdo.Set(BountyDataKey, pkg.GetBase64()); - } - _zdo.Set(MonsterIDKey, monsterID); - _zdo.Set(IsAddKey, isAdd); - _zdo.Set(BountyTargetNameKey, GetTargetName(_character.m_name, isAdd, bounty.TargetName)); - - _character.SetLevel(GetTargetLevel(bounty, monsterID, isAdd)); - _character.SetMaxHealth(GetModifiedMaxHealth(_character, bounty, isAdd)); - _character.m_baseAI.SetPatrolPoint(); - - Reinitialize(); - } - - public void Reinitialize() - { - if (ZNet.instance.IsServer() || !ZNet.instance.IsServer() && !ZNet.instance.IsDedicated()) - { - var pkgString = _zdo.GetString(BountyDataKey); - var pkg = new ZPackage(pkgString); - try - { - _bountyInfo = BountyInfo.FromPackage(pkg); - } - catch (Exception) - { - Debug.LogError($"[EpicLoot] Error loading bounty info on creature ({name})! Possibly old or outdated bounty target, destroying creature.\nBountyData:\n{pkgString}"); - _zdo.Set("BountyTarget", ""); - _zdo.Set(BountyDataKey, ""); - _character.m_nview.Destroy(); - return; - } - } - _monsterID = _zdo.GetString(MonsterIDKey); - _isAdd = _zdo.GetBool(IsAddKey); - - _character.m_name = _zdo.GetString(BountyTargetNameKey); - _character.m_boss = !_zdo.GetBool(IsAddKey); - } - - private static float GetModifiedMaxHealth(Character character, BountyInfo bounty, bool isAdd) - { - if (isAdd) - { - return character.GetMaxHealth() * AdventureDataManager.Config.Bounties.AddsHealthMultiplier; - } - - if (bounty.RewardGold > 0) - { - return character.GetMaxHealth() * AdventureDataManager.Config.Bounties.GoldHealthMultiplier; - } - - return character.GetMaxHealth() * AdventureDataManager.Config.Bounties.IronHealthMultiplier; - } - - private static string GetTargetName(string originalName, bool isAdd, string targetName) - { - return isAdd ? - Localization.instance.Localize("$mod_epicloot_bounties_minionname", originalName) - : (string.IsNullOrEmpty(targetName) ? originalName : targetName); - } - - private static int GetTargetLevel(BountyInfo bounty, string monsterID, bool isAdd) - { - if (isAdd) - { - foreach (var targetInfo in bounty.Adds) - { - if (targetInfo.MonsterID == monsterID) - { - return targetInfo.Level; - } - } - - return 1; - } - - return bounty.Target.Level; - } - } - - [HarmonyPatch(typeof(Humanoid), nameof(Humanoid.Start))] - [HarmonyPatch(typeof(Character), nameof(Character.Start))] - public static class Character_Start_Patch - { - public static void Postfix(Character __instance) - { - var zdo = __instance.m_nview != null ? __instance.m_nview.GetZDO() : null; - - if (zdo != null && zdo.IsValid()) - { - var old = !string.IsNullOrEmpty(zdo.GetString("BountyTarget")); - - if (old) - { - EpicLoot.LogWarning($"Destroying old bounty target: {__instance.name}"); - zdo.Set("BountyTarget", ""); - __instance.m_nview.Destroy(); - return; - } - - if (!string.IsNullOrEmpty(zdo.GetString(BountyTarget.BountyIDKey))) - { - var bountyTarget = __instance.gameObject.AddComponent(); - bountyTarget.Reinitialize(); - } - } - } - } -} diff --git a/EpicLoot/Adventure/CharacterDrop_Patch.cs b/EpicLoot/Adventure/CharacterDrop_Patch.cs deleted file mode 100644 index 9d0130b3c..000000000 --- a/EpicLoot/Adventure/CharacterDrop_Patch.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System.Collections.Generic; -using HarmonyLib; -using UnityEngine; - -namespace EpicLoot.Adventure -{ - //public List> GenerateDropList() - [HarmonyPatch(typeof(CharacterDrop), nameof(CharacterDrop.GenerateDropList))] - public static class CharacterDrop_GenerateDropList_Patch - { - public static void Postfix(CharacterDrop __instance, ref List> __result) - { - for (var index = 0; index < __result.Count; index++) - { - var dropEntry = __result[index]; - if (dropEntry.Key.name == "Coins") - { - __result.RemoveAt(index); - var newAmount = dropEntry.Value * AdventureDataManager.Config.FulingCoinDropScale; - __result.Insert(index, new KeyValuePair(dropEntry.Key, Mathf.RoundToInt(newAmount))); - } - } - } - } -} diff --git a/EpicLoot/Adventure/Feature/AdventureFeature.cs b/EpicLoot/Adventure/Feature/AdventureFeature.cs deleted file mode 100644 index 0b86378aa..000000000 --- a/EpicLoot/Adventure/Feature/AdventureFeature.cs +++ /dev/null @@ -1,283 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Linq; -using UnityEngine; -using Object = UnityEngine.Object; -using Random = System.Random; - -namespace EpicLoot.Adventure.Feature -{ - public enum AdventureFeatureType - { - None, - SecretStash, - Gamble, - TreasureMaps, - Bounties - } - - public abstract class AdventureFeature - { - public abstract AdventureFeatureType Type { get; } - public abstract int RefreshInterval { get; } - - public int GetSecondsUntilRefresh() - { - return GetSecondsUntilIntervalRefresh(RefreshInterval); - } - - public int GetCurrentInterval() - { - return GetCurrentInterval(RefreshInterval); - } - - public Random GetRandom() - { - return GetRandomForInterval(GetCurrentInterval(), RefreshInterval); - } - - public virtual void OnZNetStart() - { - } - - public virtual void OnZNetDestroyed() - { - } - - public virtual void OnWorldSave() - { - } - - protected static int GetSecondsUntilIntervalRefresh(int intervalDays) - { - if (ZNet.m_world == null || EnvMan.instance == null) - { - return -1; - } - - var currentDay = EnvMan.instance.GetCurrentDay(); - var startOfNextInterval = GetNextMultiple(currentDay, intervalDays); - var daysRemaining = (startOfNextInterval - currentDay) - EnvMan.instance.m_smoothDayFraction; - return (int)(daysRemaining * EnvMan.instance.m_dayLengthSec); - } - - protected static int GetNextMultiple(int n, int multiple) - { - return ((n / multiple) + 1) * multiple; - } - - protected static int GetCurrentInterval(int intervalDays) - { - var currentDay = EnvMan.instance.GetCurrentDay(); - return currentDay / intervalDays; - } - - private static int GetSeedForInterval(int currentInterval, int intervalDays) - { - var worldSeed = ZNet.m_world?.m_seed ?? 0; - var playerId = (int)(Player.m_localPlayer?.GetPlayerID() ?? 0); - return unchecked(worldSeed + playerId + currentInterval * 1000 + intervalDays * 100); - } - - protected static Random GetRandomForInterval(int currentInterval, int intervalDays) - { - return new Random(GetSeedForInterval(currentInterval, intervalDays)); - } - - public static ItemDrop CreateItemDrop(string prefabName) - { - var itemPrefab = ObjectDB.instance.GetItemPrefab(prefabName); - if (itemPrefab == null) - { - return null; - } - - var itemDropPrefab = itemPrefab.GetComponent(); - if (itemDropPrefab == null) - { - return null; - } - - ZNetView.m_forceDisableInit = true; - var item = Object.Instantiate(itemDropPrefab); - ZNetView.m_forceDisableInit = false; - - return item; - } - - public static List CollectItems(List itemList) - { - return CollectItems(itemList, (x) => x.Item, (x) => true); - } - - protected static List CollectItems( - List itemList, - Func itemIdPredicate, - Func itemOkayToAddPredicate) - { - var results = new List(); - foreach (var itemConfig in itemList) - { - var itemId = itemIdPredicate(itemConfig); - var itemDrop = CreateItemDrop(itemId); - if (itemDrop == null) - { - EpicLoot.LogWarning($"[AdventureData] Could not find item type (gated={itemId} orig={itemConfig}) in ObjectDB!"); - continue; - } - - var itemData = itemDrop.m_itemData; - if (itemOkayToAddPredicate(itemData)) - { - results.Add(new SecretStashItemInfo(itemId, itemData, itemConfig.GetCost())); - } - ZNetScene.instance.Destroy(itemDrop.gameObject); - } - - return results; - } - - protected static void RollOnListNTimes(Random random, List list, int n, List results) - { - for (var i = 0; i < n && i < list.Count; i++) - { - var index = random.Next(0, list.Count); - var item = list[index]; - results.Add(item); - list.RemoveAt(index); - } - } - - protected static T RollOnList(Random random, List list) - { - var index = random.Next(0, list.Count); - return list[index]; - } - - protected static IEnumerator GetRandomPointInBiome(Heightmap.Biome biome, AdventureSaveData saveData, Action onComplete) - { - const int maxRangeIncreases = 10; - const int maxPointsInRange = 15; - - MerchantPanel.ShowInputBlocker(true); - - var rangeTries = 0; - var radiusRange = GetTreasureMapSpawnRadiusRange(biome, saveData); - while (rangeTries < maxRangeIncreases) - { - rangeTries++; - - var tries = 0; - while (tries < maxPointsInRange) - { - tries++; - - var randomPoint = UnityEngine.Random.insideUnitCircle; - var mag = randomPoint.magnitude; - var normalized = randomPoint.normalized; - var actualMag = Mathf.Lerp(radiusRange.Item1, radiusRange.Item2, mag); - randomPoint = normalized * actualMag; - var spawnPoint = new Vector3(randomPoint.x, 0, randomPoint.y); - - var zoneId = ZoneSystem.instance.GetZone(spawnPoint); - while (!ZoneSystem.instance.SpawnZone(zoneId, ZoneSystem.SpawnMode.Client, out _)) - { - EpicLoot.LogWarning($"Spawning Zone ({zoneId})..."); - yield return null; - } - - ZoneSystem.instance.GetGroundData(ref spawnPoint, out var normal, out var foundBiome, out _, out _); - var groundHeight = spawnPoint.y; - - EpicLoot.Log($"Checking biome at ({randomPoint}): {foundBiome} (try {tries})"); - if (foundBiome != biome) - { - // Wrong biome - continue; - } - - var solidHeight = ZoneSystem.instance.GetSolidHeight(spawnPoint); - var offsetFromGround = Math.Abs(solidHeight - groundHeight); - if (offsetFromGround > 5) - { - // Don't place too high off the ground (on top of tree or something? - EpicLoot.Log($"Spawn Point rejected: too high off of ground (groundHeight:{groundHeight}, solidHeight:{solidHeight})"); - continue; - } - - // But also don't place inside rocks - spawnPoint.y = solidHeight; - - var placedNearPlayerBase = EffectArea.IsPointInsideArea(spawnPoint, EffectArea.Type.PlayerBase, AdventureDataManager.Config.TreasureMap.MinimapAreaRadius); - if (placedNearPlayerBase) - { - // Don't place near player base - EpicLoot.Log("Spawn Point rejected: too close to player base"); - continue; - } - - EpicLoot.Log($"Wards: {PrivateArea.m_allAreas.Count}"); - var tooCloseToWard = PrivateArea.m_allAreas.Any(x => x.IsInside(spawnPoint, AdventureDataManager.Config.TreasureMap.MinimapAreaRadius)); - if (tooCloseToWard) - { - EpicLoot.Log("Spawn Point rejected: too close to player ward"); - continue; - } - - var waterLevel = ZoneSystem.instance.m_waterLevel; - if (biome != Heightmap.Biome.Ocean && waterLevel > groundHeight + 1.0f) - { - // Too deep, try again - EpicLoot.Log($"Spawn Point rejected: too deep underwater (waterLevel:{waterLevel}, groundHeight:{groundHeight})"); - continue; - } - - EpicLoot.Log($"Success! (ground={groundHeight} water={waterLevel} placed={spawnPoint.y})"); - - onComplete?.Invoke(true, spawnPoint, normal); - MerchantPanel.ShowInputBlocker(false); - yield break; - } - - radiusRange = new Tuple(radiusRange.Item1 + 500, radiusRange.Item2 + 500); - } - - onComplete?.Invoke(false, new Vector3(), new Vector3()); - MerchantPanel.ShowInputBlocker(false); - } - - private static Tuple GetTreasureMapSpawnRadiusRange(Heightmap.Biome biome, AdventureSaveData saveData) - { - var biomeInfoConfig = GetBiomeInfoConfig(biome); - if (biomeInfoConfig == null) - { - EpicLoot.LogError($"Could not get biome info for biome: {biome}!"); - EpicLoot.LogWarning($"> Current BiomeInfo ({AdventureDataManager.Config.TreasureMap.BiomeInfo.Count}):"); - foreach (var biomeInfo in AdventureDataManager.Config.TreasureMap.BiomeInfo) - { - EpicLoot.Log($"- {biomeInfo.Biome}: min:{biomeInfo.MinRadius}, max:{biomeInfo.MaxRadius}"); - } - - return new Tuple(-1, -1); - } - - var minSearchRange = biomeInfoConfig.MinRadius; - var maxSearchRange = biomeInfoConfig.MaxRadius; - var searchBandWidth = AdventureDataManager.Config.TreasureMap.StartRadiusMax - AdventureDataManager.Config.TreasureMap.StartRadiusMin; - var numberOfBounties = AdventureDataManager.CheatNumberOfBounties >= 0 ? AdventureDataManager.CheatNumberOfBounties : saveData.NumberOfTreasureMapsOrBountiesStarted; - var increments = numberOfBounties / AdventureDataManager.Config.TreasureMap.IncreaseRadiusCount; - var min1 = minSearchRange + (AdventureDataManager.Config.TreasureMap.StartRadiusMin + increments * AdventureDataManager.Config.TreasureMap.RadiusInterval); - var max1 = min1 + searchBandWidth; - var min = Mathf.Clamp(min1, minSearchRange, maxSearchRange - searchBandWidth); - var max = Mathf.Clamp(max1, minSearchRange + searchBandWidth, maxSearchRange); - EpicLoot.Log($"Got biome info for biome ({biome}) - Overall search range: {minSearchRange}-{maxSearchRange}. Current increments: {increments}. Current search band: {min}-{max} (width={searchBandWidth})"); - return new Tuple(min, max); - } - - private static TreasureMapBiomeInfoConfig GetBiomeInfoConfig(Heightmap.Biome biome) - { - return AdventureDataManager.Config.TreasureMap.BiomeInfo.Find(x => x.Biome == biome); - } - } -} diff --git a/EpicLoot/Adventure/Feature/Bounties.cs b/EpicLoot/Adventure/Feature/Bounties.cs deleted file mode 100644 index 69c37ab63..000000000 --- a/EpicLoot/Adventure/Feature/Bounties.cs +++ /dev/null @@ -1,526 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using Common; -using HarmonyLib; -using Newtonsoft.Json; -using UnityEngine; -using Object = UnityEngine.Object; -using Random = System.Random; - -namespace EpicLoot.Adventure.Feature -{ - public class BountiesAdventureFeature : AdventureFeature - { - public BountyLedger BountyLedger => BountyManagmentSystem.Instance.BountyLedger; - - private const string LedgerIdentifier = "randyknapp.mods.epicloot.BountyLedger"; - - public override AdventureFeatureType Type => AdventureFeatureType.Bounties; - public override int RefreshInterval => AdventureDataManager.Config.Bounties.RefreshInterval; - - public List GetAvailableBounties() - { - return GetAvailableBounties(GetCurrentInterval()); - } - - public bool BossBountiesGated() - { - switch (EpicLoot.BossBountyMode.Value) - { - case GatedBountyMode.BossKillUnlocksCurrentBiomeBounties: - case GatedBountyMode.BossKillUnlocksNextBiomeBounties: - return true; - default: - return false; - } - } - - public List GetAvailableBounties(int interval, bool removeAcceptedBounties = true) - { - var player = Player.m_localPlayer; - var random = GetRandomForInterval(interval, RefreshInterval); - - var bountiesPerBiome = new MultiValueDictionary(); - - var defeatedBossBiomes = new List(); - var previousBossKilled = false; - var previousBoss = ""; - - if (BossBountiesGated()) - { - foreach (var bossConfig in AdventureDataManager.Config.Bounties.Bosses) - { - if (previousBoss == "" && EpicLoot.BossBountyMode.Value == GatedBountyMode.BossKillUnlocksNextBiomeBounties) - { - defeatedBossBiomes.Add(bossConfig.Biome); - previousBoss = bossConfig.BossDefeatedKey; - } - - if (ZoneSystem.instance.GetGlobalKey(bossConfig.BossDefeatedKey)) - { - defeatedBossBiomes.Add(bossConfig.Biome); - previousBossKilled = true; - previousBoss = bossConfig.BossDefeatedKey; - } - else if ((previousBossKilled || previousBoss.Equals(bossConfig.BossDefeatedKey)) && EpicLoot.BossBountyMode.Value == GatedBountyMode.BossKillUnlocksNextBiomeBounties) - { - defeatedBossBiomes.Add(bossConfig.Biome); - previousBoss = bossConfig.BossDefeatedKey; - previousBossKilled = false; - } - } - } - - foreach (var targetConfig in AdventureDataManager.Config.Bounties.Targets) - { - bountiesPerBiome.Add(targetConfig.Biome, targetConfig); - } - - var selectedTargets = new List(); - foreach (var entry in bountiesPerBiome) - { - var targets = entry.Value; - RollOnListNTimes(random, targets, 1, selectedTargets); - } - // Remove the results that the player doesn't know about yet - selectedTargets.RemoveAll(result => !player.m_knownBiome.Contains(result.Biome)); - - if (BossBountiesGated()) - { - //Remove the results of undefeated biome bosses - selectedTargets.RemoveAll(result => !defeatedBossBiomes.Contains(result.Biome)); - } - - var saveData = player.GetAdventureSaveData(); - - var results = selectedTargets.Select(targetConfig => new BountyInfo() - { - Biome = targetConfig.Biome, - Interval = interval, - PlayerID = player.GetPlayerID(), - Target = new BountyTargetInfo() { MonsterID = targetConfig.TargetID, Level = GetTargetLevel(random, targetConfig.RewardGold > 0, false), Count = 1 }, - TargetName = GenerateTargetName(random), - RewardIron = targetConfig.RewardIron, - RewardGold = targetConfig.RewardGold, - RewardCoins = targetConfig.RewardCoins, - Adds = targetConfig.Adds.Select(x => new BountyTargetInfo() { MonsterID = x.ID, Count = x.Count, Level = GetTargetLevel(random, false, true) }).ToList() - }) - .ToList(); - - if (removeAcceptedBounties) - { - results.RemoveAll(x => saveData.HasAcceptedBounty(x.Interval, x.ID)); - } - - return results; - } - - public static string PrintBounties(string label, List results) - { - var sb = new StringBuilder(); - sb.AppendLine(label); - for (var index = 0; index < results.Count; index++) - { - var bountyInfo = results[index]; - sb.AppendLine($"{index} - {bountyInfo.Interval}, {bountyInfo.Biome}, {bountyInfo.TargetName}, ID={bountyInfo.ID}, state={bountyInfo.State}"); - } - - EpicLoot.Log(sb.ToString()); - return sb.ToString(); - } - - public static string GenerateTargetName(Random random) - { - var specialNames = AdventureDataManager.Config.Bounties.Names.SpecialNames; - var prefixes = AdventureDataManager.Config.Bounties.Names.Prefixes; - var suffixes = AdventureDataManager.Config.Bounties.Names.Suffixes; - if (specialNames.Count == 0 && (prefixes.Count == 0 || suffixes.Count == 0)) - { - return string.Empty; - } - - var useSpecialName = random.NextDouble() <= AdventureDataManager.Config.Bounties.Names.ChanceForSpecialName; - if (useSpecialName) - { - return RollOnList(random, specialNames); - } - - var prefix = Localization.instance.Localize(RollOnList(random, prefixes)); - var suffix = Localization.instance.Localize(RollOnList(random, suffixes)); - var format = suffix.StartsWith(" ") || suffix.StartsWith(",") ? "$mod_epicloot_bounties_targetnameformat_nospace" : "$mod_epicloot_bounties_targetnameformat"; - return Localization.instance.Localize(format, prefix, suffix); - } - - private static int GetTargetLevel(Random random, bool isGold, bool isAdd) - { - var config = AdventureDataManager.Config.Bounties; - - var min = isAdd ? config.AddsMinLevel : (isGold ? config.GoldMinLevel : config.IronMinLevel); - var max = isAdd ? config.AddsMaxLevel : (isGold ? config.GoldMaxLevel : config.IronMaxLevel); - - return random.Next(min, max + 1); - } - - public List GetClaimableBounties() - { - var results = new List(); - - var saveData = Player.m_localPlayer?.GetAdventureSaveData(); - if (saveData == null) - { - return results; - } - - return saveData.GetClaimableBounties().Concat(saveData.GetInProgressBounties()).ToList(); - } - - public IEnumerator AcceptBounty(Player player, BountyInfo bounty, Action callback) - { - player.Message(MessageHud.MessageType.Center, "$mod_epicloot_bounties_locatingmsg"); - var saveData = player.GetAdventureSaveData(); - yield return GetRandomPointInBiome(bounty.Biome, saveData, (success, spawnPoint, _) => - { - if (success) - { - var offset2 = UnityEngine.Random.insideUnitCircle * AdventureDataManager.Config.TreasureMap.MinimapAreaRadius; - var offset = new Vector3(offset2.x, 0, offset2.y); - saveData.AcceptedBounty(bounty, spawnPoint, offset); - saveData.NumberOfTreasureMapsOrBountiesStarted++; - player.SaveAdventureSaveData(); - - // Spawn Monster - SpawnBountyTargets(bounty, spawnPoint, offset); - } - else - { - callback?.Invoke(false, Vector3.zero); - } - }); - } - - private static void SpawnBountyTargets(BountyInfo bounty, Vector3 spawnPoint, Vector3 offset) - { - var mainPrefab = ZNetScene.instance.GetPrefab(bounty.Target.MonsterID); - if (mainPrefab == null) - { - EpicLoot.LogError($"Could not find prefab for bounty target! BountyID: {bounty.ID}, MonsterID: {bounty.Target.MonsterID}"); - return; - } - - var prefabs = new List() { mainPrefab }; - foreach (var addConfig in bounty.Adds) - { - for (var i = 0; i < addConfig.Count; i++) - { - var prefab = ZNetScene.instance.GetPrefab(addConfig.MonsterID); - if (prefab == null) - { - EpicLoot.LogError($"Could not find prefab for bounty add! BountyID: {bounty.ID}, MonsterID: {addConfig.MonsterID}"); - return; - } - prefabs.Add(prefab); - } - } - for (var index = 0; index < prefabs.Count; index++) - { - var prefab = prefabs[index]; - var isAdd = index > 0; - - var creature = Object.Instantiate(prefab, spawnPoint, Quaternion.identity); - var bountyTarget = creature.AddComponent(); - bountyTarget.Initialize(bounty, prefab.name, isAdd); - - var randomSpacing = UnityEngine.Random.insideUnitSphere * 4; - spawnPoint += randomSpacing; - ZoneSystem.instance.FindFloor(spawnPoint, out var floorHeight); - spawnPoint.y = floorHeight; - } - - Minimap.instance.ShowPointOnMap(spawnPoint + offset); - - var pkg = new ZPackage(); - bounty.ToPackage(pkg); - ZRoutedRpc.instance.InvokeRoutedRPC("SpawnBounties", pkg); - } - - private static void OnBountyTargetSlain(string bountyID, string monsterID, bool isAdd) - { - var player = Player.m_localPlayer; - if (player == null) - { - return; - } - - var saveData = player.GetAdventureSaveData(); - var bountyInfo = saveData.GetBountyInfoByID(bountyID); - - if (bountyInfo == null || bountyInfo.PlayerID != player.GetPlayerID()) - { - // Someone else's bounty - return; - } - - if (!saveData.HasAcceptedBounty(bountyInfo.Interval, bountyInfo.ID) || bountyInfo.State != BountyState.InProgress) - { - return; - } - - EpicLoot.Log($"Bounty Target Slain: bounty={bountyInfo.ID} monsterId={monsterID} ({(isAdd ? "add" : "main target")})"); - - if (!isAdd && bountyInfo.Target.MonsterID == monsterID) - { - bountyInfo.Slain = true; - player.SaveAdventureSaveData(); - } - - if (isAdd) - { - foreach (var addConfig in bountyInfo.Adds) - { - if (addConfig.MonsterID == monsterID && addConfig.Count > 0) - { - addConfig.Count--; - player.SaveAdventureSaveData(); - break; - } - } - } - - var isComplete = bountyInfo.Slain && bountyInfo.Adds.Sum(x => x.Count) == 0; - if (isComplete) - { - MessageHud.instance.ShowBiomeFoundMsg("$mod_epicloot_bounties_completemsg", true); - bountyInfo.State = BountyState.Complete; - - if (!MinimapController.BountyPins.ContainsKey(bountyInfo.ID)) return; - - var pinJob = new PinJob - { - Task = MinimapPinQueueTask.RemoveBountyPin, - DebugMode = MinimapController.DebugMode, - BountyPin = new KeyValuePair(bountyInfo.ID, MinimapController.BountyPins[bountyInfo.ID]) - }; - - MinimapController.AddPinJobToQueue(pinJob); - - player.SaveAdventureSaveData(); - } - } - - public void ClaimBountyReward(BountyInfo bountyInfo) - { - var player = Player.m_localPlayer; - if (player == null) - { - return; - } - - var saveData = player.GetAdventureSaveData(); - if (!saveData.HasAcceptedBounty(bountyInfo.Interval, bountyInfo.ID) || bountyInfo.State != BountyState.Complete) - { - return; - } - - bountyInfo.State = BountyState.Claimed; - player.SaveAdventureSaveData(); - - MessageHud.instance.ShowBiomeFoundMsg("$mod_epicloot_bounties_claimedmsg", true); - - var inventory = player.GetInventory(); - if (bountyInfo.RewardIron > 0) - { - inventory.AddItem("IronBountyToken", bountyInfo.RewardIron, 1, 0, 0, string.Empty); - } - if (bountyInfo.RewardGold > 0) - { - inventory.AddItem("GoldBountyToken", bountyInfo.RewardGold, 1, 0, 0, string.Empty); - } - if (bountyInfo.RewardCoins > 0) - { - inventory.AddItem("Coins", bountyInfo.RewardCoins, 1, 0, 0, string.Empty); - } - } - - public void AbandonBounty(BountyInfo bountyInfo) - { - var saveData = Player.m_localPlayer?.GetAdventureSaveData(); - if (saveData != null && bountyInfo != null && saveData.BountyIsInProgress(bountyInfo.Interval, bountyInfo.ID)) - { - saveData.AbandonedBounty(bountyInfo.ID); - Player.m_localPlayer.SaveAdventureSaveData(); - } - } - - public void RegisterRPC(ZRoutedRpc routedRpc) - { - routedRpc.Register("SendKillLogs", RPC_Client_ReceiveKillLogs); - - if (Common.Utils.IsServer()) - { - routedRpc.Register("SlayBountyTarget", RPC_SlayBountyTarget); - routedRpc.Register("SlayBountyIDTarget", RPC_SlayBountyTargetFromBountyId); - routedRpc.Register("RequestKillLogs", RPC_Server_RequestKillLogs); - routedRpc.Register("ClearKillLogs", RPC_Server_ClearKillLogs); - } - else - { - routedRpc.Register("SlayBountyTargetFromServer", RPC_Client_SlayBountyTargetFromServer); - } - } - - private void RPC_Client_SlayBountyTargetFromServer(long sender, ZPackage pkg, string monsterID, bool isAdd) - { - if (Common.Utils.IsServer()) - { - return; - } - - RPC_SlayBountyTarget(sender, pkg, monsterID, isAdd); - } - - public void RPC_SlayBountyTarget(long sender, ZPackage pkg, string monsterID, bool isAdd) - { - var isServer = Common.Utils.IsServer(); - if (isServer) - { - ZRoutedRpc.instance.InvokeRoutedRPC(ZRoutedRpc.Everybody, "SlayBountyTargetFromServer", pkg, monsterID, isAdd); - } - - var bounty = BountyInfo.FromPackage(pkg); - - if (isServer) - { - AddSlainBountyTargetToLedger(bounty, monsterID, isAdd); - } - - if (Player.m_localPlayer == null || bounty.PlayerID != Player.m_localPlayer.GetPlayerID()) - { - // Not my bounty - return; - } - - OnBountyTargetSlain(bounty.ID, monsterID, isAdd); - } - - public void RPC_SlayBountyTargetFromBountyId(long sender, string monsterID, bool isAdd, string bountyID) - { - EpicLoot.LogWarning($"CLIENT: RPC_SlayBountyTargetFromBountyId: {monsterID} ({(isAdd ? "minion" : "target")})"); - - var bounty = BountyInfo.FromBountyID(bountyID); - - if (Player.m_localPlayer == null || bounty.PlayerID != Player.m_localPlayer.GetPlayerID()) - { - // Not my bounty - return; - } - - OnBountyTargetSlain(bounty.ID, monsterID, isAdd); - } - - private void AddSlainBountyTargetToLedger(BountyInfo bounty, string monsterID, bool isAdd) - { - if (!Common.Utils.IsServer()) - { - return; - } - - if (BountyLedger == null) - { - EpicLoot.LogError("[BountyLedger] Server tried to add kill log to bounty ledger but BountyLedger was null"); - return; - } - - if (Player.m_localPlayer != null && Player.m_localPlayer.GetPlayerID() == bounty.PlayerID) - { - EpicLoot.Log($"[BountyLedger] This player ({bounty.PlayerID}) is the local player"); - return; - } - - var characterZdos = ZNet.instance.GetAllCharacterZDOS(); - var playerIsOnline = characterZdos.Select(zdo => zdo.GetLong("playerID")).Any(playerID => playerID == bounty.PlayerID); - if (playerIsOnline) - { - EpicLoot.Log($"[BountyLedger] This player ({bounty.PlayerID}) is connected to server, don't log the kill, they'll get the RPC"); - return; - } - - BountyLedger.AddKillLog(bounty.PlayerID, bounty.ID, monsterID, isAdd); - } - - public override void OnZNetStart() - { - } - - public override void OnZNetDestroyed() - { - } - - public override void OnWorldSave() - { - } - - private void RPC_Server_RequestKillLogs(long sender, long playerID) - { - if (!Common.Utils.IsServer()) - { - return; - } - - var results = ""; - if (BountyLedger != null) - { - var logs = BountyLedger.GetAllKillLogs(playerID); - results = JsonConvert.SerializeObject(logs, Formatting.Indented); - } - - ZRoutedRpc.instance.InvokeRoutedRPC(sender, "SendKillLogs", results); - } - - private void RPC_Client_ReceiveKillLogs(long sender, string logData) - { - var logs = JsonConvert.DeserializeObject(logData); - if (logs == null) - return; - - foreach (var killLog in logs) - { - OnBountyTargetSlain(killLog.BountyID, killLog.MonsterID, killLog.IsAdd); - } - - var playerID = Player.m_localPlayer.GetPlayerID(); - ZRoutedRpc.instance.InvokeRoutedRPC("ClearKillLogs", playerID); - } - - private void RPC_Server_ClearKillLogs(long sender, long playerID) - { - if (!Common.Utils.IsServer()) - { - return; - } - - BountyLedger?.RemoveKillLogsForPlayer(playerID); - } - } - - [HarmonyPatch(typeof(Game), nameof(Game.SpawnPlayer))] - public static class Game_SpawnPlayer_Patch - { - public static void Postfix() - { - if (ZRoutedRpc.instance != null && Player.m_localPlayer != null) - { - Player.m_localPlayer.StartCoroutine(WaitThenRequestKillLogs(Player.m_localPlayer)); - } - } - - public static IEnumerator WaitThenRequestKillLogs(Player player) - { - yield return new WaitForSeconds(5); - var playerID = player.GetPlayerID(); - ZRoutedRpc.instance.InvokeRoutedRPC("RequestKillLogs", playerID); - } - } -} diff --git a/EpicLoot/Adventure/Feature/BountiesListPanel.cs b/EpicLoot/Adventure/Feature/BountiesListPanel.cs deleted file mode 100644 index 3613ccdb3..000000000 --- a/EpicLoot/Adventure/Feature/BountiesListPanel.cs +++ /dev/null @@ -1,245 +0,0 @@ -using UnityEngine; -using UnityEngine.UI; -using Object = UnityEngine.Object; - -namespace EpicLoot.Adventure.Feature -{ - public class AvailableBountiesListPanel : MerchantListPanel - { - private readonly MerchantPanel _merchantPanel; - - public AvailableBountiesListPanel(MerchantPanel merchantPanel, BountyListElement elementPrefab) - : base( - merchantPanel.transform.Find("Bounties/AvailableBountiesPanel/ItemList") as RectTransform, - elementPrefab, - merchantPanel.transform.Find("Bounties/AcceptBountyButton").GetComponent