From 4cd2e9f693988c4461c2c58b2a1ef31dc40de927 Mon Sep 17 00:00:00 2001 From: Birthright Date: Mon, 19 Jan 2026 02:49:48 +0300 Subject: [PATCH] Fix PvP/PvE ratio caps and damage multiplier handling --- .../gameserver/model/siege/Influence.java | 27 +++++----- .../model/stats/calc/StatCapUtil.java | 25 ++++++++-- .../model/stats/container/CombatMode.java | 5 ++ .../model/stats/container/RatioType.java | 5 ++ .../gameserver/utils/stats/StatFunctions.java | 49 +++++++++++-------- 5 files changed, 72 insertions(+), 39 deletions(-) create mode 100644 game-server/src/com/aionemu/gameserver/model/stats/container/CombatMode.java create mode 100644 game-server/src/com/aionemu/gameserver/model/stats/container/RatioType.java diff --git a/game-server/src/com/aionemu/gameserver/model/siege/Influence.java b/game-server/src/com/aionemu/gameserver/model/siege/Influence.java index 65e317f55..958860294 100644 --- a/game-server/src/com/aionemu/gameserver/model/siege/Influence.java +++ b/game-server/src/com/aionemu/gameserver/model/siege/Influence.java @@ -89,26 +89,23 @@ public Set getInfluenceRelevantWorldIds() { } /** - * @return float containing dmg modifier for disadvantaged race + * @return int containing dmg modifier for disadvantaged race */ - public float getPvpRaceBonus(Race attRace) { - switch (attRace) { - case ASMODIANS: - return calculatePvpRaceBonus(getAsmodianInfluenceRate(), getElyosInfluenceRate()); - case ELYOS: - return calculatePvpRaceBonus(getElyosInfluenceRate(), getAsmodianInfluenceRate()); - default: - return 1f; - } + public int getPvpRaceBonusRatio(Race attRace) { + return switch (attRace) { + case ASMODIANS -> calculatePvpRaceBonusRatio(getAsmodianInfluenceRate(), getElyosInfluenceRate()); + case ELYOS -> calculatePvpRaceBonusRatio(getElyosInfluenceRate(), getAsmodianInfluenceRate()); + default -> 0; + }; } - private float calculatePvpRaceBonus(float ownInfluence, float enemyInfluence) { + private int calculatePvpRaceBonusRatio(float ownInfluence, float enemyInfluence) { if (enemyInfluence >= 0.81f && ownInfluence <= 0.10f) - return 1.2f; + return 200; else if (enemyInfluence >= 0.81f || (enemyInfluence >= 0.71f && ownInfluence <= 0.10f)) - return 1.15f; + return 150; else if (enemyInfluence >= 0.71f) - return 1.1f; - return 1f; + return 100; + return 0; } } diff --git a/game-server/src/com/aionemu/gameserver/model/stats/calc/StatCapUtil.java b/game-server/src/com/aionemu/gameserver/model/stats/calc/StatCapUtil.java index a99c9d2d3..77c1065cf 100644 --- a/game-server/src/com/aionemu/gameserver/model/stats/calc/StatCapUtil.java +++ b/game-server/src/com/aionemu/gameserver/model/stats/calc/StatCapUtil.java @@ -4,6 +4,8 @@ import com.aionemu.gameserver.model.gameobjects.Creature; import com.aionemu.gameserver.model.gameobjects.player.Player; +import com.aionemu.gameserver.model.stats.container.CombatMode; +import com.aionemu.gameserver.model.stats.container.RatioType; import com.aionemu.gameserver.model.stats.container.StatEnum; /** @@ -57,6 +59,26 @@ private static void calculate(Stat2 stat2, int lowerCap, int upperCap) { } } + public static int limitValueForPvpOrPveStat(CombatMode mode, RatioType type, int value) { + // Note: PvP/PvE ratio caps are symmetric: + // - attack min is fixed, defense max is fixed + // - upper/lower bounds depend on combat mode + Cap cap = switch (mode) { + case PVP -> switch (type) { + case ATTACK -> new Cap(-900, 1000); + case DEFENSE -> new Cap(-1000, 900); + }; + case PVE -> switch (type) { + case ATTACK -> new Cap(-900, 5000); + case DEFENSE -> new Cap(-5000, 900); + }; + }; + + return Math.min(cap.max(), Math.max(cap.min(), value)); + } + + private record Cap(int min, int max) {} + private static class StatLimits { private final int lowerCap; @@ -111,9 +133,6 @@ private static int upperCapFor(StatEnum stat) { case FLY_SPEED: value = 16000; break; - case PVP_DEFEND_RATIO: - value = 900; - break; case HEAL_BOOST: value = 1000; break; diff --git a/game-server/src/com/aionemu/gameserver/model/stats/container/CombatMode.java b/game-server/src/com/aionemu/gameserver/model/stats/container/CombatMode.java new file mode 100644 index 000000000..ef9208468 --- /dev/null +++ b/game-server/src/com/aionemu/gameserver/model/stats/container/CombatMode.java @@ -0,0 +1,5 @@ +package com.aionemu.gameserver.model.stats.container; + +public enum CombatMode { + PVP, PVE +} diff --git a/game-server/src/com/aionemu/gameserver/model/stats/container/RatioType.java b/game-server/src/com/aionemu/gameserver/model/stats/container/RatioType.java new file mode 100644 index 000000000..8617f542b --- /dev/null +++ b/game-server/src/com/aionemu/gameserver/model/stats/container/RatioType.java @@ -0,0 +1,5 @@ +package com.aionemu.gameserver.model.stats.container; + +public enum RatioType { + ATTACK, DEFENSE +} diff --git a/game-server/src/com/aionemu/gameserver/utils/stats/StatFunctions.java b/game-server/src/com/aionemu/gameserver/utils/stats/StatFunctions.java index 5a800d2d6..52999cd08 100644 --- a/game-server/src/com/aionemu/gameserver/utils/stats/StatFunctions.java +++ b/game-server/src/com/aionemu/gameserver/utils/stats/StatFunctions.java @@ -3,6 +3,8 @@ import java.util.ArrayList; import java.util.List; +import com.aionemu.gameserver.model.stats.container.*; + import org.apache.commons.lang3.ArrayUtils; import com.aionemu.commons.utils.Rnd; @@ -19,9 +21,6 @@ import com.aionemu.gameserver.model.siege.Influence; import com.aionemu.gameserver.model.stats.calc.Stat2; import com.aionemu.gameserver.model.stats.calc.StatCapUtil; -import com.aionemu.gameserver.model.stats.container.CreatureGameStats; -import com.aionemu.gameserver.model.stats.container.PlayerGameStats; -import com.aionemu.gameserver.model.stats.container.StatEnum; import com.aionemu.gameserver.model.templates.item.WeaponStats; import com.aionemu.gameserver.model.templates.item.enums.ItemSubType; import com.aionemu.gameserver.model.templates.npc.NpcRating; @@ -445,46 +444,54 @@ public static int getApNpcRating(NpcRating npcRating) { */ public static float adjustDamageByPvpOrPveModifiers(Creature attacker, Creature target, float baseDamage, int pvpDamage, boolean useTemplateDmg, SkillElement element) { - float attackBonus = 1; - float defenseBonus = 1; + int attackBonus = 0; + int defenseBonus = 0; float damage = baseDamage; - if (attacker.isPvpTarget(target)) { + boolean pvpTarget = attacker.isPvpTarget(target); + if (pvpTarget) { if (pvpDamage > 0) damage *= pvpDamage * 0.01f; damage *= 0.42f; // PVP modifier 42%, last checked on NA (4.9) 19.03.2016 if (!useTemplateDmg) { + attackBonus = attacker.getGameStats().getStat(StatEnum.PVP_ATTACK_RATIO, 0).getCurrent(); + defenseBonus = target.getGameStats().getStat(StatEnum.PVP_DEFEND_RATIO, 0).getCurrent(); if (attacker.getRace() != target.getRace() && !attacker.isInInstance()) - damage *= Influence.getInstance().getPvpRaceBonus(attacker.getRace()); - - attackBonus = attacker.getGameStats().getStat(StatEnum.PVP_ATTACK_RATIO, 0).getCurrent() * 0.001f; - defenseBonus = target.getGameStats().getStat(StatEnum.PVP_DEFEND_RATIO, 0).getCurrent() * 0.001f; + attackBonus += Influence.getInstance().getPvpRaceBonusRatio(attacker.getRace()); switch (element) { case NONE: - attackBonus += attacker.getGameStats().getStat(StatEnum.PVP_ATTACK_RATIO_PHYSICAL, 0).getCurrent() * 0.001f; - defenseBonus += target.getGameStats().getStat(StatEnum.PVP_DEFEND_RATIO_PHYSICAL, 0).getCurrent() * 0.001f; + attackBonus += attacker.getGameStats().getStat(StatEnum.PVP_ATTACK_RATIO_PHYSICAL, 0).getCurrent(); + defenseBonus += target.getGameStats().getStat(StatEnum.PVP_DEFEND_RATIO_PHYSICAL, 0).getCurrent(); break; default: - attackBonus += attacker.getGameStats().getStat(StatEnum.PVP_ATTACK_RATIO_MAGICAL, 0).getCurrent() * 0.001f; - defenseBonus += target.getGameStats().getStat(StatEnum.PVP_DEFEND_RATIO_MAGICAL, 0).getCurrent() * 0.001f; + attackBonus += attacker.getGameStats().getStat(StatEnum.PVP_ATTACK_RATIO_MAGICAL, 0).getCurrent(); + defenseBonus += target.getGameStats().getStat(StatEnum.PVP_DEFEND_RATIO_MAGICAL, 0).getCurrent(); } } } else if (!useTemplateDmg) { if (attacker instanceof Player) { // npcs dmg is not reduced because of the level difference GF (4.9) 23.04.2016 damage *= 1f - getNpcLevelDiffMod(target, attacker); } - attackBonus = attacker.getGameStats().getStat(StatEnum.PVE_ATTACK_RATIO, 0).getCurrent() * 0.001f; - defenseBonus = target.getGameStats().getStat(StatEnum.PVE_DEFEND_RATIO, 0).getCurrent() * 0.001f; + attackBonus = attacker.getGameStats().getStat(StatEnum.PVE_ATTACK_RATIO, 0).getCurrent(); + defenseBonus = target.getGameStats().getStat(StatEnum.PVE_DEFEND_RATIO, 0).getCurrent(); switch (element) { case NONE: - attackBonus += attacker.getGameStats().getStat(StatEnum.PVE_ATTACK_RATIO_PHYSICAL, 0).getCurrent() * 0.001f; - defenseBonus += target.getGameStats().getStat(StatEnum.PVE_DEFEND_RATIO_PHYSICAL, 0).getCurrent() * 0.001f; + attackBonus += attacker.getGameStats().getStat(StatEnum.PVE_ATTACK_RATIO_PHYSICAL, 0).getCurrent(); + defenseBonus += target.getGameStats().getStat(StatEnum.PVE_DEFEND_RATIO_PHYSICAL, 0).getCurrent(); break; default: - attackBonus += attacker.getGameStats().getStat(StatEnum.PVE_ATTACK_RATIO_MAGICAL, 0).getCurrent() * 0.001f; - defenseBonus += target.getGameStats().getStat(StatEnum.PVE_DEFEND_RATIO_MAGICAL, 0).getCurrent() * 0.001f; + attackBonus += attacker.getGameStats().getStat(StatEnum.PVE_ATTACK_RATIO_MAGICAL, 0).getCurrent(); + defenseBonus += target.getGameStats().getStat(StatEnum.PVE_DEFEND_RATIO_MAGICAL, 0).getCurrent(); } } - return damage + (damage * attackBonus) - (damage * defenseBonus); + CombatMode mode = pvpTarget ? CombatMode.PVP : CombatMode.PVE; + // PvP/PvE ratio caps are applied after aggregation (retail behavior) + attackBonus = StatCapUtil.limitValueForPvpOrPveStat(mode, RatioType.ATTACK, attackBonus); + defenseBonus = StatCapUtil.limitValueForPvpOrPveStat(mode, RatioType.DEFENSE, defenseBonus); + float multiplier = 1f + (attackBonus - defenseBonus) / 1000f; + // prevent negative or zero damage + multiplier = Math.max(multiplier, 0.1f); + + return damage * multiplier; } /**