ID:150313
 
I thought I'd finally tackle this, but I guess I should see if anyone else has tried anything similar first...

I don't like letting go of a movement key, and having my character keep going a few steps before stopping. It seems like there is an input buffer if you hold a key down, and the buffer has to empty before you stop. I'd much prefer a screeching halt.

I'm going to see if I can squash this programatically. Has anyone else done anything similar to this before I start?

/mob/skysaw
I'm really not so sure that this is even possible programmatically. My understanding is that when a key is pressed and held down, either the operating system or the hardware itself queues up those repeating keypress events. Once they are in the queue, they will be sent along to the application (DreamSeeker in this case) whether it wants them or not. If the operating system falls behind and the queue grows, there will be keypress events left over after the user releases the key. These will continue to be sent to the application until the buffer is empty. As far as I know, there is no way for DreamSeeker to distinquish between a physical keypress and a key-repeat keypress event. There is, to my knowledge, no analog to mouse down and mouse up for keys. When the key is pressed, if key repeat is turned on, multiple events get generated repeatedly. This is either at the operating system or hardware level (not sure which). So not only is Dantom powerless to detect this within BYOND, but we as DM programmers are even more powerless to do so.

Furthermore, there is the problem of network delay. When the server is somewhere else on the network, I believe it has no way of distinguishing between actual movement keypress messages and further repeated messages after the key has been released.

Of course, adding a small delay to movement (even one tick might suffice) to ensure that DreamSeeker doesn't lag too far behind the keypress events due to network/graphics/etc overhead may work well enough. This has been done many times and I'm sure you know how to do that.

I'm curious how you intended to tackle the problem? Also if anyone can prove me wrong regarding the hardware/OS stuff above, by all means do so! My understanding is not complete, but I believe it describes the situation fairly well.
In my games, I handle movement a little differently. pressing the movement keys just sets the mob's action variable. Every action cycle, the program looks at the action variable to see what action to perform. Only the last action entered gets processed, no matter how many keys they mash.

