Skip to content

Instantly share code, notes, and snippets.

@gaymeowing
Created March 20, 2024 15:15
Show Gist options
  • Save gaymeowing/7870f86e8c31bd06fb4d1daa8c651e5c to your computer and use it in GitHub Desktop.
Save gaymeowing/7870f86e8c31bd06fb4d1daa8c651e5c to your computer and use it in GitHub Desktop.
Class for experience and levels
--!native
--!strict
-- Exp
-- Level and experience class modified from
-- https://rostrap.github.io/Libraries/Math/Leveler/
-- @Kalrnlo
-- 20/03/2024
local Players = game:GetService("Players")
local Bindings = {} :: {[number]: Exp}
Players.PlayerRemoving:Connect(function(Player)
Bindings[Player.UserId] = nil
end)
type ExpMethod<T> = ((self: Exp, Points: number) -> T) & ((self: Exp, ExpStruct: ExpStruct) -> T)
type Exp_Prototype = {
__add: ExpMethod<Exp>,
__sub: ExpMethod<Exp>,
__eq: ExpMethod<boolean>,
__lt: ExpMethod<boolean>,
__le: ExpMethod<boolean>,
}
export type ExpStruct = {
Experience: number,
Percent: number,
Level: number,
Total: number,
Next: number,
}
export type Exp = typeof(setmetatable({} :: ExpStruct, {} :: Exp_Prototype))
-- Level equations taken from https://minecraft.gamepedia.com/Experience
-- returns number Exp the Exp required to reach the next level
local function ToNextLevel(CurrentLevel: number)
return math.abs(
if 0 <= CurrentLevel and CurrentLevel < 16 then
(2 * CurrentLevel) + 7
elseif Lvl < 31 then
(5 * CurrentLevel) - 38
else
(9 * CurrentLevel) - 158
)
end
local function LevelFromExperience(self: Exp, Exp: number)
-- @param number Exp The Amount of Experience
-- @returns number Level, number experience within the current level
if Exp <= 0 then
return 0, 0
elseif Exp <= 352 and Exp > 0 then --Lvl 16 or under
local Lvl = math.floor(((Exp + 9) ^ 0.5) - 3)
local Experience = Exp - (((Lvl * Lvl) + 6) * Lvl)
self.Experience = ExpInLvl
self.Level = Lvl
return Lvl, ExpInLvl
elseif Exp > 352 and Exp <= 1507 then
local Lvl = math.floor((81 + ((40 * Exp) - 7839) ^ 0.5) * 0.1)
local ExpInLvl = Exp - (((((2.5 * Lvl) * Lvl) - 40.5) * Lvl) + 360)
self.Experience = ExpInLvl
self.Level = Lvl
return Lvl, ExpInLvl
else
local Lvl = math.floor((325 + ((72 * Exp) - 54215) ^ 0.5) / 18)
local ExpInLvl = Exp - (((((4.5 * Lvl) * Lvl) - 162.5) * Lvl) + 2220)
self.Experience = ExpInLvl
self.Level = Lvl
return Lvl, ExpInLvl
end
end
local function Award(self: Exp, PointsOrExpStruct: number | ExpStruct)
local Points = if type(PointsOrExpStruct) == "table" then PointsOrExpStruct.Total else PointsOrExpStruct
if Points < 0 then
error(`[Exp] Cannot award negitive points {Points}`)
end
local Total = self.Total + Points
local Experience = LevelFromExperience(self, Total)
local Next = ToNextLevel(Experience)
self.Percent = Experience / Next
self.Total = Total
self.Next = Next
return self
end
local function Deduct(self: Exp, PointsOrExpStruct: number | ExpStruct)
local Total = self.Total - if type(PointsOrExpStruct) == "table" then PointsOrExpStruct.Total else PointsOrExpStruct
if Total > 0 then
local Experience = LevelFromExperience(self, Total)
local Next = ToNextLevel(Experience)
self.Percent = Experience / Next
self.Total = Total
self.Next = Next
else
self.Experience = 0
self.Percent = 0
self.Next = 158
self.Total = 0
self.Level = 0
end
return self
end
local function LessThanOrEqualTo(self: Exp, PointsOrExpStruct: number | ExpStruct)
return self.Total <= if type(PointsOrExpStruct) == "table" then PointsOrExpStruct.Total else PointsOrExpStruct
end
local function LessThan(self: Exp, PointsOrExpStruct: number | ExpStruct)
return self.Total < if type(PointsOrExpStruct) == "table" then PointsOrExpStruct.Total else PointsOrExpStruct
end
local function Equal(self: Exp, PointsOrExpStruct: number | ExpStruct)
return self.Total == if type(PointsOrExpStruct) == "table" then PointsOrExpStruct.Total else PointsOrExpStruct
end
local Exp_Prototype = {
__le = LessThanOrEqualTo,
__lt = LessThan,
__sub = Deduct,
__add = Award,
__eq = Equal,
}
Exp_Prototype.__index = Exp_Prototype
table.freeze(Exp_Prototype)
local function Create(PointsOrExpStruct: (number | ExpStruct)?): Exp
if type(PointsOrExpStruct) == "table" then
local Total = PointsOrExpStruct.Total or 0
local self = {
Percent = PointsOrExpStruct.Percent,
Level = PointsOrExpStruct.Level,
Next = PointsOrExpStruct.Next,
Experience = PointsOrExpStruct.Experience,
Total = Total,
}
if not (self.Percent or self.Level or self.Next or self.Experience) then
local Experience = LevelFromExperience(self, Total)
local Next = ToNextLevel(Exp)
self.Percent = self.Percent or Experience / Next
self.Next = Next
end
return setmetatable(self, Exp_Prototype) :: any
else
local Total = PointsOrExpStruct or 0
local self = {
Percent = nil,
Total = Total,
Level = nil,
Next = nil,
Experience = nil,
}
local Experience = LevelFromExperience(self, Total)
local Next = ToNextLevel(Exp)
self.Percent = Experience / Next
self.Next = Next
return setmetatable(self, Exp_Prototype) :: any
end
end
local function Player(Player: Player, ExpStructOrPoints: (number | ExpStruct)?)
local Binding = Bindings[Player.UserId]
if Binding then
return Binding
else
local Exp = Create(ExpStructOrPoints)
Bindings[Player.UserId] = Exp
return Exp
end
end
local Exports = table.freeze {
player = Player :: ((Player: Player, Points: number?) -> Exp) & ((Player: Player, ExpStruct: ExpStruct?) -> Exp),
create = Create :: ((Points: number?) -> Exp) & ((ExpStruct: ExpStruct?) -> Exp),
}
return Exports
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment