ID:2006801
 
Resolved
Non-repeat macros were sometimes being culled from the client-side command queue if too many stacked up at once, which is behavior that should only be expected of repeat keys and mouse commands.
BYOND Version:509
Operating System:Windows 7 Home Premium 64-bit
Web Browser:Chrome 47.0.2526.106
Applies to:Dream Seeker
Status: Resolved (509.1319)

This issue has been resolved.
Descriptive Problem Summary:

Numbered Steps to Reproduce Problem:
It just happens randomly out of the sudden you won't be able to move one or more directions unless you relog.
Code Snippet (if applicable) to Reproduce Problem:
Macros set in Skin Macro menu!
> W MoveKey 1 1
> W+UP MoveKey 1 0
> S MoveKey 2 1
> S+UP MoveKey 2 0
> D MoveKey 4 1
> D+UP MoveKey 4 0
> A MoveKey 8 1
> A+UP MoveKey 8 0

#define TICK_LAG 0.25 //set this to 10 divided by world.FPS

//this is where you would define your binds. Simply define them as numbers starting at 1, then update the BindKey() verb as necessary to handle behavior.
#define BIND_JUMP 1
#define BIND_INVENTORY 2

#define TAP_THRESHOLD 2.5

#define MOVE_TURN 1
#define MOVE_SLIDE 2
#define MOVE_JUMP 4
#define MOVE_TELEPORT 8

#define TILE_WIDTH 32
#define TILE_HEIGHT 32
#define TILE_DIAG sqrt(TILE_WIDTH * TILE_WIDTH + TILE_HEIGHT * TILE_HEIGHT)

client
var/tmp
move_keys = 0 //stores the actual state of the movement keys
move_dir = 0 //stores the direction the client is trying to move the mob in
move_loop = 0 //stores the current move_loop's starting time

