Files
Server/src/engine/objects/Experience.java
T

374 lines
12 KiB
Java
Raw Normal View History

2026-07-26 19:08:44 -04:00
// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ .
// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀
// Magicbane Emulator Project © 2013 - 2026
// https://www.magicbane.com
2022-04-30 09:41:17 -04:00
package engine.objects;
import engine.gameManager.ZoneManager;
import engine.math.Vector3fImmutable;
2024-05-12 13:36:47 -04:00
import engine.mbEnums.TargetColor;
import engine.parsers.objects.RuneTemplate;
2022-04-30 09:41:17 -04:00
import engine.server.MBServerStatics;
import java.util.ArrayList;
2023-08-03 09:04:20 -04:00
import static engine.gameManager.LootManager.LOOTMANAGER;
2023-07-15 09:23:48 -04:00
public class Experience {
2022-04-30 09:41:17 -04:00
// Client ArcCharacter_ExpForLevelUp (0x4715D0) float constant at 0x154195C
private static final float EXP_LEVEL_SLOPE = 0.0512f;
2023-07-15 09:23:48 -04:00
private static final float[] MaxExpPerLevel = {Float.MIN_VALUE, // Pad
// everything
// over
// 1
2022-04-30 09:41:17 -04:00
2023-07-15 09:23:48 -04:00
// R0
15, // Level 1
105, // Level 2
285, // Level 3
555, // Level 4
610, // Level 5
682.5f, // Level 6
730, // Level 7
975, // Level 8
1251.92f, // Level 9
2022-04-30 09:41:17 -04:00
2023-07-15 09:23:48 -04:00
// R1
1563.46f, // Level 10
1909.62f, // Level 11
2290.38f, // Level 12
2705.77f, // Level 13
3155.77f, // Level 14
3640.38f, // Level 15
4159.62f, // Level 16
4713.46f, // Level 17
5301.92f, // Level 18
5925, // Level 19
2022-04-30 09:41:17 -04:00
2023-07-15 09:23:48 -04:00
// R2
6582.69f, // Level 20
7275, // Level 21
8001.92f, // Level 22
8763.46f, // Level 23
9559.62f, // Level 24
10390.38f, // Level 25
11255.77f, // Level 26
12155.77f, // Level 27
13090.38f, // Level 28
14059.62f, // Level 29
2022-04-30 09:41:17 -04:00
2023-07-15 09:23:48 -04:00
// R3
15063.46f, // Level 30
16101.92f, // Level 31
17175, // Level 32
18282.69f, // Level 33
19425, // Level 34
20601.92f, // Level 35
21813.46f, // Level 36
23059.62f, // Level 37
24340.38f, // Level 38
25655.77f, // Level 39
2022-04-30 09:41:17 -04:00
2023-07-15 09:23:48 -04:00
// R4
45910.38f, // Level 40
34348.87f, // Level 41
37458.16f, // Level 42
40745.21f, // Level 43
44214.76f, // Level 44
47871.68f, // Level 45
51720.87f, // Level 46
55767.16f, // Level 47
60015.37f, // Level 48
64470.37f, // Level 49
2022-04-30 09:41:17 -04:00
2023-07-15 09:23:48 -04:00
// R5
69137.03f, // Level 50
74020.16f, // Level 51
79124.63f, // Level 52
84455.34f, // Level 53
90017.03f, // Level 54
95814.66f, // Level 55
101853.03f, // Level 56
108137, // Level 57
114671.37f, // Level 58
121461.11f, // Level 59
2022-04-30 09:41:17 -04:00
2023-07-15 09:23:48 -04:00
// R6
128510.92f, // Level 60
135825.79f, // Level 61
143410.47f, // Level 62
151269.87f, // Level 63
159408.82f, // Level 64
167832.16f, // Level 65
176544.74f, // Level 66
185551.45f, // Level 67
194857.08f, // Level 68
204466.55f, // Level 69
2022-04-30 09:41:17 -04:00
2023-07-15 09:23:48 -04:00
// R7
214384.63f, // Level 70
224616.24f, // Level 71
235166.21f, // Level 72
246039.34f, // Level 73
257240.58f, // Level 74
1 // 268774.71 //Level 75
2022-04-30 09:41:17 -04:00
2023-07-15 09:23:48 -04:00
};
// Used to calcuate the amount of experience a monster grants in the
// following formula
// expGranted = a(moblevel)^2 + b(moblevel) + c
private static final float EXPQUADA = 10.0f;
private static final float EXPQUADB = 6.0f;
private static final float EXPQUADC = -10.0f;
// Adds addtional exp per addtional member of a group using the following
// (expGranted / group.size()) * (groupBonus * (group.size()-1) +1)
private static final float GROUP_BONUS = 0.5f; // 0.2 grants (20%) addtional
2022-04-30 09:41:17 -04:00
2023-07-15 09:23:48 -04:00
// exp per group member
2022-04-30 09:41:17 -04:00
2023-07-15 09:23:48 -04:00
// called to determine current level based on xp
public static int getLevel(PlayerCharacter player, int experience) {
2023-08-03 09:04:20 -04:00
int level = 1;
2023-08-03 09:04:20 -04:00
while (level < MBServerStatics.LEVELCAP && experience >= getBaseExperience(player, level + 1))
level++;
2023-08-03 09:04:20 -04:00
2023-07-15 09:23:48 -04:00
return level;
}
2022-04-30 09:41:17 -04:00
// The client function returns the XP to reach level + 1, so it is called with level - 1.
// Arithmetic order and the half-up rounding follow the client so every threshold matches.
public static int getBaseExperience(PlayerCharacter player, int level) {
2023-08-03 09:04:20 -04:00
int clientLevel = level - 1;
double levelScale;
2022-04-30 09:41:17 -04:00
levelScale = Math.max(1.0, (clientLevel - 39) * (double) EXP_LEVEL_SLOPE + 1.0);
2023-08-03 09:04:20 -04:00
return (int) (long) ((double) clientLevel * clientLevel * clientLevel * levelScale
* getClassExpFactor(player) * getRaceExpFactor(player) + 0.5);
}
// rune_exp_req_to_level; the client uses 1.0 when the rune is missing
private static double getRaceExpFactor(PlayerCharacter player) {
RuneTemplate rune = null;
if (player.race != null)
rune = RuneTemplate._runes.get(player.race.getRaceRuneID());
return (rune == null) ? 1.0 : rune.rune_exp_req_to_level;
}
// GUESS: the client's class rune (0x45EAD0) is the promotion once taken; every CLASS rune is 15 either way.
// The CSR promotions 2901+ have no rune, so they fall back to the base class.
private static double getClassExpFactor(PlayerCharacter player) {
RuneTemplate rune = null;
if (player.promotionClass != null)
rune = RuneTemplate._runes.get(player.promotionClass.getObjectUUID());
if (rune == null && player.baseClass != null)
rune = RuneTemplate._runes.get(player.baseClass.getObjectUUID());
return (rune == null) ? 1.0 : rune.rune_exp_req_to_level;
2023-07-15 09:23:48 -04:00
}
2022-04-30 09:41:17 -04:00
2023-07-15 09:23:48 -04:00
// Max XP granted for killing a blue, yellow or orange mob
public static float maxXPPerKill(int level) {
2023-08-03 09:04:20 -04:00
2023-07-15 09:23:48 -04:00
if (level < 1)
level = 1;
2023-08-03 09:04:20 -04:00
2023-07-15 09:23:48 -04:00
if (level > 75)
level = 75;
2023-08-03 09:04:20 -04:00
2023-07-15 09:23:48 -04:00
return MaxExpPerLevel[level];
}
2022-04-30 09:41:17 -04:00
2023-07-15 09:23:48 -04:00
// Returns a penalty modifier depending on mob color
public static double getConMod(AbstractCharacter pc, AbstractCharacter mob) {
2023-08-03 09:04:20 -04:00
2023-07-15 09:23:48 -04:00
switch (TargetColor.getCon(pc, mob)) {
case Red:
return 1.25;
case Orange:
return 1.15;
case Yellow:
return 1.05;
case Blue:
return 1;
case Cyan:
return 0.8;
case Green:
return 0.5;
default:
return 0;
}
}
2022-04-30 09:41:17 -04:00
2023-07-15 09:23:48 -04:00
public static double getGroupMemberPenalty(double leadership,
PlayerCharacter currPlayer, ArrayList<PlayerCharacter> players,
int highestLevel) {
2022-04-30 09:41:17 -04:00
2023-07-15 09:23:48 -04:00
double penalty = 0.0;
int level = currPlayer.getLevel();
2022-04-30 09:41:17 -04:00
2023-07-15 09:23:48 -04:00
// Group Size Penalty
2023-08-03 09:04:20 -04:00
2023-07-15 09:23:48 -04:00
if (players.size() > 2)
penalty = (players.size() - 2) * 1.5;
2022-04-30 09:41:17 -04:00
2023-07-15 09:23:48 -04:00
// Calculate Penalty For Highest level -> Current Player difference, !=
// check to prevent divide by zero error
2023-08-03 09:04:20 -04:00
2023-07-15 09:23:48 -04:00
if (highestLevel != level)
penalty += ((highestLevel - level) * .5);
2022-04-30 09:41:17 -04:00
2023-07-15 09:23:48 -04:00
// double avgLevels = totalLevels / adjustedGroupSize;
// if (adjustedGroupSize >= 1)
// if (level < avgLevels)
// penalty += ((avgLevels - level) * .5);
2022-04-30 09:41:17 -04:00
2023-07-15 09:23:48 -04:00
// Extra noob penalty
2023-08-03 09:04:20 -04:00
2023-07-15 09:23:48 -04:00
if ((highestLevel - level) > 25)
penalty += (highestLevel - level - 25);
2022-04-30 09:41:17 -04:00
2023-07-15 09:23:48 -04:00
return penalty;
}
2022-04-30 09:41:17 -04:00
2023-07-15 09:23:48 -04:00
public static void doExperience(PlayerCharacter killer, AbstractCharacter mob, Group g) {
2023-08-03 09:04:20 -04:00
2023-07-15 09:23:48 -04:00
// Check for some failure conditions
2023-08-03 09:04:20 -04:00
2023-07-15 09:23:48 -04:00
if (killer == null || mob == null)
return;
2022-04-30 09:41:17 -04:00
2023-07-15 09:23:48 -04:00
double grantedExperience = 0.0;
2022-04-30 09:41:17 -04:00
2023-07-15 09:23:48 -04:00
if (g != null) { // Do group EXP stuff
2022-04-30 09:41:17 -04:00
2023-07-15 09:23:48 -04:00
int leadership = 0;
int highestLevel = 0;
double penalty = 0.0;
2022-04-30 09:41:17 -04:00
2023-07-15 09:23:48 -04:00
ArrayList<PlayerCharacter> giveEXPTo = new ArrayList<>();
2022-04-30 09:41:17 -04:00
2023-07-15 09:23:48 -04:00
// Check if leader is within range of kill and then get leadership
// skill
2023-08-03 09:04:20 -04:00
2023-07-15 09:23:48 -04:00
Vector3fImmutable killLoc = mob.getLoc();
2023-08-03 09:04:20 -04:00
2023-07-15 09:23:48 -04:00
if (killLoc.distanceSquared2D(g.getGroupLead().getLoc()) < (MBServerStatics.EXP_RANGE * MBServerStatics.EXP_RANGE)) {
CharacterSkill leaderskill = g.getGroupLead().skills
.get("Leadership");
2023-08-03 09:04:20 -04:00
2023-07-15 09:23:48 -04:00
if (leaderskill != null)
leadership = leaderskill.getNumTrains();
if (leadership > 90)
leadership = 90; // leadership caps at 90%
}
2022-04-30 09:41:17 -04:00
2023-07-15 09:23:48 -04:00
// Check every group member for distance to see if they get xp
2023-08-03 09:04:20 -04:00
2023-07-15 09:23:48 -04:00
for (PlayerCharacter pc : g.getMembers()) {
if (pc.isAlive()) { // Skip if the player is dead.
2022-04-30 09:41:17 -04:00
2023-07-15 09:23:48 -04:00
// Check within range
2023-08-03 09:04:20 -04:00
2023-07-15 09:23:48 -04:00
if (killLoc.distanceSquared2D(pc.getLoc()) < (MBServerStatics.EXP_RANGE * MBServerStatics.EXP_RANGE)) {
2023-08-03 09:04:20 -04:00
2023-07-15 09:23:48 -04:00
giveEXPTo.add(pc);
2023-08-03 09:04:20 -04:00
2023-07-15 09:23:48 -04:00
// Track highest level character
2023-08-03 09:04:20 -04:00
2023-07-15 09:23:48 -04:00
if (pc.getLevel() > highestLevel)
highestLevel = pc.getLevel();
}
}
}
2022-04-30 09:41:17 -04:00
2023-07-15 09:23:48 -04:00
// Process every player in the group getting XP
2023-08-03 09:04:20 -04:00
2023-07-15 09:23:48 -04:00
for (PlayerCharacter playerCharacter : giveEXPTo) {
if (playerCharacter.getLevel() >= MBServerStatics.LEVELCAP)
continue;
2022-04-30 09:41:17 -04:00
2023-07-15 09:23:48 -04:00
// Sets Max XP with server exp mod taken into account.
2023-08-03 09:04:20 -04:00
grantedExperience = (double) LOOTMANAGER.NORMAL_EXP_RATE * maxXPPerKill(playerCharacter.getLevel());
2022-04-30 09:41:17 -04:00
2023-07-15 09:23:48 -04:00
// Adjust XP for Mob Level
2023-08-03 09:04:20 -04:00
2023-07-15 09:23:48 -04:00
grantedExperience *= getConMod(playerCharacter, mob);
2022-04-30 09:41:17 -04:00
2023-07-15 09:23:48 -04:00
// Process XP for this member
2023-08-03 09:04:20 -04:00
2023-07-15 09:23:48 -04:00
penalty = getGroupMemberPenalty(leadership, playerCharacter, giveEXPTo,
highestLevel);
2022-04-30 09:41:17 -04:00
2023-07-15 09:23:48 -04:00
// Leadership Penalty Reduction
2023-08-03 09:04:20 -04:00
2023-07-15 09:23:48 -04:00
if (leadership > 0)
penalty -= ((leadership) * 0.01) * penalty;
2022-04-30 09:41:17 -04:00
2023-07-15 09:23:48 -04:00
// Modify for hotzone
2023-08-03 09:04:20 -04:00
2023-07-15 09:23:48 -04:00
if (grantedExperience != 0)
if (ZoneManager.inHotZone(mob.getLoc()))
2023-08-03 09:04:20 -04:00
grantedExperience *= LOOTMANAGER.HOTZONE_EXP_RATE;
2022-04-30 09:41:17 -04:00
2023-07-15 09:23:48 -04:00
// Check for 0 XP due to white mob, otherwise subtract penalty
// xp
2023-08-03 09:04:20 -04:00
if (grantedExperience == 0)
2023-07-15 09:23:48 -04:00
grantedExperience = 1;
2023-08-03 09:04:20 -04:00
else {
2023-07-15 09:23:48 -04:00
grantedExperience -= (penalty * 0.01) * grantedExperience;
2022-04-30 09:41:17 -04:00
2023-07-15 09:23:48 -04:00
// Errant Penalty Calculation
if (playerCharacter.getGuild().isEmptyGuild())
grantedExperience *= 0.6;
}
2022-04-30 09:41:17 -04:00
2023-07-15 09:23:48 -04:00
if (grantedExperience == 0)
grantedExperience = 1;
2022-04-30 09:41:17 -04:00
2023-07-15 09:23:48 -04:00
// Grant the player the EXP
playerCharacter.grantXP((int) Math.floor(grantedExperience));
}
2022-04-30 09:41:17 -04:00
2023-07-15 09:23:48 -04:00
} else { // Give EXP to a single character
if (!killer.isAlive()) // Skip if the player is dead.
return;
2022-04-30 09:41:17 -04:00
2023-07-15 09:23:48 -04:00
if (killer.getLevel() >= MBServerStatics.LEVELCAP)
return;
2022-04-30 09:41:17 -04:00
2023-07-15 09:23:48 -04:00
// Get XP and adjust for Mob Level with world xp modifier taken into account
2023-08-03 09:04:20 -04:00
grantedExperience = (double) LOOTMANAGER.NORMAL_EXP_RATE * maxXPPerKill(killer.getLevel());
2023-07-15 09:23:48 -04:00
grantedExperience *= getConMod(killer, mob);
2022-04-30 09:41:17 -04:00
2023-07-15 09:23:48 -04:00
// Modify for hotzone
if (ZoneManager.inHotZone(mob.getLoc()))
2023-08-03 09:04:20 -04:00
grantedExperience *= LOOTMANAGER.HOTZONE_EXP_RATE;
2022-04-30 09:41:17 -04:00
2023-07-15 09:23:48 -04:00
// Errant penalty
if (grantedExperience != 1)
if (killer.getGuild().isEmptyGuild())
grantedExperience *= .6;
2022-04-30 09:41:17 -04:00
2023-07-15 09:23:48 -04:00
// Grant XP
killer.grantXP((int) Math.floor(grantedExperience));
}
}
2022-04-30 09:41:17 -04:00
}