ID:2157285
 
(See the best response by Ter13.)
Code:
client
North()
if(usr.Frozen)
return 0
South()
if(usr.Frozen)
return 0
East()
if(usr.Frozen)
return 0
West()
if(usr.Frozen)
return 0
Northeast()
if(usr.Frozen)
return 0
Northwest()
if(usr.Frozen)
return 0
Southeast()
if(usr.Frozen)
return 0
Southwest()
if(usr.Frozen)
return 0


Will this work - and is there any better way to do it?

Next - this code simply looks inefficient, yet I can't put my finger on why and what I should change.

        verb
Fly()
if(!Frozen)
if(!Flight)
Flight = 1
icon_state = "Flight"
var/X = 8 + FlightMod
step_size = X
Flight()
else
Flight = 0
icon_state = ""
step_size = 6
Train()
if(!Training)
if(!Frozen)
if(Flight)
Flight = 0
icon_state = null
step_size = 6
icon_state = "Training"
Training = 1
Frozen = 1
Training()
else
Training = 0
if(!EFroze)
Frozen = 0





mob
proc
Stop()
Training = 0
Flight = 0
if(!EFroze)
Frozen = 0
icon_state = null




Flight()
set background = 1
while(Flight)
var/Drain = 100 / (FlightMod * 5)
Energy -= Drain
if(Energy <= 0)
Energy = 0
usr << output("You have ran out of energy.","chat")
Stop()
return
sleep(15)
Training()
set background = 1
while(Training)
var/Drain = 25 / EnergyMod
Energy -= Drain
if(Energy <= 0)
Energy = 0
usr << output("You have ran out of energy.")
Stop()
return
var/stat = rand(1,8)
switch(stat)
if(1)
if(Strength >= (StrengthMod * Cap))
Strength = (StrengthMod * Cap)
else
Strength += (StrengthMod * (Rate * (Cap / 100)))
if(2)
if(Speed >= (SpeedMod * Cap))
Speed = (SpeedMod * Cap)
else
Speed += (SpeedMod * (Rate * (Cap / 100)))
if(3)
if(Endurance >= (EnduranceMod * Cap))
Endurance = (EnduranceMod * Cap)
else
Endurance += (EnduranceMod * (Rate * (Cap / 100)))
if(4)
if(Resistance >= (ResistanceMod * Cap))
Resistance = (ResistanceMod * Cap)
else
Resistance += (ResistanceMod * (Rate * (Cap / 100)))
if(5)
if(Offense >= (OffenseMod * Cap))
Offense = (OffenseMod * Cap)
else
Offense += (OffenseMod * (Rate * (Cap / 100)))
if(6)
if(Defense >= (DefenseMod * Cap))
Defense = (DefenseMod * Cap)
else
Defense += (DefenseMod * (Rate * (Cap / 100)))
if(7)
if(Force >= (ForceMod * Cap))
Force = (ForceMod * Cap)
else
Force += (ForceMod * (Rate * (Cap / 100)))
if(8)
if(prob(35))
if(Energy >= (EnergyMod * ECap))
Energy = (EnergyMod * ECap)
else
Energy += (EnergyMod * (ERate * (ECap / 100)))

sleep(20)


Best response
A few finer points here.

I'd recommend migrating any code that belongs to the mob into the mob. Rather than overriding 8 different movement button calls, just hook into client/Move(), then delegate to a new mob proc called canMove(), which will only be called by client-fed movements.

client
Move(atom/loc,dir)
return mob.canMove(loc,dir)&&..()

mob
var
Frozen

canMove(atom/NewLoc,Dir=0)
return !Frozen


Also, the way you are managing states could do with a lot of refinement. This will get rid of a ton of boolean variables on the players and make creating ongoing action states much less of a headache.

#define STATE_INTERRUPT   1
#define STATE_MOVE 2
#define STATE_MOVE_CANCEL 4

mob
var
state
state_time = 0
state_flags = STATE_INTERRUPT|STATE_MOVE
proc
startState(nstate,nflags,forced=0)
if(state==nstate||!stopState(forced)||!canMove(src))
return
state = nstate
state_flags = nflags
state_time = world.time
var/ss = "state_[state]_begin"
if(hascall(src,ss))
call(src,ss)()
return 1

stopState(forced=0)
if(!forced&&!(state_flags&STATE_INTERRUPT))
return 0
var/ss = "state_[state]_end"
if(hascall(src,ss))
call(src,ss)()
state = null
state_flags = STATE_INTERRUPT|STATE_MOVE
state_time = world.time
return 1

