Created
February 5, 2014 11:59
-
-
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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