Rework level calculation (#2196)

* rework level calculation

* rework level calculations

* generated

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: ggurdin <46800240+ggurdin@users.noreply.github.com>
This commit is contained in:
Wilson 2025-04-01 12:32:13 -04:00 committed by GitHub
parent 642dfaf4de
commit be5ea2b927
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 39 additions and 19 deletions

View file

@ -177,25 +177,50 @@ class ConstructListModel {
if (totalXP < 0) {
totalXP = 0;
}
level = calculateLevelWithXp(totalXP);
}
// Don't call .floor() if NaN or Infinity
// https://pangea-chat.sentry.io/issues/6052871310
final double levelCalculation = 1 + sqrt((1 + 8 * totalXP / 100) / 2);
if (!levelCalculation.isNaN && levelCalculation.isFinite) {
level = levelCalculation.floor();
int calculateLevelWithXp(int totalXP) {
// [D] is the "compression factor". It determines how quickly
/// or slowly the level grows relative to XP
const double D = 2500;
final doubleScore = (1 + sqrt((1 + (8.0 * totalXP / D)) / 2.0));
if (!doubleScore.isNaN && doubleScore.isFinite) {
return doubleScore.floor();
} else {
level = 1;
ErrorHandler.logError(
e: "Calculated level in Nan or Infinity",
data: {
"totalXP": totalXP,
"prevXP": prevXP,
"level": levelCalculation,
"level": doubleScore,
},
);
return 1;
}
}
int calculateXpWithLevel(int level) {
// [D] is the same "compression factor" as in calculateLevelWithXp.
const double D = 2500.0;
// If level <= 1, XP should be 0 or negative by this math.
// In practice, you might clamp it to 0:
if (level <= 1) {
return 0;
}
// Convert level to double for the math
final double lc = level.toDouble();
// XP from the inverse formula:
final double xpDouble = (D / 8.0) * (2.0 * pow(lc - 1.0, 2.0) - 1.0);
// Floor or clamp to ensure non-negative.
final int xp = xpDouble.floor();
return (xp < 0) ? 0 : xp;
}
// TODO; make this non-nullable, returning empty if not found
ConstructUses? getConstructUses(ConstructIdentifier identifier) {
final partialKey = "${identifier.lemma}-${identifier.type.string}";

View file

@ -49,22 +49,17 @@ class GetAnalyticsController extends BaseController {
// the minimum XP required for a given level
int get _minXPForLevel {
return _calculateMinXpForLevel(constructListModel.level);
return constructListModel.calculateXpWithLevel(constructListModel.level);
}
// the minimum XP required for the next level
int get _minXPForNextLevel {
return _calculateMinXpForLevel(constructListModel.level + 1);
return constructListModel
.calculateXpWithLevel(constructListModel.level + 1);
}
int get minXPForNextLevel => _minXPForNextLevel;
/// Calculates the minimum XP required for a specific level.
int _calculateMinXpForLevel(int level) {
if (level == 1) return 0; // Ensure level 1 starts at 0 XP
return ((100 / 8) * (2 * pow(level - 1, 2))).floor();
}
// the progress within the current level as a percentage (0.0 to 1.0)
double get levelProgress {
final progress = (constructListModel.totalXP - _minXPForLevel) /
@ -197,8 +192,8 @@ class GetAnalyticsController extends BaseController {
}
Future<void> _onLevelDown(final int lowerLevel, final int upperLevel) async {
final offset =
_calculateMinXpForLevel(lowerLevel) - constructListModel.totalXP;
final offset = constructListModel.calculateXpWithLevel(lowerLevel) -
constructListModel.totalXP;
await _pangeaController.userController.addXPOffset(offset);
constructListModel.updateConstructs(
[],
@ -391,8 +386,8 @@ class GetAnalyticsController extends BaseController {
// generate level up analytics as a construct summary
ConstructSummary summary;
try {
final int maxXP = _calculateMinXpForLevel(upperLevel);
final int minXP = _calculateMinXpForLevel(lowerLevel);
final int maxXP = constructListModel.calculateXpWithLevel(upperLevel);
final int minXP = constructListModel.calculateXpWithLevel(lowerLevel);
int diffXP = maxXP - minXP;
if (diffXP < 0) diffXP = 0;