0 of 0

File information

Last updated

Original upload

Created by

barteke22

Uploaded by

barteke22

Virus scan

Safe to use

Tags for this mod

About this mod

Configurable probability and uniqueness multipliers for most Museum Artifact drops. And Elite Enemy / Rusty Key spawn multiplier.

Requirements
Permissions and credits
Changelogs
Donations
Description:

Adds configurable (F1) chance (flat modifier & skill based) and uniqueness (don't drop duplicates) modifiers to the following artifact sources:
  • Mining.
  • Combat.
  • Exploration (woodcutting).
  • Fishing (with a rod).

Also the following tweaks:
  • Rusty Key chance modifiers.
  • Elite Enemy chance modifiers.
  • Fixes a bug that causes 'Book 1' of each town drop 66% less than the other 5. No matter what settings.
  • If Museum Tracker mod is present, uniqueness checks your inventory and item description, instead of encyclopaedia to prevent issues if you lose an artifact.

The mod does not affect Golden animal/tree drops, because it's easy enough to control those with skills & more animals / fruit trees.
It also doesn't affect museum fish spawn rates. 'Fish Tweaks' makes finding the right fish easier, and 'No Time For Fishing' provides a multiplier for those.


This is something I threw together quickly for my own playthrough, I'm unlikely to develop it further / maintain it.


Installation:
  • Download and Install the latest version of BepInEx.
  • [Optional] Download and Install the latest version of Museum Tracker if you don't want the uniqueness modifier to apply if you lose an item.
  • Download this mod from Files, and unzip the mod .dll into your BepInEx\plugins folder.



Compatibility:
  • Should be compatible with most mods that affect same areas.
  • Unless they edit the same exact place, in which case turning off certain features can help.
  • Combat Artifact multiplier applied on a save will (probably) not reset if you remove the mod.
- Sorry, your best bet would be running by all enemies at 1.0.


Source:
Spoiler:  
Show

using BepInEx;
using BepInEx.Bootstrap;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using PSS;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using UnityEngine;
using Wish;


[BepInPlugin(MOD_GUID, MOD_NAME, MOD_VER)]
[BepInDependency(MOD_MUSEUM_TRACKER_GUID, BepInDependency.DependencyFlags.SoftDependency)]
public class ArtifactTweaksPlugin : BaseUnityPlugin
{
    private const string MOD_GUID = "barteke22.sunhaven.artifact_tweaks";
    private const string MOD_VER = "1.0.0";
    private const string MOD_NAME = "Artifact Tweaks";
    private const string MOD_MUSEUM_TRACKER_GUID = "MuseumTracker";

    private Harmony _harmony = new Harmony(MOD_GUID);
    public static ManualLogSource logger;

    private static ConfigEntry<bool> conf_mine_book_skill;
    private static ConfigEntry<float> conf_mine_book_mult;
    private static ConfigEntry<bool> conf_mine_book_unique;
    private static ConfigEntry<bool> conf_mine_key_skill;
    private static ConfigEntry<float> conf_mine_key_mult;

    private static ConfigEntry<bool> conf_combat_skill;
    private static ConfigEntry<float> conf_combat_mult;
    private static ConfigEntry<bool> conf_combat_unique;
    private static ConfigEntry<float> conf_combat_elite_mult;
    private static ConfigEntry<bool> conf_combat_elite_skill;

    private static ConfigEntry<bool> conf_fish_skill;
    private static ConfigEntry<float> conf_fish_mult;
    private static ConfigEntry<bool> conf_fish_unique;

    private static ConfigEntry<bool> conf_tree_skill;
    private static ConfigEntry<float> conf_tree_mult;
    private static ConfigEntry<bool> conf_tree_unique;

    private static ConfigEntry<string> conf_data;

    private static bool conf_museum_tracker = false;

    private static List<int> explorationMuseumItems;
    private static List<int> fishingMuseumItems;



    private void Awake()
    {
        logger = Logger;
        try
        {
            foreach (var plugin in Chainloader.PluginInfos)
            {
                var metadata = plugin.Value.Metadata;
                if (metadata.GUID.Equals(MOD_MUSEUM_TRACKER_GUID))
                {
                    conf_museum_tracker = true;
                    break;
                }
            }

            var warning = Environment.NewLine + Environment.NewLine + (conf_museum_tracker ? "Museum Tracker mod found!" : "Museum Tracker mod missing!") + Environment.NewLine
                + " - If found: Drops when item not in Inventory and contains (0/1) in its description." + Environment.NewLine
                + " - If missing: Drops when item not in Encyclopaedia (never acquired). Means item won't drop again if lost somehow.";

            conf_mine_book_skill = Config.Bind("Mining", "Books - Skill bonus", true, "Chance of getting a book rises with your mining level.");
            conf_mine_book_mult = Config.Bind("Mining", "Books - Multiplier", 1f, "Multiplier applied to total book chance.");
            conf_mine_book_unique = Config.Bind("Mining", "Books - Always unique", true, "Drop only unique books." + warning);
            conf_mine_key_skill = Config.Bind("Mining", "Keys - Skill bonus", false, "Chance of Rusty Keys from mining raises with mining level.");
            conf_mine_key_mult = Config.Bind("Mining", "Keys -  Multiplier", 1f, "Multiplier applied to total Rusty Key chance from mining.");

            conf_combat_skill = Config.Bind("Combat", "Artifact - Skill bonus", true, "Chance of getting an artifact rises with your combat level.");
            conf_combat_mult = Config.Bind("Combat", "Artifact - Multiplier", 1f, "Multiplier applied to total artifact chance.");
            conf_combat_unique = Config.Bind("Combat", "Artifact - Always unique", true, "Drop only unique artifacts." + warning);
            conf_combat_elite_mult = Config.Bind("Combat", "Elite spawn - Multiplier", 1f, "Multiplier for Elite enemy spawn rate.");
            conf_combat_elite_skill = Config.Bind("Combat", "Elite spawn - Skill bonus", true, "Elite enemy chance rises with your combat level.");

            conf_fish_skill = Config.Bind("Fishing", "Artifact - Skill bonus", true, "Chance of getting an artifact rises with your fishing level. Fishing rod only.");
            conf_fish_mult = Config.Bind("Fishing", "Artifact - Multiplier", 1f, "Multiplier applied to total artifact chance. Fishing rod only.");
            conf_fish_unique = Config.Bind("Fishing", "Artifact - Always unique", true, "Drop only unique artifacts. Fishing rod only." + warning);

            conf_tree_skill = Config.Bind("Exploration", "Skill bonus", true, "Chance of getting an artifact rises with your exploration level.");
            conf_tree_mult = Config.Bind("Exploration", "Multiplier", 1f, "Multiplier applied to total artifact chance.");
            conf_tree_unique = Config.Bind("Exploration", "Always unique", true, "Drop only unique artifacts." + warning);

            Config.Bind("<Other>", "Golden Products", "<-- READ", "This mod doesn't affect golden animal/tree drops." + Environment.NewLine
                + "You can affect them yourself with the appropriate skills and by getting more animals / fruit trees.");

            conf_data = Config.Bind("DATA", "DATA", "", new ConfigDescription("Do NOT EDIT or RESET this.", null, "Advanced"));


            fishingMuseumItems = new List<int>(FishingRod.fishingMuseumItems);//hack: copy and empty artifact lists to avoid fully overwriting their methods
            explorationMuseumItems = new List<int>(Wish.Tree.explorationMuseumItems);
            typeof(FishingRod).GetField("fishingMuseumItems", BindingFlags.Public | BindingFlags.Static).SetValue(null, new List<int>() { 0 });
            typeof(Wish.Tree).GetField("explorationMuseumItems", BindingFlags.Public | BindingFlags.Static).SetValue(null, new List<int>() { 0 });


            _harmony.PatchAll();
            logger.LogInfo(MOD_GUID + " v" + MOD_VER + " loaded.");
        }
        catch (Exception e)
        {
            logger.LogError("** Awake FATAL - " + e);
        }
    }

    public static void LogError(Exception e)
    {
        logger.LogError(e.Message + Environment.NewLine + e.StackTrace);
    }

    //mining

    [HarmonyPatch(typeof(MineGenerator2), "AttemptToDropRustyKey")]//rusty key multiplier
    private class HarmonyPatch_MineGenerator2_AttemptToDropRustyKey
    {
        private static bool Prefix(MineGenerator2 __instance, Vector3 position, float multiplier = 1f)
        {
            try
            {
                if (__instance.canDropRustyKey)
                {
                    if (conf_mine_key_skill.Value)
                    {
                        multiplier += (0.005f * SingletonBehaviour<GameSave>.Instance.CurrentSave.characterData.Professions[ProfessionType.Mining].level) - 0.005f;
                    }
                    multiplier *= conf_mine_key_mult.Value;

                    var rustykeyAttempts = typeof(MineGenerator2).GetField("rustykeyAttempts", BindingFlags.NonPublic | BindingFlags.Instance);
                    var rustykeyAttemptsVal = (float)rustykeyAttempts.GetValue(__instance);

                    if (SceneSettingsManager.Instance.sceneDictionary[ScenePortalManager.ActiveSceneIndex].mapType == MapType.Mine
                        && !SingletonBehaviour<GameSave>.Instance.GetProgressBoolWorld("minesUnlock" + __instance.tier)
                        && !SingletonBehaviour<GameSave>.Instance.GetProgressBoolCharacter("droppedRustyKey" + __instance.tier)
                        && Wish.Utilities.Chance((0.04f + 0.0129999993f * rustykeyAttemptsVal) * multiplier * __instance.rustyKeyDropRateMultiplier * Mathf.Lerp(0.85f, 0.4f, (float)__instance.tier / 50f))
                        && rustykeyAttemptsVal >= 10f)
                    {
                        __instance.SpawnRustyKey(position);
                    }
                    rustykeyAttempts.SetValue(__instance, rustykeyAttemptsVal + multiplier);
                }
                return false;
            }
            catch (Exception e)
            {
                LogError(e);
            }
            return true;
        }
    }

    [HarmonyPatch(typeof(MineGenerator2), "AttemptToDropBook")]//book multiplier
    private class HarmonyPatch_MineGenerator2_AttemptToDropBook
    {
        private static bool Prefix(MineGenerator2 __instance, Vector3 position)
        {
            try
            {
                var chance = 0.0025f;
                if (conf_mine_book_skill.Value)
                {
                    chance += (0.0005f * SingletonBehaviour<GameSave>.Instance.CurrentSave.characterData.Professions[ProfessionType.Mining].level) - 0.0005f;
                }
                chance *= conf_mine_book_mult.Value;

                if (__instance.canDropBook && SceneSettingsManager.Instance.sceneDictionary[ScenePortalManager.ActiveSceneIndex].mapType == MapType.Mine && Wish.Utilities.Chance(chance))
                {
                    __instance.SpawnBook(position);
                }
                return false;
            }
            catch (Exception e)
            {
                LogError(e);
            }
            return true;
        }
    }

    [HarmonyPatch(typeof(MineGenerator2), "SpawnBook")]//book uniqueness
    private class HarmonyPatch_MineGenerator2_SpawnBook
    {
        private static bool Prefix(MineGenerator2 __instance, Vector3 position)
        {
            try
            {
                var _bookDrop = (List<ItemData>)typeof(MineGenerator2).GetField("_bookDrop", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(__instance);

                TrySpawnMuseumItem(_bookDrop.Select(f => f.id).ToList(), conf_mine_book_unique.Value, position);

                return false;
            }
            catch (Exception e)
            {
                LogError(e);
            }
            return true;
        }
    }


    //item uniqueness
    private static void TrySpawnMuseumItem(List<int> itemIDs, bool unique, Vector3 position, float homeInDelay = 0.4f)
    {
        var itemId = TryGetMuseumItem(itemIDs, unique);

        if (itemId != 0)
        {
            Pickup.Spawn(position.x, position.y, position.z, itemId, homeInDelay: homeInDelay);
        }
    }
    private static int TryGetMuseumItem(List<int> itemIDs, bool unique)
    {
        List<int> ids = new List<int>();
        foreach (var id in itemIDs)
        {
            if (TryGetMuseumItem(id, unique)) ids.Add(id);
        }

        if (ids.Count > 0) return ids.RandomItem();
        return 0;
    }
    private static bool TryGetMuseumItem(int id, bool unique)
    {
        if (unique)
        {
            if (conf_museum_tracker)//safer because checks museum tracker & inventory
            {
                if (!Player.Instance.PlayerInventory.HasEnough(id, 1))
                {
                    ItemData data = null;
                    Database.GetData(id, delegate (ItemData d)
                    {
                        data = d;
                    });
                    if (data?.FormattedDescription.Contains("(0/1)</size>") == true)
                    {
                        return true;
                    }
                }
            }
            else//less safe because player can have lost item
            {
                if (!GameSave.CurrentCharacter.Encylopdeia.ContainsKey((short)id)) return true;
            }
        }
        else
        {
            if (!GameSave.CurrentCharacter.Encylopdeia.ContainsKey((short)id) || !Wish.Utilities.Chance(0.6666f))
            {
                return true;
            }
        }
        return false;
    }


    //combat

    [HarmonyPatch]//generic trinket multiplier & uniqueness
    private class HarmonyPatch_EnemyAI_Die
    {
        [HarmonyReversePatch]
        [HarmonyPatch(typeof(AI), "Die")]
        [MethodImpl(MethodImplOptions.NoInlining)]
        public static void BaseDie(AI instance, bool fromLocalPlayer)
        {
            Console.WriteLine($"Patch.Die({instance}, {fromLocalPlayer})");
        }

        [HarmonyPatch(typeof(EnemyAI), "Die")]
        private static bool Prefix(EnemyAI __instance, bool fromLocalPlayer)
        {
            try
            {
                if (!fromLocalPlayer || !__instance.DropItems || (!conf_combat_unique.Value && !conf_combat_skill.Value && conf_combat_mult.Value == 1f)) return true;//only local gets these

                if ((bool)typeof(EnemyAI).GetField("_dead", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(__instance))
                {
                    return false;
                }

                HarmonyPatch_AI_Die.Prefix(__instance, fromLocalPlayer);
                BaseDie(__instance, fromLocalPlayer);

                if (fromLocalPlayer)
                {
                    if (__instance.DropItems)
                    {
                        float num = (float)typeof(EnemyAI).GetField("_experience", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(__instance);

                        if (GameSave.Combat.GetNode("Combat4c"))
                        {
                            num += (float)GameSave.Combat.GetNodeAmount("Combat4c");
                        }
                        Player.Instance.AddEXP(ProfessionType.Combat, num);
                        TownType currentTownType = SingletonBehaviour<DayCycle>.Instance.CurrentTownType;

                        var chance = (currentTownType == TownType.SunHaven) ? 0.00125f : 0.0025f;
                        if (conf_combat_skill.Value)
                        {
                            chance += (0.0003f * SingletonBehaviour<GameSave>.Instance.CurrentSave.characterData.Professions[ProfessionType.Combat].level) - 0.0003f;
                        }
                        chance *= conf_combat_mult.Value;
                        if (Wish.Utilities.Chance(chance))
                        {
                            int num2 = 20100;
                            switch (currentTownType)
                            {
                                case TownType.Withergate:
                                    num2 = 20102;
                                    break;
                                case TownType.Nelvari:
                                    num2 = 20101;
                                    break;
                            }
                            TrySpawnMuseumItem(new List<int> { num2 }, conf_combat_unique.Value, __instance.transform.position);
                        }
                    }
                    SingletonBehaviour<GameSave>.Instance.SetProgressFloatCharacter(__instance.enemyName + "killed", SingletonBehaviour<GameSave>.Instance.GetProgressFloatCharacter(__instance.enemyName + "killed") + 1f);
                    if (GameSave.Combat.GetNode("Combat3a"))
                    {
                        float a = 0.5f + 0.5f * (float)GameSave.Combat.GetNodeAmount("Combat3a");
                        Player.Instance.AddMana(Mathf.Max(a, Player.Instance.MaxMana * 0.01f));
                    }
                }
                __instance.onDie?.Invoke();

                return false;
            }
            catch (Exception e)
            {
                LogError(e);
            }
            return true;
        }
    }

    [HarmonyPatch(typeof(AI), "Die")]//monster-specific trinket multiplier & uniqueness
    private class HarmonyPatch_AI_Die
    {
        public static bool Prefix(AI __instance, bool fromLocalPlayer)
        {
            try
            {
                if (!fromLocalPlayer || (!conf_combat_unique.Value && !conf_combat_skill.Value && conf_combat_mult.Value == 1f)) return true;//only local gets these

                if ((bool)typeof(AI).GetField("_dead", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(__instance))
                {
                    return false;
                }

                var ogValues = conf_data.Value.Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries)
                 .Select(x => x.Split(':'))
                 .ToDictionary(x => int.Parse(x[0]), x => float.Parse(x[1]));

                var _drops2 = (List<RandomArray2>)typeof(AI).GetField("_drops2", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(__instance);

                var any = false;
                foreach (var drop in _drops2)
                {
                    foreach (var item in drop.drops)
                    {
                        if ((uint)(item.id - 20103) <= 10u)
                        {
                            if (!ogValues.TryGetValue(item.id, out var chance))
                            {
                                chance = item.dropChance;
                                ogValues.Add(item.id, chance);
                                any = true;
                            }
                            if (TryGetMuseumItem(item.id, conf_combat_unique.Value))
                            {
                                if (conf_combat_skill.Value)
                                {
                                    chance += (0.0003f * SingletonBehaviour<GameSave>.Instance.CurrentSave.characterData.Professions[ProfessionType.Combat].level) - 0.0003f;
                                }
                                chance *= conf_combat_mult.Value;
                            }
                            else chance = 0;
                            item.dropChance = chance;
                        }
                    }
                }
                if (any)
                {
                    var data = "";
                    foreach (var item in ogValues)
                    {
                        data += item.Key + ":" + item.Value + ";";
                    }
                    conf_data.Value = data;
                }
            }
            catch (Exception e)
            {
                LogError(e);
            }
            return true;
        }
    }

    [HarmonyPatch(typeof(EnemySpawnGroup), "SpawnGroup")]//elite spawn multiplier
    private class HarmonyPatch_EnemySpawnGroup_SpawnGroup
    {
        private static bool Prefix(EnemySpawnGroup __instance)
        {
            try
            {
                if (conf_combat_elite_skill.Value || conf_combat_elite_mult.Value != 1f)
                {
                    typeof(EnemySpawnGroup).GetField("readyForSpawn", BindingFlags.NonPublic | BindingFlags.Instance).SetValue(__instance, false);
                    typeof(EnemySpawnGroup).GetField("spawnTween", BindingFlags.NonPublic | BindingFlags.Instance).SetValue(__instance, null);

                    var SpawnerHasEnemy = typeof(EnemySpawnGroup).GetMethod("SpawnerHasEnemy", BindingFlags.NonPublic | BindingFlags.Instance);

                    foreach (EnemySpawner spawner in __instance.spawners)
                    {
                        if ((bool)SpawnerHasEnemy.Invoke(__instance, new object[] { spawner }) || !spawner)
                        {
                            continue;
                        }
                        string text = spawner.enemy;
                        bool flag = false;
                        float chance = 0.04f;
                        if (conf_combat_elite_skill.Value)
                        {
                            chance += (0.001f * SingletonBehaviour<GameSave>.Instance.CurrentSave.characterData.Professions[ProfessionType.Combat].level) - 0.001f;
                        }
                        if (Wish.Utilities.Chance(chance * conf_combat_elite_mult.Value) && EnemyManager.Instance.enemiesDictionary.ContainsKey("Elite" + text))
                        {
                            text = "Elite" + text;
                            flag = true;
                        }
                        if (!Wish.Utilities.Chance(spawner.spawnChance))
                        {
                            continue;
                        }
                        if (!flag && spawner.hasSeasonalSprites)
                        {
                            switch (SingletonBehaviour<DayCycle>.Instance.Season)
                            {
                                case Season.Summer:
                                    text += "_Summer";
                                    break;
                                case Season.Fall:
                                    text += "_Fall";
                                    break;
                                case Season.Winter:
                                    text += "_Winter";
                                    break;
                                case Season.Spring:
                                    text += "_Spring";
                                    break;
                            }
                        }
                        EnemyManager.Instance.SpawnEnemy(text, spawner.transform.position, spawner.ID);
                    }
                }
                return false;
            }
            catch (Exception e)
            {
                LogError(e);
            }
            return true;
        }
    }


    //Fishing
    [HarmonyPatch(typeof(FishingRod), "CatchFishWithDialogue")]//fishing uniqueness
    private class HarmonyPatch_FishingRod_CatchFishWithDialogue
    {
        private static void Postfix(FishingRod __instance)
        {
            try
            {
                if (__instance.Player == Player.Instance)
                {
                    var chance = 0.05f;
                    if (conf_fish_skill.Value)
                    {
                        chance += (0.0005f * SingletonBehaviour<GameSave>.Instance.CurrentSave.characterData.Professions[ProfessionType.Fishing].level) - 0.0005f;
                    }
                    if (Wish.Utilities.Chance(chance * conf_fish_mult.Value))
                    {
                        var id = TryGetMuseumItem(fishingMuseumItems, conf_fish_unique.Value);

                        if (id != 0)
                        {
                            Player.Instance.Inventory.AddItem(id);
                        }
                    }
                }
            }
            catch (Exception e)
            {
                LogError(e);
            }
        }
    }


    //Exploration
    [HarmonyPatch(typeof(Wish.Tree), "SpawnDrops")]//fishing uniqueness
    private class HarmonyPatch_Tree_SpawnDrops
    {
        private static void Postfix(Wish.Tree __instance, Vector3 fallPosition, bool stumpDrop)
        {
            try
            {
                if ((bool)typeof(Wish.Tree).GetProperty("FullyGrown", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(__instance))
                {
                    var chance = stumpDrop ? 0.02f : 0.035f;
                    if (conf_tree_skill.Value)
                    {
                        chance += (0.0005f * SingletonBehaviour<GameSave>.Instance.CurrentSave.characterData.Professions[ProfessionType.Exploration].level) - 0.0005f;
                    }
                    if (Wish.Utilities.Chance(chance * conf_tree_mult.Value))
                    {
                        TrySpawnMuseumItem(explorationMuseumItems, conf_tree_unique.Value, new Vector3(fallPosition.x, (fallPosition.y - 0.5f) * 1.41421354f, 0f));
                    }
                }
            }
            catch (Exception e)
            {
                LogError(e);
            }
        }
    }
}