Skip to content

Balance Characters

wezzzyrek1 edited this page Oct 13, 2026 · 1 revision

Character balance

How strong a character is does not come from one file. Five files decide it, and each one works at a different stage of the same calculation. Change them in the right order and every step builds on the one before. Change them in the wrong order and the last fix hides the real problem, which comes back as soon as anything else changes.

This page explains what each file decides, what each stat feeds, the formulas the GameServer uses for one hit, and the order in which to make balance changes. The formulas are the server's own; the numbers in the examples are made up to show the arithmetic.

The files

File Decides After a change
Data\CalcCharacter.ini limits (max stats, points per level, max attack speed), SD, caps, the final PvP/PvM rate tables GameServer menu Data > Common Data Reload
Data\Scripts\Character\CalcCharacter.lua how stats become numbers: damage, wizardry, defense, success rates, attack speed, AG, map penalty Lua > Reload Scripts, then relog the character
Data\FormulaData.xml shared formulas: the class power % of every class, buff strength, combat power, skill tree values restart the GameServer; make the same change in the client's FormulaData.bmd
Data\Scripts\Skills\<Class>.lua one function per skill: its damage multiplier, buff strength and duration Lua > Reload Scripts
Data\CalcMonster.ini every monster at once: HP, damage, defense, rates, exp level Data > Common Data Reload, then Data > Reload Main Data > Monsters

Every GameServer of the server (regular, Arca, Siege, ...) needs the same files.

Also involved, when the five above are not enough:

  • database table DefaultClassType - starting stats, HP and Mana per level, per Vitality and per Energy point
  • Data\Skills\SkillList.xml - each skill's base damage (Damage)
  • Data\Skills\SkillTreeData_3rd.xml, SkillTreeData_4th.xml - skill tree bonuses (their values are SkillTree formulas in FormulaData.xml)
  • Data\Items\ItemList.xml - weapon damage, magic power, combat power, defense
  • Data\Monsters\MonsterList.xml - one monster at a time
  • Data\Map\MapPenalty.xml - the level below which a map punishes a character
  • Data\Scripts\Misc\ItemCalc.lua - wings: damage increase and absorb

What each stat does

With the shipped scripts. Every line can be changed in the file named in it.

Stat Raises
Strength physical damage of every class (<Class>DamageCalc); PvM attack success rate (Strength / 4 for most classes); the class power % of Dark Knight, Elf, Magic Gladiator, Rage Fighter, Grow Lancer, Slayer, Crusader and Illusion Knight; combat power
Agility defense (CalcDefense); defense success rate, PvM and PvP; attack success rate (PvM about Agility x 1.5, PvP x 0.6 to x 4.5, by class); attack speed; the physical damage of Elf, Summoner, Grow Lancer, Slayer and Illusion Knight; the class power % of Dark Knight, Elf, Magic Gladiator, Slayer, Illusion Knight and the Grow Lancer's Rage
Vitality HP (VitalityToLife); Rage Fighter damage and its melee class power %
Energy wizardry and curse (<Class>MagicDamageCalc); Mana (EnergyToMana); Magic Gladiator and Dark Lord damage; the class power % of Dark Lord, Crusader, Rage Fighter (68) and the magic classes; Berserker, Darkness, Elemental Charge
Command Dark Lord damage, class power % and attack success rate; Crown Force
all of them SD (their sum; Command for the Dark Lord only); AG (CalcAbilityGauge, weighted per class)
Total level (normal + master) HP and Mana per level; success rates; SD (level squared); the PvP hit chance; the minimum damage (level / 10); the map penalty

How a hit is put together

When the character is calculated

