local re = re local sdk = sdk local d2d = d2d local imgui = imgui local log = log local json = json local draw = draw local thread = thread 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 _ = _ local Core = require("_CatLib") local CONST = require("_CatLib.const") local Imgui = require("_CatLib.imgui") local FSM = require("_CatLib.fsm") local mod = Core.NewMod("Smart Focus Mode and Focus Attack") if mod.Config.IsInBattleCheck == nil then mod.Config.IsInBattleCheck = true end if mod.Config.MeleeWeaponDistanceCheck == nil then mod.Config.MeleeWeaponDistanceCheck = true end if mod.Config.ValidScarsCheck == nil then mod.Config.ValidScarsCheck = true end if mod.Config.DisableAimModeOnWpOff == nil then mod.Config.DisableAimModeOnWpOff = true end if mod.Config.DisableAimModeOnMount == nil then mod.Config.DisableAimModeOnMount = true end if mod.Config.DisableAimModeOnDismount == nil then mod.Config.DisableAimModeOnDismount = true end if mod.Config.DisableAimModeOnCallPorter == nil then mod.Config.DisableAimModeOnCallPorter = true end local function IsInBattle() if mod.Config.IsInBattleCheck then return Core.IsInBattle() end return true end local function IsInMeleeDistance() if mod.Config.MeleeWeaponDistanceCheck then end return true end local TargetAccessKey_GetEnemyCharacter = Core.TypeMethod("app.TargetAccessKeyUtil", "getEnemyCharacter(app.TARGET_ACCESS_KEY, System.Boolean)") local EffectCache = {} ---@return app.cEnemyLoopEffectHighlight local function GetEnemyHighlightEffect(enemy) local array = enemy._MiniComponentHolder._EntrustEffect_LateUpdator._EffectArray Core.ForEach(array, function (effect) if effect:get_type_definition():get_full_name() == "app.cEnemyLoopEffectHighlight" then EffectCache[enemy] = effect return Core.ForEachBreak end end) return EffectCache[enemy] end ---@param effect app.cEnemyLoopEffectHighlight local function HighlightEffectHasRawScarOrWeakPoint(effect) if not effect then return true end local hasWeakPoint = false local parts = effect._Accessor:get_ContextEm().Parts if parts then hasWeakPoint = parts:getActiveWeakPointIndexList():get_Count() > 0 end if hasWeakPoint then return true end local has = false Core.ForEach(effect._ScarList, function (scarHolder) if scarHolder._Parts._State == 2 then has = true return Core.ForEachBreak end end) return has end local IsBossCache = {} local function GetFightingEnemiesHasRawScarOrWeakPoint() local player = Core.GetPlayerCharacter() local combatEnemies = player:get_CombatEnemyList() local has = false Core.ForEach(combatEnemies, function (accessKey) local enemy = TargetAccessKey_GetEnemyCharacter:call(nil, accessKey, true) if IsBossCache[enemy] == nil then IsBossCache[enemy] = enemy._Context._Em:get_IsBoss() end if IsBossCache[enemy] then has = HighlightEffectHasRawScarOrWeakPoint(GetEnemyHighlightEffect(enemy)) -- Core.SendMessage("Enemy %s has? %s", tostring(enemy), tostring(has)) if has then return Core.ForEachBreak end end end) return has -- return sdk.get_managed_singleton("app.MissionManager"):getAcceptQuestTargetBrowsers():call("get_Item", 0)._Character end local function HasValidScars() if mod.Config.ValidScarsCheck then return GetFightingEnemiesHasRawScarOrWeakPoint() end return true end local function IsSmartSheath() -- 如果近距离且有伤口,那么即使 not in battle 也不能收刀,因为可以偷袭 -- if not IsInBattle() then -- Core.SendMessage("not in battle, sheath") -- return true -- end if not Core.IsInBattle() then return false -- 在实现距离检测前,脱战状态下要允许攻击 end if not IsInMeleeDistance() then -- Core.SendMessage("too far, sheath") return true end if not HasValidScars() then -- Core.SendMessage("no valid scars, sheath") return true end return false end local function SmartFocusAttack() -- if true then -- return true -- end if IsInBattle() and HasValidScars() and IsInMeleeDistance() then return true end return false end local Wp03_ActionToIndexCancelIndexTable = Core.TypeField("app.motion_track.Wp03Cancel", "ActionToCancelIndexTable_Wp03"):get_data() local Wp03_WeakHitStart = Wp03_ActionToIndexCancelIndexTable:get_Item(29) -- TODO: 只能在玩家是主机的情况下使用 -- TODO: 非战斗 -- TODO:近战距离检测 local EnemyDistanceLimit = 50 local CheckEnemyHasTearScars = true local CheckInBattle = true local function IsAiming() return sdk.get_managed_singleton("app.PlayerManager"):getMasterPlayer():get_ContextHolder():get_Hunter():get_IsAim() end local function IsAim(command) return FSM.CheckCommandResult(command, 0, 61) end local function ThisArgIsType(args, typename) local typedef = Core.Cast(args[2]):get_type_definition() return typedef:get_full_name() == typename or typedef:is_a(typename) end Core.HookFunc("app.WpCommonActions.cWpOff", "doEnter()", function (args) -- sdk.get_managed_singleton("app.PlayerManager"):getMasterPlayer():get_ContextHolder():get_Hunter():set_AimType(0) if mod.Config.DisableAimModeOnWpOff and ThisArgIsType(args, "app.WpCommonActions.cWpOff") then Core.DisableAimMode() end end) Core.HookFunc("app.WpCommonActions.cWpMoveOff", "doEnter()", function (args) -- sdk.get_managed_singleton("app.PlayerManager"):getMasterPlayer():get_ContextHolder():get_Hunter():set_AimType(0) if mod.Config.DisableAimModeOnWpOff and ThisArgIsType(args, "app.WpCommonActions.cWpMoveOff") then Core.DisableAimMode() end end) Core.HookFunc("app.PlayerCommonAction.cPorterRideStartJumpOnto", "doEnter()", function (args) -- sdk.get_managed_singleton("app.PlayerManager"):getMasterPlayer():get_ContextHolder():get_Hunter():set_AimType(0) if mod.Config.DisableAimModeOnMount and ThisArgIsType(args, "app.PlayerCommonAction.cPorterRideStartJumpOnto") then Core.DisableAimMode() end end) Core.HookFunc("app.PlayerCommonAction.cPorterRideStart", "doEnter()", function (args) -- sdk.get_managed_singleton("app.PlayerManager"):getMasterPlayer():get_ContextHolder():get_Hunter():set_AimType(0) if mod.Config.DisableAimModeOnMount and ThisArgIsType(args, "app.PlayerCommonAction.cPorterRideStart") then Core.DisableAimMode() end end) Core.HookFunc("app.PlayerCommonSubAction.cPorterRideStart", "doEnter()", function (args) -- sdk.get_managed_singleton("app.PlayerManager"):getMasterPlayer():get_ContextHolder():get_Hunter():set_AimType(0) if mod.Config.DisableAimModeOnMount and ThisArgIsType(args, "app.PlayerCommonAction.cPorterRideStart") then Core.DisableAimMode() end end) Core.HookFunc("app.PlayerCommonAction.cPorterDismount", "doEnter()", function (args) -- sdk.get_managed_singleton("app.PlayerManager"):getMasterPlayer():get_ContextHolder():get_Hunter():set_AimType(0) if mod.Config.DisableAimModeOnDismount and ThisArgIsType(args, "app.PlayerCommonAction.cPorterDismount") then Core.DisableAimMode() end end) Core.HookFunc("app.PlayerCommonAction.cPorterDismountJumpOff", "doEnter()", function (args) -- sdk.get_managed_singleton("app.PlayerManager"):getMasterPlayer():get_ContextHolder():get_Hunter():set_AimType(0) if mod.Config.DisableAimModeOnDismount and ThisArgIsType(args, "app.PlayerCommonAction.cPorterDismountJumpOff") then Core.DisableAimMode() end end) Core.HookFunc("app.WpCommonSubAction.cCallPorter", "doEnter()", function (args) -- sdk.get_managed_singleton("app.PlayerManager"):getMasterPlayer():get_ContextHolder():get_Hunter():set_AimType(0) if mod.Config.DisableAimModeOnCallPorter and ThisArgIsType(args, "app.WpCommonSubAction.cCallPorter") then Core.DisableAimMode() end end) -- AimMode: Convert focus attack to Sheath weapon local function FsmSmartSheathPreHook(command, operator) -- local function SmartSheathPreHook(args) -- local command = Core.Cast(args[3]) -- local operator = Core.Cast(args[4]) -- local smart = SmartSheath() -- local input = FSM.InputCancelMotion(command, "AIM_SP_B", Wp03_WeakHitStart) -- if smart ~= true or input ~= false then -- Core.SendMessage("%s/%s", tostring(smart), tostring(input)) -- end if IsSmartSheath() and FSM.InputCancelMotion(command, "AIM_SP_B", Wp03_WeakHitStart) then local actionID = Core.NewActionID(1, 10) Core.GetPlayerCharacter():call("changeActionRequest(app.AppActionDef.LAYER, ace.ACTION_ID, System.Boolean)", 0, actionID, false) local storage = thread.get_hook_storage() storage["retval"] = true return sdk.PreHookResult.SKIP_ORIGINAL end end -- Non-AimMode: Convert Sheath weapon to focus attack local function Wp03SmartFocusAttackPreHook(command, operator) -- local smart = SmartFocusAttack() or true -- local input = FSM.InputCancelMotion(command, "ATTACK_SP_B", Wp03_WeakHitStart) -- if smart ~= true or input ~= false then -- Core.SendMessage("%s/%s", tostring(smart), tostring(input)) -- end -- fixme: 自由态无法使出,应该是是因为 WpOff 的优先级太高了 if SmartFocusAttack() and FSM.InputCancelMotion(command, "ATTACK_SP_B", Wp03_WeakHitStart) then local actionID = Core.NewActionID(2, 33) Core.GetPlayerCharacter():call("changeActionRequest(app.AppActionDef.LAYER, ace.ACTION_ID, System.Boolean)", 0, actionID, false) local storage = thread.get_hook_storage() storage["retval"] = true return sdk.PreHookResult.SKIP_ORIGINAL end end local function SmartPreHook(args) local command = Core.Cast(args[3]) local operator = Core.Cast(args[4]) -- local aiming = IsAim(command) -- local aiming = IsAiming() return FsmSmartSheathPreHook(command, operator) -- return Wp03SmartFocusAttackPreHook(command, operator) -- if aiming then -- return SmartSheathPreHook(command, operator) -- else -- return Wp03SmartFocusAttackPreHook(command, operator) -- end end -- 不知道 Dodge to Step 怎么触发 -- table_e573803e_7de4_47fc_873f_afc17ee07e2b -- -- 这个函数是翻滚后 -- mod.HookFunc("app.Wp03_Pack_Export", "table_3f30fad0_6b1c_46a2_8348_76ce7f92bd5b(ace.btable.cCommandWork, ace.btable.cOperatorWork)", -- SmartPreHook, FSM.AbortFSMPostHook) -- -- 这个函数是AimIdle/Move/MoveStop用的,不是翻滚后 -- mod.HookFunc("app.Wp03_Pack_Export", "table_d71beb87_52ae_4a57_9710_3f9dc7b7689f(ace.btable.cCommandWork, ace.btable.cOperatorWork)", -- SmartPreHook, FSM.AbortFSMPostHook) -- -- 这个函数是连携态用的,不是自由态 -- -- Wp03_Export -- mod.HookFunc("app.Wp03_Pack_Export", "table_a2347168_a7e6_4755_8c7c_2900945dd51f(ace.btable.cCommandWork, ace.btable.cOperatorWork)", -- SmartPreHook, FSM.AbortFSMPostHook) local function ActionSmartSheathPreHook(args) if IsSmartSheath() then local wpOffActionID = 10 local this = Core.Cast(args[2]) if this.get_ActionArg then local arg = this:get_ActionArg() if arg:get_IsEnableStartInputWorldDir() then wpOffActionID = 11 end end local actionID = Core.NewActionID(1, wpOffActionID) Core.GetPlayerCharacter():call("changeActionRequest(app.AppActionDef.LAYER, ace.ACTION_ID, System.Boolean)", 0, actionID, false) local storage = thread.get_hook_storage() storage["retval"] = true -- 不能 SKIP_ORIGINAL,自由态(非连携态)按住方向键会导致收刀后立刻转为 AimWalk,然后就收刀失败,可以再按集中键,导致抽搐 -- return sdk.PreHookResult.SKIP_ORIGINAL end end -- todo: should check character is MasterPlayer local function HookActionEnter(typename, func) mod.HookFunc(typename, "doEnter()", function (args) local this = Core.Cast(args[2]) if not this:get_Chara():get_IsMaster() then return end if this:get_type_definition():get_full_name() ~= typename then return end if func == nil then return ActionSmartSheathPreHook(args) else return func(args) end end) end HookActionEnter("app.Wp00Action.cPenetrateSlashHigh") HookActionEnter("app.Wp01Action.cAimComboStart") HookActionEnter("app.Wp02Action.cAimAttack") HookActionEnter("app.Wp03Action.cWeakHitStart") HookActionEnter("app.Wp04Action.cAimSwing") HookActionEnter("app.Wp05Action.cAimStub") HookActionEnter("app.Wp06Action.cAimAttack") HookActionEnter("app.Wp07Action.cAimStab") HookActionEnter("app.Wp08Action.cAxeAimSlashStart") HookActionEnter("app.Wp08Action.cSwordAimSlashStart") HookActionEnter("app.Wp09Action.cSwordAimSlash") HookActionEnter("app.Wp09Action.cAxeAimSlash") HookActionEnter("app.Wp11Action.cMultiLockonStart") HookActionEnter("app.Wp12Action.cWeakPointSnipeStart") HookActionEnter("app.Wp13SubAction.cWeakPointShootChargeStart") mod.HookFunc("app.Wp10Action.cAimAttack", "doEnter()", function (args) local this = Core.Cast(args[2]) if not this:get_Chara():get_IsMaster() then return end local isAir = this._IsAir if IsSmartSheath() then if isAir then -- 空中不收刀 return sdk.PreHookResult.SKIP_ORIGINAL end local actionID = Core.NewActionID(1, 10) Core.GetPlayerCharacter():call("changeActionRequest(app.AppActionDef.LAYER, ace.ACTION_ID, System.Boolean)", 0, actionID, false) end end) mod.Menu(function () local changed, configChanged = false, false Imgui.Rect(function () imgui.text("Focus Attack Condition Checks:") imgui.text("If any condition is not met, the focus attack will be disabled and the weapon sheathed instead.") -- changed, mod.Config.IsInBattleCheck = imgui.checkbox("In Battle", mod.Config.IsInBattleCheck) -- configChanged = configChanged or changed changed, mod.Config.ValidScarsCheck = imgui.checkbox("Has valid scars or weak point", mod.Config.ValidScarsCheck) configChanged = configChanged or changed end) imgui.text("") Imgui.Rect(function () imgui.text("Auto disable Focus Mode when:") changed, mod.Config.DisableAimModeOnWpOff = imgui.checkbox("WpOff##DisableAimMode", mod.Config.DisableAimModeOnWpOff) configChanged = configChanged or changed changed, mod.Config.DisableAimModeOnMount = imgui.checkbox("Mount Seikret##DisableAimMode", mod.Config.DisableAimModeOnMount) configChanged = configChanged or changed changed, mod.Config.DisableAimModeOnDismount = imgui.checkbox("Dismount Seikret##DisableAimMode", mod.Config.DisableAimModeOnDismount) configChanged = configChanged or changed changed, mod.Config.DisableAimModeOnCallPorter = imgui.checkbox("Calling Seikret##DisableAimMode", mod.Config.DisableAimModeOnCallPorter) configChanged = configChanged or changed end) return configChanged end)