ID:2160371
 
If my movement speed returns as 1 my character is forced to take two steps instead of one for the first step.
mob
proc
MoveLoop()
set waitfor = 0
if(move_loop!=null) return
move_loop = world.time
var/diag, steps = super_speed ? 1 : 0
while(move_dir)
if(!speed_run)
diag = move_dir&move_dir-1
// icon_state = run ? "Run" : null //best to do this elsewhere.
if(!step(src,move_dir)&&diag)
step(src,diag)||step(src,move_dir-diag)
if(steps++)
sleep(1+GetMovementSpeed())
else
sleep(GetMovementSpeed())
move_loop = null
icon_state = null
verb
MoveKey(dir as num,state as num) //replace all of your MoveNorth, etc verbs with this single verb. state is 0 or 1 for release or press. dir is 1 for NORTH, 2 for SOUTH, 4 for EAST, 8 for WEST.
set hidden = 1
set instant = 1
var/opp = turn(dir,180), okey = move_keys
if(state)
move_keys |= dir
move_dir |= dir
if(move_dir&opp)
move_dir &= ~opp
else
move_keys &= ~dir
move_dir &= ~dir
if(move_keys&opp)
move_dir |= opp
if(!okey&&move_keys)
spawn(0) //this is to allow pending verbs to fire before the move loop executes
MoveLoop()


Also even with no movedelay and faster movement speed this makes my character run pretty slow.
You could check out this and see if it helps.
http://www.byond.com/forum/?post=1978909
       MoveKey(dir as num,state as num) //replace all of your MoveNorth, etc verbs with this single verb. state is 0 or 1 for release or press. dir is 1 for NORTH, 2 for SOUTH, 4 for EAST, 8 for WEST.
set hidden = 1
set instant = 1
var/opp = turn(dir,180), okey = move_keys
if(state)
move_keys |= dir
move_dir |= dir
if(move_dir&opp)
move_dir &= ~opp
else
move_keys &= ~dir
move_dir &= ~dir
if(move_keys&opp)
move_dir |= opp
if(!okey&&move_keys)
spawn(0) //this is to allow pending verbs to fire before the move loop executes
MoveLoop() //this line was tabbed wrong
Since we all seem to be on the same page here with move loops, what is the best way to (least) modify the standard move loop that's used around here such as in Ter13's post to only allow cardinal directions (or lock it behind a var)?

I've tried a few things but run into the problem that when people hold multiple keys, and then release the one that they are moving in, the other keys don't kick in.
In response to Clusterfack
Clusterfack wrote:
I've tried a few things but run into the problem that when people hold multiple keys, and then release the one that they are moving in, the other keys don't kick in.

Ran into this last night and before, it's annoying.
Since we all seem to be on the same page here with move loops, what is the best way to (least) modify the standard move loop that's used around here such as in Ter13's post to only allow cardinal directions (or lock it behind a var)?

That really depends, and it's actually not a simple solution using my existing structure, which assumes 8 directions are valid.

There are several ways to do it that are common, and a lot of games follow one of a few tropes:

Always prefer a sequence of keys, as in: NORTH is always preferred over EAST is always preferred over SOUTH is always preferred over WEST:

if(move_keys[NORTH])
move_dir = NORTH
else if(move_keys[EAST])
move_dir = EAST
else if(move_keys[SOUTH])
move_dir = SOUTH
else if(move_keys[WEST])
move_dir = WEST


This works well on controllers, but not on keyboards because controller DPads generally only let a player comfortably hold the hat toward two sensors at a time.


Another method is to always prefer the most recent key pressed:

var/md, mt = 0
for(var/count in 1 to 4)
if(key_states[count]&&key_times[count]>mt)
mt = key_times[count]
md = count
move_dir = 2**(md-1)


This piece of code should probably only be called when the key that is the current movement direction is released.


move_dir will always be one of 9 possible values:

0: nothing sensible is held
1: NORTH is held
2: SOUTH is held
4: EAST is held
8: WEST is held
5: NORTH+EAST is held
6: SOUTH+EAST is held
9: NORTH+WEST is held
10: SOUTH+WEST is held


Let's look at some math:

move_dir&move_dir-1

1: 1&0 = 0
2: 2&1 = 0
4: 4&3 = 0
8: 8&7 = 0
5: 5&4 = 4
6: 6&5 = 4
9: 9&8 = 8
10: 10&9 = 8


So, we can always prefer east or west:

var/diag = move_dir&move_dir-1
if(diag) move_dir &= diag



And we can always prefer north or south:

move_dir^(move_dir&move_dir-1)

1: 1^0 = 1
2: 2^0 = 2
4: 4^0 = 4
8: 8^0 = 8
5: 5^4 = 1
6: 6^4 = 2
9: 9^8 = 1
10: 10^8 = 2


Once you know the formula you want to use, choosing where to implement it depends on the algorithm.
The thing is, key+UP in the macros isn't called sometimes, just weird that no one has reported it yet :P
I might be wrong but isn't that what including the keystate library helps avoid the issue of?
This is a simplified version of what I currently use for all of my projects' input controllers. (I have some other code that I've stripped out that does some stuff that's not necessary for this discussion)