canMove(atom/NewLoc,Dir=0)
return (state_flags&STATE_MOVE)

Move(atom/NewLoc,Dir=0,Step_x=0,Step_y=0)
var/oDir = dir
. = ..()
if(.||dir!=oDir)
if(state_flags&STATE_MOVE_CANCEL) stopState()


The above code is going to save you a lot of time in the future. This will implement the general pattern of being in an ongoing state that prevents certain actions. Currently, states can prevent movement, be canceled by movement, and prevent starting other states. States will interrupt one another as you can only be in one state at a time. Before the next state starts, the player will immediately stop their ongoing state.

Also, I really recommend some generics for working with your stats:

var
list/stat_names = list("Strength","Speed","Endurance","Resistance","Offense","Defense","Force","Energy")
mob
proc
TrainStat(stat,fac=100)
if(stat=="Energy")
var/added = EnergyMod * ERate * ECap / fac
Energy = min(Energy+added,EnergyMod*Ecap)
else
var/modval = vars["[stat]Mod"], statval = vars[stat], added = statval * Rate * Cap / fac
vars[stat] = min(statval+added,modval*Cap)

DrainStat(stat,amt)
var/oval = vars[stat]
var/loseval = max(min(oval,amt),0)
if(loseval)
vars[stat] -= loseval
return loseval



So let's see what your code would look like using these three overhauls.

mob
verb
Fly()
startState("flying",STATE_INTERRUPT|STATE_MOVE)

Train()
startState("training",STATE_INTERRUPT|STATE_MOVE|STATE_MOVE_INTERRUPT)
proc
Land()
if(state=="flying")
stopState()

StopTraining()
set name = "Stop Training"
if(state=="training")
stopState()

state_flying_begin()
set waitfor = 0
verbs -= /mob/verb/Fly
verbs += /mob/proc/Land
icon_state = "Flight"
step_size = 8*FlightMod
var/time = state_time, drain
while(state=="flying"&&state_time==time)
drain = 100 / (FlightMod * 5)
if(drain==DrainStat("Energy",drain))
src << "You have run out of energy.","chat")
stopState(1)
return
sleep(15)

state_flying_end()
step_size = 6
icon_state = ""
verbs -= /mob/proc/Land
verbs += /mob/verb/Fly

state_training_begin()
set waitfor = 0
verbs -= /mob/verb/Train
verbs += /mob/proc/StopTraining
icon_state = "Train"
var/time = state_time, drain, stat
while(state=="training"&&state_time==time)
drain = 25 / EnergyMod
if(drain==DrainState("Energy",drain))
src << "You have run out of energy.","chat")
stopState(1)
return
stat = pick(stat_names)
if(stat!="Energy"||prob(35))
TrainStat(stat)
sleep(20)

state_training_end()
icon_state = ""
verbs -= /mob/proc/StopTraining
verbs += /mob/verb/Train


It may seem like quite a lot more, but the general structures are all much more robust and will allow for a much less spaghettified codebase.
In response to Ter13
Thanks for this! But there is an issue, then I have some questions.

The issue first.

            verbs -= /mob/verb/Fly
verbs += /mob/proc/Land


Unless I redefine macros @ runtime, I don't know how I'd bind a single macro to both Flying and Landing - the same goes for training.

Next, the questions. I am still rather new to the BYOND language, and I have no clue how... most of this is working. More specifically, the following pieces of code:

            if(stat=="Energy")
var/added = EnergyMod * ERate * ECap / fac
Energy = min(Energy+added,EnergyMod*Ecap)
else
var/modval = vars["[stat]Mod"], statval = vars[stat], added = statval * Rate * Cap / fac
vars[stat] = min(statval+added,modval*Cap)

DrainStat(stat,amt)
var/oval = vars[stat]
var/loseval = max(min(oval,amt),0)
if(loseval)
vars[stat] -= loseval
return loseval


And what exactly stopState, startState & the last two lines of Move() do.

Also, unless I did something hideously wrong, your code also has a slight error. Move() is a client proc, so I had to change it to the following.

client
Move(atom/NewLoc, Dir = 0, Step_x = 0, Step_y = 0)
var/oDir = dir
. = ..()
if(. || dir != oDir)
if(usr.state_flags & STATE_MOVE_CANCEL)
usr.stopState()


Thanks for your time!

Edit

Not sure if this is another error on your end or something I'm not noticing, but STATE_MOVE_INTERRUPT is not defined - did you mean STATE_MOVE_CANCEL? As I had to change the startState for Train to:

startState("training", STATE_INTERRUPT | STATE_MOVE | STATE_MOVE_CANCEL)
In response to E52680
E52680 wrote:
Also, unless I did something hideously wrong, your code also has a slight error. Move() is a client proc, so I had to change it to the following.

 client
Move(atom/NewLoc, Dir = 0, Step_x = 0, Step_y = 0)
var/oDir = dir
. = ..()
if(. || dir != oDir)
if(usr.state_flags & STATE_MOVE_CANCEL)
usr.stopState()


I think you've gotten a bit confused. There are two separate Move() procs. client/Move() and mob/Move().

client/Move() only has two arguments, loc and dir.
mob/Move() has four args: loc, dir and step_x, step_y.
The directional procs (North, South, etc) called client.Move(); which by it's default definition then calls src.mob.Move().

Ter gave you two separate examples; one for your current issue (with client/Move() and mob/canMove()) and an other that is a better explanation of what you should (probably) be doing.
In response to Super Saiyan X
Oops - I was accidentally defining /mob/proc/Move() rather than /mob/Move().
Also, unless I did something hideously wrong, your code also has a slight error. Move() is a client proc, so I had to change it to the following.

Actually, client/Move() calls mob/Move(). If you look closer at my code, you will notice that the Move() proc I defined in the second batch of code was defined under /mob.

And what exactly stopState, startState & the last two lines of Move() do.

stopState() and startState() are generic functions for managing the current action of the player.

Your code created a rough state machine the player could be in. A state machine is basically a series of actions that can be undertaken one action at a time. The behavior of the object changes based on what the current state is.

Your implementation was a naive state machine, in that each distinct state was unaware of the others and it was making you create all kinds of unnecessary checks and whatnot.

I went ahead and migrated your specific patterns into a single generic pattern.

startstate():

    proc
startState(nstate,nflags,forced=0)
if(state==nstate||!stopState(forced)||!canMove(src)) //if we're not currently in the state we want to enter, can't exit the current state, or aren't allowed to move
return //don't enter the state
state = nstate //set the current state to the new state
state_flags = nflags //set the new state's behavior flags
state_time = world.time //set the new state's starting time.
var/ss = "state_[state]_begin"
if(hascall(src,ss)) //if the mob has a proc named: state_[state]_begin, call it.
call(src,ss)()
return 1 //return successful


So basically, startState asks the mob to enter a specific state. Entering a state calls state_STATENAME_begin() if it is defined. This will allow you to define the behavior for entering a state in state_STATENAME_begin(). If you are currently in a state, or otherwise can't enter the new state, it will fail, leaving you in the old state.

Stopstate is basically the opposite. It will ask you to return to the default state, ending the current state:

        stopState(forced=0)
if(!forced&&!(state_flags&STATE_INTERRUPT)) //if the current state can't be interrupted, don't succeed
return 0
var/ss = "state_[state]_end"
if(hascall(src,ss)) //call the current state's state_STATENAME_end proc if it is defined
call(src,ss)()
state = null //remove the current state
state_flags = STATE_INTERRUPT|STATE_MOVE //set back to default
state_time = world.time //set to the current time of the world
return 1 //return successful


As for the last two lines of move:

state_flags is what's called a bitflag. It can contain multiple on-off switches. You can test whether switches are on or off or assign multiple switches to on/off using what's called binary mathematics. Binary mathematics is a big topic, but what this example does:

mob
Move(atom/NewLoc,Dir=0,Step_x=0,Step_y=0)
var/oDir = dir
. = ..() //set the return value to the default value
if(.||dir!=oDir) //if the return value is true, or the facing direction has changed
if(state_flags&STATE_MOVE_CANCEL) //check if the STATE_MOVE_CANCEL flag is set for the current state
stopState() //end the current state, because we moved, and the current state is set to end the next time we move.


Basically, what those last few lines do is stop the current ongoing action whenever the player moves if that action is set to stop when the player moves.

mob
proc
TrainStat(stat,fac=100)
if(stat=="Energy")
var/added = EnergyMod * ERate * ECap / fac
Energy = min(Energy+added,EnergyMod*Ecap)
else
var/modval = vars["[stat]Mod"], statval = vars[stat], added = statval * Rate * Cap / fac
vars[stat] = min(statval+added,modval*Cap)

DrainStat(stat,amt)
var/oval = vars[stat]
var/loseval = max(min(oval,amt),0)
if(loseval)
vars[stat] -= loseval
return loseval


