From a753763424f1c5ee88875af54f93bd6182d93cb2 Mon Sep 17 00:00:00 2001 From: sruon Date: Fri, 24 Jan 2025 02:18:12 -0700 Subject: [PATCH 1/6] [lua] addGambit supporting multi-predicates --- scripts/specs/core/CBaseEntity.lua | 10 +++++++ src/map/lua/lua_baseentity.cpp | 46 ++++++++++++++++++++++++++++++ src/map/lua/lua_baseentity.h | 1 + 3 files changed, 57 insertions(+) diff --git a/scripts/specs/core/CBaseEntity.lua b/scripts/specs/core/CBaseEntity.lua index f1e7a7291d0..c825816084b 100644 --- a/scripts/specs/core/CBaseEntity.lua +++ b/scripts/specs/core/CBaseEntity.lua @@ -3275,6 +3275,16 @@ end function CBaseEntity:trustPartyMessage(messageId) end +---@param targ integer +---@param conditions table +---@param react integer +---@param select integer +---@param selectorArg integer +---@param retry integer? +---@return string +function CBaseEntity:addGambit(targ, conditions, react, select, selectorArg, retry) +end + ---@param targ integer ---@param cond integer ---@param condition_arg integer diff --git a/src/map/lua/lua_baseentity.cpp b/src/map/lua/lua_baseentity.cpp index 68681665f70..41bc833bb00 100644 --- a/src/map/lua/lua_baseentity.cpp +++ b/src/map/lua/lua_baseentity.cpp @@ -15104,6 +15104,51 @@ void CLuaBaseEntity::trustPartyMessage(uint32 message_id) } } +/************************************************************************ + * Function: addGambit(target, predicates, reaction, selector, selector_arg, retry) + * Purpose : Adds a behavior to the gambit system with an arbitrary number of predicates + * Example : trust:addGambit(ai.t.CASTER, { + * { cond=ai.c.NOT_STATUS, arg=xi.effect.REFRESH }, + * { cond=ai.c.NOT_STATUS, arg=xi.effect.SUBLIMATION_ACTIVATED }, + * }, ai.r.MA, ai.s.HIGHEST, xi.magic.spellFamily.REFRESH) + * Notes : Adds a behavior to the gambit system + ************************************************************************/ + +std::string CLuaBaseEntity::addGambit(uint16 targ, sol::table const& predicates, uint16 react, uint16 select, uint32 selector_arg, sol::object const& retry) +{ + if (m_PBaseEntity->objtype != TYPE_TRUST) + { + ShowWarning("Invalid Entity calling function (%s).", m_PBaseEntity->getName()); + return {}; + } + + using namespace gambits; + Gambit_t g; + + auto target = static_cast(targ); + for (const auto& kvp : predicates) + { + sol::table predicate = kvp.second; // Each entry is a table + auto condition = static_cast(predicate.get("cond")); + auto condition_arg = predicate.get("arg"); + g.predicates.emplace_back(target, condition, condition_arg); + } + + auto reaction = static_cast(react); + auto selector = static_cast(select); + // Optional + uint16 retry_delay = (retry != sol::lua_nil) ? retry.as() : 0; + + g.actions.emplace_back(reaction, selector, selector_arg); + g.retry_delay = retry_delay; + g.identifier = fmt::format("{}_{}_{}_{}_{}", targ, react, select, selector_arg, retry_delay); + + auto* trust = static_cast(m_PBaseEntity); + auto* controller = static_cast(trust->PAI->GetController()); + + return controller->m_GambitsContainer->AddGambit(g); +} + /************************************************************************ * Function: addSimpleGambit() * Purpose : @@ -19359,6 +19404,7 @@ void CLuaBaseEntity::Register() SOL_REGISTER("clearTrusts", CLuaBaseEntity::clearTrusts); SOL_REGISTER("getTrustID", CLuaBaseEntity::getTrustID); SOL_REGISTER("trustPartyMessage", CLuaBaseEntity::trustPartyMessage); + SOL_REGISTER("addGambit", CLuaBaseEntity::addGambit); SOL_REGISTER("addSimpleGambit", CLuaBaseEntity::addSimpleGambit); SOL_REGISTER("removeSimpleGambit", CLuaBaseEntity::removeSimpleGambit); SOL_REGISTER("removeAllSimpleGambits", CLuaBaseEntity::removeAllSimpleGambits); diff --git a/src/map/lua/lua_baseentity.h b/src/map/lua/lua_baseentity.h index a504a96ded7..9060d7d24cb 100644 --- a/src/map/lua/lua_baseentity.h +++ b/src/map/lua/lua_baseentity.h @@ -751,6 +751,7 @@ class CLuaBaseEntity void clearTrusts(); uint32 getTrustID(); void trustPartyMessage(uint32 message_id); + auto addGambit(uint16 targ, sol::table const& predicates, uint16 react, uint16 select, uint32 selector_arg, sol::object const& retry) -> std::string; auto addSimpleGambit(uint16 targ, uint16 cond, uint32 condition_arg, uint16 react, uint16 select, uint32 selector_arg, sol::object const& retry) -> std::string; void removeSimpleGambit(std::string const& id); void removeAllSimpleGambits(); From e66dad81a90bb4148ddcaa6549192d73fa294e66 Mon Sep 17 00:00:00 2001 From: sruon Date: Fri, 24 Jan 2025 02:37:16 -0700 Subject: [PATCH 2/6] [lua] Koru: Check for Sublimation before Refresh Koru-Moru ensures Sublimation is not active on target before casting Refresh --- scripts/actions/spells/trust/koru-moru.lua | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/scripts/actions/spells/trust/koru-moru.lua b/scripts/actions/spells/trust/koru-moru.lua index ab61d371e7b..51817a3bbd7 100644 --- a/scripts/actions/spells/trust/koru-moru.lua +++ b/scripts/actions/spells/trust/koru-moru.lua @@ -24,7 +24,11 @@ spellObject.onMobSpawn = function(mob) mob:addSimpleGambit(ai.t.PARTY, ai.c.HPP_LT, 50, ai.r.MA, ai.s.HIGHEST, xi.magic.spellFamily.CURE) mob:addSimpleGambit(ai.t.MELEE, ai.c.NOT_STATUS, xi.effect.HASTE, ai.r.MA, ai.s.HIGHEST, xi.magic.spellFamily.HASTE) - mob:addSimpleGambit(ai.t.CASTER, ai.c.NOT_STATUS, xi.effect.REFRESH, ai.r.MA, ai.s.HIGHEST, xi.magic.spellFamily.REFRESH) + mob:addGambit(ai.t.CASTER, { + { cond = ai.c.NOT_STATUS, arg = xi.effect.REFRESH }, + { cond = ai.c.NOT_STATUS, arg = xi.effect.SUBLIMATION_ACTIVATED }, + { cond = ai.c.NOT_STATUS, arg = xi.effect.SUBLIMATION_COMPLETE }, + }, ai.r.MA, ai.s.HIGHEST, xi.magic.spellFamily.REFRESH) mob:addSimpleGambit(ai.t.TANK, ai.c.NOT_STATUS, xi.effect.REFRESH, ai.r.MA, ai.s.HIGHEST, xi.magic.spellFamily.REFRESH) mob:addSimpleGambit(ai.t.RANGED, ai.c.NOT_STATUS, xi.effect.FLURRY_II, ai.r.MA, ai.s.HIGHEST, xi.magic.spellFamily.FLURRY) -- xi.effect.FLURRY_II is not a typo From 01550145eedd7a4fb4f56d596faf42cc4e9ab98c Mon Sep 17 00:00:00 2001 From: sruon Date: Fri, 24 Jan 2025 02:36:17 -0700 Subject: [PATCH 3/6] Gambits support multiple predicates Remove duplicate predicate checks Collect all possible targets before running predicates Ensure target meet all predicates --- src/map/ai/helpers/gambits_container.cpp | 253 +++++------------------ 1 file changed, 55 insertions(+), 198 deletions(-) diff --git a/src/map/ai/helpers/gambits_container.cpp b/src/map/ai/helpers/gambits_container.cpp index 87b6cabd8cd..787da180e93 100644 --- a/src/map/ai/helpers/gambits_container.cpp +++ b/src/map/ai/helpers/gambits_container.cpp @@ -91,160 +91,6 @@ namespace gambits return; } - auto runPredicate = [&](Predicate_t& predicate) -> bool - { - auto isValidMember = [&](CBattleEntity* PPartyTarget) -> bool - { - return PPartyTarget->isAlive() && POwner->loc.zone == PPartyTarget->loc.zone && distance(POwner->loc.p, PPartyTarget->loc.p) <= 15.0f; - }; - - if (predicate.target == G_TARGET::SELF) - { - return CheckTrigger(POwner, predicate); - } - else if (predicate.target == G_TARGET::TARGET) - { - return CheckTrigger(POwner->GetBattleTarget(), predicate); - } - else if (predicate.target == G_TARGET::PARTY) - { - auto result = false; - // clang-format off - static_cast(POwner->PMaster)->ForPartyWithTrusts([&](CBattleEntity* PMember) - { - if (isValidMember(PMember) && CheckTrigger(PMember, predicate)) - { - result = true; - } - }); - // clang-format on - return result; - } - else if (predicate.target == G_TARGET::MASTER) - { - return CheckTrigger(POwner->PMaster, predicate); - } - else if (predicate.target == G_TARGET::PARTY_DEAD) - { - auto result = false; - // clang-format off - static_cast(POwner->PMaster)->ForPartyWithTrusts([&](CBattleEntity* PMember) - { - if (PMember->isDead()) - { - result = true; - } - }); - // clang-format on - return result; - } - else if (predicate.target == G_TARGET::TANK) - { - auto result = false; - // clang-format off - static_cast(POwner->PMaster)->ForPartyWithTrusts([&](CBattleEntity* PMember) - { - if (isValidMember(PMember) && CheckTrigger(PMember, predicate) && (PMember->GetMJob() == JOB_PLD || PMember->GetMJob() == JOB_RUN)) - { - result = true; - } - }); - // clang-format on - return result; - } - else if (predicate.target == G_TARGET::MELEE) - { - auto result = false; - // clang-format off - static_cast(POwner->PMaster)->ForPartyWithTrusts([&](CBattleEntity* PMember) - { - if (isValidMember(PMember) && CheckTrigger(PMember, predicate) && melee_jobs.find(PMember->GetMJob()) != melee_jobs.end()) - { - result = true; - } - }); - // clang-format on - return result; - } - else if (predicate.target == G_TARGET::RANGED) - { - auto result = false; - static_cast(POwner->PMaster)->ForPartyWithTrusts([&](CBattleEntity* PMember) - { - if (isValidMember(PMember) && CheckTrigger(PMember, predicate) && (PMember->GetMJob() == JOB_RNG || PMember->GetMJob() == JOB_COR)) - { - result = true; - } }); - return result; - } - else if (predicate.target == G_TARGET::CASTER) - { - auto result = false; - // clang-format off - static_cast(POwner->PMaster)->ForPartyWithTrusts([&](CBattleEntity* PMember) - { - if (isValidMember(PMember) && CheckTrigger(PMember, predicate) && caster_jobs.find(PMember->GetMJob()) != caster_jobs.end()) - { - result = true; - } - }); - // clang-format on - return result; - } - else if (predicate.target == G_TARGET::TOP_ENMITY) - { - auto result = false; - if (auto* PMob = dynamic_cast(POwner->GetBattleTarget())) - { - // clang-format off - static_cast(POwner->PMaster)->ForPartyWithTrusts([&](CBattleEntity* PMember) - { - if (isValidMember(PMember) && CheckTrigger(PMember, predicate) && PMob->PEnmityContainer->GetHighestEnmity() == PMember) - { - result = true; - } - }); - // clang-format on - } - return result; - } - else if (predicate.target == G_TARGET::CURILLA) - { - auto result = false; - // clang-format off - static_cast(POwner->PMaster)->ForPartyWithTrusts([&](CBattleEntity* PMember) - { - if (isValidMember(PMember) && CheckTrigger(PMember, predicate)) - { - auto name = PMember->getName(); - if (strcmpi(name.c_str(), "curilla") == 0) - { - result = true; - } - } - }); - // clang-format on - return result; - } - else if (predicate.target == G_TARGET::PARTY_MULTI) - { - uint8 count = 0; - // clang-format off - static_cast(POwner->PMaster)->ForPartyWithTrusts([&](CBattleEntity* PMember) - { - if (isValidMember(PMember) && CheckTrigger(PMember, predicate)) - { - ++count; - } - }); - // clang-format on - return count > 1; - } - - // Fallthrough - return false; - }; - // Didn't WS/MS, go for other Gambits for (auto& gambit : gambits) { @@ -255,20 +101,6 @@ namespace gambits for (auto& action : gambit.actions) { - // Make sure that the predicates remain true for each action in a gambit - bool all_predicates_true = true; - for (auto& predicate : gambit.predicates) - { - if (!runPredicate(predicate)) - { - all_predicates_true = false; - } - } - if (!all_predicates_true) - { - break; - } - auto isValidMember = [this](CBattleEntity* PSettableTarget, CBattleEntity* PPartyTarget) { return !PSettableTarget && PPartyTarget->isAlive() && POwner->loc.zone == PPartyTarget->loc.zone && @@ -277,33 +109,39 @@ namespace gambits // TODO: This whole section is messy and bonkers // Try and extract target out the first predicate - CBattleEntity* target = nullptr; - if (gambit.predicates[0].target == G_TARGET::SELF) + // All predicates in a gambit should be pointing to the same target + // TODO: What's the point of predicates holding the target? + G_TARGET targetType = gambit.predicates[0].target; + + CBattleEntity* target = nullptr; + std::list potentialTargets; + + if (targetType == G_TARGET::SELF) { - target = CheckTrigger(POwner, gambit.predicates[0]) ? POwner : nullptr; + potentialTargets.push_back(POwner); } - else if (gambit.predicates[0].target == G_TARGET::TARGET) + else if (targetType == G_TARGET::TARGET) { auto* mob = POwner->GetBattleTarget(); - target = CheckTrigger(mob, gambit.predicates[0]) ? mob : nullptr; + potentialTargets.push_back(mob); } - else if (gambit.predicates[0].target == G_TARGET::PARTY) + else if (targetType == G_TARGET::PARTY) { // clang-format off static_cast(POwner->PMaster)->ForPartyWithTrusts([&](CBattleEntity* PMember) { - if (isValidMember(target, PMember) && CheckTrigger(PMember, gambit.predicates[0])) + if (isValidMember(target, PMember)) { - target = PMember; + potentialTargets.push_back(PMember); } }); // clang-format on } - else if (gambit.predicates[0].target == G_TARGET::MASTER) + else if (targetType == G_TARGET::MASTER) { - target = POwner->PMaster; + potentialTargets.push_back(POwner->PMaster); } - else if (gambit.predicates[0].target == G_TARGET::PARTY_DEAD) + else if (targetType == G_TARGET::PARTY_DEAD) { auto* mob = POwner->GetBattleTarget(); if (mob != nullptr) @@ -312,96 +150,115 @@ namespace gambits static_cast(POwner->PMaster)->ForParty([&](CBattleEntity* PMember) { if (PMember->isDead()) { - target = PMember; + potentialTargets.push_back(PMember); } }); // clang-format on } } - else if (gambit.predicates[0].target == G_TARGET::TANK) + else if (targetType == G_TARGET::TANK) { // clang-format off static_cast(POwner->PMaster)->ForPartyWithTrusts([&](CBattleEntity* PMember) { - if (isValidMember(target, PMember) && CheckTrigger(PMember, gambit.predicates[0]) && + if (isValidMember(target, PMember) && (PMember->GetMJob() == JOB_PLD || PMember->GetMJob() == JOB_RUN)) { - target = PMember; + potentialTargets.push_back(PMember); } }); // clang-format on } - else if (gambit.predicates[0].target == G_TARGET::MELEE) + else if (targetType == G_TARGET::MELEE) { // clang-format off static_cast(POwner->PMaster)->ForPartyWithTrusts([&](CBattleEntity* PMember) { - if (isValidMember(target, PMember) && CheckTrigger(PMember, gambit.predicates[0]) && + if (isValidMember(target, PMember) && melee_jobs.find(PMember->GetMJob()) != melee_jobs.end()) { - target = PMember; + potentialTargets.push_back(PMember); } }); // clang-format on } - else if (gambit.predicates[0].target == G_TARGET::RANGED) + else if (targetType == G_TARGET::RANGED) { // clang-format off static_cast(POwner->PMaster)->ForPartyWithTrusts([&](CBattleEntity* PMember) { - if (isValidMember(target, PMember) && CheckTrigger(PMember, gambit.predicates[0]) && + if (isValidMember(target, PMember) && (PMember->GetMJob() == JOB_RNG || PMember->GetMJob() == JOB_COR)) { - target = PMember; + potentialTargets.push_back(PMember); } }); // clang-format on } - else if (gambit.predicates[0].target == G_TARGET::CASTER) + else if (targetType == G_TARGET::CASTER) { // clang-format off static_cast(POwner->PMaster)->ForPartyWithTrusts([&](CBattleEntity* PMember) { - if (isValidMember(target, PMember) && CheckTrigger(PMember, gambit.predicates[0]) && + if (isValidMember(target, PMember) && caster_jobs.find(PMember->GetMJob()) != caster_jobs.end()) { - target = PMember; + potentialTargets.push_back(PMember); } }); // clang-format on } - else if (gambit.predicates[0].target == G_TARGET::TOP_ENMITY) + else if (targetType == G_TARGET::TOP_ENMITY) { if (auto* PMob = dynamic_cast(POwner->GetBattleTarget())) { // clang-format off static_cast(POwner->PMaster)->ForPartyWithTrusts([&](CBattleEntity* PMember) { - if (isValidMember(target, PMember) && CheckTrigger(PMember, gambit.predicates[0]) && + if (isValidMember(target, PMember) && PMob->PEnmityContainer->GetHighestEnmity() == PMember) { - target = PMember; + potentialTargets.push_back(PMember); } }); // clang-format on } } - else if (gambit.predicates[0].target == G_TARGET::CURILLA) + else if (targetType == G_TARGET::CURILLA) { // clang-format off static_cast(POwner->PMaster)->ForPartyWithTrusts([&](CBattleEntity* PMember) { - if (isValidMember(target, PMember) && CheckTrigger(PMember, gambit.predicates[0])) + if (isValidMember(target, PMember)) { auto name = PMember->getName(); if (strcmpi(name.c_str(), "curilla") == 0) { - target = PMember; + potentialTargets.push_back(PMember); } } }); // clang-format on } + for (auto& potentialTarget : potentialTargets) + { + // All predicates must be true for the target to be considered + // TODO: Support OR + bool targetMatchAllPredicates = true; + for (auto& predicate : gambit.predicates) + { + if (!CheckTrigger(potentialTarget, predicate)) + { + targetMatchAllPredicates = false; + } + } + // All predicates were matched, use this target + if (targetMatchAllPredicates) + { + target = potentialTarget; + break; + } + } if (!target) { From 3338508195d3ddcc043ea864f6a24196728929ea Mon Sep 17 00:00:00 2001 From: sruon Date: Sat, 25 Jan 2025 02:50:07 -0700 Subject: [PATCH 4/6] Koru-Moru: Remove duplicate Dia gambit --- scripts/actions/spells/trust/koru-moru.lua | 1 - 1 file changed, 1 deletion(-) diff --git a/scripts/actions/spells/trust/koru-moru.lua b/scripts/actions/spells/trust/koru-moru.lua index 51817a3bbd7..5b5a113f57b 100644 --- a/scripts/actions/spells/trust/koru-moru.lua +++ b/scripts/actions/spells/trust/koru-moru.lua @@ -39,7 +39,6 @@ spellObject.onMobSpawn = function(mob) mob:addSimpleGambit(ai.t.TARGET, ai.c.NOT_STATUS, xi.effect.DIA, ai.r.MA, ai.s.HIGHEST, xi.magic.spellFamily.DIA, 60) mob:addSimpleGambit(ai.t.TARGET, ai.c.NOT_STATUS, xi.effect.SLOW, ai.r.MA, ai.s.HIGHEST, xi.magic.spellFamily.SLOW, 60) mob:addSimpleGambit(ai.t.TARGET, ai.c.NOT_STATUS, xi.effect.EVASION_DOWN, ai.r.MA, ai.s.HIGHEST, xi.magic.spellFamily.DISTRACT, 60) - mob:addSimpleGambit(ai.t.TARGET, ai.c.NOT_STATUS, xi.effect.DIA, ai.r.MA, ai.s.HIGHEST, xi.magic.spellFamily.DIA, 60) mob:addSimpleGambit(ai.t.PARTY, ai.c.NOT_STATUS, xi.effect.PROTECT, ai.r.MA, ai.s.HIGHEST, xi.magic.spellFamily.PROTECT) mob:addSimpleGambit(ai.t.PARTY, ai.c.NOT_STATUS, xi.effect.SHELL, ai.r.MA, ai.s.HIGHEST, xi.magic.spellFamily.SHELL) From 16e223e013948a5d86cb4d532c6f32b41a5318a2 Mon Sep 17 00:00:00 2001 From: sruon Date: Sun, 26 Jan 2025 03:17:32 -0700 Subject: [PATCH 5/6] addGambit supports logically grouped predicates - Move targetSelector from predicates to main Gambit object - Do not reiterate the full gambit logic for each action attached to it - if the conditions were true then we want all the actions to be performed - Introduce a PredicateGroup layer between Gambits and Predicates. The group has a logic operator attached that defines how the predicates should be resolved - Resolve all predicate groups for a gambit before coming to a conclusion - --- scripts/actions/spells/trust/koru-moru.lua | 13 +- scripts/globals/gambits.lua | 30 ++ scripts/specs/core/CBaseEntity.lua | 15 +- src/map/ai/helpers/gambits_container.cpp | 577 +++++++++++---------- src/map/ai/helpers/gambits_container.h | 37 +- src/map/lua/lua_baseentity.cpp | 185 +++++-- src/map/lua/lua_baseentity.h | 4 +- 7 files changed, 533 insertions(+), 328 deletions(-) diff --git a/scripts/actions/spells/trust/koru-moru.lua b/scripts/actions/spells/trust/koru-moru.lua index 5b5a113f57b..a969e0b7503 100644 --- a/scripts/actions/spells/trust/koru-moru.lua +++ b/scripts/actions/spells/trust/koru-moru.lua @@ -25,12 +25,15 @@ spellObject.onMobSpawn = function(mob) mob:addSimpleGambit(ai.t.MELEE, ai.c.NOT_STATUS, xi.effect.HASTE, ai.r.MA, ai.s.HIGHEST, xi.magic.spellFamily.HASTE) mob:addGambit(ai.t.CASTER, { - { cond = ai.c.NOT_STATUS, arg = xi.effect.REFRESH }, - { cond = ai.c.NOT_STATUS, arg = xi.effect.SUBLIMATION_ACTIVATED }, - { cond = ai.c.NOT_STATUS, arg = xi.effect.SUBLIMATION_COMPLETE }, - }, ai.r.MA, ai.s.HIGHEST, xi.magic.spellFamily.REFRESH) + { ai.c.NOT_STATUS, xi.effect.REFRESH }, + { ai.c.NOT_STATUS, xi.effect.SUBLIMATION_ACTIVATED }, + { ai.c.NOT_STATUS, xi.effect.SUBLIMATION_COMPLETE }, + }, { ai.r.MA, ai.s.HIGHEST, xi.magic.spellFamily.REFRESH }) mob:addSimpleGambit(ai.t.TANK, ai.c.NOT_STATUS, xi.effect.REFRESH, ai.r.MA, ai.s.HIGHEST, xi.magic.spellFamily.REFRESH) - mob:addSimpleGambit(ai.t.RANGED, ai.c.NOT_STATUS, xi.effect.FLURRY_II, ai.r.MA, ai.s.HIGHEST, xi.magic.spellFamily.FLURRY) -- xi.effect.FLURRY_II is not a typo + mob:addGambit(ai.t.RANGED, { + { ai.c.NOT_STATUS, xi.effect.FLURRY_II }, + { ai.c.NOT_STATUS, xi.effect.HASTE }, -- No overwriting Haste + }, { ai.r.MA, ai.s.HIGHEST, xi.magic.spellFamily.FLURRY }) -- xi.effect.FLURRY_II is not a typo mob:addSimpleGambit(ai.t.TOP_ENMITY, ai.c.NOT_STATUS, xi.effect.PHALANX, ai.r.MA, ai.s.SPECIFIC, xi.magic.spell.PHALANX_II) diff --git a/scripts/globals/gambits.lua b/scripts/globals/gambits.lua index 882fc6cf19a..20d0aa44809 100644 --- a/scripts/globals/gambits.lua +++ b/scripts/globals/gambits.lua @@ -24,6 +24,36 @@ ai.target = } ai.t = ai.target +-- Logic +ai.logic = +{ + AND = 0, +} + +ai.logic.OR = setmetatable( + { value = 1 }, + { + __call = function(self, ...) + local args = { ... } + local conditions = {} + + for _, condition in ipairs(args) do + if type(condition) == 'table' then + table.insert(conditions, condition) + else + error('ai.logic.OR expects only tables as arguments, got ' .. type(condition)) + end + end + + return { + logic = self.value, + conditions = conditions, + } + end, + } +) +ai.l = ai.logic + -- Condition ai.condition = { diff --git a/scripts/specs/core/CBaseEntity.lua b/scripts/specs/core/CBaseEntity.lua index c825816084b..a29882bedce 100644 --- a/scripts/specs/core/CBaseEntity.lua +++ b/scripts/specs/core/CBaseEntity.lua @@ -3277,12 +3277,10 @@ end ---@param targ integer ---@param conditions table ----@param react integer ----@param select integer ----@param selectorArg integer +---@param reactions table ---@param retry integer? ---@return string -function CBaseEntity:addGambit(targ, conditions, react, select, selectorArg, retry) +function CBaseEntity:addGambit(targ, conditions, reactions) end ---@param targ integer @@ -3296,11 +3294,20 @@ end function CBaseEntity:addSimpleGambit(targ, cond, condition_arg, react, select, selectorArg, retry) end +---@param id string +---@return nil +function CBaseEntity:removeGambit(id) +end + ---@param id string ---@return nil function CBaseEntity:removeSimpleGambit(id) end +---@return nil +function CBaseEntity:removeAllGambits() +end + ---@return nil function CBaseEntity:removeAllSimpleGambits() end diff --git a/src/map/ai/helpers/gambits_container.cpp b/src/map/ai/helpers/gambits_container.cpp index 787da180e93..f7067504c1b 100644 --- a/src/map/ai/helpers/gambits_container.cpp +++ b/src/map/ai/helpers/gambits_container.cpp @@ -20,6 +20,12 @@ namespace gambits { + // Return a new unique identifier for a gambit + auto CGambitsContainer::NewGambitIdentifier(Gambit_t const& gambit) const -> std::string + { + return std::format("{}_{}_{}", gambits.size(), gambit.predicate_groups.size(), gambit.actions.size()); + } + // Validate gambit before it's inserted into the gambit list // Check levels, etc. std::string CGambitsContainer::AddGambit(Gambit_t const& gambit) @@ -99,172 +105,173 @@ namespace gambits continue; } - for (auto& action : gambit.actions) + auto isValidMember = [this](CBattleEntity* PSettableTarget, CBattleEntity* PPartyTarget) { - auto isValidMember = [this](CBattleEntity* PSettableTarget, CBattleEntity* PPartyTarget) - { - return !PSettableTarget && PPartyTarget->isAlive() && POwner->loc.zone == PPartyTarget->loc.zone && - distance(POwner->loc.p, PPartyTarget->loc.p) <= 15.0f; - }; + return !PSettableTarget && PPartyTarget->isAlive() && POwner->loc.zone == PPartyTarget->loc.zone && + distance(POwner->loc.p, PPartyTarget->loc.p) <= 15.0f; + }; - // TODO: This whole section is messy and bonkers - // Try and extract target out the first predicate - // All predicates in a gambit should be pointing to the same target - // TODO: What's the point of predicates holding the target? - G_TARGET targetType = gambit.predicates[0].target; + G_TARGET targetType = gambit.target_selector; - CBattleEntity* target = nullptr; - std::list potentialTargets; + CBattleEntity* target = nullptr; - if (targetType == G_TARGET::SELF) - { - potentialTargets.push_back(POwner); - } - else if (targetType == G_TARGET::TARGET) + // Capture all potential targets + std::vector potentialTargets; + + if (targetType == G_TARGET::SELF) + { + potentialTargets.push_back(POwner); + } + else if (targetType == G_TARGET::TARGET) + { + auto* mob = POwner->GetBattleTarget(); + potentialTargets.push_back(mob); + } + else if (targetType == G_TARGET::PARTY) + { + // clang-format off + static_cast(POwner->PMaster)->ForPartyWithTrusts([&](CBattleEntity* PMember) { - auto* mob = POwner->GetBattleTarget(); - potentialTargets.push_back(mob); - } - else if (targetType == G_TARGET::PARTY) + if (isValidMember(target, PMember)) + { + potentialTargets.push_back(PMember); + } + }); + // clang-format on + } + else if (targetType == G_TARGET::MASTER) + { + potentialTargets.push_back(POwner->PMaster); + } + else if (targetType == G_TARGET::PARTY_DEAD) + { + auto* mob = POwner->GetBattleTarget(); + if (mob != nullptr) { // clang-format off - static_cast(POwner->PMaster)->ForPartyWithTrusts([&](CBattleEntity* PMember) - { - if (isValidMember(target, PMember)) + static_cast(POwner->PMaster)->ForParty([&](CBattleEntity* PMember) { + if (PMember->isDead()) { potentialTargets.push_back(PMember); } }); // clang-format on } - else if (targetType == G_TARGET::MASTER) + } + else if (targetType == G_TARGET::TANK) + { + // clang-format off + static_cast(POwner->PMaster)->ForPartyWithTrusts([&](CBattleEntity* PMember) { - potentialTargets.push_back(POwner->PMaster); - } - else if (targetType == G_TARGET::PARTY_DEAD) + if (isValidMember(target, PMember) && + (PMember->GetMJob() == JOB_PLD || PMember->GetMJob() == JOB_RUN)) + { + potentialTargets.push_back(PMember); + } + }); + // clang-format on + } + else if (targetType == G_TARGET::MELEE) + { + // clang-format off + static_cast(POwner->PMaster)->ForPartyWithTrusts([&](CBattleEntity* PMember) { - auto* mob = POwner->GetBattleTarget(); - if (mob != nullptr) + if (isValidMember(target, PMember) && + melee_jobs.find(PMember->GetMJob()) != melee_jobs.end()) { - // clang-format off - static_cast(POwner->PMaster)->ForParty([&](CBattleEntity* PMember) { - if (PMember->isDead()) - { - potentialTargets.push_back(PMember); - } - }); - // clang-format on + potentialTargets.push_back(PMember); } - } - else if (targetType == G_TARGET::TANK) + }); + // clang-format on + } + else if (targetType == G_TARGET::RANGED) + { + // clang-format off + static_cast(POwner->PMaster)->ForPartyWithTrusts([&](CBattleEntity* PMember) { - // clang-format off - static_cast(POwner->PMaster)->ForPartyWithTrusts([&](CBattleEntity* PMember) + if (isValidMember(target, PMember) && + (PMember->GetMJob() == JOB_RNG || PMember->GetMJob() == JOB_COR)) { - if (isValidMember(target, PMember) && - (PMember->GetMJob() == JOB_PLD || PMember->GetMJob() == JOB_RUN)) - { - potentialTargets.push_back(PMember); - } - }); - // clang-format on - } - else if (targetType == G_TARGET::MELEE) + potentialTargets.push_back(PMember); + } + }); + // clang-format on + } + else if (targetType == G_TARGET::CASTER) + { + // clang-format off + static_cast(POwner->PMaster)->ForPartyWithTrusts([&](CBattleEntity* PMember) { - // clang-format off - static_cast(POwner->PMaster)->ForPartyWithTrusts([&](CBattleEntity* PMember) + if (isValidMember(target, PMember) && + caster_jobs.find(PMember->GetMJob()) != caster_jobs.end()) { - if (isValidMember(target, PMember) && - melee_jobs.find(PMember->GetMJob()) != melee_jobs.end()) - { - potentialTargets.push_back(PMember); - } - }); - // clang-format on - } - else if (targetType == G_TARGET::RANGED) + potentialTargets.push_back(PMember); + } + }); + // clang-format on + } + else if (targetType == G_TARGET::TOP_ENMITY) + { + if (auto* PMob = dynamic_cast(POwner->GetBattleTarget())) { // clang-format off static_cast(POwner->PMaster)->ForPartyWithTrusts([&](CBattleEntity* PMember) { if (isValidMember(target, PMember) && - (PMember->GetMJob() == JOB_RNG || PMember->GetMJob() == JOB_COR)) + PMob->PEnmityContainer->GetHighestEnmity() == PMember) { potentialTargets.push_back(PMember); } }); // clang-format on } - else if (targetType == G_TARGET::CASTER) + } + else if (targetType == G_TARGET::CURILLA) + { + // clang-format off + static_cast(POwner->PMaster)->ForPartyWithTrusts([&](CBattleEntity* PMember) { - // clang-format off - static_cast(POwner->PMaster)->ForPartyWithTrusts([&](CBattleEntity* PMember) + if (isValidMember(target, PMember)) { - if (isValidMember(target, PMember) && - caster_jobs.find(PMember->GetMJob()) != caster_jobs.end()) + auto name = PMember->getName(); + if (strcmpi(name.c_str(), "curilla") == 0) { potentialTargets.push_back(PMember); } - }); - // clang-format on - } - else if (targetType == G_TARGET::TOP_ENMITY) - { - if (auto* PMob = dynamic_cast(POwner->GetBattleTarget())) - { - // clang-format off - static_cast(POwner->PMaster)->ForPartyWithTrusts([&](CBattleEntity* PMember) - { - if (isValidMember(target, PMember) && - PMob->PEnmityContainer->GetHighestEnmity() == PMember) - { - potentialTargets.push_back(PMember); - } - }); - // clang-format on } - } - else if (targetType == G_TARGET::CURILLA) - { - // clang-format off - static_cast(POwner->PMaster)->ForPartyWithTrusts([&](CBattleEntity* PMember) - { - if (isValidMember(target, PMember)) - { - auto name = PMember->getName(); - if (strcmpi(name.c_str(), "curilla") == 0) - { - potentialTargets.push_back(PMember); - } - } - }); - // clang-format on - } - for (auto& potentialTarget : potentialTargets) + }); + // clang-format on + } + + // For each potential target, check if the predicates resolves + for (auto& potentialTarget : potentialTargets) + { + // All predicate groups must resolve successfully for the target to be considered + bool targetMatchAllPredicates = true; + for (auto& predicate_group : gambit.predicate_groups) { - // All predicates must be true for the target to be considered - // TODO: Support OR - bool targetMatchAllPredicates = true; - for (auto& predicate : gambit.predicates) + if (!CheckTrigger(potentialTarget, predicate_group)) { - if (!CheckTrigger(potentialTarget, predicate)) - { - targetMatchAllPredicates = false; - } - } - // All predicates were matched, use this target - if (targetMatchAllPredicates) - { - target = potentialTarget; - break; + targetMatchAllPredicates = false; } } - if (!target) + if (targetMatchAllPredicates) { + target = potentialTarget; break; } + } + + // No target matched, continue to next gambit + if (!target) + { + continue; + } + // Execute all actions defined on the Gambit + for (auto& action : gambit.actions) + { if (action.reaction == G_REACTION::RATTACK) { controller->RangedAttack(target->targid); @@ -530,177 +537,199 @@ namespace gambits } } - bool CGambitsContainer::CheckTrigger(CBattleEntity* trigger_target, Predicate_t& predicate) + bool CGambitsContainer::CheckTrigger(const CBattleEntity* triggerTarget, PredicateGroup_t& predicateGroup) { TracyZoneScoped; auto* controller = static_cast(POwner->PAI->GetController()); - switch (predicate.condition) + std::vector predicateResults; + + // Iterate and collect results from all predicates in the group + for (auto& predicate : predicateGroup.predicates) { - case G_CONDITION::ALWAYS: - { - return true; - break; - } - case G_CONDITION::HPP_LT: - { - return trigger_target->GetHPP() < predicate.condition_arg; - break; - } - case G_CONDITION::HPP_GTE: - { - return trigger_target->GetHPP() >= predicate.condition_arg; - break; - } - case G_CONDITION::MPP_LT: - { - return trigger_target->GetMPP() < predicate.condition_arg; - break; - } - case G_CONDITION::TP_LT: - { - return trigger_target->health.tp < (int16)predicate.condition_arg; - break; - } - case G_CONDITION::TP_GTE: - { - return trigger_target->health.tp >= (int16)predicate.condition_arg; - break; - } - case G_CONDITION::STATUS: - { - return trigger_target->StatusEffectContainer->HasStatusEffect(static_cast(predicate.condition_arg)); - break; - } - case G_CONDITION::NOT_STATUS: - { - return !trigger_target->StatusEffectContainer->HasStatusEffect(static_cast(predicate.condition_arg)); - break; - } - case G_CONDITION::NO_SAMBA: + switch (predicate.condition) { - bool noSamba = true; - if (trigger_target->StatusEffectContainer->HasStatusEffect(EFFECT_DRAIN_SAMBA) || - trigger_target->StatusEffectContainer->HasStatusEffect(EFFECT_HASTE_SAMBA)) + case G_CONDITION::ALWAYS: { - noSamba = false; + predicateResults.push_back(true); + continue; } - return noSamba; - break; - } - case G_CONDITION::NO_STORM: - { - bool noStorm = true; - // clang-format off - if (trigger_target->StatusEffectContainer->HasStatusEffect( - { - EFFECT_FIRESTORM, - EFFECT_HAILSTORM, - EFFECT_WINDSTORM, - EFFECT_SANDSTORM, - EFFECT_THUNDERSTORM, - EFFECT_RAINSTORM, - EFFECT_AURORASTORM, - EFFECT_VOIDSTORM, - EFFECT_FIRESTORM_II, - EFFECT_HAILSTORM_II, - EFFECT_WINDSTORM_II, - EFFECT_SANDSTORM_II, - EFFECT_THUNDERSTORM_II, - EFFECT_RAINSTORM_II, - EFFECT_AURORASTORM_II, - EFFECT_VOIDSTORM_II, - })) - { - noStorm = false; + case G_CONDITION::HPP_LT: + { + predicateResults.push_back(triggerTarget->GetHPP() < predicate.condition_arg); + continue; + } + case G_CONDITION::HPP_GTE: + { + predicateResults.push_back(triggerTarget->GetHPP() >= predicate.condition_arg); + continue; + } + case G_CONDITION::MPP_LT: + { + predicateResults.push_back(triggerTarget->GetMPP() < predicate.condition_arg); + continue; + } + case G_CONDITION::TP_LT: + { + predicateResults.push_back(triggerTarget->health.tp < (int16)predicate.condition_arg); + continue; + } + case G_CONDITION::TP_GTE: + { + predicateResults.push_back(triggerTarget->health.tp >= (int16)predicate.condition_arg); + continue; + } + case G_CONDITION::STATUS: + { + predicateResults.push_back(triggerTarget->StatusEffectContainer->HasStatusEffect(static_cast(predicate.condition_arg))); + continue; + } + case G_CONDITION::NOT_STATUS: + { + predicateResults.push_back(!triggerTarget->StatusEffectContainer->HasStatusEffect(static_cast(predicate.condition_arg))); + continue; + } + case G_CONDITION::NO_SAMBA: + { + bool noSamba = true; + if (triggerTarget->StatusEffectContainer->HasStatusEffect(EFFECT_DRAIN_SAMBA) || + triggerTarget->StatusEffectContainer->HasStatusEffect(EFFECT_HASTE_SAMBA)) + { + noSamba = false; + } + predicateResults.push_back(noSamba); + continue; + } + case G_CONDITION::NO_STORM: + { + bool noStorm = true; + // clang-format off + if (triggerTarget->StatusEffectContainer->HasStatusEffect( + { + EFFECT_FIRESTORM, + EFFECT_HAILSTORM, + EFFECT_WINDSTORM, + EFFECT_SANDSTORM, + EFFECT_THUNDERSTORM, + EFFECT_RAINSTORM, + EFFECT_AURORASTORM, + EFFECT_VOIDSTORM, + EFFECT_FIRESTORM_II, + EFFECT_HAILSTORM_II, + EFFECT_WINDSTORM_II, + EFFECT_SANDSTORM_II, + EFFECT_THUNDERSTORM_II, + EFFECT_RAINSTORM_II, + EFFECT_AURORASTORM_II, + EFFECT_VOIDSTORM_II, + })) + { + noStorm = false; + } + // clang-format on + predicateResults.push_back(noStorm); + continue; + } + case G_CONDITION::PT_HAS_TANK: + { + predicateResults.push_back(PartyHasTank()); + continue; + } + case G_CONDITION::NOT_PT_HAS_TANK: + { + predicateResults.push_back(!PartyHasTank()); + continue; + } + case G_CONDITION::STATUS_FLAG: + { + predicateResults.push_back(triggerTarget->StatusEffectContainer->HasStatusEffectByFlag(static_cast(predicate.condition_arg))); + continue; + } + case G_CONDITION::HAS_TOP_ENMITY: + { + predicateResults.push_back((controller->GetTopEnmity()) ? controller->GetTopEnmity()->targid == POwner->targid : false); + continue; + } + case G_CONDITION::NOT_HAS_TOP_ENMITY: + { + predicateResults.push_back((controller->GetTopEnmity()) ? controller->GetTopEnmity()->targid != POwner->targid : false); + continue; + } + case G_CONDITION::SC_AVAILABLE: + { + auto* PSCEffect = triggerTarget->StatusEffectContainer->GetStatusEffect(EFFECT_SKILLCHAIN); + predicateResults.push_back(PSCEffect && PSCEffect->GetStartTime() + 3s < server_clock::now() && PSCEffect->GetTier() == 0); + continue; + } + case G_CONDITION::NOT_SC_AVAILABLE: + { + auto* PSCEffect = triggerTarget->StatusEffectContainer->GetStatusEffect(EFFECT_SKILLCHAIN); + predicateResults.push_back(PSCEffect == nullptr); + continue; + } + case G_CONDITION::MB_AVAILABLE: + { + auto* PSCEffect = triggerTarget->StatusEffectContainer->GetStatusEffect(EFFECT_SKILLCHAIN); + predicateResults.push_back(PSCEffect && PSCEffect->GetStartTime() + 3s < server_clock::now() && PSCEffect->GetTier() > 0); + continue; + } + case G_CONDITION::READYING_WS: + { + predicateResults.push_back(triggerTarget->PAI->IsCurrentState()); + continue; + } + case G_CONDITION::READYING_MS: + { + predicateResults.push_back(triggerTarget->PAI->IsCurrentState()); + continue; + } + case G_CONDITION::READYING_JA: + { + predicateResults.push_back(triggerTarget->PAI->IsCurrentState()); + continue; + } + case G_CONDITION::CASTING_MA: + { + predicateResults.push_back(triggerTarget->PAI->IsCurrentState()); + continue; + } + case G_CONDITION::IS_ECOSYSTEM: + { + predicateResults.push_back(triggerTarget->m_EcoSystem == ECOSYSTEM(predicate.condition_arg)); + continue; + } + case G_CONDITION::RANDOM: + { + predicateResults.push_back(xirand::GetRandomNumber(100) < (int16)predicate.condition_arg); + continue; + } + case G_CONDITION::HP_MISSING: + { + predicateResults.push_back((triggerTarget->health.maxhp - triggerTarget->health.hp) >= (int16)predicate.condition_arg); + continue; + } + default: + { + predicateResults.push_back(false); } - // clang-format on - return noStorm; - break; - } - case G_CONDITION::PT_HAS_TANK: - { - return PartyHasTank(); - break; - } - case G_CONDITION::NOT_PT_HAS_TANK: - { - return !PartyHasTank(); - break; - } - case G_CONDITION::STATUS_FLAG: - { - return trigger_target->StatusEffectContainer->HasStatusEffectByFlag(static_cast(predicate.condition_arg)); - break; - } - case G_CONDITION::HAS_TOP_ENMITY: - { - return (controller->GetTopEnmity()) ? controller->GetTopEnmity()->targid == POwner->targid : false; - break; - } - case G_CONDITION::NOT_HAS_TOP_ENMITY: - { - return (controller->GetTopEnmity()) ? controller->GetTopEnmity()->targid != POwner->targid : false; - break; - } - case G_CONDITION::SC_AVAILABLE: - { - auto* PSCEffect = trigger_target->StatusEffectContainer->GetStatusEffect(EFFECT_SKILLCHAIN); - return PSCEffect && PSCEffect->GetStartTime() + 3s < server_clock::now() && PSCEffect->GetTier() == 0; - break; - } - case G_CONDITION::NOT_SC_AVAILABLE: - { - auto* PSCEffect = trigger_target->StatusEffectContainer->GetStatusEffect(EFFECT_SKILLCHAIN); - return PSCEffect == nullptr; - break; - } - case G_CONDITION::MB_AVAILABLE: - { - auto* PSCEffect = trigger_target->StatusEffectContainer->GetStatusEffect(EFFECT_SKILLCHAIN); - return PSCEffect && PSCEffect->GetStartTime() + 3s < server_clock::now() && PSCEffect->GetTier() > 0; - break; - } - case G_CONDITION::READYING_WS: - { - return trigger_target->PAI->IsCurrentState(); - break; - } - case G_CONDITION::READYING_MS: - { - return trigger_target->PAI->IsCurrentState(); - break; - } - case G_CONDITION::READYING_JA: - { - return trigger_target->PAI->IsCurrentState(); - break; - } - case G_CONDITION::CASTING_MA: - { - return trigger_target->PAI->IsCurrentState(); - break; - } - case G_CONDITION::IS_ECOSYSTEM: - { - return trigger_target->m_EcoSystem == ECOSYSTEM(predicate.condition_arg); - break; } - case G_CONDITION::RANDOM: + } + + // Evaluate the group of predicates + switch (predicateGroup.logic) + { + case G_LOGIC::AND: { - return xirand::GetRandomNumber(100) < (int16)predicate.condition_arg; - break; + return std::ranges::all_of(predicateResults, [](const bool result) + { return result; }); } - case G_CONDITION::HP_MISSING: + case G_LOGIC::OR: { - return (trigger_target->health.maxhp - trigger_target->health.hp) >= (int16)predicate.condition_arg; - break; + return std::ranges::any_of(predicateResults, [](const bool result) + { return result; }); } default: - { return false; - break; - } } } diff --git a/src/map/ai/helpers/gambits_container.h b/src/map/ai/helpers/gambits_container.h index 59f13b4b053..7b5962a4b05 100644 --- a/src/map/ai/helpers/gambits_container.h +++ b/src/map/ai/helpers/gambits_container.h @@ -11,6 +11,7 @@ #include "status_effect_container.h" #include +#include namespace gambits { @@ -30,6 +31,12 @@ namespace gambits PARTY_MULTI = 11, }; + enum class G_LOGIC : uint16 + { + AND = 0, + OR = 1, + }; + enum class G_CONDITION : uint16 { ALWAYS = 0, @@ -97,7 +104,6 @@ namespace gambits struct Predicate_t { - G_TARGET target; G_CONDITION condition; uint32 condition_arg; @@ -106,20 +112,15 @@ namespace gambits { } - Predicate_t(G_TARGET _target, G_CONDITION _condition, uint32 _condition_arg) - : target(_target) - , condition(_condition) + Predicate_t(G_CONDITION _condition, uint32 _condition_arg) + : condition(_condition) , condition_arg(_condition_arg) { } bool parseInput(std::string const& key, uint32 value) { - if (key.compare("target") == 0) - { - target = static_cast(value); - } - else if (key.compare("condition") == 0) + if (key.compare("condition") == 0) { condition = static_cast(value); } @@ -136,6 +137,18 @@ namespace gambits } }; + struct PredicateGroup_t + { + G_LOGIC logic; + std::vector predicates; + + PredicateGroup_t(G_LOGIC _logic, std::vector _predicates) + : logic(_logic) + , predicates(std::move(_predicates)) + { + } + }; + struct Action_t { G_REACTION reaction; @@ -174,8 +187,9 @@ namespace gambits struct Gambit_t { - std::vector predicates; + std::vector predicate_groups; std::vector actions; + G_TARGET target_selector; uint16 retry_delay; time_point last_used; std::string identifier; @@ -234,6 +248,7 @@ namespace gambits } ~CGambitsContainer() = default; + auto NewGambitIdentifier(Gambit_t const& gambit) const -> std::string; auto AddGambit(Gambit_t const& gambit) -> std::string; void RemoveGambit(std::string const& id); void RemoveAllGambits(); @@ -246,7 +261,7 @@ namespace gambits uint16 tp_value; private: - bool CheckTrigger(CBattleEntity* trigger_target, Predicate_t& predicate); + bool CheckTrigger(const CBattleEntity* triggerTarget, PredicateGroup_t& predicateGroup); bool TryTrustSkill(); bool PartyHasHealer(); bool PartyHasTank(); diff --git a/src/map/lua/lua_baseentity.cpp b/src/map/lua/lua_baseentity.cpp index 41bc833bb00..357697570b1 100644 --- a/src/map/lua/lua_baseentity.cpp +++ b/src/map/lua/lua_baseentity.cpp @@ -15105,16 +15105,22 @@ void CLuaBaseEntity::trustPartyMessage(uint32 message_id) } /************************************************************************ - * Function: addGambit(target, predicates, reaction, selector, selector_arg, retry) - * Purpose : Adds a behavior to the gambit system with an arbitrary number of predicates - * Example : trust:addGambit(ai.t.CASTER, { - * { cond=ai.c.NOT_STATUS, arg=xi.effect.REFRESH }, - * { cond=ai.c.NOT_STATUS, arg=xi.effect.SUBLIMATION_ACTIVATED }, - * }, ai.r.MA, ai.s.HIGHEST, xi.magic.spellFamily.REFRESH) + * Function: addGambit(targetSelector, conditionsTable, actionsTable, retry) + * conditionsTable: { condition, arg1 } + * actionsTable: { reactionType, selector, selectorArg } + * Purpose : Adds a behavior to the gambit system with an arbitrary number of predicates and reactions + * Examples : trust:addGambit(ai.t.CASTER, { + * { ai.c.NOT_STATUS, xi.effect.REFRESH }, + * { ai.c.NOT_STATUS, xi.effect.SUBLIMATION_ACTIVATED }, + * }, { ai.r.MA, ai.s.HIGHEST, xi.magic.spellFamily.REFRESH }) + * trust:addGambit(ai.t.SELF, { ai.c.NOT_STATUS, xi.effect.HASTE }, { ai.r.MA, ai.s.HIGHEST, xi.magic.spellFamily.HASTE }) + * trust:addGambit(ai.t.TARGET, { ai.c.NOT_STATUS, xi.effect.FLASH }, { + * { ai.r.JA, ai.s.SPECIFIC, xi.ja.DIVINE_EMBLEM }, + * { ai.r.MA, ai.s.SPECIFIC, xi.magic.spellFamily.FLASH } }) * Notes : Adds a behavior to the gambit system ************************************************************************/ -std::string CLuaBaseEntity::addGambit(uint16 targ, sol::table const& predicates, uint16 react, uint16 select, uint32 selector_arg, sol::object const& retry) +std::string CLuaBaseEntity::addGambit(uint16 targ, sol::table const& predicates, sol::table const& reactions, sol::object const& retry) { if (m_PBaseEntity->objtype != TYPE_TRUST) { @@ -15125,26 +15131,109 @@ std::string CLuaBaseEntity::addGambit(uint16 targ, sol::table const& predicates, using namespace gambits; Gambit_t g; - auto target = static_cast(targ); - for (const auto& kvp : predicates) + const auto targetSelector = static_cast(targ); + + auto extractPredicates = [](sol::table const& conditionsTable) -> std::vector { - sol::table predicate = kvp.second; // Each entry is a table - auto condition = static_cast(predicate.get("cond")); - auto condition_arg = predicate.get("arg"); - g.predicates.emplace_back(target, condition, condition_arg); - } + std::vector predicateGroups; - auto reaction = static_cast(react); - auto selector = static_cast(select); - // Optional - uint16 retry_delay = (retry != sol::lua_nil) ? retry.as() : 0; + if (conditionsTable.get_type() == sol::type::table) + { + // Check if we're dealing with a table of conditions + // { { condition, arg1 }, { condition, arg1 } } + if (conditionsTable.get>(1)) + { + // Iterate over each table in the main table + for (const auto& conditionEntry : conditionsTable) + { + std::vector wrappedPredicates; + G_LOGIC logic = G_LOGIC::AND; // Predicates are AND by default, all must be true + + auto conditionTable = conditionEntry.second.as(); + // Check if we're dealing with conditions wrapped in a logic operator + // { ai.l.OR({ condition, arg1 }, { condition, arg1 }) } + // + // The logic function will wrap the conditions in a table + // { logic = ai.l.OR, conditions = { { condition, arg1 }, { condition, arg1 } } } + if (conditionTable["logic"].valid()) + { + logic = static_cast(conditionTable.get("logic")); + auto nestedPredicates = conditionTable.get("conditions"); + for (const auto& predicateDefinitionPair : nestedPredicates) + { + const sol::table& predicateTable = predicateDefinitionPair.second.as(); + auto predicateType = static_cast(predicateTable.get(1)); + auto predicateArg = predicateTable.get(2); + wrappedPredicates.emplace_back(predicateType, predicateArg); + } + predicateGroups.emplace_back(logic, wrappedPredicates); + } + else + { + // Else we're dealing with unwrapped predicates, assumed to be AND together: + // { { condition, arg1 }, { condition, arg1 } } + auto predicateType = static_cast(conditionTable.get(1)); // First element + auto predicateArg = conditionTable.get(2); // Second element + wrappedPredicates.emplace_back(predicateType, predicateArg); + } + predicateGroups.emplace_back(logic, wrappedPredicates); + } + } + else if (conditionsTable.size() == 2) + { + // Single predicate + // { condition, arg1 } + auto condition = static_cast(conditionsTable.get(1)); + auto condition_arg = conditionsTable.get(2); + predicateGroups.emplace_back(G_LOGIC::AND, std::vector{ { condition, condition_arg } }); + } + } + return predicateGroups; + }; - g.actions.emplace_back(reaction, selector, selector_arg); - g.retry_delay = retry_delay; - g.identifier = fmt::format("{}_{}_{}_{}_{}", targ, react, select, selector_arg, retry_delay); + auto extractReactions = [](sol::table const& reactionsTable) -> std::vector + { + std::vector actions; - auto* trust = static_cast(m_PBaseEntity); - auto* controller = static_cast(trust->PAI->GetController()); + if (reactionsTable.get_type() == sol::type::table) + { + // Process table of reactions + // { { reactionType, selector, selectorArg }, { reactionType, selector, selectorArg } } + if (reactionsTable.get>(1)) + { + for (const auto& reactionDefinitionPair : reactionsTable) + { + auto reactionTable = reactionDefinitionPair.second.as(); + auto reactionType = static_cast(reactionTable.get(1)); + auto reactionSelector = static_cast(reactionTable.get(2)); + auto reactionSelectorArg = reactionTable.get(3); + actions.emplace_back(reactionType, reactionSelector, reactionSelectorArg); + } + } + else if (reactionsTable.size() == 3) + { + // Single reaction + // { reactionType, selector, selectorArg } + auto reactionType = static_cast(reactionsTable.get(1)); + auto reactionSelector = static_cast(reactionsTable.get(2)); + auto reactionSelectorArg = reactionsTable.get(3); + actions.emplace_back(reactionType, reactionSelector, reactionSelectorArg); + } + } + return actions; + }; + + g.predicate_groups = extractPredicates(predicates); + g.actions = extractReactions(reactions); + + // Optional + const uint16 retryDelay = (retry != sol::lua_nil) ? retry.as() : 0; + + g.retry_delay = retryDelay; + g.target_selector = targetSelector; + const auto* trust = static_cast(m_PBaseEntity); + const auto* controller = static_cast(trust->PAI->GetController()); + g.identifier = controller->m_GambitsContainer->NewGambitIdentifier(g); return controller->m_GambitsContainer->AddGambit(g); } @@ -15176,10 +15265,13 @@ std::string CLuaBaseEntity::addSimpleGambit(uint16 targ, uint16 cond, uint32 con uint16 retry_delay = (retry != sol::lua_nil) ? retry.as() : 0; Gambit_t g; - g.predicates.emplace_back(target, condition, condition_arg); + // Temporary backward compability for simple gambits + g.predicate_groups.emplace_back(G_LOGIC::AND, std::vector{ { condition, condition_arg } }); + g.actions.emplace_back(reaction, selector, selector_arg); - g.retry_delay = retry_delay; - g.identifier = fmt::format("{}_{}_{}_{}_{}_{}_{}", targ, cond, condition_arg, react, select, selector_arg, retry_delay); + g.retry_delay = retry_delay; + g.target_selector = target; + g.identifier = fmt::format("{}_{}_{}_{}_{}_{}_{}", targ, cond, condition_arg, react, select, selector_arg, retry_delay); auto* trust = static_cast(m_PBaseEntity); auto* controller = static_cast(trust->PAI->GetController()); @@ -15188,14 +15280,14 @@ std::string CLuaBaseEntity::addSimpleGambit(uint16 targ, uint16 cond, uint32 con } /************************************************************************ - * Function: removeSimpleGambit() + * Function: removeGambit() * Purpose : - * Example : trust:removeSimpleGambit(id) + * Example : trust:removeGambit(id) * Notes : Removes a behavior from the gambit system, using the id returned - * : from addSimpleGambit + * : from addGambit/addSimpleGambit ************************************************************************/ -void CLuaBaseEntity::removeSimpleGambit(std::string const& id) +void CLuaBaseEntity::removeGambit(std::string const& id) { if (m_PBaseEntity->objtype != TYPE_TRUST) { @@ -15210,13 +15302,26 @@ void CLuaBaseEntity::removeSimpleGambit(std::string const& id) } /************************************************************************ - * Function: removeAllSimpleGambits() + * Function: removeSimpleGambit() * Purpose : - * Example : trust:removeAllSimpleGambits() + * Example : trust:removeSimpleGambit(id) + * Notes : Removes a behavior from the gambit system, using the id returned + * : from addSimpleGambit + ************************************************************************/ + +void CLuaBaseEntity::removeSimpleGambit(std::string const& id) +{ + removeGambit(id); +} + +/************************************************************************ + * Function: removeAllGambits() + * Purpose : + * Example : trust:removeAllGambits() * Notes : Removes all gambits from a trust. ************************************************************************/ -void CLuaBaseEntity::removeAllSimpleGambits() +void CLuaBaseEntity::removeAllGambits() { if (m_PBaseEntity->objtype != TYPE_TRUST) { @@ -15230,6 +15335,18 @@ void CLuaBaseEntity::removeAllSimpleGambits() controller->m_GambitsContainer->RemoveAllGambits(); } +/************************************************************************ + * Function: removeAllSimpleGambits() + * Purpose : + * Example : trust:removeAllSimpleGambits() + * Notes : Removes all gambits from a trust. + ************************************************************************/ + +void CLuaBaseEntity::removeAllSimpleGambits() +{ + removeAllGambits(); +} + /************************************************************************ * Function: setTrustTPSkillSettings(trigger, select, value) * Purpose : @@ -19406,7 +19523,9 @@ void CLuaBaseEntity::Register() SOL_REGISTER("trustPartyMessage", CLuaBaseEntity::trustPartyMessage); SOL_REGISTER("addGambit", CLuaBaseEntity::addGambit); SOL_REGISTER("addSimpleGambit", CLuaBaseEntity::addSimpleGambit); + SOL_REGISTER("removeGambit", CLuaBaseEntity::removeGambit); SOL_REGISTER("removeSimpleGambit", CLuaBaseEntity::removeSimpleGambit); + SOL_REGISTER("removeAllGambits", CLuaBaseEntity::removeAllGambits); SOL_REGISTER("removeAllSimpleGambits", CLuaBaseEntity::removeAllSimpleGambits); SOL_REGISTER("setTrustTPSkillSettings", CLuaBaseEntity::setTrustTPSkillSettings); diff --git a/src/map/lua/lua_baseentity.h b/src/map/lua/lua_baseentity.h index 9060d7d24cb..1905e08a86b 100644 --- a/src/map/lua/lua_baseentity.h +++ b/src/map/lua/lua_baseentity.h @@ -751,9 +751,11 @@ class CLuaBaseEntity void clearTrusts(); uint32 getTrustID(); void trustPartyMessage(uint32 message_id); - auto addGambit(uint16 targ, sol::table const& predicates, uint16 react, uint16 select, uint32 selector_arg, sol::object const& retry) -> std::string; + auto addGambit(uint16 targ, sol::table const& predicates, sol::table const& reactions, sol::object const& retry) -> std::string; auto addSimpleGambit(uint16 targ, uint16 cond, uint32 condition_arg, uint16 react, uint16 select, uint32 selector_arg, sol::object const& retry) -> std::string; + void removeGambit(std::string const& id); void removeSimpleGambit(std::string const& id); + void removeAllGambits(); void removeAllSimpleGambits(); void setTrustTPSkillSettings(uint16 trigger, uint16 select, sol::object const& value); From 69b40be8031b8311127b13a1386c922a959ae05c Mon Sep 17 00:00:00 2001 From: sruon Date: Tue, 28 Jan 2025 01:16:45 -0700 Subject: [PATCH 6/6] addSimpleGambit deprecation warning - Redirect addSimpleGambit to addGambit - Rewrite Koru-Moru with addGambit - Set gambit retry delay outside of actions loop --- scripts/actions/spells/trust/koru-moru.lua | 26 ++++----- src/map/ai/helpers/gambits_container.cpp | 17 +++--- src/map/lua/lua_baseentity.cpp | 62 ++++++++-------------- 3 files changed, 46 insertions(+), 59 deletions(-) diff --git a/scripts/actions/spells/trust/koru-moru.lua b/scripts/actions/spells/trust/koru-moru.lua index a969e0b7503..1767ca92209 100644 --- a/scripts/actions/spells/trust/koru-moru.lua +++ b/scripts/actions/spells/trust/koru-moru.lua @@ -19,32 +19,32 @@ spellObject.onMobSpawn = function(mob) [xi.magic.spell.AJIDO_MARUJIDO] = xi.trust.messageOffset.TEAMWORK_2, }) - mob:addSimpleGambit(ai.t.SELF, ai.c.MPP_LT, 5, ai.r.JA, ai.s.SPECIFIC, xi.ja.CONVERT) + mob:addGambit(ai.t.SELF, { ai.c.MPP_LT, 5 }, { ai.r.JA, ai.s.SPECIFIC, xi.ja.CONVERT }) - mob:addSimpleGambit(ai.t.PARTY, ai.c.HPP_LT, 50, ai.r.MA, ai.s.HIGHEST, xi.magic.spellFamily.CURE) + mob:addGambit(ai.t.PARTY, { ai.c.HPP_LT, 50 }, { ai.r.MA, ai.s.HIGHEST, xi.magic.spellFamily.CURE }) - mob:addSimpleGambit(ai.t.MELEE, ai.c.NOT_STATUS, xi.effect.HASTE, ai.r.MA, ai.s.HIGHEST, xi.magic.spellFamily.HASTE) + mob:addGambit(ai.t.MELEE, { ai.c.NOT_STATUS, xi.effect.HASTE }, { ai.r.MA, ai.s.HIGHEST, xi.magic.spellFamily.HASTE }) mob:addGambit(ai.t.CASTER, { { ai.c.NOT_STATUS, xi.effect.REFRESH }, { ai.c.NOT_STATUS, xi.effect.SUBLIMATION_ACTIVATED }, { ai.c.NOT_STATUS, xi.effect.SUBLIMATION_COMPLETE }, }, { ai.r.MA, ai.s.HIGHEST, xi.magic.spellFamily.REFRESH }) - mob:addSimpleGambit(ai.t.TANK, ai.c.NOT_STATUS, xi.effect.REFRESH, ai.r.MA, ai.s.HIGHEST, xi.magic.spellFamily.REFRESH) + mob:addGambit(ai.t.TANK, { ai.c.NOT_STATUS, xi.effect.REFRESH }, { ai.r.MA, ai.s.HIGHEST, xi.magic.spellFamily.REFRESH }) mob:addGambit(ai.t.RANGED, { - { ai.c.NOT_STATUS, xi.effect.FLURRY_II }, + { ai.c.NOT_STATUS, xi.effect.FLURRY_II }, -- xi.effect.FLURRY_II is not a typo { ai.c.NOT_STATUS, xi.effect.HASTE }, -- No overwriting Haste - }, { ai.r.MA, ai.s.HIGHEST, xi.magic.spellFamily.FLURRY }) -- xi.effect.FLURRY_II is not a typo + }, { ai.r.MA, ai.s.HIGHEST, xi.magic.spellFamily.FLURRY }) - mob:addSimpleGambit(ai.t.TOP_ENMITY, ai.c.NOT_STATUS, xi.effect.PHALANX, ai.r.MA, ai.s.SPECIFIC, xi.magic.spell.PHALANX_II) + mob:addGambit(ai.t.TOP_ENMITY, { ai.c.NOT_STATUS, xi.effect.PHALANX }, { ai.r.MA, ai.s.SPECIFIC, xi.magic.spell.PHALANX_II }) - mob:addSimpleGambit(ai.t.TARGET, ai.c.STATUS_FLAG, xi.effectFlag.DISPELABLE, ai.r.MA, ai.s.SPECIFIC, xi.magic.spell.DISPEL) + mob:addGambit(ai.t.TARGET, { ai.c.STATUS_FLAG, xi.effectFlag.DISPELABLE }, { ai.r.MA, ai.s.SPECIFIC, xi.magic.spell.DISPEL }) - mob:addSimpleGambit(ai.t.TARGET, ai.c.NOT_STATUS, xi.effect.DIA, ai.r.MA, ai.s.HIGHEST, xi.magic.spellFamily.DIA, 60) - mob:addSimpleGambit(ai.t.TARGET, ai.c.NOT_STATUS, xi.effect.SLOW, ai.r.MA, ai.s.HIGHEST, xi.magic.spellFamily.SLOW, 60) - mob:addSimpleGambit(ai.t.TARGET, ai.c.NOT_STATUS, xi.effect.EVASION_DOWN, ai.r.MA, ai.s.HIGHEST, xi.magic.spellFamily.DISTRACT, 60) + mob:addGambit(ai.t.TARGET, { ai.c.NOT_STATUS, xi.effect.DIA }, { ai.r.MA, ai.s.HIGHEST, xi.magic.spellFamily.DIA }, 60) + mob:addGambit(ai.t.TARGET, { ai.c.NOT_STATUS, xi.effect.SLOW }, { ai.r.MA, ai.s.HIGHEST, xi.magic.spellFamily.SLOW }, 60) + mob:addGambit(ai.t.TARGET, { ai.c.NOT_STATUS, xi.effect.EVASION_DOWN }, { ai.r.MA, ai.s.HIGHEST, xi.magic.spellFamily.DISTRACT }, 60) - mob:addSimpleGambit(ai.t.PARTY, ai.c.NOT_STATUS, xi.effect.PROTECT, ai.r.MA, ai.s.HIGHEST, xi.magic.spellFamily.PROTECT) - mob:addSimpleGambit(ai.t.PARTY, ai.c.NOT_STATUS, xi.effect.SHELL, ai.r.MA, ai.s.HIGHEST, xi.magic.spellFamily.SHELL) + mob:addGambit(ai.t.PARTY, { ai.c.NOT_STATUS, xi.effect.PROTECT }, { ai.r.MA, ai.s.HIGHEST, xi.magic.spellFamily.PROTECT }) + mob:addGambit(ai.t.PARTY, { ai.c.NOT_STATUS, xi.effect.SHELL }, { ai.r.MA, ai.s.HIGHEST, xi.magic.spellFamily.SHELL }) mob:setAutoAttackEnabled(false) diff --git a/src/map/ai/helpers/gambits_container.cpp b/src/map/ai/helpers/gambits_container.cpp index 9a7078f6759..e9513ab8774 100644 --- a/src/map/ai/helpers/gambits_container.cpp +++ b/src/map/ai/helpers/gambits_container.cpp @@ -248,9 +248,9 @@ namespace gambits { // All predicate groups must resolve successfully for the target to be considered bool targetMatchAllPredicates = true; - for (auto& predicate_group : gambit.predicate_groups) + for (auto& predicateGroup : gambit.predicate_groups) { - if (!CheckTrigger(potentialTarget, predicate_group)) + if (!CheckTrigger(potentialTarget, predicateGroup)) { targetMatchAllPredicates = false; } @@ -270,6 +270,9 @@ namespace gambits } // Execute all actions defined on the Gambit + // TODO: When multiple actions are defined: + // - Recast of all actions should be considered before executing + // - Casting 2 spells in a row does not yet work for (auto& action : gambit.actions) { if (action.reaction == G_REACTION::RATTACK) @@ -527,12 +530,12 @@ namespace gambits controller->MobSkill(target->targid, action.select_arg); } } + } - // Assume success - if (gambit.retry_delay != 0) - { - gambit.last_used = tick; - } + // Assume success + if (gambit.retry_delay != 0) + { + gambit.last_used = tick; } } } diff --git a/src/map/lua/lua_baseentity.cpp b/src/map/lua/lua_baseentity.cpp index 15be2c11a27..47257ba7e6c 100644 --- a/src/map/lua/lua_baseentity.cpp +++ b/src/map/lua/lua_baseentity.cpp @@ -15170,7 +15170,8 @@ void CLuaBaseEntity::trustPartyMessage(uint32 message_id) std::string CLuaBaseEntity::addGambit(uint16 targ, sol::table const& predicates, sol::table const& reactions, sol::object const& retry) { - if (m_PBaseEntity->objtype != TYPE_TRUST) + const auto* PTrust = dynamic_cast(m_PBaseEntity); + if (!PTrust) { ShowWarning("Invalid Entity calling function (%s).", m_PBaseEntity->getName()); return {}; @@ -15220,8 +15221,8 @@ std::string CLuaBaseEntity::addGambit(uint16 targ, sol::table const& predicates, { // Else we're dealing with unwrapped predicates, assumed to be AND together: // { { condition, arg1 }, { condition, arg1 } } - auto predicateType = static_cast(conditionTable.get(1)); // First element - auto predicateArg = conditionTable.get(2); // Second element + auto predicateType = static_cast(conditionTable.get(1)); + auto predicateArg = conditionTable.get(2); wrappedPredicates.emplace_back(predicateType, predicateArg); } predicateGroups.emplace_back(logic, wrappedPredicates); @@ -15276,17 +15277,17 @@ std::string CLuaBaseEntity::addGambit(uint16 targ, sol::table const& predicates, // Optional const uint16 retryDelay = (retry != sol::lua_nil) ? retry.as() : 0; + const auto* controller = static_cast(PTrust->PAI->GetController()); - g.retry_delay = retryDelay; - g.target_selector = targetSelector; - const auto* trust = static_cast(m_PBaseEntity); - const auto* controller = static_cast(trust->PAI->GetController()); - g.identifier = controller->m_GambitsContainer->NewGambitIdentifier(g); + g.retry_delay = retryDelay; + g.target_selector = targetSelector; + g.identifier = controller->m_GambitsContainer->NewGambitIdentifier(g); return controller->m_GambitsContainer->AddGambit(g); } /************************************************************************ + * DEPRECATED: Use trust:addGambit(...) instead * Function: addSimpleGambit() * Purpose : * Example : trust:addSimpleGambit(target, condition, condition_arg, reaction, selector, selector_arg) @@ -15295,36 +15296,17 @@ std::string CLuaBaseEntity::addGambit(uint16 targ, sol::table const& predicates, std::string CLuaBaseEntity::addSimpleGambit(uint16 targ, uint16 cond, uint32 condition_arg, uint16 react, uint16 select, uint32 selector_arg, sol::object const& retry) { - if (m_PBaseEntity->objtype != TYPE_TRUST) + const auto* PTrust = dynamic_cast(m_PBaseEntity); + if (!PTrust) { ShowWarning("Invalid Entity calling function (%s).", m_PBaseEntity->getName()); return {}; } + ShowWarning("%s: addSimpleGambit is deprecated. Please use addGambit instead.", m_PBaseEntity->getName()); + const auto conditionsTable = lua.create_table_with(1, cond, 2, condition_arg); + const auto reactionsTable = lua.create_table_with(1, react, 2, select, 3, selector_arg); - using namespace gambits; - - auto target = static_cast(targ); - auto condition = static_cast(cond); - - auto reaction = static_cast(react); - auto selector = static_cast(select); - - // Optional - uint16 retry_delay = (retry != sol::lua_nil) ? retry.as() : 0; - - Gambit_t g; - // Temporary backward compability for simple gambits - g.predicate_groups.emplace_back(G_LOGIC::AND, std::vector{ { condition, condition_arg } }); - - g.actions.emplace_back(reaction, selector, selector_arg); - g.retry_delay = retry_delay; - g.target_selector = target; - g.identifier = fmt::format("{}_{}_{}_{}_{}_{}_{}", targ, cond, condition_arg, react, select, selector_arg, retry_delay); - - auto* trust = static_cast(m_PBaseEntity); - auto* controller = static_cast(trust->PAI->GetController()); - - return controller->m_GambitsContainer->AddGambit(g); + return addGambit(targ, conditionsTable, reactionsTable, retry); } /************************************************************************ @@ -15337,14 +15319,14 @@ std::string CLuaBaseEntity::addSimpleGambit(uint16 targ, uint16 cond, uint32 con void CLuaBaseEntity::removeGambit(std::string const& id) { - if (m_PBaseEntity->objtype != TYPE_TRUST) + const auto* PTrust = dynamic_cast(m_PBaseEntity); + if (!PTrust) { ShowWarning("Invalid Entity calling function (%s).", m_PBaseEntity->getName()); return; } - auto* trust = static_cast(m_PBaseEntity); - auto* controller = static_cast(trust->PAI->GetController()); + auto* controller = static_cast(PTrust->PAI->GetController()); controller->m_GambitsContainer->RemoveGambit(id); } @@ -15359,6 +15341,7 @@ void CLuaBaseEntity::removeGambit(std::string const& id) void CLuaBaseEntity::removeSimpleGambit(std::string const& id) { + ShowWarning("%s: removeSimpleGambit is deprecated. Please use removeGambit instead.", m_PBaseEntity->getName()); removeGambit(id); } @@ -15371,14 +15354,14 @@ void CLuaBaseEntity::removeSimpleGambit(std::string const& id) void CLuaBaseEntity::removeAllGambits() { - if (m_PBaseEntity->objtype != TYPE_TRUST) + const auto* PTrust = dynamic_cast(m_PBaseEntity); + if (!PTrust) { ShowWarning("Invalid Entity calling function (%s).", m_PBaseEntity->getName()); return; } - auto* trust = static_cast(m_PBaseEntity); - auto* controller = static_cast(trust->PAI->GetController()); + auto* controller = static_cast(PTrust->PAI->GetController()); controller->m_GambitsContainer->RemoveAllGambits(); } @@ -15392,6 +15375,7 @@ void CLuaBaseEntity::removeAllGambits() void CLuaBaseEntity::removeAllSimpleGambits() { + ShowWarning("%s: removeAllSimpleGambits is deprecated. Please use removeAllGambits instead.", m_PBaseEntity->getName()); removeAllGambits(); }