local re = re local sdk = sdk local d2d = d2d local imgui = imgui local log = log local json = json local draw = draw local require = require local tostring = tostring local pairs = pairs local ipairs = ipairs local math = math local string = string local table = table local type = type local Core = require("_CatLib") local CONST = require("_CatLib.const") local Imgui = require("_CatLib.imgui") local MOD_NAME = "Auto Restock" local mod = Core.NewMod(MOD_NAME) mod.EnableCJKFont(18) -- if needed local MAX_SHORTCUT_PAGE = 8 local MAX_ITEM_MYSET = nil local MAX_SHORTCUT_MYSET = 36 local function InitDefaultShortcutSet() local myset = {} for _ = 0, MAX_SHORTCUT_PAGE - 1, 1 do table.insert(myset, -1) end return myset end local function InitCustomConfig() return { DefaultSet = -1, UseCustomShortcutSet = false, DefaultShortcutSet = InitDefaultShortcutSet(), } end if mod.Config.DefaultSet == nil then mod.Config.DefaultSet = -1 mod.Config.UseCustomShortcutSet = false mod.Config.DefaultShortcutSet = InitDefaultShortcutSet() end if mod.Config.NotifyNoEnoughItems == nil then mod.Config.NotifyNoEnoughItems = true end if mod.Config.NotifyNoEnoughItemDetails == nil then mod.Config.NotifyNoEnoughItemDetails = false end if mod.Config.NotifyDetailedShortcutMenu == nil then mod.Config.NotifyDetailedShortcutMenu = true end if mod.Config.FallbackAnotherWeapon == nil then mod.Config.AlsoFillShells = true mod.Config.AlsoFillOtherItems = true mod.Config.FallbackAnotherWeapon = true -- mod.Config.AlsoApplySubWeapon = false end if mod.Config.WeaponTypeConfig == nil then mod.Config.WeaponTypeConfig = {} for _ = 0, 14-1, 1 do table.insert(mod.Config.WeaponTypeConfig, InitCustomConfig()) end end if mod.Config.EquipMySetConfig == nil then mod.Config.EquipMySetConfig = {} end local NOTIFICATION_MSG = { [CONST.LanguageType.English] = { FromDefault = "Apply default item set: %s", FromOption = "Apply item set: %s [%s]", SubWeaponFallback = "SubWeapon ", Manual = "Manual", FillShell = "Shell filled.", FillOthers = "Other items filled.", ShortcutApply = "Shortcut menu set [%s]", ShortcutApplyNotify = "Shortcut page %d set as %s", UseDefaultSetting = "Use Default Setting", InvalidSetting = "Auto Restock: Invalid Setting", NotEnough = "Item not enough.", NotEnoughDetail = "Item not enough, %s.", }, [CONST.LanguageType.SimplifiedChinese] = { FromDefault = "已补充默认道具组合:%s", FromOption = "已补充道具组合:%s%s】", SubWeaponFallback = "副武器", Manual = "手动", FillShell = "弹药已补充。", FillOthers = "其他道具已补充。", ShortcutApply = "快捷菜单已设置【%s】", ShortcutApplyNotify = "快捷菜单第 %d 页设置为:%s", UseDefaultSetting = "使用默认设置", InvalidSetting = "Auto Restock: 无效设置", NotEnough = "道具数量不足。", NotEnoughDetail = "道具数量不足:%s。", }, [CONST.LanguageType.TraditionalChinese] = { FromDefault = "已補充默认道具組合:%s", FromOption = "已補充道具組合:%s%s】", SubWeaponFallback = "副武器", Manual = "手动", FillShell = "彈藥已補充", FillOthers = "其他道具已補充。", ShortcutApply = "快捷菜單已設置【%s】", ShortcutApplyNotify = "快捷菜單第 %d 頁設置為:%s", UseDefaultSetting = "使用默认设置", InvalidSetting = "Auto Restock: 無效設定", NotEnough = "道具數量不足。", NotEnoughDetail = "道具數量不足:%s。", }, } -- app.FacilityUtil isEnoughItem(app.ItemDef.ID, System.Int16, app.ItemUtil.STOCK_TYPE) local function Localized() local lang = Core.GetLanguage() if not NOTIFICATION_MSG[lang] then lang = CONST.LanguageType.English end return NOTIFICATION_MSG[lang] end -- app.savedata.cEquipIndex ---@param equipParam app.savedata.cEquipParam ---@param myset app.savedata.cEquipMyset local function EquipSetEquals(equipParam, myset) local usingIndex = equipParam._EquipIndex local box = equipParam._EquipBox ---@type app.savedata.cMysetEquipWork[] local mysetWorks = myset._MysetEquipWork ---@type app.savedata.cEquipIndex local mysetIndex = myset._MysetParam._EquipIndex local usingIndexArray = usingIndex.Index local mysetIndexArray = mysetIndex.Index local len = usingIndexArray:get_Count() if len ~= mysetIndexArray:get_Count() then return false end for i = 0, len-1, 1 do if usingIndexArray:get_Item(i) ~= mysetIndexArray:get_Item(i) then return false end end if len ~= mysetWorks:get_Count() then return false end for i = 0, len-1, 1 do local idx = usingIndexArray:get_Item(i) if idx == -1 then goto continue end local usingWork = box:get_Item(idx) local mysetWork = mysetWorks:get_Item(i) if usingWork.Category ~= mysetWork.Category then return false end if usingWork.Category == -1 then goto continue end if usingWork.GenderType ~= mysetWork.GenderType then return false end if usingWork.FreeVal0 ~= mysetWork.FreeVal0 then return false end if usingWork.FreeVal1 ~= mysetWork.FreeVal1 then return false end -- TODO:FIXME using 有 FreeVal2/FreeVal3;Myset 有 InsectId/IsNone -- if usingWork.FreeVal2 ~= mysetWork.FreeVal2 then -- return false -- end if not Core.CSharpIntArrayEquals(usingWork.EquipmentAccessoryIdArray, mysetWork.EquipmentAccessoryIdArray) then return false end if not Core.CSharpIntArrayEquals(usingWork.BowgunCustomizeId, mysetWork.BowgunCustomizeId) then return false end ::continue:: end return true end local ShortcutUtil = Core.WrapTypedef("app.CustomShortcutUtil") local ItemMySetUtil = Core.WrapTypedef("app.ItemMySetUtil") local ItemUtil = Core.WrapTypedef("app.ItemUtil") local ItemIDFixedToID = Core.TypeField("app.ItemDef", "FixedToID"):get_data() local ItemUtil_StockTypeBoth = 2 ---@return app.savedata.cItemMySet local function GetItemMySet(index, requireValid) local data = ItemMySetUtil:StaticCall("getItemMySetData(System.Int32)", index) if requireValid == true then if data == nil then log.error(string.format("Meal at %d is nil", index)) Core.SendMessage(string.format("Meal at %d is nil", index)) return nil end if not data:isValidData() then local msgCfg = Localized() Core.SendMessage(msgCfg.InvalidSetting) return nil end end return data end ---@return app.savedata.cCustomShortcutParam local function GetShortcutMySet(index) return ShortcutUtil:StaticCall("getMysetCustomShortcut(System.Int32)", index) end -- 参数2是个看不懂且不变的数字:0x04000000 -- Core.HookFunc("app.CustomShortcutUtil", "applyMyset(System.Int32, System.Int32)", function (args) -- Core.SendMessage(tostring(sdk.to_int64(args[3])) .. "-" .. tostring(sdk.to_int64(args[4]) & 0xFFFFFFFF)) -- end) local RequestRestock = nil local RequestMessage = nil local function Apply(request, msg) if mod.Config.AlsoFillShells then ItemUtil:StaticCall("fillShellPouchItems()") end if mod.Config.AlsoFillOtherItems then ItemUtil:StaticCall("fillPouchItems()") end local data = GetItemMySet(request.ItemSet) if not data:isValidData() then return end -- DO NOT CALL DIRECTLY OR GAME WILL CRASH ItemMySetUtil:StaticCall("applyMySetToPouch(System.Int32)", request.ItemSet) -- ItemMySetUtil:StaticCall("apply(app.savedata.cItemMySet)", data) if request.UseShortcutSet then local msgCfg = Localized() for i = 0, MAX_SHORTCUT_PAGE - 1, 1 do local idx = request.ShortcutSet[i+1] if idx ~= -1 then local param = ShortcutUtil:StaticCall("getMysetCustomShortcut(System.Int32)", idx) if param:isValid() then local existed = ShortcutUtil:StaticCall("getShortcutPallet(System.Int32)", i) existed:setShortcut(param) if mod.Config.NotifyDetailedShortcutMenu then Core.BuildLongMessage(msgCfg.ShortcutApplyNotify, i+1, tostring(param.Name)) end end end end Core.FinishLongMessage() end Core.SendMessage(msg) if mod.Config.NotifyNoEnoughItems then local items = data._PouchItem local enough = true local ms = {} local s = {} Core.ForEach(items, function (item) local idFixed = item.ItemIdFixed local num = item.Num if ItemIDFixedToID:ContainsKey(idFixed) then local id = ItemIDFixedToID:get_Item(idFixed) local haveNum = ItemUtil:StaticCall("getItemNum(app.ItemDef.ID, app.ItemUtil.STOCK_TYPE)", id, ItemUtil_StockTypeBoth) if id < 0 then return Core.ForEachBreak end if haveNum <= num then enough = false if mod.Config.NotifyNoEnoughItemDetails then local name = Core.GetItemName(id) if mod.Config.Debug then Core.SendMessage("%s wants %d, has %d", name, num, haveNum) end s[#s+1] = name if #s >= 4 then ms[#ms+1] = table.concat(s,", ") s = {} end -- return Core.ForEachBreak else return Core.ForEachBreak end end end end) if not enough then if mod.Config.NotifyNoEnoughItemDetails then if #s > 0 then ms[#ms+1] = table.concat(s,", ") end local msg = table.concat(ms,"\n") Core.SendMessage(Localized().NotEnoughDetail, msg) else Core.SendMessage(Localized().NotEnough) end end end end mod.HookFunc("app.GUIManager", "update()", function () if RequestRestock then local req = RequestRestock local msg = RequestMessage RequestRestock = nil RequestMessage = nil Apply(req, msg) end end) local function ShellMessage() local msg = "" local msgCfg = Localized() if mod.Config.AlsoFillShells then msg = msg .. "\n" .. msgCfg.FillShell end if mod.Config.AlsoFillOtherItems then if mod.Config.AlsoFillShells then msg = msg .. " " .. msgCfg.FillOthers else msg = msg .. "\n" .. msgCfg.FillOthers end end return msg end local function RequestAutoRestock(idx) local msgCfg = Localized() if idx ~= nil then local data = GetItemMySet(idx, true) if not data then return end RequestMessage = string.format(msgCfg.FromOption, data.Name, msgCfg.Manual) .. ShellMessage() RequestRestock = { ItemSet = idx, } return end local msg local conf local mgr = Core.GetSaveDataManager() local equipMysetName if mgr ~= nil then local save = mgr:getCurrentUserSaveData() -- app.savedata.cUserSaveParam local equipParam = save:get_Equip() -- app.savedata.cEquipParam local equipMyset = equipParam:get_EquipMySet() -- app.savedata.cEquipMyset[] for i, cfg in pairs(mod.Config.EquipMySetConfig) do local myset = equipMyset:get_Item(i-1) local mysetName = myset._MysetParam.MysetName if mysetName == "" then goto continue end if EquipSetEquals(equipParam, myset) then conf = cfg equipMysetName = mysetName break end ::continue:: end end if conf ~= nil then local data = GetItemMySet(conf.DefaultSet, true) if not data then return end msg = string.format(msgCfg.FromOption, data.Name, equipMysetName) if conf.UseCustomShortcutSet then msg = msg .. "\n3" .. string.format(msgCfg.ShortcutApply, equipMysetName) end else local weaponType = Core.GetPlayerWeaponType() local cfg = mod.Config.WeaponTypeConfig[weaponType+1] local isFallbackWeapon = false if cfg.DefaultSet == -1 and mod.Config.FallbackAnotherWeapon then weaponType = Core.GetPlayerSubWeaponType() cfg = mod.Config.WeaponTypeConfig[weaponType+1] isFallbackWeapon = true end if cfg.DefaultSet == -1 then conf = mod.Config local data = GetItemMySet(conf.DefaultSet, true) if not data then return end msg = string.format(msgCfg.FromDefault, data.Name) if conf.UseCustomShortcutSet then msg = msg .. "\n1" .. string.format(msgCfg.ShortcutApply, data.Name) end else conf = cfg local data = GetItemMySet(conf.DefaultSet, true) if not data then return end local wpName = Core.GetWeaponTypeName(weaponType) if isFallbackWeapon then wpName = msgCfg.SubWeaponFallback .. wpName end msg = string.format(msgCfg.FromOption, data.Name, wpName) if conf.UseCustomShortcutSet then msg = msg .. "\n2" .. string.format(msgCfg.ShortcutApply, wpName) end end end msg = msg .. ShellMessage() conf = { ItemSet = conf.DefaultSet, UseShortcutSet = conf.UseCustomShortcutSet, ShortcutSet = conf.DefaultShortcutSet } RequestMessage = msg RequestRestock = conf end Core.OnAcceptQuest(function () RequestAutoRestock() end) Core.OnQuestEnd(function () RequestAutoRestock() end) local ItemMySetData = {} local ItemMySetNameMap = {} -- local ShortcutMySetData = {} local ShortcutMySetNameMap = {} -- local EquipMySetData = {} -- local EquipMySetNameMap = {} local function InitItemMySetData() local firstSetIndex = -1 if MAX_ITEM_MYSET == nil then MAX_ITEM_MYSET = Core.TypeField("app.ItemMySetUtil", "SET_NUM_MAX"):get_data() or 80 end for i = 0, MAX_ITEM_MYSET - 1, 1 do local myset = GetItemMySet(i) if not myset:isValidData() then goto continue end if firstSetIndex == -1 then firstSetIndex = i end ItemMySetData[i] = myset ItemMySetNameMap[i] = myset.Name ::continue:: end ItemMySetNameMap[-1] = Localized().UseDefaultSetting return firstSetIndex end local function InitShortcutMySetData() for i = 0, MAX_SHORTCUT_MYSET - 1, 1 do local myset = GetShortcutMySet(i) if not myset:isValid() then goto continue end -- ShortcutMySetData[i] = myset ShortcutMySetNameMap[i] = myset.Name ::continue:: end ShortcutMySetNameMap[-1] = Localized().UseDefaultSetting end -- local function InitEquipMySetData() -- local mgr = Core.GetSaveDataManager() -- local save = mgr:getCurrentUserSaveData() -- app.savedata.cUserSaveParam -- local equipParam = save:get_Equip() -- app.savedata.cEquipParam -- local equipMyset = equipParam:get_EquipMySet() -- app.savedata.cEquipMyset[] -- Core.ForEach(equipMyset, function (myset, i) -- if myset._MysetParam.MysetName == "" then -- return -- end -- EquipMySetData[i] = myset -- EquipMySetNameMap[i] = myset._MysetParam.MysetName -- end) -- end mod.Menu(function () local configChanged = false local changed changed, mod.Config.NotifyNoEnoughItems = imgui.checkbox("Notify No Enough Items", mod.Config.NotifyNoEnoughItems) configChanged = configChanged or changed changed, mod.Config.NotifyNoEnoughItemDetails = imgui.checkbox("Notify No Enough Item Names", mod.Config.NotifyNoEnoughItemDetails) configChanged = configChanged or changed changed, mod.Config.NotifyDetailedShortcutMenu = imgui.checkbox("Notify Shortcut Menu Details", mod.Config.NotifyDetailedShortcutMenu) configChanged = configChanged or changed changed, mod.Config.AlsoFillShells = imgui.checkbox("Also Fill Shells", mod.Config.AlsoFillShells) configChanged = configChanged or changed changed, mod.Config.AlsoFillOtherItems = imgui.checkbox("Also Fill Other Items", mod.Config.AlsoFillOtherItems) configChanged = configChanged or changed local firstSetIndex = InitItemMySetData() if firstSetIndex == -1 then imgui.text("No item set") return end if mod.Config.DefaultSet == -1 then mod.Config.DefaultSet = firstSetIndex end InitShortcutMySetData() -- InitEquipMySetData() if mod.Config.Debug then for i = 0, MAX_ITEM_MYSET - 1, 1 do if not ItemMySetData[i] then goto continue end imgui.text(tostring(i) .. " MySet: " .. tostring(ItemMySetData[i].Name)) imgui.same_line() if imgui.button("Apply #" .. tostring(i) .. ": " .. tostring(ItemMySetData[i].Name)) then RequestAutoRestock(i) end ::continue:: end end Imgui.Rect(function () mod.Runtime.ManualSet = mod.Runtime.ManualSet or mod.Config.DefaultSet Imgui.Button("Restock this item set:", function () RequestAutoRestock(mod.Runtime.ManualSet) end) imgui.same_line() _, mod.Runtime.ManualSet = imgui.combo("Manual Restock This", mod.Runtime.ManualSet, ItemMySetNameMap) imgui.text("this will not apply shortcut menu") end) imgui.text() if imgui.button("Apply Restock") then RequestAutoRestock() end changed, mod.Config.DefaultSet = imgui.combo("Default Set", mod.Config.DefaultSet, ItemMySetNameMap) configChanged = configChanged or changed changed, mod.Config.UseCustomShortcutSet = imgui.checkbox("Use Custom Shortcut Set", mod.Config.UseCustomShortcutSet) configChanged = configChanged or changed if mod.Config.UseCustomShortcutSet then if imgui.tree_node("Default Shortcut Set") then for i = 0, MAX_SHORTCUT_PAGE-1, 1 do local defaultSet = mod.Config.DefaultShortcutSet[i+1] changed, defaultSet = imgui.combo("Page " .. tostring(i+1), defaultSet, ShortcutMySetNameMap) configChanged = configChanged or changed if changed then mod.Config.DefaultShortcutSet[i+1] = defaultSet end end imgui.tree_pop() end end if imgui.tree_node("Weapon Type") then -- changed, mod.Config.AlsoApplySubWeapon = -- imgui.checkbox("Also Apply Sub Weapon", mod.Config.AlsoApplySubWeapon) -- configChanged = configChanged or changed changed, mod.Config.FallbackAnotherWeapon = imgui.checkbox("Use Another Weapon If Main Weapon is Default", mod.Config.FallbackAnotherWeapon) configChanged = configChanged or changed local wpType = Core.GetPlayerWeaponType() local wp2Type = Core.GetPlayerSubWeaponType() for i = 0, 14-1, 1 do local weaponName = Core.GetWeaponTypeName(i) local opened = imgui.tree_node(weaponName) if mod.Config.WeaponTypeConfig[i+1].DefaultSet ~= -1 then imgui.same_line() imgui.text(ItemMySetNameMap[mod.Config.WeaponTypeConfig[i+1].DefaultSet]) end if i == wpType then imgui.same_line() imgui.text("#1 weapon type") end if i == wp2Type then imgui.same_line() imgui.text("#2 weapon type") end if opened then changed, mod.Config.WeaponTypeConfig[i+1].DefaultSet = imgui.combo(weaponName, mod.Config.WeaponTypeConfig[i+1].DefaultSet, ItemMySetNameMap) configChanged = configChanged or changed changed, mod.Config.WeaponTypeConfig[i+1].UseCustomShortcutSet = imgui.checkbox("Use Custom Shortcut Set", mod.Config.WeaponTypeConfig[i+1].UseCustomShortcutSet) configChanged = configChanged or changed if mod.Config.WeaponTypeConfig[i+1].UseCustomShortcutSet then for j = 0, MAX_SHORTCUT_PAGE-1, 1 do local set = mod.Config.WeaponTypeConfig[i+1].DefaultShortcutSet[j+1] changed, set = imgui.combo("Page " .. tostring(j+1), set, ShortcutMySetNameMap) configChanged = configChanged or changed if changed then mod.Config.WeaponTypeConfig[i+1].DefaultShortcutSet[j+1] = set end end end imgui.tree_pop() end end imgui.tree_pop() end if imgui.tree_node("Equipment Loadout") then local mgr = Core.GetSaveDataManager() local save = mgr:getCurrentUserSaveData() -- app.savedata.cUserSaveParam local equipParam = save:get_Equip() -- app.savedata.cEquipParam local equipMyset = equipParam:get_EquipMySet() -- app.savedata.cEquipMyset[] Core.ForEach(equipMyset, function (myset, i) local name = myset._MysetParam.MysetName if name == "" then mod.Config.EquipMySetConfig[i+1] = nil return end -- EquipMySetData[i] = myset -- EquipMySetNameMap[i] = name if mod.Config.EquipMySetConfig[i+1] == nil then mod.Config.EquipMySetConfig[i+1] = InitCustomConfig() end local equipWorks = myset._MysetEquipWork -- 0 and 7: EQUIP_INDEX WEAPON, RESERVE_WEAPON local wpMsg = string.format("%s|%s", Core.GetWeaponTypeName(equipWorks:get_Item(0).FreeVal0), Core.GetWeaponTypeName(equipWorks:get_Item(7).FreeVal0)) local opened = imgui.tree_node(name .. ": " .. wpMsg) if mod.Config.EquipMySetConfig[i+1].DefaultSet ~= -1 then imgui.same_line() imgui.text(ItemMySetNameMap[mod.Config.EquipMySetConfig[i+1].DefaultSet]) end if EquipSetEquals(equipParam, myset) then imgui.same_line() imgui.text("using") end if opened then changed, mod.Config.EquipMySetConfig[i+1].DefaultSet = imgui.combo(name, mod.Config.EquipMySetConfig[i+1].DefaultSet, ItemMySetNameMap) configChanged = configChanged or changed changed, mod.Config.EquipMySetConfig[i+1].UseCustomShortcutSet = imgui.checkbox("Use Custom Shortcut Set", mod.Config.EquipMySetConfig[i+1].UseCustomShortcutSet) configChanged = configChanged or changed if mod.Config.EquipMySetConfig[i+1].UseCustomShortcutSet then for j = 0, MAX_SHORTCUT_PAGE-1, 1 do local set = mod.Config.EquipMySetConfig[i+1].DefaultShortcutSet[j+1] changed, set = imgui.combo("Page " .. tostring(j+1), set, ShortcutMySetNameMap) configChanged = configChanged or changed if changed then mod.Config.EquipMySetConfig[i+1].DefaultShortcutSet[j+1] = set end end end imgui.tree_pop() end end) imgui.tree_pop() end return configChanged end)