ID:2023020
 
(See the best response by Ter13.)
Code:
//Login for players
mob
Login()
src << "Hello, [src]."
switch(input(src,"Pick your class") in list("Student","Squire")) //Players pick their class
if("Student")
src.class = "Student" //Set class to Student
//src = new/mob/Magic/Student() //mob casting
src << "You are now a <b>Student</b>!"
else
src.class = "Squire" //Set class to Squire
//src = new/mob/Melee/Squire() //mob casting
src << "You are now a <b>Squire</b>!"
..()
//Player variables and verbs
mob
var
class as text
list/unlockedclasses = list("Student","Squire")
list/uclasslevels = list(1,1)
Stat/CLevel/clvl = new()
Stat/CExperience/Cexp = new()
verb
GiveCExp()
set name= "Give Class EXP"
set category = "Give Stats"
var/e = rand(20,50)
src.Cexp.increaseCExp(src,e) //Call increaseCExp proc for CExperience datum

ChangeClass()
set name = "Change Class"
set category = "Class"
switch(input(src,"Are you sure you want to change your class?")in list("Yes","No"))
if("Yes")
src.class = input("What class do you want to change to?")in list(unlockedclasses)

UnlockedClasses()
set name = "Classes Unlocked"
set category = "Class"
src << "<center><b><u>Classes Unlocked:"
var/j
for(j = 1, j <= src.unlockedclasses.len,j++)
src << "<center><b> - [src.unlockedclasses[j]] Lvl. [src.uclasslevels[j]]<BR>"

Stat/CLevel
name = "Class Level"
abbr = "C. Lvl" //Goes into the statpanel
min = 1
var/evolveat as num //5 for Tier 1 classes, 10 for Tier 2+
max = 5 //Changes depending on the class
New(evnum)
current = 1
evolveat = evnum //Class level that unlocks the next class
proc
increase(mob/P,num)
current += num
if(current >= max)
current = max
P << "<center>CLASS Level: <b>MAX"
if(current == evolveat)
P<< "<center><b>You can now advance to the next class!"
var/tmp/index= P.unlockedclasses.Find("[P.class]",1,0) //Find class name in list
P.uclasslevels[index] = current //Update the level in the parallel list

changeMax(newmax)
max = newmax

Stat/CExperience
name = "Class Experience"
abbr = "C. EXP" //Goes into the statpanel
var/needed = 100
New()
current = 0
proc
increaseCExp(mob/P,num)
if(P.clvl.current == P.clvl.max)
else
P << "<b>Gained [num] Class Exp"
current += num
while(current >= needed)
P.clvl.increase(P,1)
P << "<center><b>CLASS LEVEL UP!</b> You are now a [P.class] level <b>[P.clvl.current]</b>!<BR>-----------------<BR>"
newNeeded(P.clvl.current)

newNeeded(clvl)
needed += (clvl*10) + 50

//Below is the Stat datum code
Stat
var
name
abbr as text
min = 0
current = 0
max as num
mob/Human/owner
bonus = 0 //Increased or decreased when you equip an item


/* Classes for mob casting and istype calls
mob
var
list/nextclass = list()
Melee
Squire
cname = "Squire"
Magic
cname = "Student"
*/


Problem description: I'm trying to make a class system resembling the type made famous by the Final Fantasy Tactics, and Fire Emblem, series. For those who're unfamiliar with either game, let me elaborate. Each player has a class, like Warrior, Magician, or Rogue. Each player has a class level for their class, which is used to gain class exp whenever they use their class' skills in battle. At certain class levels, the player learns a class skill, and/or can unlock a more powerful class for them to change/advance to later. The player can also change freely between classes and continue their progress. The way I have my classes currently, each player has a class level variable and class variable. When they change classes, the class' name and the level is added to lists. I don't think this way is efficient in the long run. I also tried creating mob datums for each class, but keeping track of class levels and advancements that way seems impossible to me. So in short, What would be the most efficient way to code a class system like this?

Let's talk a bit about list sorting.

By far one of the most time consuming things about handling lists of things that you need to keep track of is iterating through the whole list when only part of it needs to be managed.

For instance, let's say we have a list of skill names that are stored associated to a level requirement.

var/list/skills = list("fire"=1,"ice"=2,"lit"=3,"fire II"=9,"ice II"=10,"lit II"=11,"fire III"=14,"ice III"=15,"lit III"=16)


Now, in order to check which skills we should learn at level 9, we really only need to know which skills have a value of 9.

for(var/v in skills)
if(skills[v]==9)
//add the skill


Unfortunately, you have to iterate through the whole list every single time you want to level up. This wastes a ton of time.

Sorting algorithms are your friend. If you can guarantee that a list is in the correct order sequentially, you can actually check much smaller portions of the list in order to find only the portion that's relevant to your specific case.

Let's take a look at a really easy to implement sorting algorithm.

1) Look at the first and last items in the list. If seek is less than item 1, or greater than item size, stop the search. The value isn't in the list.

2) Halve the size of the list (rounded down) and store as value hsize.

3) Begin search at pivot point hsize+1 store as pos.

4) halve the size of hsize

5a) if seek is <= the value at position pos, subtract hsize from pos.

5b) else, add hsize from pos.

6a) if hsize>1 go to step 4

6b) else, return pos. The first incident of seek is at pos.



^This is a single example of this searching algorithm in action.

A very simplistic implementation:

proc/sortedlist_find(list/list,value)
var/pos = 1, size = list.len
if(list[list[pos]]>value||list[list[size]]<value) return 0 //step 1
var/hsize = round(size/2) //step 2
pos += hsize //step 3
do
hsize /= 2 //step 4
if(list[list[pos]]>=value)
pos -= hsize //step 5a
else
pos += hsize //step 5b
while(hsize>1) //step 6a
return pos //step 6b


