鱼类调整

鱼类调整

一些可配置的鱼类调整,例如可见的鱼类精灵。

杂项

鱼微调

描述:

我逐渐意识到,我喜欢采矿/务农的原因是,我可以自己挑选想要开采/耕种的东西。
我不讨厌这个迷你游戏,但既然游戏把鱼生成为实体……
你基本上在1-5个"未知数"中挑选,等待它们被下一批随机生成的"未知数"取代...
结果却发现,在本赛季结束之前,你一直在找的那一款始终没有出现……

现在你可以在决定捕捉之前,查看那些‘未知’精灵是什么,以及其他可配置的调整(F1):

  • 精灵设置:
    0: 永远不要使用修改过的精灵图。
    1: 在拥有“锐利之眼”技能前,不要使用模组化的精灵。之后显示黑暗模组化的精灵。
    2: 在获得'敏锐目光'技能前显示黑暗修改过的精灵。之后显示修改过的精灵。
    3:总是显示深色模组精灵。
    4: 始终显示模组化精灵。

  • 应用光泽:
    应用"锐利目光"技能的"稀有光泽"效果。如果你启用了非暗色鱼类精灵,这个功能可能毫无意义。
    此光芒效果也得到了改进,现在能正确在水面上方绘制并反射稀有度的颜色方案(取代了之前带有错误且单一颜色的表现)。

  • 几率:
    根据你的钓鱼等级,会获得一个钓鱼生成的小额加成。
    根据所使用的鱼饵,鱼群出现的概率会有小幅提升。
    应用于整体鱼类生成几率的倍数。

这是我为了自己游玩快速拼凑的东西,不太可能继续开发或维护。

安装:

  1. 下载并安装 最新版本的 BepInEx
  2. 下载 此模组从 文件 中,并将模组的 .dll 文件 解压 到你的 BepInEx\plugins 文件夹中。

兼容性:

  • 应当与大多数钓鱼模组兼容。
  • 除非他们编辑同一位置,在这种情况下,关闭某些功能可以帮助解决。

来源:

点击展开源代码
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using DG.Tweening;
using HarmonyLib;
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Reflection;
using UnityEngine;
using Wish;

[BepInPlugin(MOD_GUID, MOD_NAME, MOD_VER)]
public class GenericPortraitsPlugin : BaseUnityPlugin
{
    private const string MOD_GUID = "barteke22.sunhaven.fish_tweaks";
    private const string MOD_VER = "1.0.2";
    private const string MOD_NAME = "鱼微调";
    private Harmony _harmony = new Harmony("barteke22.sunhaven.fish_tweaks");
    public static ManualLogSource logger;

    private static ConfigEntry<bool> conf_visual_override;
    private static ConfigEntry<SpriteMode> conf_visual_applySprite;
    private static ConfigEntry<bool> conf_visual_applyShine;
    private static ConfigEntry<bool> conf_visual_keepUncaughtDark;
    private static ConfigEntry<bool> conf_chance_levelScale;
    private static ConfigEntry<bool> conf_chance_baitBonus;
    private static ConfigEntry<float> conf_chance_mult;
    private static Dictionary<MeshGenerator, FishEntry> _meshToFishDict = new Dictionary<MeshGenerator, FishEntry>();

    private void Awake()
    {
        logger = Logger;
        try
        {
            Config.Bind("Info", "Info", "<-- 悬停以查看信息。", "这些设置中的大部分仅在你更换地点后才会生效。禁用'几率'下的所有选项将禁用对FishSpawnManager Initialize()和SpawnRoutine()代码的任何前缀修改。");

            conf_visual_override = Config.Bind("Visual", "覆盖咬钩", true, "在BiteRoutine前添加前缀,将咬钩动画应用于鱼类精灵。建议保持此选项启用,除非你使用了另一个修改BiteRoutine的钓鱼模组。");
            conf_visual_applySprite = Config.Bind("Visual", "Apply Sprite", SpriteMode.Dark_KeenEye_Light, "0: 永远不使用修改过的精灵图。" + Environment.NewLine
                + "1: 在获得‘敏锐目光’技能之前,不要使用修改的精灵。之后显示暗色的修改精灵。" + Environment.NewLine
                + "2: 在获得'锐利之眼'技能前,显示经过黑暗修改的精灵图。之后显示经过修改的精灵图。" + Environment.NewLine
                + "3: 始终显示黑暗模组精灵。" + Environment.NewLine
                + "4:始终显示模组精灵。");
            conf_visual_applyShine = Config.Bind("Visual", "Apply Shine", true, "应用来自'锐眼'技能的'稀有度闪光'。如果启用了非深色鱼类精灵,这可能会显得多余。");
            conf_visual_keepUncaughtDark = Config.Bind("Visual", "Keep Uncaught Dark", true, "无论是否拥有“锐眼”技能,始终将未捕获的鱼类保持为暗色。");
            conf_chance_levelScale = Config.Bind("Chance", "Level Bonus", true, "根据你的钓鱼等级,对鱼群生成几率施加一个微小的加成。");
            conf_chance_baitBonus = Config.Bind("Chance", "Bait Bonus", true, "根据所使用的诱饵,对鱼类生成概率施加一个小额加成。");
            conf_chance_mult = Config.Bind("Chance", "Multiplier", 1f, "应用于整体鱼生成几率的乘数。非常高的乘数可能导致比正常情况更多的鱼生成。为平衡这一点,某些数值下生成延迟可能会增加。");

            _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);
    }

