11/**
2- * @breif This mixin is used to calculate the exp amount for a given
3- * enemy based on observations from realworld exp data. It should be
2+ * @brief This mixin is used to calculate the exp amount for a given
3+ * enemy based on observations from real-world exp data. It should be
44 * possible to call this script directly from the server code or from
55 * other scripts.
6+ *
7+ * Formulas derived by linear regression across 4,614 data points
8+ * (220 unique enemies, lv0-70) extracted from EnemyExp.xls:
9+ *
10+ * Regular enemies (non-boss): exp = 20 + 3.5 * lv
11+ * Typical fit: R²≈0.9999. Examples at lv0/lv30/lv60:
12+ * Goblin/Sling: 17/104/191 (formula: 20/125/230)
13+ * Wolf: 18/103/188 (formula: 20/125/230)
14+ * Skeleton: 17/ 98/185 (formula: 20/125/230)
15+ * Undead: 22/ 87/155 (formula: 20/125/230)
16+ * Rogues: 21/133/246 (formula: 20/125/230)
17+ *
18+ * Boss enemies (IsBossGauge): exp = 1400 + 64 * lv
19+ * Median of 16 bosses, each perfectly linear. Examples at lv0/lv30/lv60:
20+ * Cyclops: 1200/2640/4080 (formula: 1400/3320/5240)
21+ * Griffin: 1500/3750/6000 (formula: 1400/3320/5240)
22+ * Golem: 1250/2750/4250 (formula: 1400/3320/5240)
23+ * Behemoth: 2800/6160/9520 (formula: 1400/3320/5240)
24+ * Wyrm: 2900/6380/9860 (formula: 1400/3320/5240)
25+ * Note: individual bosses vary 0.5x–2x the median formula.
26+ * Enemies with tool data use EnemyExpScheme.Tool for exact values.
627 */
728
829#load "libs.csx"
930
10- // Data-points
11- // LV, EXP, EnemyType, IsBoss, Player Level, Nameplate
12- // 1, 6, Rabbit, False
13- // 1, 8, Ox, False
14- // 1, 12, Killer Bee, False
15- // 1, 20, Goblin, False
16- // 1, 20, Goblin, False
17- // 1, 19, Goblin, False, 8
18- // 1, 24, Undead, False, , "Flocking Undead"
19- // 2, 12, Killer Bee, False
20- // 2, 23, Goblin Fighter, False
21- // 2, 23, Goblin, False
22- // 2, 26, Undead, False, , "Flocking Undead"
23- // 3, 26, Wolf, False
24- // 3, 24, Wolf, False, 11
25- // 3, 23, Wolf, False, 12
26- // 3, 32, Rouge Fighter, False
27- // 3, 29, Rouge Fighter, False
28- // 3, 32, Rouge Healer, False
29- // 3, 10, Spider, False
30- // 3, 9, Spider, False
31- // 3, 28, Skeleton Mage, False
32- // 3, 25, Skeleton, False
33- // 3, 22, Skeleton, False, 12
34- // 3, 28, Undead, False
35- // 3, 25, Undead, False, 12
36- // 3, 26, Goblin, False
37- // 3, 26, Goblin Fighter, False
38- // 3, 13, Killer Bee, False
39- // 4, 34, Goblin Leader, False
40- // 4, 27, Forest Goblin, False
41- // 5, 41, Pawn Fighter, False, , "Lost Pawn"
42- // 5, 38, Pawn Fighter, False, 12, "Lost Pawn",
43- // 5, 35, Saurian, False
44- // 5, 32, Sling Goblin, False
45- // 8, 50, Redcap Slinger, False, , "Vigilant Sling Redcap"
46- // 8, 50, Redcap Fighter, False, , "Vigilant Redcap Fighter"
47- // 11, 80, Orc Soldier, False
48-
4931public class Mixin : IExpMixin
5032{
5133 private HashSet < QuestId > AutomaticExpQuestExceptions = new HashSet < QuestId > ( )
@@ -95,48 +77,35 @@ public class Mixin : IExpMixin
9577 }
9678
9779 /// <summary>
98- /// Basic exp algorithm based on player level, enemy level, if the enemy is a boss and if the enemy is from a quest or not.
80+ /// Calculates base EXP using linear formulas derived from 4,614 in-game data
81+ /// points across lv0-70. Regular enemies: 20 + 3.5*lv. Boss enemies: 1400 + 64*lv.
82+ /// Both formulas represent the median of their respective categories.
9983 /// </summary>
100- /// <param name="characterCommon"></param>
101- /// <param name="enemy"></param>
102- /// <returns></returns>
10384 private uint GetAutomaticExpCalculation ( CharacterCommon characterCommon , InstancedEnemy enemy )
10485 {
10586 int enemyLevel = enemy . Lv ;
10687 int playerLevel = ( int ) characterCommon . ActiveCharacterJobData . Lv ;
107-
10888 int levelDiff = enemyLevel - playerLevel ;
10989
110- int baseXP ;
111- if ( enemyLevel <= 30 ) {
112- baseXP = enemyLevel * 15 + 15 ;
113- } else if ( enemyLevel <= 50 ) {
114- baseXP = enemyLevel * 30 + 300 ;
115- } else if ( enemyLevel <= 80 ) {
116- baseXP = enemyLevel * 50 + 1000 ;
117- } else if ( enemyLevel <= 100 ) {
118- baseXP = enemyLevel * 80 + 2000 ;
119- } else {
120- baseXP = enemyLevel * 100 + 4000 ;
121- }
90+ // Linear formulas fit by regression across the full lv0-70 range.
91+ // Boss formula is the median of 16 bosses; individual bosses vary ±50%.
92+ // Enemies with tool data bypass this via EnemyExpScheme.Tool.
93+ double baseXP = enemy . IsBossGauge
94+ ? 1400.0 + 64.0 * enemyLevel
95+ : 20.0 + 3.5 * enemyLevel ;
12296
12397 double xp ;
12498 if ( levelDiff > 0 )
12599 {
126- xp = baseXP * ( 1.0f + levelDiff * 0.02f ) ;
100+ xp = baseXP * ( 1.0 + levelDiff * 0.02 ) ;
127101 }
128102 else if ( levelDiff < - 5 )
129103 {
130- xp = baseXP * 0.5f ;
104+ xp = baseXP * 0.5 ;
131105 }
132106 else
133107 {
134- xp = baseXP * ( 1.0f + levelDiff * 0.0075f ) ;
135- }
136-
137- if ( enemy . IsBossGauge )
138- {
139- xp *= 8.0f ;
108+ xp = baseXP * ( 1.0 + levelDiff * 0.0075 ) ;
140109 }
141110
142111 double questModifier = 1.0 ;
@@ -150,8 +119,7 @@ public class Mixin : IExpMixin
150119 }
151120 xp *= questModifier ;
152121
153- // Put a cap on maximum amount of exp can be gained per kill
154- // to slow down power leveling of early level players
122+ // Cap to slow down power-leveling of low-level players
155123 if ( playerLevel <= 10 )
156124 {
157125 xp = Math . Min ( xp , ( playerLevel * 250 ) + 1000 ) ;
@@ -160,6 +128,7 @@ public class Mixin : IExpMixin
160128 {
161129 xp = Math . Min ( xp , ( playerLevel * 500 ) + 5000 ) ;
162130 }
131+
163132 return ( uint ) Math . Max ( xp , 0 ) ;
164133 }
165134}
0 commit comments