Skip to content

Instantly share code, notes, and snippets.

@balaam
Created February 5, 2014 11:59
Show Gist options
  • Select an option

  • Save balaam/8822221 to your computer and use it in GitHub Desktop.

Select an option

Save balaam/8822221 to your computer and use it in GitHub Desktop.
Example code for a levelling up system in a JRPG. As explained on my blog http://www.godpatterns.com and upcoming book: www.howtomakeanrpg.com
function nextLevel(level)
local exponent = 1.5
local baseXP = 1000
return math.floor(baseXP * (level ^ exponent))
end
Stats = {}
Stats.__index = Stats
function Stats:Create(stats)
local this =
{
mBase = {},
mModifiers = {}
}
-- Shallow copy
for k, v in pairs(stats) do
this.mBase[k] = v
end
setmetatable(this, self)
return this
end
function Stats:GetBase(id)
return self.mBase[id]
end
--
-- id = used to uniquely identify the modifier
-- modifier =
-- {
-- add = { table of stat increments }
-- mult = { table of stat multipliers }
-- }
function Stats:AddModifier(id, modifier)
self.mModifiers[id] =
{
add = modifier.add or {},
mult = modifier.mult or {}
}
end
function Stats:RemoveModifier(id)
self.mModifiers[id] = nil
end
function Stats:Get(id)
local total = self.mBase[id] or 0
local multiplier = 0
for k, v in pairs(self.mModifiers) do
total = total + (v.add[id] or 0)
multiplier = multiplier + (v.mult[id] or 0)
end
--print(string.format("Multiplier: %f", multiplier))
return total + (total * multiplier)
end
Roll = {}
Roll.__index = Roll
function Roll:Create(diceStr)
local this =
{
dice = {}
}
setmetatable(this, self)
this:Parse(diceStr)
return this
end
function Roll:Parse(diceStr)
local len = string.len(diceStr)
local index = 1
local allDice = {}
while index <= len do
local die
die, index = self:ParseDie(diceStr, index)
table.insert(self.dice, die)
index = index + 1 -- eat ' '
end
end
function Roll:ParseDie(diceStr, i)
local rolls
rolls, i = self:ParseNumber(diceStr, i)
i = i + 1 -- Move past the 'D'
local sides
sides, i = self:ParseNumber(diceStr, i)
if i == string.len(diceStr) or
string.sub(diceStr, i, i) == ' ' then
return { rolls, sides, 0 }, i
end
if string.sub(diceStr, i, i) == '+' then
i = i + 1 -- move past the '+'
local plus
plus, i = self:ParseNumber(diceStr, i)
return { rolls, sides, plus }, i
end
end
function Roll:ParseNumber(str, index)
local isNum =
{
['0'] = true,
['1'] = true,
['2'] = true,
['3'] = true,
['4'] = true,
['5'] = true,
['6'] = true,
['7'] = true,
['8'] = true,
['9'] = true
}
local len = string.len(str)
local subStr = {}
for i = index, len do
local char = string.sub(str, i, i)
if not isNum[char] then
return tonumber(table.concat(subStr)), i
end
table.insert(subStr, char)
end
return tonumber(table.concat(subStr)), len
end
-- Notice this uses a '.' not a ':' meaning the function can be called
-- without having a class
function Roll.Die(rolls, faces, modifier)
local total = 0
for i = 1, rolls do
total = total + math.random(1, faces)
end
return total + (modifier or 0)
end
function Roll:Roll()
local total = 0
for _, die in ipairs(self.dice) do
total = total + Roll.Die(unpack(die))
end
return total
end
local Growth =
{
fast = Roll:Create("3d2"), -- 3d2
med = Roll:Create("1d3"), -- 1d3
slow = Roll:Create("1d2") -- 1d2
}
heroDef =
{
stats =
{
["hp_now"] = 300,
["hp_max"] = 300,
["mp_now"] = 300,
["mp_max"] = 300,
["str"] = 10,
["spd"] = 10,
["int"] = 10,
},
statGrowth =
{
["hp_max"] = Roll:Create("4d50+100"),
["mp_max"] = Roll:Create("2d50+10"),
["str"] = Growth.fast,
["spd"] = Growth.fast,
["int"] = Growth.med,
},
}
Character = {}
Character.__index = Character
-- In this case we're only interested in the character def but
-- in the final game, the character will take more parameters.
function Character:Create(def)
local this =
{
def = def,
stats = Stats:Create(def.stats),
statGrowth = def.statGrowth,
xp = 0,
level = 1,
}
this.nextLevelXP = nextLevel(this.level)
setmetatable(this, self)
return this
end
function Character:ReadyToLevelUp()
return self.xp >= self.nextLevelXP
end
function Character:AddXP(xp)
self.xp = self.xp + xp
return self:ReadyToLevelUp()
end
function Character:ApplyLevel(levelup)
self.xp = self.xp + levelup.xp
self.level = self.level + levelup.level
self.nextLevelXP = nextLevel(self.level)
assert(self.xp >= 0)
for k, v in pairs(levelup.stats) do
self.stats.mBase[k] = self.stats.mBase[k] + v
end
-- Unlock any special abilities etc.
end
function Character:CreateLevelUp()
local levelup =
{
xp = -self.nextLevelXP,
level = 1,
stats = {},
}
for id, dice in pairs(self.statGrowth) do
levelup.stats[id] = dice:Roll()
end
-- Additional level up code
-- e.g. if you want to apply
-- a bonus every 4 levels
-- or heal the players MP/HP
return levelup
end
function PrintLevelUp(levelup)
local stats = levelup.stats
print(string.format("HP:+%d MP:+%d",
stats["hp_max"],
stats["mp_max"]))
print(string.format("str:+%d spd:+%d int:+%d",
stats["str"],
stats["spd"],
stats["int"]))
print("")
end
function ApplyXP(char, xp)
char:AddXP(xp)
while(char:ReadyToLevelUp()) do
local levelup = char:CreateLevelUp()
local levelNumber = char.level + levelup.level
print(string.format("Level Up! (Level %d)", levelNumber))
PrintLevelUp(levelup)
char:ApplyLevel(levelup)
end
end
hero = Character:Create(heroDef)
ApplyXP(hero, 10000)
--
-- Print out the final stats
--
print("==XP applied==")
print("Level:", hero.level)
print("XP:", hero.xp)
print("Next Level XP:", hero.nextLevelXP)
local stats = hero.stats
print(string.format("HP:%d MP:%d",
stats:Get("hp_max"),
stats:Get("mp_max")))
print(string.format("str:%d spd:%d int:%d",
stats:Get("str"),
stats:Get("spd"),
stats:Get("int")))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment