Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update Sange & Yonin to better match retail #6442

Merged
merged 5 commits into from
Nov 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions scripts/effects/sange.lua
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
local effectObject = {}

effectObject.onEffectGain = function(target, effect)
effect:addMod(xi.mod.DAKEN, 100) -- 100% daken, mod is cleaned up on effect expiry
end

effectObject.onEffectTick = function(target, effect)
Expand Down
13 changes: 5 additions & 8 deletions scripts/effects/yonin.lua
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@
---@type TEffect
local effectObject = {}

effectObject.onEffectGain = function(target, effect) --power=30 initially, subpower=20 for enmity
effectObject.onEffectGain = function(target, effect) -- power = 30 initially
target:addMod(xi.mod.ACC, -effect:getPower())
target:addMod(xi.mod.NINJA_TOOL, effect:getPower())
target:addMod(xi.mod.ENMITY, effect:getSubPower())
target:addMod(xi.mod.ENMITY, effect:getPower())

local yoninMerits = target:getMerit(xi.merit.YONIN_EFFECT)
if yoninMerits ~= 0 then
Expand All @@ -20,20 +20,17 @@ end

effectObject.onEffectTick = function(target, effect)
--tick down the effect and reduce the overall power
effect:setPower(effect:getPower()-1)
effect:setPower(effect:getPower() - 1)
target:delMod(xi.mod.ACC, -1)
target:delMod(xi.mod.NINJA_TOOL, 1)
if effect:getPower() % 2 == 0 then -- enmity+ decays from 20 to 10, so half as often as the rest.
effect:setSubPower(effect:getSubPower()-1)
target:delMod(xi.mod.ENMITY, 1)
end
target:delMod(xi.mod.ENMITY, 1)
end

effectObject.onEffectLose = function(target, effect)
--remove the remaining power
target:delMod(xi.mod.ACC, -effect:getPower())
target:delMod(xi.mod.NINJA_TOOL, effect:getPower())
target:delMod(xi.mod.ENMITY, effect:getSubPower())
target:delMod(xi.mod.ENMITY, effect:getPower())

local yoninMerits = target:getMerit(xi.merit.YONIN_EFFECT)
if yoninMerits ~= 0 then
Expand Down
3 changes: 3 additions & 0 deletions scripts/enum/mod.lua
Original file line number Diff line number Diff line change
Expand Up @@ -470,6 +470,9 @@ xi.mod =
SENGIKORI_MB_DMG_DEBUFF = 1089, -- % Increase to magic burst damage. Applied to defender.
SENGIKORI_BONUS = 1090, -- additive % increase to Sengikori

-- Ninja
ENHANCES_SANGE = 1091, -- 1 = +1 attack for Daken during Sange per Sange merit (i.e. 20 with 5 merits = +100 attack during Sange)

-- Dragoon
WYVERN_LVL_BONUS = 1043, -- Wyvern: Lv.+ (Increases wyvern's base level above 99)

Expand Down
2 changes: 1 addition & 1 deletion scripts/globals/job_utils/ninja.lua
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ end
xi.job_utils.ninja.useYonin = function(player, target, ability, action)
target:delStatusEffect(xi.effect.INNIN)
target:delStatusEffect(xi.effect.YONIN)
target:addStatusEffect(xi.effect.YONIN, 30, 15, 300, 0, 20)
target:addStatusEffect(xi.effect.YONIN, 30, 15, 300, 0, 0)
end

xi.job_utils.ninja.useInnin = function(player, target, ability, action)
Expand Down
2 changes: 1 addition & 1 deletion sql/abilities.sql
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@ INSERT INTO `abilities` VALUES (167,'shikikoyo',12,75,2,300,136,452,0,152,2000,0
INSERT INTO `abilities` VALUES (168,'blade_bash',12,75,4,180,137,110,0,202,2000,0,3,4.4,0,1,900,2754,1,'TOAU');
INSERT INTO `abilities` VALUES (169,'deep_breathing',14,75,1,300,164,0,0,153,2000,0,6,20.0,0,0,0,2880,1,'TOAU');
INSERT INTO `abilities` VALUES (170,'angon',14,75,4,180,165,127,0,245,2000,0,3,20.0,0,1,600,2882,1,'TOAU');
INSERT INTO `abilities` VALUES (171,'sange',13,75,1,300,145,0,0,200,2000,0,6,20.0,0,1,0,2816,1,'TOAU');
INSERT INTO `abilities` VALUES (171,'sange',13,75,1,300,145,0,0,23,2000,0,6,20.0,0,1,0,2816,1,'TOAU');
INSERT INTO `abilities` VALUES (172,'blood_pact_ward',15,1,1,60,174,0,0,0,2000,0,6,20.0,0,1,300,0,256,NULL); -- new windower states 969
INSERT INTO `abilities` VALUES (173,'hasso',12,25,1,60,138,0,0,163,2000,0,6,20.0,0,1,300,0,0,'TOAU');
INSERT INTO `abilities` VALUES (174,'seigan',12,35,1,60,139,0,0,164,2000,0,6,20.0,0,1,300,0,0,'TOAU');
Expand Down
2 changes: 1 addition & 1 deletion sql/augments.sql
Original file line number Diff line number Diff line change
Expand Up @@ -1689,7 +1689,7 @@ INSERT INTO `augments` VALUES (1397,0,0,0,0,0); -- Enhances "Ikishoten" effect
INSERT INTO `augments` VALUES (1398,0,0,0,0,0); -- 1398 currently unused. Leave at zero. Edit+move or remove this note as new augments get discovered.
INSERT INTO `augments` VALUES (1399,0,0,0,0,0); -- 1399 currently unused. Leave at zero. Edit+move or remove this note as new augments get discovered.
INSERT INTO `augments` VALUES (1400,0,0,0,0,0); -- Increases elem. ninjutsu III damage
INSERT INTO `augments` VALUES (1401,0,0,0,0,0); -- Enhances "Sange" effect
INSERT INTO `augments` VALUES (1401,1,1091,1,0,0); -- Enhances "Sange" effect
INSERT INTO `augments` VALUES (1402,0,0,0,0,0); -- Enh. Ninja Tool Expertise effect
INSERT INTO `augments` VALUES (1403,0,0,0,0,0); -- Reduces elem. ninjutsu III cast time
INSERT INTO `augments` VALUES (1404,0,0,0,0,0); -- 1404 currently unused. Leave at zero. Edit+move or remove this note as new augments get discovered.
Expand Down
2 changes: 1 addition & 1 deletion sql/merits.sql
Original file line number Diff line number Diff line change
Expand Up @@ -263,7 +263,7 @@ INSERT INTO `merits` VALUES (2752,'shikikoyo',5,12,2048,7,42);
INSERT INTO `merits` VALUES (2754,'blade_bash',5,15,2048,7,42);
INSERT INTO `merits` VALUES (2756,'ikishoten',5,30,2048,7,42);
INSERT INTO `merits` VALUES (2758,'overwhelm',5,1,2048,7,42);
INSERT INTO `merits` VALUES (2816,'sange',5,25,4096,7,43);
INSERT INTO `merits` VALUES (2816,'sange',5,1,4096,7,43);
INSERT INTO `merits` VALUES (2818,'ninja_tool_expertise',5,5,4096,7,43);
INSERT INTO `merits` VALUES (2820,'katon_san',5,5,4096,7,43);
INSERT INTO `merits` VALUES (2822,'hyoton_san',5,5,4096,7,43);
Expand Down
33 changes: 31 additions & 2 deletions src/map/attack.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,22 @@ void CAttack::SetCritical(bool value)

if (m_attackType == PHYSICAL_ATTACK_TYPE::DAKEN)
{
m_damageRatio = battleutils::GetRangedDamageRatio(m_attacker, m_victim, m_isCritical);
uint16 bonusRatt = 0;

if (m_attacker->StatusEffectContainer)
{
const CStatusEffect* sangeEffect = m_attacker->StatusEffectContainer->GetStatusEffect(EFFECT_SANGE);
CCharEntity* PChar = dynamic_cast<CCharEntity*>(m_attacker);

if (sangeEffect && PChar && PChar->PMeritPoints)
{
int32 meritValue = PChar->PMeritPoints->GetMeritValue(MERIT_SANGE, PChar);

// Add N ranged attack * merit level during Sange effect
bonusRatt += PChar->getMod(Mod::ENHANCES_SANGE) * meritValue;
}
}
m_damageRatio = battleutils::GetRangedDamageRatio(m_attacker, m_victim, m_isCritical, bonusRatt);
}
else
{
Expand Down Expand Up @@ -318,7 +333,21 @@ uint8 CAttack::GetHitRate()
}
else if (m_attackType == PHYSICAL_ATTACK_TYPE::DAKEN)
{
m_hitRate = battleutils::GetRangedHitRate(m_attacker, m_victim, false, 100);
int16 accBonus = 100;

if (m_attacker->StatusEffectContainer)
{
const CStatusEffect* sangeEffect = m_attacker->StatusEffectContainer->GetStatusEffect(EFFECT_SANGE);
CCharEntity* PChar = dynamic_cast<CCharEntity*>(m_attacker);
if (sangeEffect && PChar && PChar->PMeritPoints)
{
int32 meritValue = PChar->PMeritPoints->GetMeritValue(MERIT_SANGE, PChar);

accBonus += (meritValue - 1) * 25; // add 25 acc per merit past the first (you have to merit Sange to even have the status effect, so this will never be negative acc bonus)
}
}

m_hitRate = battleutils::GetRangedHitRate(m_attacker, m_victim, false, accBonus);
}
else if (m_attackDirection == RIGHTATTACK)
{
Expand Down
16 changes: 16 additions & 0 deletions src/map/entities/battleentity.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2509,6 +2509,22 @@ bool CBattleEntity::OnAttack(CAttackState& state, action_t& action)
}
}

// Remove shuriken if Daken proc and Sange is up
if (attack.GetAttackType() == PHYSICAL_ATTACK_TYPE::DAKEN)
{
if (StatusEffectContainer && StatusEffectContainer->HasStatusEffect(EFFECT_SANGE))
{
CCharEntity* PChar = dynamic_cast<CCharEntity*>(this);
CItemWeapon* PAmmo = dynamic_cast<CItemWeapon*>(PChar->getEquip(SLOT_AMMO));

if (PChar && PAmmo && PAmmo->isShuriken()) // Not sure how they wouldn't have a shuriken by this point, but just in case...
{
// Removing ammo here is safe because you can only create one Daken attack per attack round
battleutils::RemoveAmmo(PChar, 1);
}
}
}

attackRound.DeleteAttackSwing();

if (list.actionTargets.size() == 8)
Expand Down
33 changes: 6 additions & 27 deletions src/map/entities/charentity.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1983,22 +1983,16 @@ void CCharEntity::OnRangedAttack(CRangeState& state, action_t& action)

uint8 shadowsTaken = 0;
uint8 hitCount = 1; // 1 hit by default
uint8 realHits = 0; // to store the real number of hit for tp multipler
uint8 realHits = 0; // to store the real number of hit for tp multiplier
auto ammoConsumed = 0;
bool hitOccured = false; // track if player hit mob at all
bool isSange = false;
bool isBarrage = StatusEffectContainer->HasStatusEffect(EFFECT_BARRAGE, 0);

// if barrage is detected, getBarrageShotCount also checks for ammo count
if (!ammoThrowing && !rangedThrowing && isBarrage)
{
hitCount += battleutils::getBarrageShotCount(this);
}
else if (ammoThrowing && this->StatusEffectContainer->HasStatusEffect(EFFECT_SANGE))
{
isSange = true;
hitCount += getMod(Mod::UTSUSEMI);
}
else if (this->StatusEffectContainer->HasStatusEffect(EFFECT_DOUBLE_SHOT) && xirand::GetRandomNumber(100) < (40 + this->getMod(Mod::DOUBLE_SHOT_RATE)))
{
hitCount = 2;
Expand All @@ -2011,7 +2005,8 @@ void CCharEntity::OnRangedAttack(CRangeState& state, action_t& action)
// loop for barrage hits, if a miss occurs, the loop will end
for (uint8 i = 1; i <= hitCount; ++i)
{
if (xirand::GetRandomNumber(100) < battleutils::GetRangedHitRate(this, PTarget, isBarrage)) // hit!
// TODO: add Barrage mod racc bonus
if (xirand::GetRandomNumber(100) < battleutils::GetRangedHitRate(this, PTarget, isBarrage, 0)) // hit!
{
// absorbed by shadow
if (battleutils::IsAbsorbByShadow(PTarget, this))
Expand All @@ -2020,8 +2015,9 @@ void CCharEntity::OnRangedAttack(CRangeState& state, action_t& action)
}
else
{
// TODO: add Barrage ratt bonus from job points
bool isCritical = xirand::GetRandomNumber(100) < battleutils::GetRangedCritHitRate(this, PTarget);
float pdif = battleutils::GetRangedDamageRatio(this, PTarget, isCritical);
float pdif = battleutils::GetRangedDamageRatio(this, PTarget, isCritical, 0);

if (isCritical)
{
Expand All @@ -2033,12 +2029,6 @@ void CCharEntity::OnRangedAttack(CRangeState& state, action_t& action)
hitOccured = true;
realHits++;

if (isSange)
{
// change message to sange
actionTarget.messageID = 77;
}

damage = (int32)((this->GetRangedWeaponDmg() + battleutils::GetFSTR(this, PTarget, slot)) * pdif);

if (slot == SLOT_RANGED)
Expand Down Expand Up @@ -2099,7 +2089,7 @@ void CCharEntity::OnRangedAttack(CRangeState& state, action_t& action)
if (hitOccured)
{
// any misses with barrage cause remaining shots to miss, meaning we must check Action.reaction
if ((actionTarget.reaction & REACTION::MISS) != REACTION::NONE && (this->StatusEffectContainer->HasStatusEffect(EFFECT_BARRAGE) || isSange))
if ((actionTarget.reaction & REACTION::MISS) != REACTION::NONE && StatusEffectContainer->HasStatusEffect(EFFECT_BARRAGE))
{
actionTarget.messageID = 352;
actionTarget.reaction = REACTION::HIT;
Expand Down Expand Up @@ -2151,18 +2141,7 @@ void CCharEntity::OnRangedAttack(CRangeState& state, action_t& action)
{
StatusEffectContainer->DelStatusEffect(EFFECT_BARRAGE, 0);
}
else if (isSange)
{
uint16 power = StatusEffectContainer->GetStatusEffect(EFFECT_SANGE)->GetPower();

// remove shadows
while (realHits-- && xirand::GetRandomNumber(100) <= power && battleutils::IsAbsorbByShadow(this, this))
{
;
}

StatusEffectContainer->DelStatusEffect(EFFECT_SANGE);
}
battleutils::ClaimMob(PTarget, this);
battleutils::RemoveAmmo(this, ammoConsumed);

Expand Down
32 changes: 5 additions & 27 deletions src/map/entities/trustentity.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -275,7 +275,6 @@ void CTrustEntity::OnRangedAttack(CRangeState& state, action_t& action)
uint8 realHits = 0; // to store the real number of hit for tp multipler
// auto ammoConsumed = 0;
bool hitOccured = false; // track if player hit mob at all
bool isSange = false;
bool isBarrage = StatusEffectContainer->HasStatusEffect(EFFECT_BARRAGE, 0);

/*
Expand All @@ -284,17 +283,13 @@ void CTrustEntity::OnRangedAttack(CRangeState& state, action_t& action)
{
hitCount += battleutils::getBarrageShotCount(this);
}
else if (ammoThrowing && this->StatusEffectContainer->HasStatusEffect(EFFECT_SANGE))
{
isSange = true;
hitCount += getMod(Mod::UTSUSEMI);
}
*/

// loop for barrage hits, if a miss occurs, the loop will end
// TODO: do trusts need barrage racc & ratt bonus mods?
for (uint8 i = 1; i <= hitCount; ++i)
{
if (xirand::GetRandomNumber(100) < battleutils::GetRangedHitRate(this, PTarget, isBarrage)) // hit!
if (xirand::GetRandomNumber(100) < battleutils::GetRangedHitRate(this, PTarget, isBarrage, 0)) // hit!
{
// absorbed by shadow
if (battleutils::IsAbsorbByShadow(PTarget, this))
Expand All @@ -304,7 +299,7 @@ void CTrustEntity::OnRangedAttack(CRangeState& state, action_t& action)
else
{
bool isCritical = xirand::GetRandomNumber(100) < battleutils::GetCritHitRate(this, PTarget, true);
float pdif = battleutils::GetRangedDamageRatio(this, PTarget, isCritical);
float pdif = battleutils::GetRangedDamageRatio(this, PTarget, isCritical, 0);

if (isCritical)
{
Expand All @@ -316,12 +311,6 @@ void CTrustEntity::OnRangedAttack(CRangeState& state, action_t& action)
hitOccured = true;
realHits++;

if (isSange)
{
// change message to sange
actionTarget.messageID = 77;
}

damage = (int32)((this->GetRangedWeaponDmg() + battleutils::GetFSTR(this, PTarget, slot)) * pdif);
/*
if (slot == SLOT_RANGED)
Expand Down Expand Up @@ -385,8 +374,8 @@ void CTrustEntity::OnRangedAttack(CRangeState& state, action_t& action)
// if a hit did occur (even without barrage)
if (hitOccured)
{
// any misses with barrage cause remaing shots to miss, meaning we must check Action.reaction
if ((actionTarget.reaction & REACTION::MISS) != REACTION::NONE && (this->StatusEffectContainer->HasStatusEffect(EFFECT_BARRAGE) || isSange))
// any misses with barrage cause remaining shots to miss, meaning we must check Action.reaction
if ((actionTarget.reaction & REACTION::MISS) != REACTION::NONE && StatusEffectContainer->HasStatusEffect(EFFECT_BARRAGE))
{
actionTarget.messageID = 352;
actionTarget.reaction = REACTION::HIT;
Expand Down Expand Up @@ -438,18 +427,7 @@ void CTrustEntity::OnRangedAttack(CRangeState& state, action_t& action)
{
StatusEffectContainer->DelStatusEffect(EFFECT_BARRAGE, 0);
}
else if (isSange)
{
uint16 power = StatusEffectContainer->GetStatusEffect(EFFECT_SANGE)->GetPower();

// remove shadows
while (realHits-- && xirand::GetRandomNumber(100) <= power && battleutils::IsAbsorbByShadow(this, this))
{
;
}

StatusEffectContainer->DelStatusEffect(EFFECT_SANGE);
}
battleutils::ClaimMob(PTarget, this);
// battleutils::RemoveAmmo(this, ammoConsumed);
// only remove detectables
Expand Down
3 changes: 2 additions & 1 deletion src/map/modifier.h
Original file line number Diff line number Diff line change
Expand Up @@ -563,6 +563,7 @@ enum class Mod
NIN_NUKE_BONUS_GEAR = 522, // Ninjutsu damage multiplier from gear.
DAKEN = 911, // chance to throw a shuriken without consuming it
NINJUTSU_DURATION = 1000,
ENHANCES_SANGE = 1091, // 1 = +1 attack for Daken during Sange per Sange merit (i.e. 20 with 5 merits = +100 attack during Sange)

// Dragoon
ANCIENT_CIRCLE_DURATION = 859, // Ancient Circle extended duration in seconds
Expand Down Expand Up @@ -1029,7 +1030,7 @@ enum class Mod
// The spares take care of finding the next ID to use so long as we don't forget to list IDs that have been freed up by refactoring.
// 570 through 825 used by WS DMG mods these are not spares.
//
// SPARE IDs: 1091 and onward
// SPARE IDs: 1092 and onward
};

// temporary workaround for using enum class as unordered_map key until compilers support it
Expand Down
6 changes: 4 additions & 2 deletions src/map/utils/battleutils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1514,7 +1514,7 @@ namespace battleutils
// TODO: remove function
}

uint8 GetRangedHitRate(CBattleEntity* PAttacker, CBattleEntity* PDefender, bool isBarrage, int8 accBonus)
uint8 GetRangedHitRate(CBattleEntity* PAttacker, CBattleEntity* PDefender, bool isBarrage, int16 accBonus)
{
int acc = 0;
int hitrate = 75;
Expand Down Expand Up @@ -1581,7 +1581,7 @@ namespace battleutils
}

// todo: need to penalise attacker's RangedAttack depending on distance from mob. (% decrease)
float GetRangedDamageRatio(CBattleEntity* PAttacker, CBattleEntity* PDefender, bool isCritical)
float GetRangedDamageRatio(CBattleEntity* PAttacker, CBattleEntity* PDefender, bool isCritical, int16 bonusRangedAttack)
{
// get ranged attack value
uint16 rAttack = 1;
Expand Down Expand Up @@ -1619,6 +1619,8 @@ namespace battleutils
rAttack = battleutils::GetMaxSkill(SKILL_ARCHERY, JOB_RNG, PAttacker->GetMLevel());
}

rAttack += bonusRangedAttack;

// get ratio (not capped for RAs)
float ratio = (float)rAttack / (float)PDefender->DEF();

Expand Down
4 changes: 2 additions & 2 deletions src/map/utils/battleutils.h
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ namespace battleutils
int32 TakeSwipeLungeDamage(CBattleEntity* PDefender, CCharEntity* PAttacker, int32 damage, ATTACK_TYPE attackType, DAMAGE_TYPE damageType);

bool TryInterruptSpell(CBattleEntity* PAttacker, CBattleEntity* PDefender, CSpell* PSpell);
float GetRangedDamageRatio(CBattleEntity* PAttacker, CBattleEntity* PDefender, bool isCritical);
float GetRangedDamageRatio(CBattleEntity* PAttacker, CBattleEntity* PDefender, bool isCritical, int16 bonusRangedAttack);
void HandleRangedAdditionalEffect(CCharEntity* PAttacker, CBattleEntity* PDefender, apAction_t* Action);
int32 CalculateSpikeDamage(CBattleEntity* PAttacker, CBattleEntity* PDefender, actionTarget_t* Action, uint16 damageTaken);
bool HandleSpikesDamage(CBattleEntity* PAttacker, CBattleEntity* PDefender, actionTarget_t* Action, int32 damage);
Expand All @@ -174,7 +174,7 @@ namespace battleutils
void HandleSpikesStatusEffect(CBattleEntity* PAttacker, CBattleEntity* PDefender, actionTarget_t* Action);
void HandleEnspell(CBattleEntity* PAttacker, CBattleEntity* PDefender, actionTarget_t* Action, bool isFirstSwing, CItemWeapon* weapon, int32 damage);
uint8 GetRangedHitRate(CBattleEntity* PAttacker, CBattleEntity* PDefender, bool isBarrage);
uint8 GetRangedHitRate(CBattleEntity* PAttacker, CBattleEntity* PDefender, bool isBarrage, int8 accBonus);
uint8 GetRangedHitRate(CBattleEntity* PAttacker, CBattleEntity* PDefender, bool isBarrage, int16 accBonus);
int32 CalculateEnspellDamage(CBattleEntity* PAttacker, CBattleEntity* PDefender, uint8 Tier, uint8 element);

int16 GetEnmityModDamage(int16 level);
Expand Down
Loading