    [HarmonyPatch(typeof(Fish), "Start")]
    class HarmonyPatch_Fish_Start
    {
        private static bool Prefix(Fish __instance)
        {
            try
            {
                FishData fishData = __instance.fishData;
                if (conf_visual_applyShine.Value)
                {
                    if (fishData.rarity > ItemRarity.Uncommon && GameSave.Fishing.GetNode("Fishing8b"))
                    {
                        var particles = SingletonBehaviour<Prefabs>.Instance.fishShineParticles;
                        particles.GetComponent<ParticleSystemRenderer>().sortingOrder = 1000;
                        var main = particles.main;
                        switch (fishData.rarity)
                        {
                            case ItemRarity.Rare:
                                main.startColor = new Color(0f, 50f, 255f);
                                break;
                            case ItemRarity.Epic:
                                main.startColor = new Color(144f, 0f, 255f);
                                break;
                            case ItemRarity.Legendary:
                                main.startColor = new Color(255f, 200f, 0f);
                                break;
                        }
                        Instantiate(particles, __instance.transform.position + new Vector3(0f, -1f, -1f), Quaternion.identity, __instance.transform);
                    }
                }
                typeof(Fish).GetProperty("Caught").SetValue(__instance, false);

                if (conf_visual_applySprite.Value != 0 && (conf_visual_applySprite.Value > SpriteMode.Disabled_KeenEye_Dark || GameSave.Fishing.GetNode("Fishing8b")))
                {
                    var offsetBugFix = "ElvenForest2".Equals(ScenePortalManager.ActiveSceneName, StringComparison.Ordinal) ? -1.2f : 0.1f;
                    var obj = Instantiate(fishData.fishVisualPrefab, __instance.transform.position + new Vector3(0f, 0f, offsetBugFix), Quaternion.identity, __instance.transform);
                    var meshGen = obj.GetComponentInChildren<MeshGenerator>();
                    meshGen.transform.localScale = new Vector3(0.6f, 0.6f, 0.6f);
                    meshGen.GetComponent<MeshRenderer>().material.color = new Color(0f, 0f, 0f, 0.65f);
                    _meshToFishDict[meshGen] = new FishEntry() { fish = __instance, oldDirection = __instance.FacingDirection };
                    __instance.Graphics.position = new Vector3(0f, 0f, -99999f);
                }
#if DEBUG
                logger.LogWarning(fishData.Name + " (" + fishData.rarity + ") in " + ScenePortalManager.ActiveSceneName);
#endif
                return false;
            }
            catch (Exception e)
            {
                LogError(e);
                return true;
            }
        }
    }

    [HarmonyPatch(typeof(FishSpawnManager), "Initialize")]
    class HarmonyPatch_FishSpawnManager_Initialize
    {
        private static bool Prefix(FishSpawnManager __instance)
        {
            try
            {
                if (conf_chance_levelScale.Value || conf_chance_baitBonus.Value || conf_chance_mult.Value != 1f)
                {
                    var _spawnRoutine = typeof(FishSpawnManager).GetField("_spawnRoutine", BindingFlags.NonPublic | BindingFlags.Instance);
                    var _fishSpawners = typeof(FishSpawnManager).GetField("_fishSpawners", BindingFlags.NonPublic | BindingFlags.Instance);
                    var currentFishDictionary = (Dictionary<int, Fish>)typeof(FishSpawnManager).GetField("currentFishDictionary", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(__instance);
                    if (_spawnRoutine.GetValue(__instance) is Coroutine r)
                    {
                        __instance.StopCoroutine(r);
                    }
                    _fishSpawners.SetValue(__instance, UnityEngine.Object.FindObjectsOfType<FishSpawner>());
                    var _fishSpawnersVal = (FishSpawner[])_fishSpawners.GetValue(__instance);
                    for (int i = 0; i < _fishSpawnersVal.Length; i++)
                    {
                        FishSpawner fishSpawner = _fishSpawnersVal[i];
                        fishSpawner.ID = (ushort)(i + ScenePortalManager.ActiveSceneIndex * 100);
                        if (currentFishDictionary.TryGetValue(fishSpawner.ID, out var value))
                        {
                            fishSpawner.CurrentFish = value;
                        }
                    }
                    _spawnRoutine.SetValue(__instance, __instance.StartCoroutine(FishSpawnManager_SpawnRoutine.SpawnRoutine(__instance)));
                    return false;
                }
                return true;
            }
            catch (Exception e)
            {
                LogError(e);
                return true;
            }
        }
    }