This runs on login, on equipping an item, on adding stat points, on level up and when a buff starts or ends.

  1. Stats. Base points plus everything added by items, sets and buffs, each capped by MaxStrength ... MaxCommand in CalcCharacter.ini.
  2. Stats become numbers in CalcCharacter.lua:
    • <Class>DamageCalc - physical damage (left and right hand)
    • <Class>MagicDamageCalc - wizardry, and curse for the Summoner
    • CalcDefense
    • CalcAttackSuccessRate_PvM / _PvP, CalcDefenseSuccessRate_PvM / _PvP
    • CalcAttackSpeed (the class maximum is <Class>MaxAttackSpeed in CalcCharacter.ini)
    • CalcAbilityGauge (AG), the elemental functions, the two-weapon bonuses
  3. Equipment and buffs on top. Weapon damage, item options, sets, sockets, skill tree passives and buffs are added to those numbers. A buff's value comes from its skill script or, for Berserker, Darkness, the Charges, Wrath, Crown Force and a few more, from the Character section of FormulaData.xml.
  4. Combat power, for Dark Knight, Magic Gladiator, Rage Fighter, Slayer, Grow Lancer, Dark Lord, Crusader, Elf and Illusion Knight only. The class formula (CombatPower 9-14, 17, 18, 21) plus the weapons' combat power becomes a bonus on every hit: weapon max damage x combat power % x 60 % with two weapons (CombatPower 7) or 40 % with one (CombatPower 8).

When the character attacks

  1. Hit or miss - attack success rate against defense success rate.
  2. The target's defense for this hit - debuffs, ignore defense, basic defense.
  3. The damage roll - character damage + skill damage, critical or excellent.
  4. Defense is taken off, and the result never falls under the minimum damage.
  5. The target's reductions and the gear's bonuses - damage decrease, pets, wings, mounts.
  6. The map penalty - a character below the map's level deals less and takes more.
  7. The skill's function in Scripts\Skills\<Class>.lua - mostly the class power %.
  8. 4th skill tree damage, then the rate tables [PvPDamage] / [PvMDamage].
  9. Where it lands - SD and HP, then reflect, combo, double and triple damage.

Every step is explained with its formula below.

The key point: steps 7 and 8 multiply what is left after defense. A bonus added in step 3 is partly eaten by defense, so against a well-defended target it counts for less; a percent in step 7 or 8 scales the hit the same against every target.

The formulas

HP, Mana and SD

MaxHP   = Life + (TotalLevel - 1) * LevelLife + (Vitality - start Vitality) * VitalityToLife
MaxMana = Mana + (TotalLevel - 1) * LevelMana + (Energy - start Energy) * EnergyToMana
MaxSD   = (Strength + Agility + Vitality + Energy) * ShieldGageConstA / 10
        + TotalLevel * TotalLevel / ShieldGageConstB
        + Defense

Life, LevelLife, VitalityToLife and the rest are columns of DefaultClassType; the Dark Lord adds Command to the SD stat sum. ShieldGageConstA and ShieldGageConstB are per class in [ShieldGage]. Item options and buffs add to all three afterwards.

At high level the level part of SD outgrows the stat part: with ShieldGageConstB = 30 level 1000 gives 1000 x 1000 / 30 = 33,333 SD, while 10,000 stat points with ShieldGageConstA = 12 give 12,000.

Defense

Defense = (CalcDefense + armor, shield and wing defense + set and option bonuses) / 2
        + defense buffs

Everything before the buffs is halved together, the script's part included: a Dark Knight with 1500 Agility gets 1500 / 3 = 500 from CalcDefense and keeps 250 of it. Buffs such as Greater Defense come after the halving and count in full.

Against one attacker the defense changes once more:

  • defense-down debuffs take their percent, then their flat value; defense-up buffs add
  • ignore defense: the attacker's ignore-defense options (wings, sets, skill trees, Armor Breaker) minus the target's resistance give a chance; when it hits, the defense is 0 for this hit
  • basic (perfect) defense from sets and skill trees is added after that, so it still counts on an ignore-defense hit; with SteelArmorLimitByDefense = 1 it is never more than the character's own defense

Hit or miss

PvM - and PvP too when ShieldSystem = 0:

AttackRate >= DefenseRate:  hit chance = (AttackRate - DefenseRate) / AttackRate
AttackRate <  DefenseRate:  5 % of the attacks hit, and they do 30 % damage
Side AttackRate DefenseRate
player CalcAttackSuccessRate_PvM + options CalcDefenseSuccessRate_PvM + options
monster AttackRate in MonsterList.xml x AttackRatePercent BlockRate in MonsterList.xml x DefenseRatePercent

A player with 3000 attack rate against a monster with 1000: (3000 - 1000) / 3000 = 67 % of the hits land. With 900 against 1000 only 5 % land, at 30 % damage - the attack rate has to stay above the monster's defense rate of the maps a character is meant for.

PvP with ShieldSystem = 1:

A = AttackRate / (AttackRate + DefenseRate)            the PvP rates from CalcCharacter.lua
B = AttackerLevel / (AttackerLevel + TargetLevel)      total levels
HitChance % = 100 * A * B * SuccessAttackRateOption / 10000
              - 5 when the target is 100 or more levels higher

SuccessAttackRateOption is the attacker's class entry in [ShieldGage]. With the default 20000, two equal characters (same rates, same level) hit 100 x 0.5 x 0.5 x 2 = 50 % of the time; with three times the target's rate, 100 x 0.75 x 0.5 x 2 = 75 %. This option is the per-class knob for PvP hit chance; the success rate functions decide how stats move it.

The code checks "100 or more levels higher" first, so the steps written for 200 and 300 levels (-10, -15) never apply. Chain Drive hits 10 % less often.

The damage roll

Physical (most classes):

range     = character min + skill min  ..  character max + skill max     (per hand)
normal    = a random value in the range
critical  = character max + skill max + critical damage bonuses
excellent = (character max + skill max) * 1.2 + critical and excellent damage bonuses
  • character min / max: <Class>DamageCalc plus weapon and options
  • skill min / max: SkillList Damage and Damage x 1.5, plus the skill tree, 4th and 5th tree damage and flat skill damage options
  • combat power classes: the skill part is multiplied by (100 + right-hand weapon's combat power / 2) %
  • critical and excellent chance: options, sets and skill trees, minus the target's resistance, capped by MaxCriticalDamageRate / MaxExcellentDamageRate in [OptionLimit]; when both roll, the hit is excellent

Critical and excellent always take the maximum. Raising the max damage (the divisor of AttackDamageMax...) strengthens builds with high critical or excellent chance more than raising the min.

Magic (Dark Wizard, Rune Wizard, Light Wizard, Lemuria Mage, Gun Crusher, Alchemist, and the Magic Gladiator's spells):

range         = skill min + wizardry min  ..  skill max + wizardry max  (+ flat skill damage)
range         = range * (100 + weapon rise) / 100
weapon rise % = weapon Magic Power / 2 + weapon level * 2      (less when the weapon is worn)

Wizardry min / max come from <Class>MagicDamageCalc. Some pairs of weapons use 65 % of both rises added together. Critical and excellent work as above, on the maximum of the range. The Summoner works the same way with curse and the book.

Against defense

damage = roll - target defense          (two weapons: right + left - defense, once)
  • below 0 it becomes 0 with ReduceDefenseFromFullDamage = 0. With = 1 it stays below 0, so flat bonus damage added later (critical or excellent bonuses, Muun options) first has to fill the gap.
  • the combat power bonus is added after the defense is taken off.
  • an all-miss hit (attack rate under defense rate) is cut to 30 %.
  • minimum damage: with EnableLevelBasedDmg = 1 a hit under TotalLevel / 10 becomes TotalLevel / 10 (at least 1). A monster uses its own level / 10. That small, steady number is what you see when an attack cannot get through the defense - and the skill's class power % still multiplies it.

After defense, in this order

  1. Damage decrease of the target: its percent (capped by MaxDamageDecreaseRate), then its flat value.

  2. Defense skill: a target using the shield skill Defense (SkillID 18) takes what its class's Defense_<Class> function returns (the Dark Knight's: half the hit).

  3. Pets, VIP and buffs: Satan and Demon add, Guardian Angel, Safeguard and the damage-decrease buffs take off, each a percent.

  4. Wings: the attacker's wings add Wings_CalcIncAttack %, the target's wings absorb Wings_CalcAbsorb % (Scripts\Misc\ItemCalc.lua).

  5. Mounts: Dinorant, Fenrir and Dark Horse absorb.

  6. Map penalty, when the character's total level is below PenaltyLevel of the map in MapPenalty.xml (CalcPenaltyDamageFromUser / FromMonster in CalcCharacter.lua):

    LevelDiff  = PenaltyLevel - TotalLevel, at most 7
    Correction = monster DamageCorrection - player DamageCorrection, at least 0
    player hits monster:  damage * (100 - LevelDiff * 10 - Correction) / 100
    monster hits player:  damage * (100 + LevelDiff * 10 + Correction) / 100
    

    The monster's DamageCorrection is in MonsterList.xml, the player's comes from 5th wings (WingCore formula 1). Seven levels under the map's level already cost 70 %.

  7. The skill's function in Scripts\Skills\<Class>.lua (see Step 4 below).

  8. 4th skill tree skill damage, a flat value.

  9. Rate tables: [PvPDamage] <Attacker>To<Target>_PvPDamageRate against players, <Class>PvMDamageRate against monsters.

Where the hit lands

Only in PvP with ShieldSystem = 1; monsters always hit HP:

to SD = damage * DamageDevideToSD / 100       the attacker's class entry, 0-100
to HP = damage - to SD                        when SD runs out, the rest goes to HP too

Jewel of Harmony options move DamageDevideToSD by a few points either way.

After that: reflect (capped by MaxDamageReflectRate), combo (Combo in Scripts\Skills\Common.lua, then [ComboDamage]), double damage (the hit lands once more; chance capped by MaxDoubleDamageRate) and triple damage (it lands again with twice the value; capped by MaxTripleDamageRate).

A worked example

A Dark Knight with 3000 Strength and 1500 Agility, a weapon of 400-500 damage, uses Anger Strike (SkillList Damage 60) on a target with 900 defense. Combat power and other bonuses are left out.

character damage   3000 / 6 + 400 = 900   ..   3000 / 4 + 500 = 1250
with the skill     900 + 60 = 960         ..   1250 + 90 = 1340
normal roll        1150 - 900 defense = 250
excellent          1340 * 1.2 = 1608 - 900 defense = 708
class power        Character 65: 225 + 3000 / 55 + 1500 / 50 = 309.5 %
normal hit         250 * 3.095 = 773
excellent hit      708 * 3.095 = 2191
in PvP, 90 % to SD 773 -> 695 SD and 78 HP

The same Dark Knight against 1500 defense: 1150 - 1500 is below 0, so the hit becomes the minimum, level 800 / 10 = 80, and 80 x 3.095 = 247. Defense, not the class power, decided this hit - which is why defense problems are fixed in step 2, not with rate tables.

The order of changes

Rules for every change

  • One change at a time, then measure again. Two changes at once never tell you which one worked.
  • Small steps: 5-10 % per change.
  • The same test every time: the same level and resets, the same stat points, comparable gear, the same target (one fixed monster for PvM, one test character for PvP).
  • Measure real hits, not only the C window. Some window lines come from the server, others the client calculates with its own built-in formulas, so after a change the window can disagree with what the server really does.
  • Write down every change with the old value, the new value and why.
  • Fix the cause where it is: limits, then stats, then the class, then the skill, then a single matchup. A fix one level too late hides the cause.

Step 1 - Limits

CalcCharacter.ini, section [General], and the DefaultClassType table.

  • MaxStrength ... MaxCommand - the highest stat a character can reach
  • <Class>LevelUpPoint - stat points per level
  • <Class>LevelUp3rdTreePoint, <Class>LevelUp4thTreePoint - skill tree points per level
  • <Class>MaxAttackSpeed - the fastest a class can attack
  • DefaultClassType - HP and Mana per level and per point
  • EnableLevelBasedDmg, ReduceDefenseFromFullDamage, SteelArmorLimitByDefense - how damage meets defense (see above)

Set these first. They decide the range of stats every later formula works with, so changing them later undoes the tuning of every step after this one.

Step 2 - Stats into numbers

Scripts\Character\CalcCharacter.lua.

Compare the classes at the same stat budget: give each test character the same number of points, spread the way players of that class really spread them, and compare the base damage, wizardry, defense and success rates. Change the divisors in the class's function:

-- Dark Knight, KnightDamageCalc: Strength / 6 .. Strength / 4
AttackDamageMinLeft = Strength / 6 -- Minimum Left Hand Damage
AttackDamageMaxLeft = Strength / 4 -- Maximum Left Hand Damage

A smaller divisor makes the class grow faster with that stat. Things to keep in mind:

  • Defense from CalcDefense is halved with the armor (see Defense above).
  • A PvM hit chance depends on the gap between the two rates; check it against the monsters of the maps the class is meant for.
  • PvM and PvP success rates are separate functions, so a PvP problem never needs a PvM change.
  • CalcPenaltyDamageFromUser / FromMonster hold the map penalty: 10 % per level and the cap of 7 levels are written there.

Two functions at the end of the file, GladiatorChaosAttackPowerCalc and IllusionKnightIllusionAttackPowerCalc, are no longer called. Those powers are FormulaData Character 71 and 200 (step 3).

Step 3 - Class power

FormulaData.xml. Most classes have one formula that every skill of the class multiplies its damage by. It is a percent: 100 leaves the damage as it is, 250 makes it 2.5 times. This is the knob for "this class is too strong or too weak at everything".

Class Formula Arguments
Dark Knight Character 65 Strength, Agility
Dark Lord Character 54 Energy, Command
Elf Character 61 Strength, Agility
Magic Gladiator Character 66 (with Crusher Charge), 71 (without); CombatPower 26 (magic) Strength, Agility; Energy
Rage Fighter Character 69 (Killing Blow, Beast Uppercut, Chain Drive, Charge, Phoenix Shot, Spirit Hook), 68 (Dragon Roar, Dragon Slasher), 67 + 68 added together (Dark Side) 69: Strength, Vitality; 68: Strength, Energy; 67: Strength, Agility
Grow Lancer Character 63 (Retaliation), 64 (Rage) Strength; Agility
Slayer Character 70 Strength, Agility
Crusader Character 60 Strength, Energy
Illusion Knight Character 200 Strength, Agility
Gun Crusher CombatPower 27 Energy
Lemuria Mage CombatPower 28 Energy
Alchemist CombatPower 200 Energy
Rune Wizard CombatPower 201 Energy
Light Wizard CombatPower 202 Energy
Dark Wizard, Summoner none - their power is wizardry and curse from step 2
<Formula ID="65" Data="225+(%d/55)+(%d/50)" /> <!-- Strength, Agility -->

Each %d is one argument, in the order the comment lists them. The header of the file describes what every formula is for. The fixed part (225) sets the class's power at low stats, the divisors how fast it grows: with 3000 Strength and 1500 Agility the formula above gives 309.5 %, so changing 225 to 200 takes about 8 % off every Dark Knight skill.

The client reads the same formulas from its FormulaData.bmd (the class lines of the C window, for example), so make every change there as well. FormulaData.xml is read only when the GameServer starts.

The same file holds a number of buffs (Character 0-9 Berserker and Darkness, 30-35 the Charges, 44-49 Wrath and Crown Force, 50-51 the Crusader auras, ...) and combat power (CombatPower 7 and 8 for the weapon share, 9-14, 17, 18 and 21 for the classes). Tune a buff here when the buff, not the class, is the problem.

Step 4 - Single skills

Scripts\Skills\<Class>.lua and Skills\SkillList.xml.

Every skill has a function named after it, commented with its SkillID from SkillList.xml:

-- SkillID: 41, Twisting Slash
function TwistingSlash_DarkKnight(oPlayer, InDamage)
	local Strength = Stats.Get(oPlayer, STAT_STRENGTH, STAT_BOTHTYPE)
	local Agility = Stats.Get(oPlayer, STAT_AGILITY, STAT_BOTHTYPE)
	local RageAttackPower = Formula.GetValue(FORMULA_TYPE_CHARACTER, 65, Strength, Agility)
	local OutDamage = InDamage * RageAttackPower / 100

	return OutDamage
end

To make only Twisting Slash 10 % weaker, change the one line:

	local OutDamage = InDamage * RageAttackPower / 100 * 0.9

What to know about these files:

  • All of them are loaded into one Lua state. TwistingSlash_DarkKnight is used for the Dark Knight; a function without the class suffix (DeathStab) is used for every class that has no own version. Keep the names unique when you copy a function.
  • InDamage is the hit after defense and after steps 1-6 of "After defense".
  • Stats.Get(oPlayer, STAT_STRENGTH, STAT_BOTHTYPE) reads a stat: STAT_BOTHTYPE is base plus added points, STAT_PERMANENT the base points only. Formula.GetValue(type, id, ...) runs a FormulaData.xml formula, Buff.CheckUsed(oPlayer, buff) tells whether a buff is active.
  • Some functions get more arguments: BarrageCount (which hit of a multi-hit skill), SkillTreeValue / SkillTreeBonus (the skill tree bonus of that skill).
  • Buff functions return the strength and the duration in seconds (SkillEffect, SkillTime).
  • A function that only returns InDamage (the Dark Wizard's, for example) is the place to add a multiplier for that one skill.
  • if (oPlayer.Type == OBJ_USER) around the class power keeps it from monsters that use player skills.

Damage in SkillList.xml is the skill's own base damage, part of the roll before defense (the hit rolls between Damage and Damage x 1.5 on top of the character's damage). It weighs most for low-level characters and against high defense; the Lua multiplier scales the skill the same at every level.

Step 5 - Monsters

CalcMonster.ini and Monsters\MonsterList.xml.

Set monsters after the classes are even among themselves: the monsters should follow the players' new power, not the other way round. CalcMonster.ini changes every monster at once (HPPercent, DamageMinPercent, DefensePercent, AttackRatePercent, DefenseRatePercent, ...); MonsterList.xml changes one.

A monster's hit is the same calculation from the other side: a random value between its DamageMin and DamageMax, minus the player's defense, at least the monster's level / 10, then the player's reductions and the map penalty. Its hit chance is its AttackRate against the player's PvM defense success rate.

Do not make monsters easier by raising <Class>PvMDamageRate for every class. That hides a monster problem in sixteen class entries; change the monsters instead.

Step 6 - PvP and single matchups

CalcCharacter.ini, last.

  1. SD and hit chance - [ShieldGage]: how much SD a character has (ShieldGageConstA, ShieldGageConstB), how much of a hit SD takes (DamageDevideToSD) and the PvP hit chance (SuccessAttackRateOption). These decide how long a PvP fight lasts for everyone, so set them before the rate tables.
  2. Rate tables - [PvPDamage] <Attacker>To<Target>_PvPDamageRate, [PvMDamage], [ComboDamage], [DarkSpirit], [Elemental]. They multiply the final hit, so they fix anything, and that is the danger: use them only for a problem left in one matchup ("Elf against Dark Wizard is too strong"), never for a class that is too strong against everyone (step 3).
  3. Caps - [OptionLimit]: the highest chance of excellent, critical, double and triple damage, damage decrease and reflect. Check them when options from many sources stack.

Where to look

Problem Step
a class is too strong or too weak at everything 3 - its class power
one skill is out of line 4 - that skill's function
a class is weak with few stats and too strong with many 2 - the stat divisors; 3 - the divisors of its class power
hits do only the minimum damage 2 - defense against damage; Step 1 options
too many misses, or never a miss 2 - the success rates (PvM and PvP are separate); 6 - SuccessAttackRateOption for PvP
PvP fights end in one or two hits 6 - SD, then the defense in step 2
a buff is too strong 3 for Berserker, Darkness, the Charges, Wrath, Crown Force and the other FormulaData buffs; 4 - the buff's function - for the rest
characters under the map's level are useless there MapPenalty.xml, or the penalty functions in step 2
every monster is too easy or too hard 5 - CalcMonster.ini
one class is too strong against one other class only 6 - its [PvPDamage] entry
attacks faster than the animation 1 - <Class>MaxAttackSpeed
critical, excellent or double damage on every hit 6 - [OptionLimit]

Before you change a value, check who else uses it. Character 65 is in every Dark Knight attack skill; CalcDefense counts in PvM as well as PvP; one [PvPDamage] entry is one direction only (Elf to Dark Wizard is not Dark Wizard to Elf).

Clone this wiki locally