It allows:

  • Delegation from the client to mob input scheme, allowing different mobs to handle input differently while using unified code.
  • Binding multiple keys to the same bind.
  • Differentiating between a single, double, triple, or beyond keytap
  • Tracking key holding durations
  • Rectifying incorrect input states to minimize junk data getting stuck in the lists due to focus traversal
  • Inputs can happen at any time and you can calculate the time between keypress/release without regard to tick granularity


Macro setup:


#define ACCURATE_TIME (world.tick_usage/100*world.tick_lag + world.time)

var/list/__dirs = list(NORTH,SOUTH,EAST,WEST)
var/list/__invdirs = list(SOUTH,NORTH,WEST,EAST)
var/list/__oppdirs = list(SOUTH,NORTH,NORTH|SOUTH,WEST,SOUTHWEST,NORTHWEST,NORTHWEST|SOUTH,EAST,SOUTHEAST,NORTHEAST,NORTHEAST|SOUTH,EAST|WEST,SOUTHEAST|WEST,NORTHEAST|WEST,NORTHEAST|SOUTHWEST)

//these are for conveniently getting the direction of a movement from bind numbers
#define bind2dir(b) __dirs[b]
#define bind2opp(b) __invdirs[b]
#define dir2opp(d) __oppdirs[d]

client
var
#define KEYBINDS 4 //this define needs to be the same number as the number of keybinds allowed by the system.
list/key_binds = list("W"=1,"S"=2,"D"=3,"A"=4) //The name of the key is associated with a numeric bind. movement binds need to be 1,2,3,4.
list/key_state[KEYBINDS] //stores the current press state of a key. 0 is not pressed 1 or above is pressed. Binds can have more than one key associated, so this may be greater than one.
#define TAP_THRESHOLD 2.5 //how long should a key-release allow the same key-press to trigger a multitap.
list/key_taps[KEYBINDS] //stores the current number of taps in sequence on a particular key. 1 is default. Releasing and re-pressing the same key within TAP_THRESHOLD causes this counter to go up.
list/key_time[KEYBINDS] //stores the time of the last time the key was pressed (if being held) or released (if not being held)
last_key = 0 //stores the last key that was pressed. Used for tracking multi-tap inputs.

verb
//called when a key is pressed
keyPress(key as text)
set instant = 1
set hidden = 1
var/bind = key_binds[key] //get the bind associated with the key
if(bind)
var/olk = last_key //store the last key pressed/released
last_key = bind //replace the last key value with the current press
if(++key_state[bind]==1) //if this bind wasn't already being held down
//calculate the bind metadata
var/otime = key_time[bind] //get the time of the last release of this key
var/ntime = ACCURATE_TIME //get in-frame time rather than the time of the beginning of the frame
var/tdelta = ntime-otime //calculate the time between the last release and this press
var/taps = key_taps[bind] //get the number of taps this key sequence is currently registering
if(olk==bind&&tdelta<=TAP_THRESHOLD) //check if this is a multitap sequence.
taps++
else //reset the tap sequence if not a multitap
taps = 1
key_time[bind] = ntime //store the time and tap metadata
key_taps[bind] = taps
mob.onKeyPress(bind,taps,ntime,tdelta) //tell the mob that a key has been pressed and pass all the metadata

//called when a key is released
keyRelease(key as text)
set instant = 1
set hidden = 1
var/bind = key_binds[key] //get the bind association
if(bind)
var/ks = --key_state[bind] //store the new keystate
var/olk = last_key //store the last key pressed/released
last_key = bind //replace the last key value with the current bind
if(!ks) //if this bind is no longer being pressed, but was before
var/otime = key_time[bind] //calculate the metadata for the keypress
var/ntime = ACCURATE_TIME
var/tdelta = ntime-otime
var/taps = key_taps[bind]
if(olk!=bind||tdelta>TAP_THRESHOLD) //check if this is a failed multitap release
key_taps[bind] = 1 //reset the tap sequence to 1
key_time[bind] = ntime //store the time data for this release
mob.onKeyRelease(bind,taps,ntime,tdelta) //tell the mob that a key has been released and pass all the metadata
else if(ks<0)
key_state[bind] = 0 //make sure we don't set the key state below zero


mob
var/tmp
move_keys //current move_keys being held down
move_dir //the current direction of movement we are attempting to step.
proc
//called from client/keyPress
onKeyPress(bind,taps,time,delta)
switch(bind)
if(1 to 4)
var/d = bind2dir(bind)
var/opp = bind2opp(bind)
move_keys |= d
move_dir |= d
if(move_keys&opp)
move_dir &= ~opp

//called from client/keyRelease
onKeyRelease(bind,taps,time,delta)
switch(bind)
if(1 to 4)
var/d = bind2dir(bind)
var/opp = bind2opp(bind)
move_keys &= ~d
move_dir &= ~d
if(move_keys&opp)
move_dir |= opp