    [HarmonyPatch(typeof(MeshGenerator), "ChangeSprite")]
    class HarmonyPatch_MeshGenerator_ChangeSprite
    {
        private static void Postfix(MeshGenerator __instance)
        {
            try
            {
                if (_meshToFishDict.TryGetValue(__instance, out var fishEntry))
                {
                    var curDir = fishEntry.fish.FacingDirection;
                    var oldDir = fishEntry.oldDirection;
                    fishEntry.oldDirection = curDir;
                    if (curDir < Direction.East)
                    {
                        if (curDir != oldDir) __instance.flip = UnityEngine.Random.Range(0, 2) == 1;
                        if ((__instance.flip && curDir == Direction.North) || (!__instance.flip && curDir == Direction.South))
                        {
                            __instance.transform.eulerAngles = new Vector3(-5f, -80f, -60f);
                        }
                        else
                        {
                            __instance.transform.eulerAngles = new Vector3(-5f, 80f, 60f);
                        }
                    }
                    else
                    {
                        __instance.flip = curDir == Direction.West;
                        __instance.transform.eulerAngles = Vector3.zero;
                    }
                    if (conf_visual_applySprite.Value == SpriteMode.Disabled_KeenEye_Dark
                        || conf_visual_applySprite.Value == SpriteMode.Always_Dark
                        || (conf_visual_applySprite.Value == SpriteMode.Dark_KeenEye_Light && !GameSave.Fishing.GetNode("Fishing8b"))
                        || (conf_visual_keepUncaughtDark.Value && !GameSave.CurrentCharacter.Encylopdeia.ContainsKey((short)fishEntry.fish.fishData.id)))
                    {
                        __instance.GetComponent<MeshRenderer>().material.color = new Color(0f, 0f, 0f, 0.65f);
                    }
                }
            }
            catch (Exception e)
            {
                LogError(e);
            }
        }
    }

    [HarmonyPatch(typeof(MeshGenerator), "OnDestroy")]
    class HarmonyPatch_MeshGenerator_OnDestroy
    {
        private static void Postfix(MeshGenerator __instance)
        {
            try
            {
                _meshToFishDict.Remove(__instance);
            }
            catch (Exception e)
            {
                LogError(e);
            }
        }
    }

    [HarmonyPatch(typeof(Fish), "BiteRoutine")]
    class HarmonyPatch_Fish_BiteRoutine
    {
        private static bool Prefix(Fish __instance, ref IEnumerator __result)
        {
            try
            {
                if (conf_visual_override.Value)
                {
                    __result = BiteRoutine(__instance);
                    return false;
                }
                return true;
            }
            catch (Exception e)
            {
                LogError(e);
                return true;
            }
        }