last_key = 0 //stores the last key that was actually pressed
//these lists must be as long as 4+ the number of binds you define.
list/key_state = list(0,0,0,0,0,0) //stores the on-off state of each registered key
list/key_time = list(-1#INF,-1#INF,-1#INF,-1#INF,-1#INF,-1#INF) //stores the time that the registered key was pressed or released
list/key_taps = list(0,0,0,0,0,0) //stores the number of times a key has been pressed in sequence. This allows for dealing with double and triple taps.
proc
//this drives movement behavior.
MoveLoop()
if(move_loop) return //only call once while active
// var/x,y
move_loop = world.time
//repeat while move_keys are being held down
var/turf/t
while(move_keys)
if(move_dir)
t = get_step(mob,move_dir)
if(t)
Move(t,move_dir)
sleep(TICK_LAG)
move_loop = 0
verb
//called when a movement key has been pressed.
MoveKey(dir as num,state as num)
set hidden = 1
var/opp = turn(dir,180)
var/pos = log(2,dir)+1
if(state&&++key_state[pos]==1)
//key track of the keytap state
if(world.time-key_time[pos]>TAP_THRESHOLD)
key_taps[pos] = 1
else if(last_key==pos)
++key_taps[pos]
else
key_taps[pos] = 1
last_key = pos

//keep track of the move keys and direction
move_keys |= dir
if(move_keys&opp)
move_dir &= ~opp
else
move_dir |= dir
//store the time of the event
key_time[pos] = world.time
//this is an example of handling double-taps for movement
/*if(move_keys==dir&&key_taps[pos]==2)
mob.Dash(dir,0,pos)*/

else if(!state&&--key_state[pos]==0)
//this is a key release
move_keys &= ~dir
if(move_keys&opp)
move_dir |= opp
else
move_dir &= ~dir
//store the time of the event
key_time[pos] = world.time
//attempt to call MoveLoop if there are keys being held down
if(move_keys)
MoveLoop()

//called when a registered bind has been pressed
BindKey(key as num,state as num)
set hidden = 1
var/pos = key+4
key_state[pos] = state
//this is a keypress
if(state)
last_key = pos
//keep track of double, triple, quadruple, etc taps
if(world.time-key_time[pos]>TAP_THRESHOLD)
key_taps[pos] = 1
else
++key_taps[pos]
/*
switch(key)
//these are just examples, use your game's binds and functions here.

if(BIND_JUMP)
if(state)
switch(move_dir&(EAST|WEST))
if(EAST)
mob.Jump(60)
if(WEST)
mob.Jump(120)
else
mob.Jump(90)

if(BIND_INVENTORY)
if(state)
if(interface.inventory.showing)
interface.inventory.Hide()
interface.equipment.Hide()
else
interface.inventory.Show()
interface.equipment.Show()
*/


North()
South()
East()
West()
Northeast()
Southeast()
Southwest()
Northwest()

atom/movable
var
tmp
next_move = 0
move_type = 0
move_delay = 2
glide_delay = 35

Move(atom/NewLoc,Dir=0,Step_x=0,Step_y=0,Source=null)
if(!Source) Source = src
var/mtime = world.time
//make sure that we're not moving before we're allowed
if(next_move>mtime&&Source==src)
return 0
//this bit sets what kind of move we just tried to do and manages the glide size
if(loc&&NewLoc.z==z)
var/mx = abs(NewLoc.x - x)
var/my = abs(NewLoc.y - y)
move_type = MOVE_SLIDE
//determine if jump, turn or slide.
if(mx>1||my>1)
//more than one tile at a time movement is a jump
move_type = MOVE_JUMP
else if(mx)
//set the glide size based on the movement direction
if(my)
glide_size = TILE_DIAG / glide_delay / TICK_LAG
else
glide_size = TILE_WIDTH / glide_delay / TICK_LAG
else if(my)
glide_size = TILE_HEIGHT / glide_delay / TICK_LAG
else
//no movement is a turn
move_type = MOVE_TURN
else
//swapping z layers is a teleport
move_type = MOVE_TELEPORT
. = ..()
//set the next movement time
if(Source==src)
next_move = mtime + move_delay


Expected Results:
To work perfectly
Actual Results:

Does the problem occur:
Every time? Or how often?
Yes
In other games?
no?
In other user accounts?
no?
On other computers?
no?

When does the problem NOT occur?
It always occures

Did the problem NOT occur in any earlier versions? If so, what was the last version that worked? (Visit http://www.byond.com/download/build to download old versions for testing.)
I've only tested it on 509.1312

Workarounds:
No Workaround!
Can you try printing the values in key_state at each call?
You speak nonsense myfriend.
At the top of MoveKey just after var/pos=, put in
world << "MoveKey pos=[pos]; state=[state]; stored state=[key_state[pos]]"

and see what it prints when movement stops working. My guess is somewhere a key-up or key-down event is being missed, that should help show that it's BYOND doing it and not your code.
It's an extreme byond bug known amost us proers devopepers, needs to be address!
KeyUp events being missed is something I reported before and it was fixed, in 508 I think. But clearly there has been a regression.

KeyUp events will be lost if anything takes focus off the map in between a KeyDown and KeyUp event. For example if you press a KeyDown and then a prompt from input() or alert() appears, and you then let the KeyUp, it will be lost. BYOND will think the key is still down. And if it is a directional key then your character will just run off in that direction forever.
Less bloated demo case:

/client
var/k = list(0, 0)
verb/State(i as num, n as num)
k[i] += n ? 1 : -1
world << "([i],[n]); k=[k[1]]/[k[2]]"

Replace the 'macro' section of the default skin.dmf with:
macro "macro"
elem
name = "W"
command = "State 1 1"
is-disabled = false
elem
name = "W+UP"
command = "State 1 0"
is-disabled = false
elem
name = "S"
command = "State 2 1"
is-disabled = false
elem
name = "S+UP"
command = "State 2 0"
is-disabled = false


Include the skin into the project with the above code, boot it up, and hit S and W several times fairly fast. The expected result is that k[1] and k[2] are always either 0 or 1 (since they start as 0, we add 1 for key down, and subtract 1 for key up), but they can quickly become -5 or even further negative.

Hitting either W or S on its own quickly enough produces no key down events, only key up events (using the above code, a key up event is "(n,0)" and a key down event is "(n,1)").
509.1303: "Key-up macros didn't fire if the message was not received by the right window and there was no associated key-down macro with the repeat flag. (Ter13)"

Yea that bug has now come back somehow in 509.1318. If an input() or alert() prompt appears between a KeyDown and KeyUp event then the KeyUp even is lost.
Tens of DU, did you read my post? The key-up macros are firing perfectly, it's the key-down macros that fail.

edit: adding 'set instant = 1' to the State verb makes this work perfectly. It appears the rate limiting of verbs is causing *only* key-down events to be lost here.
Thinking on this a bit further, likely the best solution is for your code to not require catching all events anyway. If you receive a key-up event, don't decrement the state; just set it to zero. Don't increment on key-down, just set to one (true). No possibility of getting stuck; if you miss an event, the next event from that key will resolve the state.
Do you have a full demo project that shows this issue in action? Bugs like this one are notoriously difficult, often impossible, to reconstruct without a project.
You don't need a project, the Code its right there run it in a simple format and you will get the hickups
Lummox: http://gn32.uk/f/movekey.zip

Compile, run, quickly press W, S, or both. See my earlier post ( http://www.byond.com/forum/?post=2006801#comment17888054 ) for what it should do.
Ter13 and Lige can confirm this aswell, although they are busy and not here right now to comment on the matter. :V
This sounds like exactly what I had explained to you via PM last week, Lummox. The environment I sent you should work, in the break-y way.

@Zasif: Pretty much what was happening in my case, with the same bug, is that macros were randomly being treated as +REP regardless of their additives or lack thereof. In fact, the test project I sent handles movement in a very similar way, including the macro setup.

Using your macro setup as an example, if I were to press W when the macros malfunctioned, both W and W+UP would be called as W+REP, which is why it ended up breaking diagonal movement since it's constantly spamming key up/down values.
In response to Lige
This sounds like exactly what is happening.
I'm not seeing them acting as +REP, I'm just seeing missing key-down events.
In response to Lige
I also notice this same problem in Dragon-Ki if you open a window selection from the Menu or an alert popups while moving, you're moving would be stuck on that direction and you can't break out of the direction unless you press that direction 1 time so it knows you've released it(This is another example on another movemenet system).
In response to GinjaNinja32
That's odd. Most of my time debugging, I thought it was a user-error, but I ended up adding an output to MoveKey once none of my workarounds worked so I could see what key was being called and it's state.

Using the macro setup Zasif provided for the sake of consistency: In my environment, when macros would malfunction, the output would be spammed with "1 1" and "1 0" until I let go of W.

Edit: You might be right. I was testing it again this morning, and for some unknown reason only 3 of the 4 key-down macros were messing up.
In response to Lige
Interesting. Possibly there's two bugs here, then; one that causes rapid key-down key-up events if the key is held, the other (the one in my demo) AFAICT caused by one-verb-per-tick throttling dropping the key-down verb calls.
Page: 1 2 3 4