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)