        private static IEnumerator BiteRoutine(Fish __instance)
        {
            if (!__instance.isActiveAndEnabled)
                yield break;

            float seconds = UnityEngine.Random.Range(0.1f, 0.2f);
            yield return new WaitForSeconds(seconds);

            var _targetBobber = typeof(Fish).GetField("_targetBobber", BindingFlags.NonPublic | BindingFlags.Instance);
            var _targetBobberInstance = (Bobber)_targetBobber.GetValue(__instance);

            Transform graphics = null;
            if (_meshToFishDict.FirstOrDefault(f => f.Value.fish == __instance) is KeyValuePair<MeshGenerator, FishEntry> fishEntry)
            {
                graphics = fishEntry.Value.fish.transform;
            }
            else graphics = __instance.Graphics;

            if (Wish.Utilities.Chance(0.65f))
            {
                _targetBobberInstance.SmallBite();
                float num = UnityEngine.Random.Range(1f, 2f);
                switch (__instance.FacingDirection)
                {
                    case Direction.North:
                        graphics.DOBlendableLocalMoveBy(new Vector3(0f, -0.35f, 0f), num);
                        break;
                    case Direction.South:
                        graphics.DOBlendableLocalMoveBy(new Vector3(0f, 0.35f, 0f), num);
                        break;
                    case Direction.East:
                        graphics.DOBlendableLocalMoveBy(new Vector3(-0.35f, 0f, 0f), num);
                        break;
                    case Direction.West:
                        graphics.DOBlendableLocalMoveBy(new Vector3(0.35f, 0f, 0f), num);
                        break;
                }
                yield return new WaitForSeconds(num);
                switch (__instance.FacingDirection)
                {
                    case Direction.North:
                        graphics.DOBlendableLocalMoveBy(new Vector3(0f, 0.35f, 0f), 0.275f);
                        break;
                    case Direction.South:
                        graphics.DOBlendableLocalMoveBy(new Vector3(0f, -0.35f, 0f), 0.275f);
                        break;
                    case Direction.East:
                        graphics.DOBlendableLocalMoveBy(new Vector3(0.35f, 0f, 0f), 0.275f);
                        break;
                    case Direction.West:
                        graphics.DOBlendableLocalMoveBy(new Vector3(-0.35f, 0f, 0f), 0.275f);
                        break;
                }
                yield return new WaitForSeconds(0.275f);

                if (Wish.Utilities.Chance(0.4f))
                {
                    _targetBobberInstance.SmallBite();
                    float num2 = UnityEngine.Random.Range(0.7f, 2.6f);
                    switch (__instance.FacingDirection)
                    {
                        case Direction.North:
                            graphics.DOBlendableLocalMoveBy(new Vector3(0f, -0.35f, 0f), num2);
                            break;
                        case Direction.South:
                            graphics.DOBlendableLocalMoveBy(new Vector3(0f, 0.35f, 0f), num2);
                            break;
                        case Direction.East:
                            graphics.DOBlendableLocalMoveBy(new Vector3(-0.35f, 0f, 0f), num2);
                            break;
                        case Direction.West:
                            graphics.DOBlendableLocalMoveBy(new Vector3(0.35f, 0f, 0f), num2);
                            break;
                    }
                    yield return new WaitForSeconds(num2);
                    switch (__instance.FacingDirection)
                    {
                        case Direction.North:
                            graphics.DOBlendableLocalMoveBy(new Vector3(0f, 0.35f, 0f), 0.275f);
                            break;
                        case Direction.South:
                            graphics.DOBlendableLocalMoveBy(new Vector3(0f, -0.35f, 0f), 0.275f);
                            break;
                        case Direction.East:
                            graphics.DOBlendableLocalMoveBy(new Vector3(0.35f, 0f, 0f), 0.275f);
                            break;
                        case Direction.West:
                            graphics.DOBlendableLocalMoveBy(new Vector3(-0.35f, 0f, 0f), 0.275f);
                            break;
                    }
                    yield return new WaitForSeconds(0.275f);
                }

                _targetBobberInstance.Bite();
                _targetBobberInstance.FishingRod.HasFish(__instance);
                typeof(Fish).GetField("fishRunAwayTween", BindingFlags.NonPublic | BindingFlags.Instance).SetValue(__instance, DOVirtual.DelayedCall(1f, delegate
                {
                    if (!(_targetBobber != null) || !_targetBobberInstance.FishingRod.Reeling)
                        __instance.Flee();
                    var canBite = typeof(Fish).GetField("canBite", BindingFlags.NonPublic | BindingFlags.Instance);
                    canBite.SetValue(__instance, false);
                    DOVirtual.DelayedCall(4f, delegate
                    {
                        canBite.SetValue(__instance, true);
                    });
                    if (_targetBobber != null)
                    {
                        if (_targetBobberInstance.FishingRod.CurrentFish == __instance)
                            _targetBobberInstance.FishingRod.CurrentFish = null;
                        if (_targetBobberInstance.FishingRod.TargetFish == __instance)
                            _targetBobberInstance.FishingRod.TargetFish = null;
                    }
                    __instance.SetTarget(__instance.transform.position + new Vector3(UnityEngine.Random.Range(-1f, 1f), UnityEngine.Random.Range(-1f, 1f)).normalized * 2.25f, 0f);
                    _targetBobberInstance?.FailMiniGame();
                    _targetBobber.SetValue(__instance, null);
                }));
            }
        }
    }

    private class FishSpawnManager_SpawnRoutine
    {
        public static IEnumerator SpawnRoutine(FishSpawnManager __instance)
        {
            var time = Math.Max(0.25f, 0.25f * (conf_chance_mult.Value < 3f ? conf_chance_mult.Value / 1.5f : conf_chance_mult.Value / 3));
            var ResetCurrentFishDictionary = typeof(FishSpawnManager).GetMethod("ResetCurrentFishDictionary", BindingFlags.NonPublic | BindingFlags.Instance);
            var _fishSpawners = (FishSpawner[])typeof(FishSpawnManager).GetField("_fishSpawners", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(__instance);
            var numFish = typeof(FishSpawnManager).GetField("numFish", BindingFlags.NonPublic | BindingFlags.Instance);
            var buffs = (Dictionary<BuffType, Buff>)typeof(Player).GetField("_buffs", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(Player.Instance);

            while (true)
            {
                var skillBonus = (conf_chance_levelScale.Value ? (SingletonBehaviour<GameSave>.Instance.CurrentSave.characterData.Professions[ProfessionType.Fishing].level * 0.0003f - 0.0006f) : 0f);
                ResetCurrentFishDictionary.Invoke(__instance, null);

                if (_fishSpawners.Length != 0)
                {
                    var numFishVal = (int)numFish.GetValue(__instance);
                    float fishSpawnMultiplier = 1f;
                    if (GameSave.Fishing.GetNode("Fishing9b"))
                    {
                        fishSpawnMultiplier = 1.05f + 0.05f * (float)GameSave.Fishing.GetNodeAmount("Fishing9b");
                    }

                    if (numFishVal < 10)
                    {
                        List<int> list = Enumerable.Range(0, _fishSpawners.Length).ToList();
                        list.Shuffle();
                        foreach (int item in list)
                        {
                            if (item >= 0 && item < _fishSpawners.Length)
                            {
                                FishSpawner fishSpawner = _fishSpawners[item];
                                if (fishSpawner == null) continue;
                                float num = (0.03f * fishSpawnMultiplier * FishSpawnManager.fishSpawnGlobalMultiplier + skillBonus) * conf_chance_mult.Value;
                                if (conf_chance_baitBonus.Value)
                                {
                                    num += 0.05f * buffs.Count(f => nameof(f.Key).EndsWith("Bait", StringComparison.Ordinal) && !f.Value.Complete());
                                }
                                if ((bool)Player.Instance)
                                {
                                    FishingRod fishingRod = Player.Instance.FishingRod;
                                    if ((bool)fishingRod)
                                    {
                                        switch (fishingRod.fishingRodType)
                                        {
                                            case FishingRodType.Withergate:
                                                if (SceneSettingsManager.Instance.GetCurrentSceneSettings != null && SceneSettingsManager.Instance.GetCurrentSceneSettings.townType == TownType.Withergate)
                                                    num *= 1.15f;
                                                break;
                                            case FishingRodType.Nelvari:
                                                if (SceneSettingsManager.Instance.GetCurrentSceneSettings != null && SceneSettingsManager.Instance.GetCurrentSceneSettings.townType == TownType.Nelvari)
                                                    num *= 1.15f;
                                                break;
                                        }
                                    }
                                }
                                float num2 = ((numFishVal == 0) ? 1.25f : (1f / numFishVal));
                                var currentFishDictionary = (Dictionary<int, Fish>)typeof(FishSpawnManager).GetField("currentFishDictionary", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(__instance);
                                if (FishSpawnManager.CanSpawnFish && fishSpawner.CurrentFish == null && (!currentFishDictionary.TryGetValue(fishSpawner.ID, out var value) || value == null) && UnityEngine.Random.value < num * num2)
                                {
                                    FishData fish = fishSpawner.GetFish();
                                    if ((bool)fish)
                                    {
                                        Vector3 position = fishSpawner.transform.position;
                                        __instance.SpawnFish(fish.name, position, fishSpawner.ID);
                                    }
                                }
                                if ((bool)fishSpawner.CurrentFish)
                                {
                                    numFishVal++;
                                    numFish.SetValue(__instance, numFishVal);
                                }
                            }
                        }
                    }
                    yield return new WaitForSeconds(time + skillBonus);
                }
                else
                {
                    yield return new WaitForSeconds(time + (skillBonus * 10f));
                }
            }
        }
    }

    enum SpriteMode
    {
        [Description("0:已禁用。")]
        Disabled,
        [Description("1:此前禁用,此后黑暗。")]
        Disabled_KeenEye_Dark,
        [Description("2: 之前黑暗,之后光明。")]
        Dark_KeenEye_Light,
        [Description("3: 始终黑暗。")]
        Always_Dark,
        [Description("4: 始终明亮。")]
        Always_Light
    }

    private class FishEntry
    {
        public Fish fish;
        public Direction oldDirection;
    }
}