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 = Core.NewMod("Auto Meal")
mod.EnableCJKFont(18)
if mod.Config.CurrentSet == nil then
mod.Config.CurrentSet = -1
end
if mod.Config.FallbackAnotherWeapon == nil then
mod.Config.FallbackAnotherWeapon = true
end
if mod.Config.WeaponTypeConfig == nil then
mod.Config.WeaponTypeConfig = {}
for _ = 0, 14-1, 1 do
table.insert(mod.Config.WeaponTypeConfig, -1)
end
end
if mod.Config.GUIDelaySeconds == nil then
mod.Config.GUIDelaySeconds = 3.0
end
if mod.Config.EquipMySetConfig == nil then
mod.Config.EquipMySetConfig = {}
end
if mod.Config.ConsumeItems == nil then
mod.Config.ConsumeItems = true
end
if mod.Config.NotifyNoEnoughItems == nil then
mod.Config.NotifyNoEnoughItems = true
end
if mod.Config.NotifyNoEnoughItemDetails == nil then
mod.Config.NotifyNoEnoughItemDetails = true
end
if mod.Config.SystemLog == nil then
mod.Config.SystemLog = true
mod.Config.MealUI = true
end
local RequestUTCTime = 0
local RequestMealSetIndex = -1
local RequestMessage = ""
local NOTIFICATION_MSG = {
[CONST.LanguageType.English] = {
FromDefault = "Apply default meal set: %s",
FromOption = "Apply meal set: %s [%s]",
ManualEat = "Manual",
UseDefaultSetting = "Use Default Setting",
InvalidSetting = "Auto Meal: Invalid Setting",
NotEnough = "Food not enough.",
NotEnoughDetail = "Food not enough, %s.",
},
[CONST.LanguageType.SimplifiedChinese] = {
FromDefault = "已享用默认烹饪组合:%s",
FromOption = "已享用烹饪组合:%s【%s】",
ManualEat = "手动",
UseDefaultSetting = "使用默认设置",
InvalidSetting = "Auto Meal: 无效设置",
NotEnough = "食材数量不足。",
NotEnoughDetail = "食材数量不足:%s。",
},
[CONST.LanguageType.TraditionalChinese] = {
FromDefault = "已享用默认烹飪組合:%s",
FromOption = "已享用烹飪組合:%s【%s】",
ManualEat = "手动",
UseDefaultSetting = "使用默认设置",
InvalidSetting = "Auto Meal: 無效設定",
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 -- TODO: 这里并没有 Weapon type,在哪区分的?
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
-- cMealMySet
local MealMySetUtil = Core.WrapTypedef("app.MealMySetUtil")
local MealUtil = Core.WrapTypedef("app.MealUtil")
local ChatLogUtil = Core.WrapTypedef("app.ChatLogUtil")
local ItemUtil = Core.WrapTypedef("app.ItemUtil")
local ItemIDFixedToID = Core.TypeField("app.ItemDef", "FixedToID"):get_data()
local ItemUtil_StockTypePouch = 0
local ItemUtil_StockTypeBox = 1
local ItemUtil_StockTypeBoth = 2
local ItemUtil_StockTypeBoth_PouchBox = 3
local ItemUtil_StockTypeBoth_BoxPouch = 4
---@return app.savedata.cMealMySet
local function GetMealMySet(index, requireValid)
local data = MealMySetUtil:StaticCall("getMySetData(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
local MealStatusGUI = nil
local function InitGUI()
if mod.Config.MealUI and MealStatusGUI == nil then
local comps = Core.FindComponents("app.GUI090201")
if comps:get_Count() <= 0 then
return
end
MealStatusGUI = comps:get_Item(0)
end
end
-- DO NOT CALL THIS DIRECTLY
local function ShowMealStatus(mealEffect)
if not mod.Config.MealUI then return end
InitGUI()
if not MealStatusGUI then
return
end
if mealEffect then
MealStatusGUI:setupParameters(mealEffect)
-- DO NOT CALL THIS DIRECTLY OR GAME WILL CRASH
MealStatusGUI:requestOpen()
end
end
local function RequestMeal(idx, msg)
RequestMealSetIndex = idx
RequestMessage = msg
RequestUTCTime = Core.GetTime() + mod.Config.GUIDelaySeconds
InitGUI()
if MealStatusGUI ~= nil then
MealStatusGUI:set__Timer(3.0) -- indicates it needs to be shown
end
end
-- 我不知道为什么,但如果预先生成全局变量,那么接取并出发会报错(第一次不会),但是手动、接取但不出发不会报错
---@param idx number custom meal set index
---@return app.cMealEffect
local function MakeMealEffect(idx)
if idx < 0 then return end
local meal = GetMealMySet(idx)
local mysetUse = Core.Ctor("app.cMealMySetUse")
mysetUse:fromSaveFormat(meal)
-- 基础食材 PORTABLE_FOOD + 主食物 + 副食物
-- app.user_data.MealData.cData
local mealData = Core.GetVariousDataManager()._Setting._FacilitySetting._FacilityDiningSetting._MealData
local data = mealData:getDataByIndex(mysetUse:get_PortableFoodType())
local name = Core.GetItemName(mysetUse:get_PortableFoodType())
-- Core.SendMessage("主食材:" .. tostring(name))
local mealEffect = Core.Ctor("app.cMealEffect")
-- nobody knows the bool arg, but it was false 3tims and true at last call
mealEffect:copyFrom(data, true)
local ids = {7} -- 7 携带食料
local itemIDs = mysetUse:get_AddFoods()
local foodCount = itemIDs:get_Count()
if foodCount > 0 then
Core.ForEach(itemIDs, function (id)
local food = MealUtil:StaticCall("getFoodDataFromItemId(app.ItemDef.ID)", id)
if mod.Config.Debug then
Core.SendMessage("SubFood " .. tostring(Core.GetItemName(id)))
end
mealEffect:addParam(food, true) -- 预览的话就是 false
table.insert(ids, id)
end)
end
return mealEffect, ids
end
mod.HookFunc("app.GUIManager", "update()", function ()
if RequestUTCTime <= 0 then return end
local cur = Core.GetTime()
if cur < RequestUTCTime then return end
RequestUTCTime = 0
if Core.IsLoading() then return end
local idx = RequestMealSetIndex
local msg = RequestMessage
RequestMealSetIndex = -1 -- make sure no constant call if exeption thrown
RequestMessage = ""
local meal, itemIDs = MakeMealEffect(idx)
if not meal then return end
local hunter = Core.GetPlayerCharacter()
if not hunter then return end
-- setMealEffect throws exception??
local enough = true
local s = {}
if mod.Config.ConsumeItems then
for _, id in pairs(itemIDs) do
local haveNum = ItemUtil:StaticCall("getItemNum(app.ItemDef.ID, app.ItemUtil.STOCK_TYPE)", id, ItemUtil_StockTypeBoth)
if haveNum <= 0 then
enough = false
if mod.Config.NotifyNoEnoughItems and mod.Config.NotifyNoEnoughItemDetails then
local name = Core.GetItemName(id)
if mod.Config.Debug then
Core.SendMessage("%s wants %d, has %d", name, 1, haveNum)
end
s[#s+1] = name
else
break
end
end
end
end
if enough then
hunter:get_HunterStatus()._MealEffect:setMealEffect(meal, true)
if mod.Config.ConsumeItems then
for _, id in pairs(itemIDs) do
ItemUtil:StaticCall("changeItemNum(app.ItemDef.ID, System.Int16, app.ItemUtil.STOCK_TYPE)", id, -1, ItemUtil_StockTypeBoth_BoxPouch)
end
end
ShowMealStatus(meal)
if mod.Config.SystemLog then
Core.SendMessage(msg)
end
else
if mod.Config.NotifyNoEnoughItems then
if mod.Config.NotifyNoEnoughItemDetails then
Core.SendMessage(Localized().NotEnoughDetail, table.concat(s,", "))
else
Core.SendMessage(Localized().NotEnough)
end
end
end
end)
Core.OnQuestStartEnter(function()
if MealStatusGUI ~= nil and MealStatusGUI:get__Timer() > 2 then
RequestUTCTime = Core.GetTime() + mod.Config.GUIDelaySeconds
end
end)
local function AutoApplyMeal(setIndex)
local msg
local msgCfg = Localized()
local mgr = Core.GetSaveDataManager()
local equipMysetName
if setIndex ~= nil then
equipMysetName = msgCfg.ManualEat
elseif setIndex == nil and 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[]
local equipIndex = equipParam._EquipIndex -- app.savedata.cEquipIndex
for i, equipSetIndex in pairs(mod.Config.EquipMySetConfig) do
local myset = equipMyset:get_Item(i-1)
if myset._MysetParam.MysetName == "" then
goto continue
end
if EquipSetEquals(equipParam, myset) then
setIndex = equipSetIndex
equipMysetName = myset._MysetParam.MysetName
break
end
::continue::
end
end
if setIndex ~= nil then
local data = GetMealMySet(setIndex, true)
if not data then
return
end
msg = string.format(msgCfg.FromOption, data.Name, equipMysetName)
else
local weaponType = Core.GetPlayerWeaponType()
local wpSetIndex = mod.Config.WeaponTypeConfig[weaponType+1]
if wpSetIndex == -1 and mod.Config.FallbackAnotherWeapon then
weaponType = Core.GetPlayerSubWeaponType()
wpSetIndex = mod.Config.WeaponTypeConfig[weaponType+1]
end
if wpSetIndex == -1 then
setIndex = mod.Config.CurrentSet
local data = GetMealMySet(setIndex, true)
if not data then
return
end
msg = string.format(msgCfg.FromDefault, data.Name)
else
setIndex = wpSetIndex
local data = GetMealMySet(setIndex, true)
if not data then
return
end
msg = string.format(msgCfg.FromOption, data.Name, Core.GetWeaponTypeName(weaponType))
end
end
RequestMeal(setIndex, msg)
end
Core.OnAcceptQuest(function ()
AutoApplyMeal()
end)
local MYSET_MAX = Core.TypeField("app.MealDef", "MYSET_MAX"):get_data() or 40
local MealMySetNames = {}
local function InitMealMySetNames()
local firstMealIndex = -1
for i = 0, MYSET_MAX - 1, 1 do
local myset = GetMealMySet(i) -- cMealMySet
if myset ~= nil and myset:isValidData() then
if firstMealIndex == -1 then
firstMealIndex = i
end
MealMySetNames[i] = myset:get_field("Name")
-- if mod.Config.Debug then
-- -- mealsData[i] = myset
-- local msg = "Name " .. MealMySetNames[i] .. ": ".. tostring(myset:get_field("BaseFoodType"))
-- local adds = myset:get_field("AddFood")
-- local len = adds:call("get_Count")
-- for j = 0, len - 1, 1 do
-- local addFood = adds:call("get_Item", j)
-- msg = msg .. ", " .. tostring(addFood)
-- end
-- imgui.text(msg)
-- end
end
end
MealMySetNames[-1] = Localized().UseDefaultSetting
return firstMealIndex
end
mod.Menu(function ()
local configChanged = false
local changed = false
changed, mod.Config.ConsumeItems = imgui.checkbox("Consume Items", mod.Config.ConsumeItems)
configChanged = configChanged or changed
Imgui.Tree("Notification and Dispaly", function ()
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.MealUI = imgui.checkbox("Display Meal UI", mod.Config.MealUI)
configChanged = configChanged or changed
if mod.Config.MealUI then
changed, mod.Config.GUIDelaySeconds = imgui.slider_float("GUI Delay Seconds", mod.Config.GUIDelaySeconds, 0.0, 6.0)
configChanged = configChanged or changed
end
changed, mod.Config.SystemLog = imgui.checkbox("Display System Notification", mod.Config.SystemLog)
configChanged = configChanged or changed
end)
-- local mealsData = {}
local firstMealIndex = InitMealMySetNames()
if firstMealIndex == -1 then
imgui.text("No meal set")
return
end
Imgui.Rect(function ()
mod.Runtime.ManualSet = mod.Runtime.ManualSet or mod.Config.CurrentSet
Imgui.Button("Apply this meal:", function ()
AutoApplyMeal(mod.Runtime.ManualSet)
RequestUTCTime = 1
end)
imgui.same_line()
_, mod.Runtime.ManualSet = imgui.combo("Manual Eat This", mod.Runtime.ManualSet, MealMySetNames)
end)
imgui.text()
if imgui.button("Apply Auto Meal Now") then
AutoApplyMeal()
RequestUTCTime = 1
end
if mod.Config.CurrentSet == -1 then
mod.Config.CurrentSet = firstMealIndex
end
changed, mod.Config.CurrentSet = imgui.combo("Default Set", mod.Config.CurrentSet, MealMySetNames)
configChanged = configChanged or changed
-- imgui.text("Current Set: " .. tostring(mod.Config.CurrentSet))
if mod.Config.Debug then
if mod.Config.MealUI then
InitGUI()
imgui.text("Timer: " .. tostring(MealStatusGUI:get__Timer()))
end
imgui.text("UTCTime: " ..tostring(Core.GetTime()))
imgui.text("Req UTCTime: " ..tostring(RequestUTCTime))
end
if imgui.tree_node("Weapon Type") then
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] ~= -1 then
imgui.same_line()
imgui.text(MealMySetNames[mod.Config.WeaponTypeConfig[i+1]])
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] = imgui.combo(weaponName, mod.Config.WeaponTypeConfig[i+1], MealMySetNames)
configChanged = configChanged or changed
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] = -1
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] ~= -1 then
imgui.same_line()
imgui.text(MealMySetNames[mod.Config.EquipMySetConfig[i+1]])
end
if EquipSetEquals(equipParam, myset) then
imgui.same_line()
imgui.text("using")
end
if opened then
changed, mod.Config.EquipMySetConfig[i+1] = imgui.combo(name, mod.Config.EquipMySetConfig[i+1], MealMySetNames)
configChanged = configChanged or changed
imgui.tree_pop()
end
end)
imgui.tree_pop()
end
return configChanged
end)