ID:2183843
 
I have always been a fan of rpg games that use skill gain through use for progression above a level based system. Over the years I have developing a way around that in my games from VB,C++,Unity and now in BYOND I have been applying the same system in my games and I would like to share my ideas with everyone.


I start with 50% then I add the user's skill and subtract the challenge rating to come out with the final goal, but I found that even capping the chance to 5% minimum and 95% maximum didn't provide the graduation into likelihood that I was craving.

The concept of this skill use function is to allow a user to constantly progress in levels but also have a chance at long shots as well as failure at something he is trained in. In other words keeping the challenge within reach for longer then a normal experience. It also increases the reward of progression the more well matched the challenge encouraging the user to seek out challenges his or her level of skill.

Thanks to Lummox, Kaiochao, Flame Guardian and everyone else who joined in my many questions for all for helping me understand what I know so far.

skill
var
label
learnrate
rank
exp


mob/proc/SkillUse(skill/Skill, TargetSkill,TargetLabel=null)

//constructing skill or challenge
var/tmp/skill/TSkill
if(istype(TargetSkill,/skill))
TSkill = TargetSkill
else
TSkill.rank = TargetSkill
TSkill.label = TargetLabel

// Initialize Skill Check
var/tmp/Roll = rand(1,100)
var/tmp/Diff = 50 + Skill.rank - TSkill.rank

//Difficulty Throttling - Used to keep the worlds difficulties more capable with all ranges.
var/tmp/Trade
var/tmp/OrigDiff = Diff

//Underkill Throttling - For keeping things from getting Weak too Fast!
if(Diff < -75) {Diff = 1;goto DiffChanged}
if(Diff >= -75 && Diff < -45) {Trade = abs(Diff) - 45;Trade = round(Trade / 6); Diff = 5 - Trade;goto DiffChanged}
if(Diff >= -45 && Diff < -20) {Trade = abs(Diff) - 20;Trade = round(Trade / 5); Diff = 10 - Trade;goto DiffChanged}
if(Diff >= -20 && Diff < 0) {Trade = abs(Diff);Trade = round(Trade / 4); Diff = 15 - Trade;goto DiffChanged}
if(Diff >= 0 && Diff < 15) {Trade = Diff;Trade = round(Trade / 3); Diff = 20 - Trade;goto DiffChanged}
if(Diff >= 15 && Diff < 25) {Trade = Diff - 15;Trade = round(Trade / 2); Diff = 25 - Trade;goto DiffChanged}

//OverKill Throttling - For keeping things from getting Strong too Fast!
if(Diff > 75 && Diff <= 85) {Trade = Diff - 75;Trade = round(Trade / 2); Diff = 75 + Trade;goto DiffChanged}
if(Diff > 85 && Diff <= 100){Trade = Diff - 85;Trade = round(Trade / 3); Diff = 80 + Trade;goto DiffChanged}
if(Diff > 100 && Diff <= 120){Trade = Diff - 100;Trade = round(Trade / 4); Diff = 85 + Trade;goto DiffChanged}
if(Diff > 120 && Diff <= 145){Trade = Diff - 120;Trade = round(Trade / 5); Diff = 90 + Trade;goto DiffChanged}
if(Diff > 145 && Diff <= 175){Trade = Diff - 145;Trade = round(Trade / 6); Diff = 95 + Trade;goto DiffChanged}
if(Diff > 175) {Diff = 99;goto DiffChanged}

// Difficulty Change due to Throttle reporting
DiffChanged:
usr << "[OrigDiff]% Throttled to [Diff]% Difficulty."

// Roll for Skill Check
var/tmp/Result
if(Roll <= Diff)
Result = abs(Roll - Diff)
if(Roll > Diff)
var/tmp/Fail = Roll - Diff
Result = (Fail - Fail) - Fail

usr << "[usr.name]'s [Skill.label] vs [TSkill.label]<br>50% +[Skill.rank] vs [TSkill.rank] = [Diff]% 1d100([Roll]) *[Result]*"

// Skill advancement
var/tmp/XP
if(Diff < 30) XP = 1
if(Diff >= 30 && Diff < 35) XP = 2
if(Diff >= 35 && Diff < 40) XP = 3
if(Diff >= 40 && Diff < 45) XP = 4
if(Diff >= 45 && Diff < 55) XP = 5
if(Diff >= 55 && Diff < 60) XP = 4
if(Diff >= 60 && Diff < 65) XP = 3
if(Diff >= 65 && Diff < 70) XP = 2
if(Diff >= 70 && Diff < 100) XP = 1

