From b46fdbf59f13bdcb84bd18bbf832c5d2adbed572 Mon Sep 17 00:00:00 2001 From: fenghou Date: Sun, 25 Nov 2018 17:59:42 +0800 Subject: [PATCH] =?UTF-8?q?feat(Majordomo):=20update=20to=200.5.0=20?= =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E5=8A=9F=E8=83=BD=EF=BC=9A=E8=B5=84=E6=BA=90?= =?UTF-8?q?=E9=87=8F=E6=9C=AA=E8=BE=BE=E5=88=B0=E9=A2=84=E6=9C=9F=E6=97=B6?= =?UTF-8?q?=E8=87=AA=E8=A1=8C=E8=B4=AD=E5=85=A5=E3=80=82=20=E5=A2=9E?= =?UTF-8?q?=E5=8A=A0=E5=8A=9F=E8=83=BD=EF=BC=9A=E8=B5=84=E6=BA=90=E4=B8=8D?= =?UTF-8?q?=E8=B6=B3=E6=97=B6=E8=BF=9B=E8=A1=8C=E6=8F=90=E9=86=92=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Majordomo/.gitignore | 1 + Majordomo/AutoHarvest.cs | 47 +-- Majordomo/Info.json | 2 +- Majordomo/Majordomo.cs | 141 +++++++- Majordomo/README.md | 41 ++- Majordomo/ResourceMaintainer.cs | 564 ++++++++++++++++++++++++++++++++ 6 files changed, 748 insertions(+), 48 deletions(-) create mode 100644 Majordomo/.gitignore create mode 100644 Majordomo/ResourceMaintainer.cs diff --git a/Majordomo/.gitignore b/Majordomo/.gitignore new file mode 100644 index 0000000..fcd3efb --- /dev/null +++ b/Majordomo/.gitignore @@ -0,0 +1 @@ +*.psd diff --git a/Majordomo/AutoHarvest.cs b/Majordomo/AutoHarvest.cs index ba7dd5a..3a9c14d 100644 --- a/Majordomo/AutoHarvest.cs +++ b/Majordomo/AutoHarvest.cs @@ -14,11 +14,8 @@ namespace Majordomo { - class AutoHarvest + public class AutoHarvest { - // 自动收获过月事件 ID - public const int TURN_EVENT_ID = 1001; - // 收获物类型 public const int BOOTY_TYPE_RESOURCE = 0; public const int BOOTY_TYPE_ITEM = 1; @@ -81,17 +78,14 @@ public static void RecordBooty(int[] booty) public static string GetBootiesSummary() { - if (AutoHarvest.harvestedResources.Count == 0 && - AutoHarvest.harvestedItems.Count == 0 && - AutoHarvest.harvestedActors.Count == 0) - return "您的管家禀告:本月尚无收获。\n"; - - string summary = "您的管家禀告了如下收获:\n"; - + string summary = ""; summary += GetHarvestedResourcesSummary(); summary += GetHarvestedItemsSummary(); summary += GetHarvestedActorsSummary(); + if (string.IsNullOrEmpty(summary)) summary = "本月尚无收获。\n"; + else summary = "本月收获" + summary; + return summary; } @@ -237,6 +231,8 @@ private static bool GetBooty(int partId, int placeId, int buildingIndex, int[] b DateFile.instance.FamilyActorLeave(bootyId, 16); DateFile.instance.MoveToPlace(int.Parse(DateFile.instance.GetGangDate(16, 3)), int.Parse(DateFile.instance.GetGangDate(16, 4)), bootyId, fromPart: false); UIDate.instance.UpdateManpower(); + + AutoHarvest.LogNewVillagerMerchantType(bootyId); break; } default: @@ -260,30 +256,13 @@ private static bool GetBooty(int partId, int placeId, int buildingIndex, int[] b } - // 注册月初事件 - // changTrunEvent format: [turnEventId, param1, param2, ...] - // current changTrunEvent: [AutoHarvest.TURN_EVENT_ID] - // current GameObject.name: "TrunEventIcon,{AutoHarvest.TURN_EVENT_ID}" - public static void RegisterEvent(ref UIDate __instance) - { - __instance.changTrunEvents.Add(new int[] { AutoHarvest.TURN_EVENT_ID }); - } - - - // 设置月初事件文字 - public static void SetEventText(WindowManage __instance, bool on, GameObject tips) + // 在 log 中输出新村民的潜在商队 + private static void LogNewVillagerMerchantType(int actorId) { - if (tips == null || !on) return; - if (tips.tag != "TrunEventIcon") return; - - string[] eventParams = tips.name.Split(','); - int eventId = (eventParams.Length > 1) ? int.Parse(eventParams[1]) : 0; - - if (eventId != AutoHarvest.TURN_EVENT_ID) return; - - __instance.informationName.text = DateFile.instance.trunEventDate[eventId][0]; - - __instance.informationMassage.text = AutoHarvest.GetBootiesSummary(); + int gangType = int.Parse(DateFile.instance.GetActorDate(actorId, 9, addValue: false)); + int merchantType = int.Parse(DateFile.instance.GetGangDate(gangType, 16)); + string merchantTypeName = DateFile.instance.storyShopDate[merchantType][0]; + Main.Logger.Log($"新村民:{DateFile.instance.GetActorName(actorId)},潜在商队:{merchantTypeName}"); } } } diff --git a/Majordomo/Info.json b/Majordomo/Info.json index 6861c99..54c4fdc 100644 --- a/Majordomo/Info.json +++ b/Majordomo/Info.json @@ -2,7 +2,7 @@ "Id": "Majordomo", "DisplayName": "太吾管家", "Author": "fenghou", - "Version": "0.0.2", + "Version": "0.5.0", "AssemblyName": "Majordomo.dll", "EntryMethod": "Majordomo.Main.Load", "Requirements": ["BaseResourceMod-1.0.7"] diff --git a/Majordomo/Majordomo.cs b/Majordomo/Majordomo.cs index 9305536..24848a8 100644 --- a/Majordomo/Majordomo.cs +++ b/Majordomo/Majordomo.cs @@ -17,10 +17,15 @@ namespace Majordomo { public class Settings : UnityModManager.ModSettings { - - public bool autoHarvestItems = true; // 自动收获物品 - public bool autoHarvestActors = true; // 自动接纳新村民 + // 自动收获 + public bool autoHarvestItems = true; // 自动收获物品 + public bool autoHarvestActors = true; // 自动接纳新村民 + // 资源维护 + public int resMinHolding = 3; // 资源保有量警戒值(每月消耗量的倍数) + public int[] resIdealHolding = null; // 期望资源保有量 + public float resInitIdealHoldingRatio = 0.8f; // 期望资源保有量的初始值(占当前最大值的比例) + public int moneyMinHolding = 10000; // 银钱最低保有量(高于此值管家可花费银钱进行采购) public override void Save(UnityModManager.ModEntry modEntry) { @@ -72,11 +77,36 @@ public static bool OnToggle(UnityModManager.ModEntry modEntry, bool value) static void OnGUI(UnityModManager.ModEntry modEntry) { GUILayout.BeginHorizontal(); - GUILayout.Label("自动收获"); + GUILayout.Label("自动收获"); GUILayout.EndHorizontal(); GUILayout.BeginHorizontal(); - Main.settings.autoHarvestItems = GUILayout.Toggle(Main.settings.autoHarvestItems, "自动收获物品"); - Main.settings.autoHarvestActors = GUILayout.Toggle(Main.settings.autoHarvestActors, "自动接纳新村民"); + Main.settings.autoHarvestItems = GUILayout.Toggle(Main.settings.autoHarvestItems, "自动收获物品", GUILayout.Width(120)); + Main.settings.autoHarvestActors = GUILayout.Toggle(Main.settings.autoHarvestActors, "自动接纳新村民", GUILayout.Width(120)); + GUILayout.FlexibleSpace(); + GUILayout.EndHorizontal(); + + GUILayout.BeginHorizontal(); + GUILayout.Label("\n资源维护"); + GUILayout.EndHorizontal(); + GUILayout.BeginHorizontal(); + GUILayout.Label("资源保有量警戒值:每月消耗量的"); + var resMinHolding = GUILayout.TextField(Main.settings.resMinHolding.ToString(), 4, GUILayout.Width(45)); + if (GUI.changed && !int.TryParse(resMinHolding, out Main.settings.resMinHolding)) + { + Main.settings.resMinHolding = 3; + } + GUILayout.Label("倍,低于此值管家会进行提醒"); + GUILayout.FlexibleSpace(); + GUILayout.EndHorizontal(); + GUILayout.BeginHorizontal(); + GUILayout.Label("银钱最低保有量:"); + var moneyMinHolding = GUILayout.TextField(Main.settings.moneyMinHolding.ToString(), 9, GUILayout.Width(85)); + if (GUI.changed && !int.TryParse(moneyMinHolding, out Main.settings.moneyMinHolding)) + { + Main.settings.moneyMinHolding = 10000; + } + GUILayout.Label(",高于此值管家可花费银钱进行采购"); + GUILayout.FlexibleSpace(); GUILayout.EndHorizontal(); } @@ -88,24 +118,72 @@ static void OnSaveGUI(UnityModManager.ModEntry modEntry) } - // 月初自动工作入口 + public class TurnEvent + { + // 太吾管家过月事件 ID + public const int EVENT_ID = 1001; + + + // 注册过月事件 + // changTrunEvent format: [turnEventId, param1, param2, ...] + // current changTrunEvent: [TurnEvent.EVENT_ID] + // current GameObject.name: "TrunEventIcon,{TurnEvent.EVENT_ID}" + public static void RegisterEvent(UIDate __instance) + { + __instance.changTrunEvents.Add(new int[] { TurnEvent.EVENT_ID }); + } + + + // 设置过月事件文字 + public static void SetEventText(WindowManage __instance, bool on, GameObject tips) + { + if (tips == null || !on) return; + if (tips.tag != "TrunEventIcon") return; + + string[] eventParams = tips.name.Split(','); + int eventId = (eventParams.Length > 1) ? int.Parse(eventParams[1]) : 0; + + if (eventId != TurnEvent.EVENT_ID) return; + + __instance.informationName.text = DateFile.instance.trunEventDate[eventId][0]; + + __instance.informationMassage.text = "您的管家向您禀报:\n" + AutoHarvest.GetBootiesSummary(); + + if (!string.IsNullOrEmpty(ResourceMaintainer.shoppingRecord)) + { + __instance.informationMassage.text += "\n" + ResourceMaintainer.shoppingRecord; + } + + if (!string.IsNullOrEmpty(ResourceMaintainer.resourceWarning)) + { + __instance.informationMassage.text += "\n" + ResourceMaintainer.resourceWarning; + } + } + } + + + // Patch: 展示过月事件 [HarmonyPatch(typeof(UIDate), "SetTrunChangeWindow")] public static class UIDate_SetTrunChangeWindow_Patch { - private static bool Prefix(ref UIDate __instance) + private static bool Prefix(UIDate __instance) { if (!Main.enabled) return true; AutoHarvest.GetAllBooties(); - AutoHarvest.RegisterEvent(ref __instance); + ResourceMaintainer.TryBuyingResources(); + + ResourceMaintainer.UpdateResourceWarning(); + + TurnEvent.RegisterEvent(__instance); return true; } } - // 月初事件图标文字 + // Patch: 设置浮窗文字 [HarmonyPatch(typeof(WindowManage), "WindowSwitch")] public static class WindowManage_WindowSwitch_Patch { @@ -113,7 +191,48 @@ static void Postfix(WindowManage __instance, bool on, GameObject tips) { if (!Main.enabled) return; - AutoHarvest.SetEventText(__instance, on, tips); + TurnEvent.SetEventText(__instance, on, tips); + } + } + + + // Patch: 创建 UI + [HarmonyPatch(typeof(UIDate), "Start")] + public static class UIDate_Start_Patch + { + static void Postfix() + { + if (!Main.enabled) return; + + ResourceMaintainer.InitialzeResourcesIdealHolding(); + } + } + + + // Patch: 更新 UI + [HarmonyPatch(typeof(UIDate), "Update")] + public static class UIDate_Update_Patch + { + static void Postfix() + { + if (!Main.enabled) return; + + ResourceMaintainer.ShowResourceIdealHoldingText(); + } + } + + + // Patch: 显示浮窗 + [HarmonyPatch(typeof(WindowManage), "LateUpdate")] + public static class WindowManage_LateUpdate_Patch + { + static bool Prefix(WindowManage __instance) + { + if (!Main.enabled) return true; + + ResourceMaintainer.InterfereFloatWindow(__instance); + + return true; } } } diff --git a/Majordomo/README.md b/Majordomo/README.md index a0a73cf..7daf1af 100644 --- a/Majordomo/README.md +++ b/Majordomo/README.md @@ -1,9 +1,46 @@ # 简介 -帮助太吾传人收获太吾村的产出,并在月初事件列表中展示当月数据统计。 -后续还将加入自动购入资源、自动安排工作人员、提出改善经营状况的建议等功能。 +此 mod 作用为帮助太吾传人管理太吾村,目前实现的功能有: + +- 收获太吾村的产出 +- 在过月事件列表中展示统计信息 +- 资源量未达到预期时自行购入 +- 资源不足时进行提醒 + +后续还将加入自动安排工作人员、提出改善经营状况的建议等功能。 + + +# 功能说明 +## 收获太吾村的产出 +- 在过月时自动进行产出品(资源、物品、村民)的收获。 +- 可配置不自动收获物品、不自动接纳新村民。 + +## 在过月事件列表中展示统计数据 +目前展示的统计信息有: + +- 上月自动收获统计 +- 上月资源购买情况 +- 资源严重不足提醒 + +## 资源量未达到预期时自行购入 +- 主界面的资源栏上,当前资源的下方,会显示灰色的资源期望保有量。当资源低于期望值时,管家会尝试自行购买资源。 +- 在资源栏上点击左键会增加资源期望保有量,点击右键会减少资源期望保有量,可按住鼠标左/右键快速调整该值。 +- 当鼠标移动到屏幕顶部时,才会显示资源期望保有量。 +- 如果当前银钱大于配置项“银钱最低保有量”,且高于配置项“资源保有量警戒值”,则换季时会用多出的银钱按照期望值购买资源。每次可购买量和服牛帮行商保持一致。 + +## 资源不足时进行提醒 +- 配置项:资源保有量警戒值(为每月资源消耗量的倍数),如果当前资源低于警戒值,则管家会在过月事件中进行提醒。 + + +# 注意事项 +在安装 0.0.2 及后续版本的时候,必须先卸载 0.0.1 版再安装,否则新旧版本的文件混合,就会导致在读档界面卡死。 +如果想手动确认旧版是否已卸载,可以看游戏根目录的 Mods 目录下,是否有 Majordomo 文件夹,没有就是卸载了。 # 历史 +## 0.5.0 +- 增加功能:资源量未达到预期时自行购入。 +- 增加功能:资源不足时进行提醒。 + ## 0.0.2 - 可选择不自动收获物品(也同时不显示在统计中)。 - 更改月初事件图标。 diff --git a/Majordomo/ResourceMaintainer.cs b/Majordomo/ResourceMaintainer.cs new file mode 100644 index 0000000..a59a287 --- /dev/null +++ b/Majordomo/ResourceMaintainer.cs @@ -0,0 +1,564 @@ +using Harmony12; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Reflection.Emit; +using System.Runtime.Serialization; +using System.Text; +using UnityEngine; +using UnityEngine.EventSystems; +using UnityEngine.UI; +using UnityModManagerNet; + + +namespace Majordomo +{ + public class ResourceInfo + { + public int current; + public int max; + public int consumed; + + + public ResourceInfo(int current, int max, int consumed) + { + this.current = current; + this.max = max; + this.consumed = consumed; + } + } + + + public class TemporaryResourceShop + { + private class ItemInfo + { + public int id; + public int price; + public int amount; + + + public ItemInfo(int id, int price, int amount) + { + this.id = id; + this.price = price; + this.amount = amount; + } + } + + + private static readonly int[] RES_PACK_ITEM_IDS = { 11, 12, 13, 14, 15 }; + + private readonly Dictionary resources; // resourceId -> itemInfo + + + // 大部分内容摘抄自 ShopSystem::SetShopItems 方法 + // 以服牛帮行商为模板 + public TemporaryResourceShop() + { + this.resources = new Dictionary(); + + int shopTyp = 0; + int levelAdd = 0; + int moneyCost = 0; + int newShopLevel = DateFile.instance.storyShopLevel[shopTyp] + levelAdd; + int shopSystemCost = 200 - Mathf.Clamp(100 * newShopLevel / 5000, -100, 100); + + for (int i = 0; i < RES_PACK_ITEM_IDS.Length; ++i) + { + int resPackItemId = RES_PACK_ITEM_IDS[i]; + int itemPrice = int.Parse(DateFile.instance.GetItemDate(resPackItemId, 905)) * shopSystemCost / 100; + int nItemAmount = Mathf.Max(3 * UnityEngine.Random.Range(50, 151) / 100, 1); + this.resources[i] = new ItemInfo(resPackItemId, itemPrice, nItemAmount); + } + + //// TEST ------------------------------------------------------------ + //Main.Logger.Log("临时商店商品:"); + //foreach (var entry in resources) + //{ + // int resourceId = entry.Key; + // var itemInfo = entry.Value; + // string name = DateFile.instance.resourceDate[resourceId][1]; + // Main.Logger.Log($" {name}: 价格 {itemInfo.price}, 数量 {itemInfo.amount}"); + //} + //// ----------------------------------------------------------------- + } + + + // 以随机顺序购买,以达到均匀化的效果 + // @return: spentMoney + public int Buy(int initialMoney, Dictionary resPacksNeedToBuy, SortedList boughtResources) + { + //// TEST ------------------------------------------------------------ + //Main.Logger.Log($"可用银钱 {initialMoney}, 待购买商品:"); + //foreach (var entry in resPacksNeedToBuy) + //{ + // int resourceId = entry.Key; + // int amount = entry.Value; + // string name = DateFile.instance.resourceDate[resourceId][1]; + // Main.Logger.Log($" {name}: 待购买数量 {amount}"); + //} + //// ----------------------------------------------------------------- + + int remainedMoney = initialMoney; + var rand = new System.Random(); + List resourceIds; + int minPrice = this.GetAvailableResourceIds(resPacksNeedToBuy, out resourceIds); + + while (resPacksNeedToBuy.Count > 0 && this.resources.Count > 0 && remainedMoney >= minPrice) + { + int resourceId = resourceIds[rand.Next(resourceIds.Count)]; + var itemInfo = this.resources[resourceId]; + + if (remainedMoney < itemInfo.price) continue; + + int nResources = int.Parse(DateFile.instance.GetItemDate(itemInfo.id, 55)) * UnityEngine.Random.Range(80, 121) / 100; + int actorId = DateFile.instance.MianActorID(); + UIDate.instance.ChangeResource(actorId, resourceId, nResources, canShow: false); + UIDate.instance.ChangeResource(actorId, ResourceMaintainer.RES_ID_MONEY, -itemInfo.price, canShow: false); + + remainedMoney -= itemInfo.price; + + int nOriResources; + boughtResources.TryGetValue(resourceId, out nOriResources); + boughtResources[resourceId] = nOriResources + nResources; + + --resPacksNeedToBuy[resourceId]; + if (resPacksNeedToBuy[resourceId] <= 0) resPacksNeedToBuy.Remove(resourceId); + + --this.resources[resourceId].amount; + if (this.resources[resourceId].amount <= 0) this.resources.Remove(resourceId); + + minPrice = this.GetAvailableResourceIds(resPacksNeedToBuy, out resourceIds); + + //// TEST -------------------------------------------------------- + //string name = DateFile.instance.resourceDate[resourceId][1]; + //Main.Logger.Log($"购买 {name}, 价格 {itemInfo.price}"); + //// ------------------------------------------------------------- + } + + return initialMoney - remainedMoney; + } + + + // 获取想买而且也有的商品列表,以及需要购买的物品的最低单价 + // 没有想买的东西,或商店没有想买的东西时,返回 int.MaxValue + private int GetAvailableResourceIds(Dictionary resPacksNeedToBuy, out List availableResIds) + { + availableResIds = new List(); + int minPrice = int.MaxValue; + + foreach (var entry in resPacksNeedToBuy) + { + int resouceId = entry.Key; + if (this.resources.ContainsKey(resouceId)) + { + availableResIds.Add(resouceId); + minPrice = Math.Min(this.resources[resouceId].price, minPrice); + } + } + + return minPrice; + } + } + + + public class ResourceMaintainer + { + // 资源 ID + public const int RES_ID_FOOD = 0; + public const int RES_ID_WOOD = 1; + public const int RES_ID_STONE = 2; + public const int RES_ID_SILK = 3; + public const int RES_ID_HERBAL = 4; + public const int RES_ID_MONEY = 5; + + public const int RESOURCE_PACK_SIZE = 480; + + + // cache for reshow last turn event window + public static string resourceWarning; + public static string shoppingRecord; + + // cached data of the last turn + public static int spentMoney; + public static SortedList boughtResources; // resourceId -> nResources + + public static Dictionary resIdealHoldingText = new Dictionary(); + public static bool changingResIdealHolding; + + + public static SortedList GetResourcesInfo() + { + int[] currResources = ActorMenu.instance.ActorResource(DateFile.instance.MianActorID()); + int maxResource = 1000 + UIDate.instance.GetMaxResource(); + + return new SortedList + { + [RES_ID_FOOD] = new ResourceInfo( + currResources[RES_ID_FOOD], + maxResource, + UIDate.instance.ResourceUPValue(RES_ID_FOOD, DateFile.instance.foodUPList)), + + [RES_ID_WOOD] = new ResourceInfo( + currResources[RES_ID_WOOD], + maxResource, + UIDate.instance.ResourceUPValue(RES_ID_WOOD, DateFile.instance.woodUPList)), + + [RES_ID_STONE] = new ResourceInfo( + currResources[RES_ID_STONE], + maxResource, + UIDate.instance.ResourceUPValue(RES_ID_STONE, DateFile.instance.stoneUPList)), + + [RES_ID_SILK] = new ResourceInfo( + currResources[RES_ID_SILK], + maxResource, + UIDate.instance.ResourceUPValue(RES_ID_SILK, DateFile.instance.silkUPList)), + + [RES_ID_HERBAL] = new ResourceInfo( + currResources[RES_ID_HERBAL], + maxResource, + UIDate.instance.ResourceUPValue(RES_ID_HERBAL, DateFile.instance.herbalUPList)), + + [RES_ID_MONEY] = new ResourceInfo( + currResources[RES_ID_MONEY], + maxResource, + UIDate.instance.ResourceUPValue(RES_ID_MONEY, DateFile.instance.moneyUPList)), + }; + } + + + public static void TryBuyingResources() + { + ResourceMaintainer.shoppingRecord = ""; + + // 过季的时候才能采购 + int solarTerms = DateFile.instance.GetDayTrun(); + if (!(solarTerms == 4 || solarTerms == 10 || solarTerms == 16 || solarTerms == 22)) return; + + var resourcesInfo = ResourceMaintainer.GetResourcesInfo(); + + var resPacksNeedToBuy = ResourceMaintainer.GetResPacksNeedToBuy(resourcesInfo); + if (resPacksNeedToBuy.Count == 0) return; + + var moneyInfo = resourcesInfo[RES_ID_MONEY]; + int usableMoney = Math.Min( + moneyInfo.current - Main.settings.moneyMinHolding, + moneyInfo.current - Math.Max(-moneyInfo.consumed * Main.settings.resMinHolding, 0)); + if (usableMoney <= 0) return; + + ResourceMaintainer.BuyResources(usableMoney, resPacksNeedToBuy); + } + + + private static Dictionary GetResPacksNeedToBuy(SortedList resourcesInfo) + { + // resouceId -> nPacks + Dictionary resPacksNeedToBuy = new Dictionary(); + + for (int i = 0; i < Main.settings.resIdealHolding.Length; ++i) + { + int ideal = Main.settings.resIdealHolding[i]; + int current = resourcesInfo[i].current; + int nLackedPacks = (ideal - current) / RESOURCE_PACK_SIZE; + if (nLackedPacks > 0) resPacksNeedToBuy[i] = nLackedPacks; + } + + return resPacksNeedToBuy; + } + + + private static void BuyResources(int usableMoney, Dictionary resPacksNeedToBuy) + { + TemporaryResourceShop shop = new TemporaryResourceShop(); + ResourceMaintainer.boughtResources = new SortedList(); + ResourceMaintainer.spentMoney = shop.Buy(usableMoney, resPacksNeedToBuy, ResourceMaintainer.boughtResources); + ResourceMaintainer.UpdateShoppingRecord(); + } + + + private static void UpdateShoppingRecord() + { + string text = ""; + + if (ResourceMaintainer.spentMoney > 0) + { + text += "花费银钱\u00A0" + ResourceMaintainer.spentMoney + "\u00A0,购入了"; + + foreach (var entry in ResourceMaintainer.boughtResources) + { + int resourceId = entry.Key; + int nResources = entry.Value; + string name = DateFile.instance.resourceDate[resourceId][1]; + text += name + "\u00A0" + nResources + "、"; + } + text = text.Substring(0, text.Length - 1) + "。\n"; + } + + ResourceMaintainer.shoppingRecord = text; + } + + + // 因为每时每刻的资源数量都可能变化,因此要显示月初的资源警示,就必须缓存住 + public static void UpdateResourceWarning() + { + string text = ""; + + foreach (var entry in ResourceMaintainer.GetResourcesInfo()) + { + var resourceId = entry.Key; + var resourceInfo = entry.Value; + if (resourceInfo.current < -resourceInfo.consumed * Main.settings.resMinHolding) + { + string name = DateFile.instance.resourceDate[(int)resourceId][1]; + text += name + "、"; + } + } + if (text.Length > 0) + { + text = "以下资源库存不足,需要尽快补充,否则将导致建筑损坏:" + + text.Substring(0, text.Length - 1) + "。\n"; + text = DateFile.instance.SetColoer(20009, text); // 橙色文字 + } + + ResourceMaintainer.resourceWarning = text; + } + + + public static void InitialzeResourcesIdealHolding() + { + //ResourceMaintainer.ShowDebugInfo(); + + // 初始化资源保有目标 + if (Main.settings.resIdealHolding == null) + { + Main.settings.resIdealHolding = new int[5]; + + var resourcesInfo = ResourceMaintainer.GetResourcesInfo(); + + for (int i = 0; i < Main.settings.resIdealHolding.Length; ++i) + { + int ideal = (int)(resourcesInfo[i].max * Main.settings.resInitIdealHoldingRatio); + ideal = (ideal / 100) * 100; + Main.settings.resIdealHolding[i] = ideal; + } + } + + // 向资源图标注册鼠标事件 + GameObject[] resourceIcons = GameObject.FindGameObjectsWithTag("ResourceIcon"); + foreach (GameObject resourceIcon in resourceIcons) + { + int resourceId = int.Parse(resourceIcon.name.Split(',')[1]); + switch (resourceId) + { + case RES_ID_FOOD: + case RES_ID_WOOD: + case RES_ID_STONE: + case RES_ID_SILK: + case RES_ID_HERBAL: + var handler = resourceIcon.AddComponent(); + handler.resourceId = resourceId; + break; + } + } + + // 增加资源保有目标文本控件 + foreach (var text in GameObject.FindObjectsOfType(typeof(Text)) as Text[]) + { + switch (text.name) + { + case "FoodUPText": + ResourceMaintainer.RegisterResourceIdealHoldingText(RES_ID_FOOD, text.transform.parent); + break; + case "WoodUPText": + ResourceMaintainer.RegisterResourceIdealHoldingText(RES_ID_WOOD, text.transform.parent); + break; + case "StoneUPText": + ResourceMaintainer.RegisterResourceIdealHoldingText(RES_ID_STONE, text.transform.parent); + break; + case "SilkUPText": + ResourceMaintainer.RegisterResourceIdealHoldingText(RES_ID_SILK, text.transform.parent); + break; + case "HerbalUPText": + ResourceMaintainer.RegisterResourceIdealHoldingText(RES_ID_HERBAL, text.transform.parent); + break; + } + } + } + + + private static void ShowDebugInfo() + { + // 查看资源图标及其子控件的各种属性 + foreach (GameObject resourceIcon in GameObject.FindGameObjectsWithTag("ResourceIcon")) + { + if (resourceIcon.name != "FoodIcon,0") continue; + + Main.Logger.Log($"resourceIcon.name: {resourceIcon.name}"); + Main.Logger.Log($"resourceIcon.transform.parent.name: {resourceIcon.transform.parent.name}"); + Main.Logger.Log($"resourceIcon.transform.localPosition: {resourceIcon.transform.localPosition.ToString()}"); + + foreach (var component in resourceIcon.GetComponentsInChildren()) + { + if (component == resourceIcon) continue; + Main.Logger.Log($" {component.name}: {component.GetType().ToString()}, {component.tag}"); + if (component is Text) + { + Text text = component as Text; + Main.Logger.Log($" text.font: {text.font.ToString()}"); + Main.Logger.Log($" text.text: {text.text}"); + Main.Logger.Log($" text.color: {text.color.ToString()}"); + Main.Logger.Log($" text.fontSize: {text.fontSize}"); + Main.Logger.Log($" text.alignment: {text.alignment.ToString()}"); + } + if (component is RectTransform) + { + RectTransform rectTransform = component as RectTransform; + Main.Logger.Log($" rectTransform.localPosition: {rectTransform.localPosition.ToString()}"); + Main.Logger.Log($" rectTransform.sizeDelta: {rectTransform.sizeDelta.ToString()}"); + } + if (component is Outline) + { + Outline outline = component as Outline; + Main.Logger.Log($" outline.effectColor: {outline.effectColor.ToString()}"); + Main.Logger.Log($" outline.effectDistance: {outline.effectDistance.ToString()}"); + Main.Logger.Log($" outline.useGraphicAlpha: {outline.useGraphicAlpha}"); + } + } + } + } + + + public static bool ChangeResourceIdealHolding(int resourceId, bool add, int nMultiple) + { + int amount = (add ? 100 : -100) * nMultiple; + + int ideal = Main.settings.resIdealHolding[resourceId] + amount; + + var resourcesInfo = ResourceMaintainer.GetResourcesInfo(); + int max = resourcesInfo[resourceId].max; + + if (ideal > max) ideal = max; + if (ideal < 0) ideal = 0; + + if (Main.settings.resIdealHolding[resourceId] == ideal) return false; + + Main.settings.resIdealHolding[resourceId] = ideal; + ResourceMaintainer.resIdealHoldingText[resourceId].text = ideal.ToString(); + return true; + } + + + public static void RegisterResourceIdealHoldingText(int resourceId, Transform parent) + { + var textGO = new GameObject($"ResourceIdealHolding,{resourceId + 101}", typeof(Text)); + textGO.transform.SetParent(parent, false); + + var text = textGO.GetComponent(); + text.font = DateFile.instance.font; + text.text = "" + Main.settings.resIdealHolding[resourceId].ToString() + ""; + text.fontSize = 16; + text.color = Color.gray; + text.alignment = TextAnchor.MiddleRight; + text.gameObject.SetActive(false); + + var outline = textGO.AddComponent(); + outline.effectColor = Color.black; + outline.effectDistance = new Vector2(1, -1); + outline.useGraphicAlpha = true; + + var textTransform = textGO.GetComponent(); + textTransform.localPosition = new Vector3(-9.0f, -18.0f, 0.0f); + textTransform.sizeDelta = new Vector2(110.0f, 30.0f); + + ResourceMaintainer.resIdealHoldingText[resourceId] = text; + } + + + public static void InterfereFloatWindow(WindowManage __instance) + { + if (ResourceMaintainer.changingResIdealHolding) __instance.anTips = false; + } + + + // 根据鼠标位置,显示或隐藏资源保有目标 + public static void ShowResourceIdealHoldingText() + { + var posY = Input.mousePosition.y; + bool active = (posY >= Screen.height - 60) && (posY < Screen.height); + foreach (var text in ResourceMaintainer.resIdealHoldingText.Values) + { + text.gameObject.SetActive(active); + } + } + } + + + public class ResourceIconPointerHandler : MonoBehaviour, IPointerDownHandler, IPointerUpHandler + { + public int resourceId; + private bool isPressed; + private PointerEventData.InputButton pressedButton; + private int nPressedFrames; + + + public void OnPointerDown(PointerEventData eventData) + { + if (!Main.enabled) return; + + this.isPressed = true; + this.nPressedFrames = 0; + ResourceMaintainer.changingResIdealHolding = true; + + switch (eventData.button) + { + case PointerEventData.InputButton.Left: + case PointerEventData.InputButton.Right: + this.pressedButton = eventData.button; + this.OnClick(); + break; + } + } + + + public void OnPointerUp(PointerEventData eventData) + { + if (!Main.enabled) return; + + this.isPressed = false; + this.nPressedFrames = 0; + ResourceMaintainer.changingResIdealHolding = false; + } + + + public void Update() + { + if (!Main.enabled) return; + + if (!this.isPressed) return; + + ResourceMaintainer.changingResIdealHolding = true; + ++this.nPressedFrames; + + if (this.nPressedFrames >= 25) + { + if (this.nPressedFrames % 5 == 0) + { + int nMultiple = this.nPressedFrames / 25; + if (nMultiple < 1) nMultiple = 1; + if (nMultiple > 10) nMultiple = 10; + this.OnClick(nMultiple); + } + } + } + + + private void OnClick(int nMultiple = 1) + { + bool add = this.pressedButton == PointerEventData.InputButton.Left; + bool changed = ResourceMaintainer.ChangeResourceIdealHolding(this.resourceId, add, nMultiple); + if (changed) DateFile.instance.PlayeSE(2); + } + } +}