From 9a2000d7c6b95f8743f3483a3c09884e8a80a149 Mon Sep 17 00:00:00 2001 From: Umeboshi Date: Thu, 23 Apr 2026 20:46:20 -0700 Subject: [PATCH] [core] Unify more Core/Lua TP Functions --- scripts/commands/gettp.lua | 33 +++++ src/map/lua/lua_baseentity.cpp | 79 +++------- src/map/lua/lua_baseentity.h | 4 +- src/map/utils/battleutils.cpp | 262 ++++++++++++++++----------------- src/map/utils/battleutils.h | 4 + 5 files changed, 192 insertions(+), 190 deletions(-) create mode 100644 scripts/commands/gettp.lua diff --git a/scripts/commands/gettp.lua b/scripts/commands/gettp.lua new file mode 100644 index 00000000000..351d475bfa0 --- /dev/null +++ b/scripts/commands/gettp.lua @@ -0,0 +1,33 @@ +----------------------------------- +-- func: gettp +-- desc: prints tp of the player and the target(if applicable), for debugging. +----------------------------------- +---@type TCommand +local commandObj = {} + +commandObj.cmdprops = +{ + permission = 1, + parameters = 's' +} + +commandObj.onTrigger = function(player, option) + local target = player:getCursorTarget() or player + + local targetType = target:getObjType() + + if targetType == xi.objType.NPC then + player:printToPlayer('Target something other than an NPC') + return + end + + if target == player then + player:printToPlayer(string.format('Player TP: %i', player:getTP()), xi.msg.channel.SYSTEM_3) + return + else + player:printToPlayer(string.format('Player TP: %i', player:getTP()), xi.msg.channel.SYSTEM_3) + player:printToPlayer(string.format('Target TP: %i', target:getTP()), xi.msg.channel.SYSTEM_3) + end +end + +return commandObj diff --git a/src/map/lua/lua_baseentity.cpp b/src/map/lua/lua_baseentity.cpp index 55f5b30b207..e81d202ca10 100644 --- a/src/map/lua/lua_baseentity.cpp +++ b/src/map/lua/lua_baseentity.cpp @@ -13042,40 +13042,23 @@ uint16 CLuaBaseEntity::getBaseWeaponDelay(uint16 slot) * Notes : ************************************************************************/ -uint16 CLuaBaseEntity::getBaseDelay() +auto CLuaBaseEntity::getBaseDelay() -> uint16 { - CCharEntity* PCharEntity = dynamic_cast(m_PBaseEntity); - CBattleEntity* PBattleEntity = dynamic_cast(m_PBaseEntity); - uint16 baseDelay = 480; // h2h "unequipped" base delay + uint16 baseDelay = 0; - if (PCharEntity) + if (m_PBaseEntity == nullptr) { - CItemWeapon* PMainWeapon = dynamic_cast(PCharEntity->getEquip(SLOT_MAIN)); - CItemWeapon* PSubWeapon = dynamic_cast(PCharEntity->getEquip(SLOT_SUB)); + ShowWarning("CLuaBaseEntity::getBaseDelay() - Entity called is nullptr (%s)."); + return 0; + } - if (PMainWeapon) - { - if (PMainWeapon->getSkillType() == SKILLTYPE::SKILL_HAND_TO_HAND) - { - baseDelay = PMainWeapon->getBaseDelay(); // h2h items include 480 base delay - } - else - { - baseDelay = PMainWeapon->getBaseDelay(); - if (PSubWeapon) - { - baseDelay += PSubWeapon->getBaseDelay(); - } - } - } + if (CCharEntity* PCharEntity = dynamic_cast(m_PBaseEntity)) + { + baseDelay = battleutils::GetBaseDelay(PCharEntity); } - else if (PBattleEntity) + else if (CBattleEntity* PBattleEntity = dynamic_cast(m_PBaseEntity)) { - CItemWeapon* PWeapon = dynamic_cast(PBattleEntity->m_Weapons[SLOT_MAIN]); - if (PWeapon) - { - baseDelay = std::round(PWeapon->getBaseDelay() * 60.0 / 1000.0); // there is some precision loss that results in delays of 319.98 instead of 320, etc, so round to nearest. - } + baseDelay = battleutils::GetBaseDelay(PBattleEntity); } return baseDelay; @@ -13088,42 +13071,26 @@ uint16 CLuaBaseEntity::getBaseDelay() * Notes : ************************************************************************/ -uint16 CLuaBaseEntity::getBaseRangedDelay() +auto CLuaBaseEntity::getBaseRangedDelay() -> uint16 { - CCharEntity* PCharEntity = dynamic_cast(m_PBaseEntity); - CBattleEntity* PBattleEntity = dynamic_cast(m_PBaseEntity); - uint16 baseDelay = 0; // return 0 if not able to actually ranged attack + uint16 baseRangedDelay = 0; - if (PCharEntity) + if (m_PBaseEntity == nullptr) { - CItemWeapon* PRangedWeapon = dynamic_cast(PCharEntity->getEquip(SLOT_RANGED)); - CItemWeapon* PAmmo = dynamic_cast(PCharEntity->getEquip(SLOT_AMMO)); + ShowWarning("CLuaBaseEntity::getBaseRangedDelay() - Entity called is nullptr (%s)."); + return 0; + } - if (PRangedWeapon && PRangedWeapon->isRanged()) - { - if (PRangedWeapon->isThrowing()) // Throwing, like Chakram/Boomerang in ranged slot - { - baseDelay = PRangedWeapon->getBaseDelay(); - } - else if (PAmmo) // Bow/gun etc, but only valid if Ammo is equipped. - { - baseDelay = PRangedWeapon->getBaseDelay() + PAmmo->getBaseDelay(); - } - } - else if (PAmmo && PAmmo->isRanged()) // Throwing, Pebble/Shuriken in ammo slot - { - baseDelay = PAmmo->getBaseDelay(); - } + if (CCharEntity* PCharEntity = dynamic_cast(m_PBaseEntity)) + { + baseRangedDelay = battleutils::GetBaseRangedDelay(PCharEntity); } - else if (PBattleEntity) + else if (CBattleEntity* PBattleEntity = dynamic_cast(m_PBaseEntity)) { - baseDelay = 360; // Tested using Fatso Fargann's TP Drainkiss @ 3000 TP after a mob landed a ranged attack. - // TP Drainkiss is unaspected and is not affected by MDB or multipliers like shell. - // Fatso's TP Drainkiss fTPs are 0.625~ @1000TP and 1.0~ @3000TP - // TP Drained at 1.0 fTP was 93 on every normal non NM ranged mob no matter the family. + baseRangedDelay = battleutils::GetBaseRangedDelay(PBattleEntity); } - return baseDelay; + return baseRangedDelay; } /************************************************************************ diff --git a/src/map/lua/lua_baseentity.h b/src/map/lua/lua_baseentity.h index a3913127159..afa17d9392a 100644 --- a/src/map/lua/lua_baseentity.h +++ b/src/map/lua/lua_baseentity.h @@ -652,8 +652,8 @@ class CLuaBaseEntity bool isDualWielding(); bool isUsingH2H(); uint16 getBaseWeaponDelay(uint16 slot); // get base delay of weapon - uint16 getBaseDelay(); // get base delay of entity, melee only - uint16 getBaseRangedDelay(); // get base delay of entity, ranged only + auto getBaseDelay() -> uint16; // get base delay of entity, melee only + auto getBaseRangedDelay() -> uint16; // get base delay of entity, ranged only float checkLiementAbsorb(uint16 damageType); // return 1.0 if did not absorb, return >= -1.0 if did absorb diff --git a/src/map/utils/battleutils.cpp b/src/map/utils/battleutils.cpp index f9c021f6ffa..6813ec4bfbc 100644 --- a/src/map/utils/battleutils.cpp +++ b/src/map/utils/battleutils.cpp @@ -1783,6 +1783,111 @@ int16 CalculateBaseTP(CBattleEntity* PEntity, int32 delay) return baseTPReturn; } +auto GetBaseDelay(CBattleEntity* PEntity) -> uint16 +{ + CCharEntity* PCharEntity = dynamic_cast(PEntity); + CMobEntity* PMobEntity = dynamic_cast(PEntity); + uint16 baseDelay = 480; // h2h "unequipped" base delay + + if (PCharEntity) + { + CItemWeapon* PMainWeapon = dynamic_cast(PCharEntity->getEquip(SLOT_MAIN)); + CItemWeapon* PSubWeapon = dynamic_cast(PCharEntity->getEquip(SLOT_SUB)); + + if (PMainWeapon) + { + if (PMainWeapon->getSkillType() == SKILLTYPE::SKILL_HAND_TO_HAND) + { + baseDelay = PMainWeapon->getBaseDelay(); // h2h items include 480 base delay + } + else + { + baseDelay = PMainWeapon->getBaseDelay(); + if (PSubWeapon) + { + baseDelay += PSubWeapon->getBaseDelay(); + } + } + } + } + else if (PMobEntity) + { + CItemWeapon* PWeapon = dynamic_cast(PMobEntity->m_Weapons[SLOT_MAIN]); + if (PWeapon) + { + baseDelay = std::round(PWeapon->getBaseDelay() * 60.0 / 1000.0); // there is some precision loss that results in delays of 319.98 instead of 320, etc, so round to nearest. + } + } + + return baseDelay; +} + +auto GetBaseRangedDelay(CBattleEntity* PEntity) -> uint16 +{ + CCharEntity* PCharEntity = dynamic_cast(PEntity); + CMobEntity* PMobEntity = dynamic_cast(PEntity); + + uint16 baseDelay = 0; + + if (PCharEntity) + { + CItemWeapon* PRangedWeapon = dynamic_cast(PCharEntity->getEquip(SLOT_RANGED)); + CItemWeapon* PAmmo = dynamic_cast(PCharEntity->getEquip(SLOT_AMMO)); + + if (PRangedWeapon && PRangedWeapon->isRanged()) + { + if (PRangedWeapon->isThrowing()) // Throwing, like Chakram/Boomerang in ranged slot + { + baseDelay = PRangedWeapon->getBaseDelay(); + } + else if (PAmmo) // Bow/gun etc, but only valid if Ammo is equipped. + { + baseDelay = PRangedWeapon->getBaseDelay() + PAmmo->getBaseDelay(); + } + } + else if (PAmmo && PAmmo->isRanged()) // Throwing, Pebble/Shuriken in ammo slot + { + baseDelay = PAmmo->getBaseDelay(); + } + } + else if (PMobEntity) + { + CItemWeapon* PWeapon = dynamic_cast(PMobEntity->m_Weapons[SLOT_MAIN]); + if (PWeapon) + { + baseDelay = std::round(PWeapon->getBaseDelay() * 60.0 / 1000.0); // there is some precision loss that results in delays of 319.98 instead of 320, etc, so round to nearest. + } + } + + return baseDelay; +} + +auto CalculateTPFromDamageDealt(CBattleEntity* PAttacker, bool isZanshin) -> int32 +{ + if (PAttacker == nullptr) + { + ShowWarning("battleutils::CalculateTPFromDamageDealt() - PAttacker was null."); + return 0; + } + + int32 tpReturn = luautils::callGlobal("xi.combat.tp.getSingleMeleeHitTPReturn", PAttacker, isZanshin); + + return tpReturn; +} + +auto CalculateTPFromDamageTaken(CBattleEntity* PAttacker, CBattleEntity* PDefender, int32 damage, uint16 delay) -> int32 +{ + if (PAttacker == nullptr || PDefender == nullptr) + { + ShowWarning("battleutils::CalculateTPFromDamageTaken() - PAttacker or PDefender was null."); + return 0; + } + + int32 tpReturn = luautils::callGlobal("xi.combat.tp.calculateTPGainOnPhysicalDamage", PAttacker, PDefender, damage, delay); + + return tpReturn; +} + bool TryInterruptSpell(CBattleEntity* PAttacker, CBattleEntity* PDefender, CSpell* PSpell) { // Exceptions. @@ -2156,87 +2261,31 @@ int32 TakePhysicalDamage(CBattleEntity* PAttacker, CBattleEntity* PDefender, PHY PDefender->TryHitInterrupt(PAttacker); } - int16 baseTp = 0; - - if ((slot == SLOT_RANGED || slot == SLOT_AMMO) && PAttacker->objtype == TYPE_PC) - { - int32 delay = PAttacker->GetRangedWeaponDelay(true); - - baseTp = CalculateBaseTP(PAttacker, delay * 120 / 1000); - } - else - { - int32 delay = PAttacker->GetWeaponDelay(true); - auto* sub_weapon = dynamic_cast(PAttacker->m_Weapons[SLOT_SUB]); - - if (sub_weapon && sub_weapon->getDmgType() > DAMAGE_TYPE::NONE && sub_weapon->getDmgType() < DAMAGE_TYPE::HTH && - weapon && weapon->getSkillType() != SKILL_HAND_TO_HAND) - { - delay = delay / 2; - } - - float ratio = 1.0f; - - if (weapon && weapon->getSkillType() == SKILL_HAND_TO_HAND) - { - ratio = 2.0f; - } - - baseTp = CalculateBaseTP(PAttacker, delay * 60.0f / 1000.0f / ratio); - } - if (giveTPtoAttacker) { - if (PAttacker->objtype == TYPE_PC && physicalAttackType == PHYSICAL_ATTACK_TYPE::ZANSHIN) - { - baseTp += ((CCharEntity*)PAttacker)->PMeritPoints->GetMeritValue(MERIT_IKISHOTEN, (CCharEntity*)PAttacker); - } + bool isZanshin = physicalAttackType == PHYSICAL_ATTACK_TYPE::ZANSHIN; - PAttacker->addTP( - (int16)(tpMultiplier * (baseTp * (1.0f + 0.01f * (float)((PAttacker->getMod(Mod::STORETP) + getStoreTPbonusFromMerit(PAttacker))))))); + int16 attackerTPReturn = CalculateTPFromDamageDealt(PAttacker, isZanshin); + + PAttacker->addTP((int16)(tpMultiplier * attackerTPReturn)); } if (giveTPtoVictim) { - uint32 sBlowMerit = 0; - if (CCharEntity* PChar = dynamic_cast(PAttacker)) - { - sBlowMerit = PChar->PMeritPoints->GetMeritValue(MERIT_TYPE::MERIT_SUBTLE_BLOW_EFFECT, PChar); - } + int32 delay = 0; - // Check for Tandem Blow bonus while pet+master are fighting same target - int32 tandemBlowBonus = 0; - if (petutils::IsTandemActive(PAttacker)) + if (isRanged && PAttacker->objtype == TYPE_PC) { - if (PAttacker->PMaster && PAttacker->PMaster->objtype == TYPE_PC) - { - tandemBlowBonus = PAttacker->PMaster->getMod(Mod::TANDEM_BLOW_POWER); - } - else - { - tandemBlowBonus = PAttacker->getMod(Mod::TANDEM_BLOW_POWER); - } - } - - // account for attacker's subtle blow which reduces the baseTP gain for the defender - float sBlow1 = std::clamp((float)(PAttacker->getMod(Mod::SUBTLE_BLOW) + sBlowMerit), -50.0f, 50.0f); - float sBlow2 = std::clamp((float)(PAttacker->getMod(Mod::SUBTLE_BLOW_II) + tandemBlowBonus), -50.0f, 50.0f); - float sBlowMult = ((100.0f - std::clamp(sBlow1 + sBlow2, -75.0f, 75.0f)) / 100.0f); - - // mobs hit get basetp+30 whereas pcs hit get basetp/3 - if (PDefender->objtype == TYPE_PC || (PDefender->objtype == TYPE_PET && PDefender->PMaster && PDefender->PMaster->objtype == TYPE_PC)) - { - PDefender->addTP( - (int16)(tpMultiplier * ((baseTp / 3) * sBlowMult * - (1.0f + 0.01f * (float)((PDefender->getMod(Mod::STORETP) + - getStoreTPbonusFromMerit(PAttacker))))))); // yup store tp counts on hits taken too! + delay = GetBaseRangedDelay(PAttacker); } else { - PDefender->addTP((uint16)(tpMultiplier * - ((baseTp + 30) * sBlowMult * - (1.0f + 0.01f * (float)PDefender->getMod(Mod::STORETP))))); // subtle blow also reduces the "+30" on mob tp gain + delay = GetBaseDelay(PAttacker); } + + int16 defenderTPReturn = CalculateTPFromDamageTaken(PAttacker, PDefender, damage, delay); + + PDefender->addTP((int16)(tpMultiplier * defenderTPReturn)); } } else if (PDefender->objtype == TYPE_MOB) @@ -2260,8 +2309,7 @@ int32 TakePhysicalDamage(CBattleEntity* PAttacker, CBattleEntity* PDefender, PHY int32 TakeWeaponskillDamage(CBattleEntity* PAttacker, CBattleEntity* PDefender, int32 damage, ATTACK_TYPE attackType, DAMAGE_TYPE damageType, uint8 slot, bool primary, float tpMultiplier, uint16 bonusTP, float targetTPMultiplier) { - auto* weapon = GetEntityWeapon(PAttacker, (SLOTTYPE)slot); - bool isRanged = (slot == SLOT_AMMO || slot == SLOT_RANGED); + bool isRanged = (slot == SLOT_AMMO || slot == SLOT_RANGED); if (attackType == ATTACK_TYPE::PHYSICAL && PDefender->StatusEffectContainer->HasStatusEffect(EFFECT_DEFENSE_BOOST) && @@ -2347,80 +2395,30 @@ int32 TakeWeaponskillDamage(CBattleEntity* PAttacker, CBattleEntity* PDefender, int16 baseTp = 0; - if (isRanged) - { - int32 delay = PAttacker->GetRangedWeaponDelay(true); - baseTp = CalculateBaseTP(PAttacker, (delay * 120) / 1000); - } - else - { - int32 delay = PAttacker->GetWeaponDelay(true); - - auto* sub_weapon = dynamic_cast(PAttacker->m_Weapons[SLOT_SUB]); - - if (sub_weapon && sub_weapon->getDmgType() > DAMAGE_TYPE::NONE && sub_weapon->getDmgType() < DAMAGE_TYPE::HTH && - weapon->getSkillType() != SKILL_HAND_TO_HAND) - { - delay /= 2; - } - - float ratio = 1.0f; - - if (weapon && weapon->getSkillType() == SKILL_HAND_TO_HAND) - { - ratio = 2.0f; - } - - baseTp = CalculateBaseTP(PAttacker, delay * 60 / 1000 / ratio); - } - - // add tp to attacker + // Add tp to attacker if (primary) // Calculate TP Return from WS { - standbyTp = bonusTP + ((int16)((tpMultiplier * baseTp) * - (1.0f + 0.01f * (float)((PAttacker->getMod(Mod::STORETP) + getStoreTPbonusFromMerit(PAttacker)))))); - } + int16 baseTp = CalculateTPFromDamageDealt(PAttacker, false); - uint32 sBlowMerit = 0; - if (CCharEntity* PChar = dynamic_cast(PAttacker)) - { - sBlowMerit = PChar->PMeritPoints->GetMeritValue(MERIT_TYPE::MERIT_SUBTLE_BLOW_EFFECT, PChar); + standbyTp = bonusTP + (int16)((tpMultiplier * baseTp)); } - // Check for Tandem Blow bonus while pet+master are fighting same target - int32 tandemBlowBonus = 0; - if (petutils::IsTandemActive(PAttacker)) - { - if (PAttacker->PMaster && PAttacker->PMaster->objtype == TYPE_PC) - { - tandemBlowBonus = PAttacker->PMaster->getMod(Mod::TANDEM_BLOW_POWER); - } - else - { - tandemBlowBonus = PAttacker->getMod(Mod::TANDEM_BLOW_POWER); - } - } + // Add TP to defender + int32 delay = 0; - // account for attacker's subtle blow which reduces the baseTP gain for the defender - float sBlow1 = std::clamp((float)(PAttacker->getMod(Mod::SUBTLE_BLOW) + sBlowMerit), -50.0f, 50.0f); - float sBlow2 = std::clamp((float)(PAttacker->getMod(Mod::SUBTLE_BLOW_II) + tandemBlowBonus), -50.0f, 50.0f); - float sBlowMult = (100.0f - std::clamp(sBlow1 + sBlow2, -75.0f, 75.0f)) / 100.0f; - - // mobs hit get basetp+30 whereas pcs hit get basetp/3 - if (PDefender->objtype == TYPE_PC) + if (isRanged) { - PDefender->addTP((int16)(tpMultiplier * targetTPMultiplier * - ((baseTp / 3) * sBlowMult * - (1.0f + 0.01f * (float)((PDefender->getMod(Mod::STORETP) + - getStoreTPbonusFromMerit(PAttacker))))))); // yup store tp counts on hits taken too! + delay = GetBaseRangedDelay(PAttacker); } else { - PDefender->addTP((int16)(tpMultiplier * targetTPMultiplier * - ((baseTp + 30) * sBlowMult * - (1.0f + 0.01f * (float)PDefender->getMod(Mod::STORETP))))); // subtle blow also reduces the "+30" on mob tp gain + delay = GetBaseDelay(PAttacker); } + + baseTp = CalculateTPFromDamageTaken(PAttacker, PDefender, damage, delay); + + PDefender->addTP((int16)(tpMultiplier * targetTPMultiplier * baseTp)); } else if (PDefender->objtype == TYPE_MOB) { diff --git a/src/map/utils/battleutils.h b/src/map/utils/battleutils.h index 52d456d077c..c330cf1be0d 100644 --- a/src/map/utils/battleutils.h +++ b/src/map/utils/battleutils.h @@ -178,6 +178,10 @@ int32 GetEnmityModCure(int16 level); bool isValidSelfTargetWeaponskill(int wsid); bool CanUseWeaponskill(CCharEntity* PChar, CWeaponSkill* PSkill); int16 CalculateBaseTP(CBattleEntity* PEntity, int32 delay); +auto GetBaseDelay(CBattleEntity* PEntity) -> uint16; // get base delay of entity, melee only +auto GetBaseRangedDelay(CBattleEntity* PEntity) -> uint16; // get base delay of entity, ranged only +auto CalculateTPFromDamageDealt(CBattleEntity* PAttacker, bool isZanshin) -> int32; +auto CalculateTPFromDamageTaken(CBattleEntity* PAttacker, CBattleEntity* PDefender, int32 damage, uint16 delay) -> int32; void GenerateCureEnmity(CBattleEntity* PSource, CBattleEntity* PTarget, int32 amount, int32 fixedCE = 0, int32 fixedVE = 0); void GenerateInRangeEnmity(CBattleEntity* PSource, int32 CE, int32 VE); void handleKillshotEnmity(CBattleEntity* PAttacker, CBattleEntity* PTarget);