//XP Report
if(XP >= 3)
var/image/I = image('cloud.dmi',usr)
I.icon_state = "exp"
I.pixel_y = 32
I.pixel_x = -8
usr << I
animate(I, pixel_y = 64, time = 30)
spawn(20) animate(I, alpha = 0, time = 10)
spawn(30) del I

//Success/Failure Adjustment for XP
if(Result < 0) XP = XP * 2

//Award XP to player skill
Skill.exp += XP

//Skill Leveling & Reporting
var/tmp/GoalLvl = Skill.rank + 1
var/tmp/NxtLvl = round((GoalLvl * 10) * Skill.learnrate)// <-------Testing OUT 10 instead
var/tmp/ToLvl = NxtLvl - Skill.exp
if(Skill.exp >= NxtLvl)
Skill.rank++
Skill.exp = 0
usr << "You gained a New Rank of [Skill.rank] in [Skill.label]"
else
usr << "[XP] Exp - [Skill.label] Next Level in [ToLvl] Exp"


return Result
Yes EVERYTHING is tmp I am just addicted to it and I can't stop myself.
There's no need to mark proc/verb variables as tmp. It only really applies to datum variables.

Not that it's hurting anything.
I have been trying to tell myself that, but once upon a time when I was originally learning about save files I read this example:

client/New()
if(usr) return ..() //reconnecting to existing mob
else
var/player_sav = "players/[ckey].sav"
if(length(file(player_sav))) //if player savefile exists
var/savefile/F = new(player_sav) //open it
F >> usr //create saved mob
return ..() //creates a new mob if necessary
mob/Logout()
var/player_sav = "players/[ckey].sav"
var/savefile/F = new(player_sav)
F << src
del src


and below it said:

"Note that for the above example to work, you must make proper use of the tmp flag when defining new object variables. Otherwise, this can end up sucking large portions of your world into each player savefile, which can have all sorts of unexpected consequences!"

ever since then I have been scared of the unexpected consequences LOL
In response to Zamargo
"Note that for the above example to work, you must make proper use of the tmp flag when defining new object variables. Otherwise, this can end up sucking large portions of your world into each player savefile, which can have all sorts of unexpected consequences!"

Variables local to procs aren't object variables, and it doesn't make sense for them to be saved or loaded since they only exist inside each proc call.

The compiler could be changed to give an error for local tmp vars and zero games would be broken.
Among a few other things, you should also convert to switch:
    if(Diff < 30) XP = 1
if(Diff >= 30 && Diff < 35) XP = 2
if(Diff >= 35 && Diff < 40) XP = 3
if(Diff >= 40 && Diff < 45) XP = 4
if(Diff >= 45 && Diff < 55) XP = 5
if(Diff >= 55 && Diff < 60) XP = 4
if(Diff >= 60 && Diff < 65) XP = 3
if(Diff >= 65 && Diff < 70) XP = 2
if(Diff >= 70 && Diff < 100) XP = 1


switch(Diff)
if(31 to 34) XP = 2
if(35 to 39) XP = 3
if(40 to 44) XP = 4
if(45 to 54) XP = 5
if(55 to 59) XP = 4
if(60 to 64) XP = 3
if(65 to 69) XP = 2
else XP = 1
Ohh okay Kaiochao I had the misunderstanding that whatever was src was getting the variable as if I had declared it in startup. So I thought it mob was the src then it was going to suck in that variable into my savefile later when saving said mob if it wasn't tmp.

Thanks ForwardslashN I recently discovered switch, but i didn't realize "to" could be used like that! That will save me a lot of double references in my conditional statements.
#define ceil(x) (-round(-(x)))
#define clamp(v,l,h) min(max(v,l),h)


XP = ceil( 5 - abs(clamp(Diff,30,70) / 5 - 10) )


Good place to ask, but which is faster? switch() or algebra?
Good place to ask, but which is faster? switch() or algebra?

The switch() is faster in this case by a factor of about 20%.

Though, the difference we're talking here is absolutely miniscule. In order for there to be a single second's difference between the two algorithms, they would have to run over 11 million times on a mid-tier CPU.

Fractions of microseconds aren't worth worrying about when it's something that's going to be triggered less often than every second.
In response to Ter13
Good to know, thanks Ter. I do prefer the algebra, but I wanted to be sure when to use both.