DrainStat() is removing between 0 and amt from the specified stat. The stat is being looked up from the vars list by name. This saves us having to write simple draining code over and over again for every stat. It also will never take more than the current stat has, and will never subtract a negative number. It returns the amount that was actually removed successfully.

TrainStat() does exactly the same thing as this big ol' piece of code:

                var/stat = rand(1,8)
switch(stat)
if(1)
if(Strength >= (StrengthMod * Cap))
Strength = (StrengthMod * Cap)
else
Strength += (StrengthMod * (Rate * (Cap / 100)))
if(2)
if(Speed >= (SpeedMod * Cap))
Speed = (SpeedMod * Cap)
else
Speed += (SpeedMod * (Rate * (Cap / 100)))
if(3)
if(Endurance >= (EnduranceMod * Cap))
Endurance = (EnduranceMod * Cap)
else
Endurance += (EnduranceMod * (Rate * (Cap / 100)))
if(4)
if(Resistance >= (ResistanceMod * Cap))
Resistance = (ResistanceMod * Cap)
else
Resistance += (ResistanceMod * (Rate * (Cap / 100)))
if(5)
if(Offense >= (OffenseMod * Cap))
Offense = (OffenseMod * Cap)
else
Offense += (OffenseMod * (Rate * (Cap / 100)))
if(6)
if(Defense >= (DefenseMod * Cap))
Defense = (DefenseMod * Cap)
else
Defense += (DefenseMod * (Rate * (Cap / 100)))
if(7)
if(Force >= (ForceMod * Cap))
Force = (ForceMod * Cap)
else
Force += (ForceMod * (Rate * (Cap / 100)))
if(8)
if(prob(35))
if(Energy >= (EnergyMod * ECap))
Energy = (EnergyMod * ECap)
else
Energy += (EnergyMod * (ERate * (ECap / 100)))


Except it looks up the variables by name to perform the operation. I also swapped out the factor (100) with a variable, so you can add more or less to a stat using the fac argument.

A few things to look up in the DM reference (press f1 in Dream Maker):

hascall()
call()()
vars list
In response to Ter13
Thank you so much! I've got big goals but little knowledge x-x.

One last questions. Why did I use #define for the 3 variables STATE_INTERRUPT, STATE_MOVE & STATE_MOVE_CANCEL?
STATE_INTERRUPT, STATE_MOVE, and STATE_MOVE_CANCEL aren't variables.

Variables store data.

They are preprocessor definitions. Anything that starts with # is a preprocessor command. When you compile your code a program called a compiler processes your code. Preprocessor commands get replaced with the value associated with them during the pre-processing phase. So any reference to STATE_INTERRUPT is handled as though you typed 1 in our case. We use them because names are far easier for humans to remember than numbers, and they are also used in binary math so they have to be powers of 2 between 20 and 215.

20 = 1
21 = 2
22 = 4
23 = 8
24 = 16
25 = 32
26 = 64
27 = 128

28 = 256
29 = 512
210 = 1024
211 = 2048
212 = 4096
213 = 8192
214 = 16384
215 = 32768

Binary numbers in BYOND can store 16 indivudal bitflags. I really strongly recommend you read a proper binary math tutorial. It's too much for me to write again. Here's a link to one of mine:

http://www.byond.com/forum/?post=2078491

When you run a BYOND game, there is a program called the interpreter that runs the code for the game that the compiler created. The interpreter doesn't actually know what's in variables ahead of time. It has to pull those values out of memory. However, the interpreter knows what CONSTANT values are ahead of time, so using them in your code is much faster than using variables for the same job. The only issue with constants? They can't be changed. If the values need to change over time, use a variable. If the values don't need to be changed, use a preprocessor macro or just a constant value in the expression.
In response to Ter13
I do have one last issue. How exactly will I handle macros? If I want to bind, say, E to Fly - how will I also bind it to "Land", or change the macro appropriately?
In response to E52680
One way to avoid having to rebind macros is to bind them to something that decides how to react to the key pressed, rather than always doing the same thing.

mob
// When the player wants to toggle flying,
verb/toggle_fly()

// This isn't necessary, but you can use this to hide the verb
// from any verb panels so it's only accessible by a macro
// whose command is set to "toggle-fly" (or if the player types
// it out and executes it, but it won't be auto-completed).
set hidden = TRUE

// If the player is currently flying, then Land.
if(IsFlying()) Land()

// Otherwise, Fly.
else Fly()

// Implement this proc to return TRUE if you're currently flying
// (and FALSE if you're not flying)
proc/IsFlying()

// For example, using Ter's state example:
return state == "flying"