ID:2193325
 
(See the best response by Ter13.)
Problem description:
Hey guys, been a while since I posted here. What I'm having trouble with is getting my keyboard setup right. I've implemented a keyboard library that sets all the macros for each key (which I have because players cant set their own in the editor.) It's useful for some of the built in stuff I want like movement, targeting, and attacking. The problem is after I switch the client to a few different mobs the focus gets jacked up and key presses dont seem to be directed towards them. I don't want that anymore.

What I'm really looking for is a permanent keyboard solution. I saw a few things about a new "Any" macro, but I know little about it and I'm not sure what to do with it. I'd like to be able to let players set their own macros but also be able to over ride certain keys to do what I want.

Also, I'm having a hard time finding a good reference as to how to manipulate macros with winset(), I'm hoping I don't have to use it but it seems like the only way.

Thanks.
Best response
What I'm really looking for is a permanent keyboard solution.

I have a perfect, extensible solution that exposes all the information you will ever need, and also as an added bonus, isn't a pain in the ass to set up and use.

http://www.byond.com/developer/Ter13/ControlLib

How to use it:

1) include the library.

2) Set up your macros in the ui editor:



Just setting these two macros lets us manage everything else in code, rather than using the bullshit dmf editor.

Here's a simple example of how to set up default macros:

#define BIND_NORTH 1
#define BIND_WEST 2
#define BIND_SOUTH 3
#define BIND_EAST 4

var
list/default_keybinds = list("W"=BIND_NORTH,"A"=BIND_WEST,"S"=BIND_SOUTH,"D"=BIND_EAST)
list/default_keybind_sz = 4

client
New()
InitKeybinds(default_keybinds,default_keybind_sz)
..()


Next, we need to make binds actually do stuff:

client
BindPress(bind,taps,time,delta)
mob.onBindPress(bind,taps,time,delta)

BindRelease(bind,taps,time,delta)
mob.onBindRelease(bind,taps,time,delta)

mob
var/tmp
move_keys = 0
move_dir = 0
move_delay = TICK_LAG

last_move = -1#INF
next_move = 0
proc
onBindPress(bind,taps,time,delta)
switch(bind)
if(BIND_NORTH)
move_keys |= NORTH
if(move_dir&SOUTH) move_dir &= ~SOUTH
move_dir |= NORTH
if(BIND_SOUTH)
move_keys |= SOUTH
if(move_dir&NORTH) move_dir &= ~NORTH
move_dir |= SOUTH
if(BIND_EAST)
move_keys |= EAST
if(move_dir&WEST) move_dir &= ~WEST
move_dir |= EAST
if(BIND_WEST)
move_keys |= WEST
if(move_dir&EAST) move_dir &= ~EAST
move_dir |= WEST

onBindRelease(bind,taps,time,delta)
switch(bind)
if(BIND_NORTH)
move_keys &= ~NORTH
if(move_keys&SOUTH) move_dir |= SOUTH
move_dir &= ~NORTH
if(BIND_SOUTH)
move_keys &= ~SOUTH
if(move_keys&NORTH) move_dir |= NORTH
move_dir &= ~SOUTH
if(BIND_EAST)
move_keys &= ~EAST
if(move_keys&WEST) move_dir |= EAST
move_dir &= ~EAST
if(BIND_WEST)
move_keys &= ~WEST
if(move_keys&EAST) move_dir |= WEST
move_dir &= ~WEST

ControlLoop()
set waitfor = 0
if(!client) return
var/client/c = client
while(client==c)
if(move_dir)
if(next_move<=world.time)
glide_size = TILE_WIDTH/move_delay*world.tick_lag
if(step(src,move_dir))
last_move = world.time
next_move = last_move+move_delay
sleep(world.tick_lag)

Login()
ControlLoop()
..()


The trick to making customizable input, is to decouple keys from binds. A bind is just a command that multiple keys can be associated to.

Further, responding to inputs is best done via tracking the state of keybinds and responding to presses/releases. A polling loop should be operating on each player responding to inputs once per frame.

It takes a little bit more work, but it is much more flexible than the default system.
Ter13 you're awesome. This looks really nice, I'll give it a go soon. I can see right off the bat that it doesn't deal with winsets or interface crap, which is nice. Some of the binary operators you use (like ~) I'm not that familiar with, but most of it makes sense.

Thanks for posting this. Also it looks like you have an open string issue in the wasd part :)
Some of the binary operators you use (like ~) I'm not that familiar with, but most of it makes sense.

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

I have stuff to help you with that too.

In response to Ter13
Funny, I've read that before (I believe I even commented in it)

It wasn't until this time that I understood your explanation of ~, the BNOT operator. Good stuff :D
I see that move delay is set to TICK_LAG, but there's no preprocessor TICK_LAG macro, is that built in now?

Also, I can't find a waitfor setting in the reference :|
TICK_LAG is something you will need to calculate. Its default value should be the value of world.tick_lag, or roughly 10/FPS. In reality, it's round(1000/FPS)/100 after BYOND 510, and ceil(1000/FPS)/100 before BYOND 510.

waitfor:

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

You got questions? I got links.
I think I like you my friend. I'll give that link a read, and thanks for the calculation.

#define TICK_LAG round(1000/world.fps)/100


Would that work just fine, or is world.fps not set during the preprocessor stage?
Gave that a read. I would comment there but the topic is closed.

In some cases I use spawn() to call a new stack and allow the current proc to return. Such a case is when I'm calling a proc that contains an infinite loop, such as health regeneration or the like. In those cases I want the current proc to send it off and be able to end. Why is waitfor better in that case, and can it achieve the same thing?
The difference is that waitfor does NOT create a new stack. The proc simply returns (the value assigned to . ) when it hits a sleeping proc (like sleep) but continues execution. It can be used in a lot of the same cases, but you generally have to be careful.
In response to Super Saiyan X
Oh, hrm. So say I have this case:

mob
Login()
..()
spawn() regenerate()

proc
regenerate()
while(src)
// do stuff


Would it be better to set waitfor in regenerate or to spawn it in?
mob
Login()
regenerate()
..()

proc
regenerate()
set waitfor = 0
while(src)
// do stuff
sleep(world.tick_lag)
In response to Ter13
Alright then, guess I'll have to start making that switch, although it feels weird because I've been using spawn() for so long.

Also, what's the word on my TICK_LAG solution?

Edit: Updated it to this

#define FPS 40
#define TICK_LAG round(1000/FPS)/100


I'm getting an expected constant expression error though when trying to set move_delay to TICK_LAG
I'm getting an expected constant expression error though when trying to set move_delay to TICK_LAG

That's why YOU have to calculate it.

TICK_LAG would be 0.25 with 40 FPS
In response to Ter13
I thought so, I ended up doing that and coming up with 0.25 to fit in as a placeholder, but it turns out it was the right move. Thanks for all your help.