If you want to see a brief demo, let me know. I have one around here somewhere from when I was going to release it as a library.
In response to Shadowdarke (#2)
Shadowdarke wrote:
In my games, I handle movement a little differently. pressing the movement keys just sets the mob's action variable. Every action cycle, the program looks at the action variable to see what action to perform. Only the last action entered gets processed, no matter how many keys they mash.

If you want to see a brief demo, let me know. I have one around here somewhere from when I was going to release it as a library.

This is sort of the direction I was thinking in. I was pretty sure I'd have to override the existing movement commands.

Sounds pretty easy to code, but if you have time to post a demo that would be nice.
In response to Skysaw (#3)
Here is the bare bones of the movement scheme I use for Darke Dungeon and Tanks. I use the status cycle to do several things unrelated to movement, so it spawns each tick. If you are only using it for movement, you might want to get rid of the speed system here and just spawn the statuscycle after an appropriate delay.

// Increase or decrease ACTIONRATE to slow down or spead up ALL mobs
// lower = faster
#define ACTIONRATE 30

mob
var
speed = 10 // change this to make a mob faster or slower. Higher = faster
tmp
action = "" // what the player is trying to do
speedcounter = 0 // keeps track of when the mob can act

proc
statuscycle()
speedcounter += speed
if (speedcounter >= ACTIONRATE)
speedcounter -= ACTIONRATE
// I only subtract instead of clearing it to 0 so faster
// mobs can get a little headstart in the next cycle if
// there are a few points left over.

switch (action)
if ("") // do nothing
if ("move") step(src,dir)
// define any other actions here

else world.log << "Unknown action:[action]\n src:[src]"

action = "" // clear the action

spawn() statuscycle()

New()
..()
// kick off the statuscycle() when a mob is created
spawn() statuscycle()

client
// overide all the directional procs

North()
mob.action = "move"
mob.dir = NORTH

South()
mob.action = "move"
mob.dir = SOUTH

East()
mob.action = "move"
mob.dir = EAST

West()
mob.action = "move"
mob.dir = WEST

Northeast()
mob.action = "move"
mob.dir = NORTHEAST

Southeast()
mob.action = "move"
mob.dir = SOUTHEAST

Northwest()
mob.action = "move"
mob.dir = NORTHWEST

Southwest()
mob.action = "move"
mob.dir = SOUTHWEST
In response to Air Mapster (#1)
Air Mapster wrote:
if anyone can prove me wrong regarding the hardware/OS stuff above, by all means do so! My understanding is not complete, but I believe it describes the situation fairly well.

You can trap keypresses with software. I don't remember the addresses, but everytime you press a key it sends a signal. Releasing the key sends a different signal. You can keep track of several keys being held down at once. It's not very well documented and I had to dig through a lot of reference books before I finally found it. Most texts I read take you down to the layer of the keyboard buffer (which is a hardware function, I think) and leave you there without digging any further.

I'll look up the C code to do this later when I get home if you want it. I think I got it from an old book called "Secrets of the Game Programming Gurus" or something like that. It walks you through the process of making a Doom style game in C with a little ASM.
In response to Shadowdarke (#4)
I wonder if this system might still be prone to the movement buffer problem. Won't buffered keystrokes still be setting the action to move?
In response to Skysaw (#6)
Skysaw wrote:
I wonder if this system might still be prone to the movement buffer problem. Won't buffered keystrokes still be setting the action to move?

The keystrokes keep setting action to "move", but it doesn't matter how many times the action is changed. All that matters is what action is set to when speedcounter goes over ACTIONRATE. Sometimes with this system you might take one step too many, but that's rare. If the network lag is really bad and your packets are arriving late, you might get semisporadic movement, but there is nothing that can be done about that until the internet becomes perfect :P

If you want to see that movement system in action, try out Tanks. It's a rotational movement system, but the core is still the same. When you press up, it sets the tank's action to "move". You can see if it prevents the trouble you're having. (Rotation in Tanks doesn't set the player's action, so sometimes you will get problems with rotating too far or wiggling back and forth as you try to get to the right direction on a laggy server.)
In response to Shadowdarke (#5)
Here you go, Mapster. The raw keyboard code for my old C version of Dungeon Delvers (that never made it much past the input routines :/) To be honest, I'm not even certain it will work with modern computers.

<big><font color=red>C code!
Do not try this in DM.</font>
</big>
// Raw Keyboard Interface

// Sets a vector on the keyboard interrupt to keep track of arrow key
// status and return raw IO codes in the variable RawKey

// The following variables must be Global Variables in the main program:
// void (_interrupt _far *OldIsr)(void); // Old com port interrupt handler
// int RawKey;
// int KeyDown[9]={0,0,0,0,0,0,0,0,0};

// Use the following commands to enable this routine
// OldIsr=_dos_getvect(0x09);
// _dos_setvect(0x09, NewKeyInt);

// This command removes the Interrupt Service Routine
// _dos_setvect(0x09, OldIsr)

void _interrupt _far NewKeyInt(void)
{
_asm
{
sti ; Disable interrupts
in al, 0x60 ; Get the key that was pressed
xor ah, ah ; Zero out the upper 8 bits of AX
mov RawKey, ax ; Store the keypress in a global variable
in al, 0x61 ; Set the control register
or al, 82h ; Set the proper bits to reset the FF
out 0x61, al ; Send the data back to the control register
and al,7fh
out 0x61, al ; Complete the reset
mov al,20h
out 0x20,al ; Reenable interrupts
}
switch (RawKey)
{
case 77: // pressing right arrow key
{
KeyDown[0]=1;
} break;
case 80: // pressing down arrow key
{
KeyDown[2]=1;
} break;
case 75: // pressing left arrow key
{
KeyDown[4]=1;
} break;
case 72: // pressing up arrow key
{
KeyDown[6]=1;
} break;
case 29: // pressing Ctrl key
{
KeyDown[8]=1;
} break;
case 56: // pressing Alt key
{
KeyDown[9]=1;
} break;
case 205: // released right arrow key
{
KeyDown[0]=0;
} break;
case 208: // released down arrow key
{
KeyDown[2]=0;
} break;
case 203: // released left arrow key
{
KeyDown[4]=0;
} break;
case 200: // released up arrow key
{
KeyDown[6]=0;
} break;
case 157: // released Ctrl key
{
KeyDown[8]=0;
} break;
case 184: // released Alt key
{
KeyDown[9]=0;
} break;
default: break;
}
} // end of NewKeyInt



I stripped out the diagonals to remove some clutter and still leave a usable hunk of code. I'll dig up a list of the raw key codes if you want them. The key release codes are the same as the key down code + 128.