Why do I recommend this approach? Well, it's much easier to build a list of what skills a player has when they swap classes than to keep track of it all. Our sorted list thing here will actually make our work way easier. Continued in the next post.
Best response
Now, let's talk a bit about classes.

Classes are nothing more than a grouping of data. You don't NEED datums to manage classes, but it sure makes creating new ones insanely convenient.

#define UNRELEASED 0
#define RELEASED 1

var/job_manager/job_manager = new()

job_manager
var
list/jobclasses
list/level_requirements
New()
jobclasses = list()
level_requirements = list()
var/jobclass/j
for(var/v in typesof(/jobclass))
j = v
if(initial(j.status)==RELEASED)
new v()

proc
//call when setting up a player that can use job classes
PlayerLoad(mob/player/p)
var/jobclass/j
for(var/v in p.known_jobs)
j = jobclasses[v]
j.JobCheck(p,p.job_levels[v])
var/list/l = jobclasses-p.known_jobs
for(var/v in l)
j = jobclasses[v]
if(j.isUnlocked(p))
p.known_jobs += v
if(!p.job_levels[v]) p.job_levels[v] = 1
j.JobCheck(p,p.job_levels[v])

jobclass
var
name
list/skills //a list of skills this class unlocks at specific levels. format = "skill name"=level. MUST BE IN ORDER low to high
list/job_unlocks //a list of class names that this class unlocks points for at specific levels format = "class name"=level MUST BE IN ORDER low to high
level_requirement //what character level this job is unlocked at
point_requirement //how many points this class needs to be used (If two jobs are required to unlock this one, set their job_unlocks and set point requirement to 2
status = UNRELEASED //this will come in handy later
proc
//called when the player levels up
LevelUp(mob/player/p)
var/pos = sortedlist_find(skills,p.joblevel), count
if(pos==p.joblevel)
var/skill
for(count in pos to skills.len)
skill = skills[count]
if(skills[skill]>p.joblevel) break
p.LearnSkill(name,skill)
pos = sortedlist_find(job_unlocks,p.joblevel)
if(pos==p.joblevel)
var/class
for(count in pos to job_unlocks.len)
class = job_unlocks[pos]
if(job_unlocks[class]>p.joblevel) break
p.UnlockJob(class)

//called when the player connects to the world. populate the skills list and set up the job points according to job level
JobCheck(mob/player/p,joblevel)
var/pos = sortedlist_find(skills,joblevel+1), count
if(pos)
var/skill
for(count in 1 to pos)
skill = skills[count]
if(skills[skill]>joblevel) break
p.LearnSkill(name,skill)
pos = sortedlist_find(job_unlocks,joblevel+1)
if(pos)
for(count in 1 to pos)
class = job_unlocks[pos]
if(job_unlocks[class]>joblevel) break
p.UnlockJob(class)

//called to see if the player meets the unlock requirements for this job
isUnlocked(mob/player/p)
return p.level>level_requirement&&p.job_points[name]>=point_requirement

//called when the player switches to this job
setJob(mob/player/p)

//called when the player switches out of this job
unsetJob(mob/player/p)

New()
job_manager.jobclasses[name] = src
job_manager.level_requirements[name] = level_requirement

mob
player
var
tmp
list/known_jobs = list() //populate with strings for starting jobs
list/job_skills
list/skills
list/job_points
job_level = 1
job //set to the default starting job
level = 1
joblevel = 1
list/job_levels = list() //populate with "job name"=job level pairs for starting job levels
proc
//called when a job point is unlocked
UnlockJob(job)
if(!job_points) job_points = list()
++job_points[job]
var/jobclass/j = job_manager.jobclasses[job]
if(known_jobs[job]==null&&j.isUnlocked(src))
known_jobs += job
if(!job_levels[job]) job_levels[job] = 1
j.JobCheck(src,job_levels[job])

//called when a job skill is unlocked
LearnSkill(name,skill)
var/list/l = job_skills[name]
if(!l)
l = list()
job_skills[name] = l
l += skill

//called when the player's level should increase
LevelUp()
level++
var/list/l = job_manager.jobclasses-known_jobs
var/jobclass/j
for(var/v in l)
if(known_jobs[v]==null&&j.isUnlocked(src))
known_jobs += v
j = l[v]
if(!job_levels[v]) job_levels[v] = 1
j.JobCheck(src,job_levels[v])

//called when the player's job level should increase
JobLevelUp()
joblevel++
job_levels[job]++
var/jobclass/j = job_manager.jobclasses[job]
j.LevelUp(src)

//called to change the player's job
changeJob(job)
if(src.job)
var/jobclass/oldj = job_manager.jobclasses[src.job]
oldj.unsetJob(src)
if(!known_skills[job]) known_skills[job] = list()
joblevel = job_levels[job]
skills = known_skills[job]
var/jobclass/newj = job_manager.jobclasses[job]
newj.setJob(src)

//called when the player is created
New()
job_manager.PlayerLoad(src)
var/jobclass/j = job_manager.jobclasses[job]
j.setJob(src)


The above code implements something of a fairly flexible and adaptable job class handler. It saves a bare minimum of data: Only the job levels are saved. There's no reason to save the unlocked skills or unlocked classes because we can generate that data again on login/creation rather than saving it. This also allows you to remove skills from classes and change class requirements.

If you have any questions/issues, this is the place.
Wow thanks a lot for breaking this down for me. I can't believe I didnt think about using sorting. Your second post really helps to point me in the right direction. Thanks.