Repository navigation
Balance Characters
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.
| 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 areSkillTreeformulas 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
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 |
This runs on login, on equipping an item, on adding stat points, on level up and when a buff starts or ends.
-
Stats. Base points plus everything added by items, sets and buffs, each capped by
MaxStrength...MaxCommandin CalcCharacter.ini. -
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>MaxAttackSpeedin CalcCharacter.ini) -
CalcAbilityGauge(AG), the elemental functions, the two-weapon bonuses
-
-
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
Charactersection of FormulaData.xml. -
Combat power, for Dark Knight, Magic Gladiator, Rage Fighter, Slayer, Grow Lancer,
Dark Lord, Crusader, Elf and Illusion Knight only. The class formula (
CombatPower9-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 (CombatPower7) or 40 % with one (CombatPower8).
- Hit or miss - attack success rate against defense success rate.
- The target's defense for this hit - debuffs, ignore defense, basic defense.
- The damage roll - character damage + skill damage, critical or excellent.
- Defense is taken off, and the result never falls under the minimum damage.
- The target's reductions and the gear's bonuses - damage decrease, pets, wings, mounts.
- The map penalty - a character below the map's level deals less and takes more.
-
The skill's function in
Scripts\Skills\<Class>.lua- mostly the class power %. -
4th skill tree damage, then the rate tables
[PvPDamage]/[PvMDamage]. - 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.
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 = (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 = 1it is never more than the character's own defense
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.
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>DamageCalcplus weapon and options - skill min / max: SkillList
DamageandDamagex 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/MaxExcellentDamageRatein[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.
damage = roll - target defense (two weapons: right + left - defense, once)
- below 0 it becomes 0 with
ReduceDefenseFromFullDamage = 0. With= 1it 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 = 1a 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.
-
Damage decrease of the target: its percent (capped by
MaxDamageDecreaseRate), then its flat value. -
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). -
Pets, VIP and buffs: Satan and Demon add, Guardian Angel, Safeguard and the damage-decrease buffs take off, each a percent.
-
Wings: the attacker's wings add
Wings_CalcIncAttack%, the target's wings absorbWings_CalcAbsorb% (Scripts\Misc\ItemCalc.lua). -
Mounts: Dinorant, Fenrir and Dark Horse absorb.
-
Map penalty, when the character's total level is below
PenaltyLevelof the map inMapPenalty.xml(CalcPenaltyDamageFromUser/FromMonsterin 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) / 100The monster's
DamageCorrectionis in MonsterList.xml, the player's comes from 5th wings (WingCoreformula 1). Seven levels under the map's level already cost 70 %. -
The skill's function in
Scripts\Skills\<Class>.lua(see Step 4 below). -
4th skill tree skill damage, a flat value.
-
Rate tables:
[PvPDamage]<Attacker>To<Target>_PvPDamageRateagainst players,<Class>PvMDamageRateagainst monsters.
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 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.
- 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.
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.
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 DamageA smaller divisor makes the class grow faster with that stat. Things to keep in mind:
- Defense from
CalcDefenseis 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/FromMonsterhold 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).
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.
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
endTo make only Twisting Slash 10 % weaker, change the one line:
local OutDamage = InDamage * RageAttackPower / 100 * 0.9What to know about these files:
- All of them are loaded into one Lua state.
TwistingSlash_DarkKnightis 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. -
InDamageis the hit after defense and after steps 1-6 of "After defense". -
Stats.Get(oPlayer, STAT_STRENGTH, STAT_BOTHTYPE)reads a stat:STAT_BOTHTYPEis base plus added points,STAT_PERMANENTthe 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.
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.
CalcCharacter.ini, last.
-
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. -
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). -
Caps -
[OptionLimit]: the highest chance of excellent, critical, double and triple damage, damage decrease and reflect. Check them when options from many sources stack.
| 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).
MuLua Scripting Plugin | Home
🖼️ Lua UI
- File map
- Creating windows
- Client and server
- Designing a window
- Protocol
- Scrolling and scrollbars
- Controls
- Custom text
- Text input
- Debug console
- Bitmap Slicer
- Encrypting scripts
Maps
Monsters
Shared
Version 1.14 | 2026-09-09