Files
mc/server/entity/experience.go
T

118 lines
3.6 KiB
Go
Raw Permalink Normal View History

2026-07-09 08:33:57 +08:00
package entity
import (
"fmt"
"math"
)
// ExperienceManager manages experience and levels for entities, and provides functions to add, remove, and calculate
// experience needed for upcoming levels.
type ExperienceManager struct {
experience int
dec float64
}
// NewExperienceManager returns a new ExperienceManager with no experience.
func NewExperienceManager() *ExperienceManager {
return &ExperienceManager{}
}
// Experience returns the amount of experience the manager currently has.
func (e *ExperienceManager) Experience() int {
return e.experience
}
// Add adds experience to the total experience and recalculates the level and progress if necessary. Passing a negative
// value is valid. If the new experience would otherwise drop below 0, it is set to 0.
func (e *ExperienceManager) Add(amount int) (level int, progress float64) {
if e.experience += amount; e.experience < 0 {
e.Reset()
}
return progressFromExperience(e.total())
}
// total returns the total amount of experience including the extra decimals provided for more accuracy.
func (e *ExperienceManager) total() float64 {
return float64(e.experience) + e.dec
}
// Level returns the current experience level.
func (e *ExperienceManager) Level() int {
level, _ := progressFromExperience(e.total())
return level
}
// SetLevel sets the level of the manager.
func (e *ExperienceManager) SetLevel(level int) {
if level < 0 || level > math.MaxInt32 {
panic(fmt.Sprintf("level must be between 0 and 2,147,483,647, got %v", level))
}
_, progress := progressFromExperience(e.total())
e.experience = experienceForLevels(level) + int(float64(experienceForLevel(level))*progress)
}
// Progress returns the progress towards the next level.
func (e *ExperienceManager) Progress() float64 {
_, progress := progressFromExperience(e.total())
return progress
}
// SetProgress sets the progress of the manager.
func (e *ExperienceManager) SetProgress(progress float64) {
if progress < 0 || progress > 1 {
panic(fmt.Sprintf("progress must be between 0 and 1, got %f", progress))
}
currentLevel, _ := progressFromExperience(e.total())
progressExp := float64(experienceForLevel(currentLevel)) * progress
e.experience = experienceForLevels(currentLevel) + int(progressExp)
e.dec = progressExp - math.Trunc(progressExp)
}
// Reset resets the total experience, level, and progress of the manager to zero.
func (e *ExperienceManager) Reset() {
e.experience, e.dec = 0, 0
}
// progressFromExperience returns the level and progress from the total experience given.
func progressFromExperience(experience float64) (level int, progress float64) {
var a, b, c float64
switch {
case experience <= float64(experienceForLevels(16)):
a, b = 1.0, 6.0
case experience <= float64(experienceForLevels(31)):
a, b, c = 2.5, -40.5, 360.0
default:
a, b, c = 4.5, -162.5, 2220.0
}
var sol float64
if d := b*b - 4*a*(c-experience); d > 0 {
s := math.Sqrt(d)
sol = math.Max((-b+s)/(2*a), (-b-s)/(2*a))
} else if d == 0 {
sol = -b / (2 * a)
}
return int(sol), sol - math.Trunc(sol)
}
// experienceForLevels calculates the amount of experience needed in total to reach a certain level.
func experienceForLevels(level int) int {
if level <= 16 {
return level*level + level*6
} else if level <= 31 {
return int(float64(level*level)*2.5 - 40.5*float64(level) + 360)
}
return int(float64(level*level)*4.5 - 162.5*float64(level) + 2220)
}
// experienceForLevel returns the amount experience needed to reach level + 1.
func experienceForLevel(level int) int {
if level <= 15 {
return 2*level + 7
} else if level <= 30 {
return 5*level - 38
}
return 9*level - 158
}