//ensures that movements are handled when appropriate
MoveLoop()
set waitfor = 0
while(client)
if(loc&&move_dir)
step(src,move_dir)
sleep(world.tick_lag)
Login()
MoveLoop()
..()


Let's do some modification though, of this setup to allow what you are asking for in the next post.
Test Case? https://www.sendspace.com/file/hl6ctt

Just this code wise.

verb
North()
set instant = 1;direction ++
NorthUp()
set instant = 1;direction --
South()
set instant = 1;direction += 2
SouthUp()
set instant = 1;direction -= 2
East()
set instant = 1;direction += 4
EastUp()
set instant = 1;direction -= 4
West()
set instant = 1;direction += 8
WestUp()
set instant = 1;direction -= 8
FN()
set instant = 1;fire_direction ++
FNU()
set instant = 1;fire_direction --
FS()
set instant = 1;fire_direction += 2
FSU()
set instant = 1;fire_direction -= 2
FE()
set instant = 1;fire_direction += 4
FEU()
set instant = 1;fire_direction -= 4
FW()
set instant = 1;fire_direction += 8
FWU()
set instant = 1;fire_direction -= 8
In response to Kozuma3
Kozuma3 wrote:
The thing is, key+UP in the macros isn't called sometimes, just weird that no one has reported it yet :P

key+Up macros are only not called when you transition to interfaces that don't have a macro set with a matching capture. Basically, make sure that every window in your interface has the same macro set in use.

Also, I don't tend to use alert() input(), or visible browsers anymore because they have too many focus traversal problems.
I don't use input or alert and it's a single window with a map element.
In response to Kozuma3
For implementing the different formulas with a single input control project-wide, this where each implementation would be put:

#define INPUT_MODE_8DIR 0
#define INPUT_MODE_4DIR_ARBITRARY 1
#define INPUT_MODE_4DIR_TIME 2
#define INPUT_MODE_FORMULA 3

#define INPUT_MODE INPUT_MODE8DIR

#if INPUT_MODE==INPUT_MODE_4DIR_ARBITRARY

#define ARBITRARY_DIR1 NORTH
#define ARBITRARY_DIR2 EAST
#define ARBITRARY_DIR3 SOUTH
#define ARBITRARY_DIR4 WEST

#elif INPUT_MODE==INPUT_MODE_FORMULA

var/__diag
//prefer East/West:
#define INPUT_FORMULA(dir) ((__diag = (dir)&(dir)-1) ? (dir)&diag : (dir))
//prefer North/South:
//#define INPUT_FORMULA(dir) ((dir)^((dir)&(dir)-1))

#endif


mob
var/tmp
move_keys //current move_keys being held down
move_dir //the current direction of movement we are attempting to step.
proc
//called from client/keyPress
onKeyPress(bind,taps,time,delta)
switch(bind)
if(1 to 4)
var/d = bind2dir(bind)
var/opp = bind2opp(bind)
move_keys |= d

#if INPUT_MODE==INPUT_MODE_4DIR_ARBITRARY

if(move_keys&ARBITRARY_DIR1) move_dir = ARBITRARY_DIR1
else if(move_keys&ARBITRARY_DIR2) move_dir = ARBITRARY_DIR2
else if(move_keys&ARBITRARY_DIR3) move_dir = ARBITRARY_DIR3
else if(move_keys&ARBITRARY_DIR4) move_dir = ARBITRARY_DIR4

#elif INPUT_MODE==INPUT_MODE_4DIR_TIME

move_dir = d

#else
move_dir |= d
if(move_keys&opp)
move_dir &= ~opp
#endif

//called from client/keyRelease
onKeyRelease(bind,taps,time,delta)
switch(bind)
if(1 to 4)
var/d = bind2dir(bind)
var/opp = bind2opp(bind)
move_keys &= ~d

#if INPUT_MODE==INPUT_MODE_4DIR_ARBITRARY

if(move_keys&ARBITRARY_DIR1) move_dir = ARBITRARY_DIR1
else if(move_keys&ARBITRARY_DIR2) move_dir = ARBITRARY_DIR2
else if(move_keys&ARBITRARY_DIR3) move_dir = ARBITRARY_DIR3
else if(move_keys&ARBITRARY_DIR4) move_dir = ARBITRARY_DIR4

#elif INPUT_MODE==INPUT_MODE_4DIR_TIME

var/md = 0, mt = 0
for(var/count in 1 to 4)
if(client.key_state[count]&&client.key_time[count]>mt)
md = count
mt = client.key_time[count]
move_dir = md

#else
move_dir &= ~d
if(move_keys&opp)
move_dir |= opp
#endif

//ensures that movements are handled when appropriate
MoveLoop()
set waitfor = 0
while(client)
if(loc&&move_dir)
#if INPUT_MODE==INPUT_MODE_FORMULA
step(src,INPUT_FORMULA(move_dir))
#else
step(src,move_dir)
#endif
sleep(world.tick_lag)
Login()
MoveLoop()
..()