local function load_config()
local ok, data = pcall(json.load_file, CONFIG_PATH)
if not ok or type(data) ~= "table" then
return
end
for key, default_value in pairs(default_cfg) do
if type(data[key]) == type(default_value) then
cfg[key] = data[key]
else
cfg[key] = default_value
end
end
end
local function save_config()
pcall(json.dump_file, CONFIG_PATH, cfg)
end
local function try_call(obj, method_names, ...)
if obj == nil then
return false, nil, nil
end
for _, name in ipairs(method_names) do
local ok, ret = pcall(obj.call, obj, name, ...)
if ok then
return true, ret, name
end
end
return false, nil, nil
end
local function try_set_field(obj, field_names, value)
if obj == nil then
return false
end
for _, name in ipairs(field_names) do
local ok = pcall(obj.set_field, obj, name, value)
if ok then
return true
end
end
return false
end
local function get_singleton(cache_key, type_name)
local cached = cache[cache_key]
if cached ~= nil then
local alive = pcall(cached.get_type_definition, cached)
if alive then
return cached
end
cache[cache_key] = nil
end
local ok, inst = pcall(sdk.get_managed_singleton, type_name)
if ok and inst ~= nil then
cache[cache_key] = inst
return inst
end
return nil
end
local function get_hook_storage()
if thread ~= nil and thread.get_hook_storage ~= nil then
return thread.get_hook_storage()
end
return nil
end
local function get_type_full_name(obj)
if obj == nil then
return nil
end
local ok_td, td = pcall(obj.get_type_definition, obj)
if not ok_td or td == nil then
return nil
end
local ok_name, full_name = pcall(td.get_full_name, td)
if ok_name and type(full_name) == "string" then
return full_name
end
return nil
end
local function is_supported_player_updater(updater)
local type_name = get_type_full_name(updater)
if type_name == nil then
return false
end
if type_name == "app.Cp_A000Updater" then
return true
end
return type_name:find("app.Cp_A", 1, true) ~= nil and type_name:find("Updater", 1, true) ~= nil
end
local function is_supported_attack_damage_driver(driver)
if driver == nil then
return false
end
local type_name = get_type_full_name(driver)
if type_name == nil or type_name:find("AttackDamageDriver", 1, true) == nil then
return false
end
local ok, updater = try_call(driver, {
"get_Updater()",
"get_Updater",
})
if ok and updater ~= nil then
return is_supported_player_updater(updater)
end
return false
end
local function is_damage_info_object(obj)
local type_name = get_type_full_name(obj)
if type_name == nil then
return false
end
return type_name:find("HitController.DamageInfo", 1, true) ~= nil
end
local function find_method(type_name, signatures)
local td = sdk.find_type_definition(type_name)
if td == nil then
return nil
end
for _, sig in ipairs(signatures) do
local method = td:get_method(sig)
if method ~= nil then
return method
end
end
return nil
end
local function get_this_from_args(args)
if args == nil then
return nil
end
local this_obj = sdk.to_managed_object(args[2])
if this_obj ~= nil then
return this_obj
end
return sdk.to_managed_object(args[1])
end
local function get_damage_info_from_args(args)
if args == nil then
return nil
end
local candidates = { args[3], args[4], args[1] }
for _, ptr in ipairs(candidates) do
local obj = sdk.to_managed_object(ptr)
if obj ~= nil and is_damage_info_object(obj) then
return obj
end
end
return nil
end
local function is_auto_parry_ready(damage_info)
local frame_delta = state.frame_count - state.last_auto_parry_frame
if frame_delta <= AUTO_PARRY_COOLDOWN_FRAMES then
return false
end
if damage_info ~= nil
and state.last_auto_parry_damage_info ~= nil
and damage_info == state.last_auto_parry_damage_info
and frame_delta <= (AUTO_PARRY_COOLDOWN_FRAMES + 2) then
return false
end
return true
end
local function get_player_context()
local mgr = get_singleton("character_manager", "app.CharacterManager")
if mgr == nil then
return nil
end
local ok, player = try_call(mgr, {
"getPlayerContextRef()",
"getPlayerContextRef",
"getPlayerContextRefFast()",
"getPlayerContextRefFast",
})
if ok and player ~= nil then
return player
end
return nil
end
local function get_player_updater(player)
local ok, updater = try_call(player, {
"get_Updater()",
"get_Updater",
})
if ok and updater ~= nil then
return updater
end
return nil
end
local function get_player_parry_driver(player)
local updater = get_player_updater(player)
if updater == nil then
return nil
end
local ok, parry_driver = try_call(updater, {
"get_ParryDriver()",
"get_ParryDriver",
"getParryDriver()",
"getParryDriver",
})
if ok and parry_driver ~= nil then
return parry_driver
end
return nil
end
local function apply_parry_damageinfo_flags(damage_info)
if damage_info == nil then
return
end
if cfg.auto_just_parry then
try_call(damage_info, {
"onFlag(app.HitController.DamageInfo.AttackDamageFlagEnum)",
"onFlag",
}, FLAG_JUST_PARRY)
end
end
local function apply_player_just_parry_state(this_obj)
if not cfg.auto_just_parry or this_obj == nil then
return
end
local ok_unit, parry_unit = try_call(this_obj, {
"get_ParryUnit()",
"get_ParryUnit",
})
if not ok_unit or parry_unit == nil then
return
end
local set_ok = try_call(parry_unit, {
"set_IsJustParry(System.Boolean)",
"set_IsJustParry",
}, true)
if not set_ok then
try_set_field(parry_unit, {
"IsJustParry",
"_IsJustParry",
}, true)
end
end
local function request_parry_permit()
local player = get_player_context()
if player == nil then
return
end
local updater = get_player_updater(player)
if not is_supported_player_updater(updater) then
return
end
local parry_driver = get_player_parry_driver(player)
if parry_driver == nil then
return
end
try_call(parry_driver, {
"requestPermitParryTransitFrame()",
"requestPermitParryTransitFrame",
})
end
local function install_check_parry_hook()
if state.check_parry_hook_installed then
return
end
local method = find_method("app.PlayerAttackDamageDriver", {
"checkParry(app.HitController.DamageInfo)",
"checkParry",
})
if method == nil then
return
end
sdk.hook(method, function(args)
local storage = get_hook_storage()
if storage ~= nil then
storage.force_auto_parry = false
end
local this_obj = get_this_from_args(args)
if this_obj ~= nil and is_supported_attack_damage_driver(this_obj) then
local damage_info = get_damage_info_from_args(args)
if cfg.auto_parry and storage ~= nil and is_auto_parry_ready(damage_info) then
storage.force_auto_parry = true
end
end
return sdk.PreHookResult.CALL_ORIGINAL
end, function(retval)
local storage = get_hook_storage()
if storage ~= nil and storage.force_auto_parry then
return true
end
return retval
end)
state.check_parry_hook_installed = true
end
local function install_on_parry_success_hook()
if state.on_parry_success_hook_installed then
return
end
local method = find_method("app.PlayerAttackDamageDriver", {
"onParrySuccess(app.HitController.DamageInfo)",
"onParrySuccess",
})
if method == nil then
return
end
sdk.hook(method, function(args)
local this_obj = get_this_from_args(args)
if this_obj ~= nil and is_supported_attack_damage_driver(this_obj) then
state.parry_success_seq = state.parry_success_seq + 1
state.last_auto_parry_frame = state.frame_count
state.last_auto_parry_damage_info = get_damage_info_from_args(args)
end
return sdk.PreHookResult.CALL_ORIGINAL
end, nil)
state.on_parry_success_hook_installed = true
end
local function install_on_check_damage_hit_hook()
if state.on_check_damage_hit_hook_installed then
return
end
local method = find_method("app.PlayerAttackDamageDriver", {
"onCheckDamageHit(app.HitController.DamageInfo)",
"onCheckDamageHit",
})
if method == nil then
return
end
sdk.hook(method, function(args)
local storage = get_hook_storage()
if storage ~= nil then
storage.auto_parry_candidate = false
end
local this_obj = get_this_from_args(args)
if this_obj == nil or not is_supported_attack_damage_driver(this_obj) then
return sdk.PreHookResult.CALL_ORIGINAL
end
if cfg.auto_parry then
local damage_info = get_damage_info_from_args(args)
if not is_auto_parry_ready(damage_info) then
return sdk.PreHookResult.CALL_ORIGINAL
end
Q:Auto Parry預設是關閉的,Auto Just Parry預設是開啟的,這樣有差嗎?
有差別。Auto Parry是基礎自動格擋,需要你手動在選單開啟才會生效。Auto Just Parry則是在基礎格擋觸發時,額外嘗試輸出「精準格擋」判定,預設開啟代表只要你打開Auto Parry,精準格擋效果也會一起套用。
Q:Fluffy Mod Manager一定要裝嗎?不裝可以用嗎?
Fluffy Mod Manager並非強制必要,它主要是用來方便管理其他類型的MOD(例如外觀MOD)。自動格擋腳本本身只需要把 .lua 檔放進 \reframework\autorun 就能運作,不一定需要透過Fluffy Mod Manager安裝。