From a4f088aa2f68c23045eb643614919713e4bc3901 Mon Sep 17 00:00:00 2001 From: WinterSolstice8 <60417494+wintersolstice8@users.noreply.github.com> Date: Thu, 5 Feb 2026 09:36:19 -0700 Subject: [PATCH] [core] add onMobSkillReadyTime to choose ready time for mobskill return nil for no override --- scripts/specs/types/MobEntity.lua | 1 + src/map/ai/controllers/mob_controller.cpp | 4 +++- src/map/lua/luautils.cpp | 29 +++++++++++++++++++++++ src/map/lua/luautils.h | 1 + 4 files changed, 34 insertions(+), 1 deletion(-) diff --git a/scripts/specs/types/MobEntity.lua b/scripts/specs/types/MobEntity.lua index 4d4e287e8de..64b54fc8e7e 100644 --- a/scripts/specs/types/MobEntity.lua +++ b/scripts/specs/types/MobEntity.lua @@ -29,6 +29,7 @@ ---@field onMobMobskillChoose? fun(mob: CBaseEntity, target: CBaseEntity, skillId: integer): integer? ---@field onMobWeaponSkill? fun(target: CBaseEntity, mob: CBaseEntity, mobSkill: CMobSkill, action: CAction): integer? ---@field onMobSkillTarget? fun(target: CBaseEntity, mob: CBaseEntity, mobSkill: CMobSkill): CBaseEntity? +---@field onMobSkillReadyTime? fun(target: CBaseEntity, mob: CBaseEntity, mobSkill: CMobSkill): integer? ---@field onAdditionalEffect? fun(mob: CBaseEntity, target: CBaseEntity, damage: integer): (any, any, integer?) ---@field onMobSpellChoose? fun(mob: CBaseEntity, target: CBaseEntity, spell: CSpell?): xi.magic.spell|0?, CBaseEntity? ---@field onWeaponskillHit? fun(mob: CBaseEntity, attacker: CBaseEntity, weaponskillId: xi.weaponskill) diff --git a/src/map/ai/controllers/mob_controller.cpp b/src/map/ai/controllers/mob_controller.cpp index de924dbb8de..f1f0accf841 100644 --- a/src/map/ai/controllers/mob_controller.cpp +++ b/src/map/ai/controllers/mob_controller.cpp @@ -397,12 +397,14 @@ auto CMobController::MobSkill(int listId) -> bool PActionTarget = luautils::OnMobSkillTarget(PActionTarget, PMob, PMobSkill); + std::optional mobSkillReadyTime = luautils::OnMobSkillReadyTime(PActionTarget, PMob, PMobSkill); + if (PActionTarget && !PMobSkill->isAstralFlow() && luautils::OnMobSkillCheck(PActionTarget, PMob, PMobSkill) == 0) // A script says that the move in question is valid { const float currentDistance = distance(PMob->loc.p, PActionTarget->loc.p); if (currentDistance <= PMobSkill->getDistance()) { - return MobSkill(PActionTarget->targid, PMobSkill->getID(), std::nullopt); + return MobSkill(PActionTarget->targid, PMobSkill->getID(), mobSkillReadyTime); } } diff --git a/src/map/lua/luautils.cpp b/src/map/lua/luautils.cpp index 94a93c42353..9c0ab1e40f7 100644 --- a/src/map/lua/luautils.cpp +++ b/src/map/lua/luautils.cpp @@ -3908,6 +3908,35 @@ CBattleEntity* OnMobSkillTarget(CBattleEntity* PTarget, CBaseEntity* PMob, CMobS return PTarget; } +std::optional OnMobSkillReadyTime(CBattleEntity* PTarget, CBaseEntity* PMob, CMobSkill* PMobSkill) +{ + TracyZoneScoped; + + auto zone = PMob->loc.zone->getName(); + auto name = PMob->getName(); + + auto onMobSkillReadyTime = lua["xi"]["zones"][zone]["mobs"][name]["onMobSkillReadyTime"]; + if (!onMobSkillReadyTime.valid()) + { + return std::nullopt; + } + + auto result = onMobSkillReadyTime(PTarget, PMob, PMobSkill); + if (!result.valid()) + { + sol::error err = result; + ShowError("luautils::onMobSkillReadyTime: %s", err.what()); + return std::nullopt; + } + + if (result.get_type(0) == sol::type::number) + { + return std::chrono::milliseconds(result.template get(0)); + } + + return std::nullopt; +} + // onMobSkillFinalize always executes once per uninterrupted mobskill use, independently of any target being found. void OnMobSkillFinalize(CBaseEntity* PMob, CMobSkill* PMobSkill) { diff --git a/src/map/lua/luautils.h b/src/map/lua/luautils.h index 7f89f6aba2b..72222ad998c 100644 --- a/src/map/lua/luautils.h +++ b/src/map/lua/luautils.h @@ -385,6 +385,7 @@ uint16 OnMobMobskillChoose(CBattleEntity* PMob, CBattleEntity* PTarget, uint16 c int32 OnMobWeaponSkill(CBaseEntity* PChar, CBaseEntity* PMob, CMobSkill* PMobSkill, action_t* action); int32 OnMobSkillCheck(CBaseEntity* PChar, CBaseEntity* PMob, CMobSkill* PMobSkill); // triggers before mob weapon skill is used, returns 0 if the move is valid auto OnMobSkillTarget(CBattleEntity* PTarget, CBaseEntity* PMob, CMobSkill* PMobSkill) -> CBattleEntity*; +auto OnMobSkillReadyTime(CBattleEntity* PTarget, CBaseEntity* PMob, CMobSkill* PMobSkill) -> std::optional; void OnMobSkillFinalize(CBaseEntity* PMob, CMobSkill* PMobSkill); // triggers when mob skill state cleanup runs int32 OnAutomatonAbilityCheck(CBaseEntity* PChar, CAutomatonEntity* PAutomaton, CMobSkill* PMobSkill); int32 OnAutomatonAbility(CBaseEntity* PTarget, CBaseEntity* PMob, CMobSkill* PMobSkill, CBaseEntity* PMobMaster, action_t* action);