HDK Mouse And Keys

by Hiro the Dragon King
HDK Mouse And Keys
Real-time mouse updates as well as left and right macros for the Shift, Ctrl, and Alt keys.
ID:591109
 
/*-------------------------------------------------------------------------------------*\
HDK Mouse And Keys Update [Version 3.1]
\\-------------------------------------------------------------------------------------//

The update for this library is substantial. The biggest update is the addition of
the Screen datum. If you already use this library, you will have to rewrite portions of
your code for compatibility. You may also want to delete the library from your computer
and redownload it, as BYOND doesn't clear out and update older help files properly.
Instead of accessing lists by numbers, lists are indexed using the IDs. This makes the
entire system quicker and more efficient, especially across multiple players. The other
updates are rather minor, such as how pausing the scripts and naming elements is handled.
Now included is support for individual left and right key up and key down macros for the
modifier keys.

\*-------------------------------------------------------------------------------------*/




/*-------------------------------------------------------------------------------------*\
Screen Handling
\\-------------------------------------------------------------------------------------//

The Screen (/Screen) datum allows the developer to create and manage "screens" across
multiple clients. A Screen has a view size, an ID for referencing it in lists and its
map element, a list of atoms displayed by the screen, and a list of clients currently
viewing the screen. Clients also have a map variable that dictates which screen it uses
as its default map. Let's take a closer look at the Screen datum and its interface.
*/

Screen/var

/*This variable is used to to identify the map element associated with this Screen and
also used for accessing the mouse coordinate lists for individual Screens.*/

id = ""

/*These variables are the equivalent of client.view. They hold the Screen to a specific
number of tiles on each axis, When the Screen is used on the client's default map,
client.view is actally set to "[view_x]x[view_y]"*/

view_x = 0
view_y = 0

/*These are temporary variables written to by ScreenSize() for the purpose of ease
of use. They represent the view size of the Screen in pizels, i.e., view*icon_size.*/

tmp/size_x
tmp/size_y

/*This is the list of clients currently viewing the Screen. you should only access and
change this list via AddClient() and RemoveClient().*/

list/clients = list()

/*This is a lits of atoms in the screen. When a client is added to the Screen, all of the
atoms in this list are added to client.screen.*/

list/objects = list()
/*

Now let's take a look at how to use a Screen. The Screen datum has six procedures
that you'll need and you'll use thes as the interface. Save for cases where you'd need to
change the id of a Screen, you shouldn't interact with a Screen's variables directly.
*/

Screen/proc
New(id, x, y, client/c, map)
/*
This creates a new screen with the specified id, and view size. The fourth variable,
c, allows you to shortcut creating a new Screen for a specific client. Instead of
creating a Screen, then adding the client to it, you can simply pass the client through
Screen.New(). The fifth variable, map, like the client shortcut, allows the Screen to be
created as the default map for the specified client, when set to TRUE. By default, id and
client/c are null, x and y are 5, and map is FALSE.
*/

ScreenSize(x, y)
/*
This procedure updates the size parameters of the Screen with a new size, x and y,
with the default for each being 5.
*/

AddClient(client/c, map)
/*
This is procedure will add the specified client to Screen.clients and adds the atoms
from Screen.objects to client.screen, and if map=TRUE, sets the client's default map to
this Screen.
*/

RemoveClient(client/c, map)
/*
This procedure removes the specified client from Screen.clients, removes the atoms
in Screen.objects from the client's screen, and if map=TRUE, sets the client's default
map to false.
*/

AddAtom(atom/a, x, y)
/*
This adds the specified atom to the Screen and adds it to the screens of all clients
in Screen.clients. If x and/or y are specified, it will locate the object at that
position on the screen.
*/

RemoveAtom(atom/a)
/*
This removes the specified atom to the Screen and removes it from the screens of all
clients in Screen.clients.

Now, here, I'll write a small snippet that shows how to use this to make a unified
title screen for all new clients connecting to a server.
*/

var/Screen/titlescreen

world
New()
..()
titlescreen=new("map", 16, 9)
var/obj/title=new()
titlescreen.AddAtom(title)

client
New()
..()
titlescreen.AddClient(src,map)
/*
Here, we define a global variable referencing a titlescreen. On world.New(), we create
the titlescreen that displays on a client's map element titled "map" with a size of 16x9.
We also create an object to be displayed on this screen and add it to the screen. On
client.New(), we add the client to this screen. Using this example, on login, all clients
will be directed to the same, reusable title screen. Any visibly interactable objects,
such as a button that animates when pressed, should be added to the client's screen
rather than to Screen, as to avoid other clients noticing when you press buttons.

\*-------------------------------------------------------------------------------------*/




/*-------------------------------------------------------------------------------------*\
Mouse Tracking
\\-------------------------------------------------------------------------------------//

Mouse tracking brings to the developer, complete and up to date information on the
client's mouse. The mouse tracking system works on each individual client's computer,
client side, by sending a script to a browser on the client's end, which then keeps runs
and keeps the server updated on the position of the mouse. In order to begin tracking,
you simply pass the desired screen through StartMouseTracking().
*/

client/proc
StartMouseTracking(Screen/screen)
/*
This procedure queries for the parent window of the specified screen, then copies all
of the properties of the map element matching Screen.id and uses them create a perfectly
cloned browser element. THe browser element is then provided a script that updates the
client of the mouses position approximately every tick, calculated using the properties
copied from the map element. There are also three corresponding procedures.
*/

PauseMouseTracking(Screen/screen)

ResumeMouseTracking(Screen/screen)

StopMouseTracking(Screen/screen)
/*
The first two procedures simply send a message through JavaScript, telling the script
to stop updating the server. The third deletes the browser element. There is one more
relevant procedure.
*/

UpdateMouseTracking(Screen/screen)
/*
This updates the script of the framerate and the icon-size of the map element. This
procedure should only be called in the instance in which either of those changes. Every
update sent to the server from the client decoded first through client.Topic() and then
through UpdateMouse().
*/

UpdateMouse(Screen/screen, x, y)
/*
The arguments here are rather self-explanatory. The first refers to the screen for
which the coordinates correspond. The other two are the actual coordinates of the mouse.
The default behavior is to write the coordinates to two lists, using screen.id as the
index.
*/

client/var
list/mouse_x = list()
list/mouse_y = list()
/*
Below is are examples of how to access the lists.
*/

client.mouse_x["map"]
client.mouse_x[screen.id]
client.mouse_x[client.map] //This shortcut uses the client's default map variable.k
/*
There also four other sets of coordinates that you have access to, as a developer
using this library. They are translated from the previous set, on the server, using
information available only to the server. Each set of coordinates has a corresponding
variable to flag whether or not to translate them.
*/

client/var
GenerateScreenLoc = TRUE //This is FALSE by default, as are the rest.
list/screen_tx = list()
list/screen_px = list()
list/screen_ty = list()
list/screen_py = list()
/*
The previous are accessed in exactly the same way as client. mouse_z/y.
*/

GenerateMapCoordinates = TRUE
map_x
map_y
/*
These act the same way as mouse_x/y, in that they represent the number of pixels that
the mouse is removed from position 1:1, 1:1 over the actual map.
*/

GenerateMapLoc = TRUE
map_tx
map_px
map_ty
map_py
/*
These are the actual tile and pixel coordinates of the mouse over the actual map.
*/

atom/movable/var/current_screen

client/var
current_atom

GenerateIconCoordinates = TRUE
icon_x
icon_y
/*
These work a tad differently. When GenerateIconCoordinates is set to TRUE, the atom
that the client's mouse is over is kept track of at all times and the Screen on which
that atom resides, if not on the map, is kept track of as well, under current_atom and
current_screen, respectively. Keeping those in mind, the position of the mouse over the
actual icon are generated using information such as loc, step_x/y, screen_loc, et cetera.

\*-------------------------------------------------------------------------------------*/




/*-------------------------------------------------------------------------------------*\
Absolute Mouse Position
\\-------------------------------------------------------------------------------------//

Absolute mouse position refers to the postion of the client's mouse over the actual
screen of his computer. The procedures controlling this are the same as for mouse
tracking.
*/

client/proc
StartJavaScriptMouse(Screen/screen)

PauseJavaScriptMouse(Screen/screen)

ResumeJavaScriptMouse(Screen/screen)

StopJavaScriptMouse(Screen/screen)

UpdateJavaScriptMouse(Screen/screen)

client/var
screenX
screeny

jsMouse = FALSE //This is a read-only variable that tells wether this is running.

/*
\*-------------------------------------------------------------------------------------*/




/*-------------------------------------------------------------------------------------*\
Modifier Keys
\\-------------------------------------------------------------------------------------//

This system gives the developer access to individual left and right key up and key
down events for the Alt, Shift, and Ctrl modifier keys This is done by JavaScript on the
client's end, due in part to a special variable that available from Internet Explorer 8,
and up. Every millisecond, the script checks the position of all six keys and updates the
server if any of them change. Unless your player can change the state of one of them more
than twice per millisecond (1/1000th of a second, A.K.A., they're cheating), this system
is foolproof. Along with hooks for all three, there are variables for which you can check
the state of each key, as well as the same controlling procedures as with the previous
systems.
*/

client/var
shiftLeft = FALSE
shiftRight = FALSE
ctrlLeft = FALSE
ctrlRight = FALSE
altLeft = FALSE
altRight = FALSE

jsKeys = FALSE

client/proc
shiftLeftDown()
shiftLeftUp()
shiftRightDown()
shiftRightUp()

ctrlLeftDown()
ctrlLeftUp()
ctrlRightDown()
ctrlRightUp()

altLeftDown()
altLeftUp()
altRightDown()
altRightUp()

StartJavaScriptKeys(Screen/screen)

PauseJavaScriptKeys(Screen/screen)

ResumeJavaScriptKeys(Screen/screen)

StopJavaScriptKeys(Screen/screen)

UpdateJavaScriptKeys(Screen/screen)
/*
\*-------------------------------------------------------------------------------------*/




/*-------------------------------------------------------------------------------------*\
Modifier Keys
\\-------------------------------------------------------------------------------------//

This system gives the developer access to several numbers from the client's computer
that they would not normally have access to. The system works exactly the same as the
rest of them.
*/

client/var
tmp/availWidth //This is the usable width of the client's desktop.
tmp/availHeight //This is the usable height of the client's desktop.
tmp/width //This is the actual width of the client's desktop.
tmp/height //This is the actual height of the client's desktop.
tmp/colorDepth //This is the color depth of the client's desktop.

tmp/jsVars = FALSE

client/proc
StartJavaScriptVars(Screen/screen)

PauseJavaScriptVars(Screen/screen)

ResumeJavaScriptVars(Screen/screen)

StopJavaScriptVars(Screen/screen)

UpdateJavaScriptVars(Screen/screen)
/*
\*-------------------------------------------------------------------------------------*/




/*-------------------------------------------------------------------------------------*\
Thank you for using my library. I hope it comes in handy.

-Hiro TDK
\*-------------------------------------------------------------------------------------*/
Can't you create macros for the alt, shift, and ctrl keys now?
It seems we can now. I guess I should change that to legacy, huh? Maybe remove it. I would have to change the name of the library now, too... Anyway. All of the other features are still relevant.
I wasn't sure if this offered something extra, like distinguishing between the left ctrl and right ctrl keys (I'm not sure if BYOND can do that).
If I run the demo, set the framerate to 50 fps, then open the fourth map, the label that displays the atom doesn't update when moving over map1. It'll update when I mouse over the mob or over another map control, but not when I move over map1. This seems to happen consistently. My first guess was that the main map control had the same name as the 4th map control (which'd be fine since they're on separate windows) but that's not the case.
Actually, now that you mention it, I remember reading that Microsoft added support for a alt/ctrl/shiftLeft variable so that a developer can distinguish between them. I can't believe I had forgotten about it. I never thought I'd be glad for being forced to use Internet Explorer.

I am rewriting a portion of the demo, so I will look into that. While you're there, can you tell me if the image on the fourth map takes a while to load. I had an inexplicable two minute hang time before the image showed up.
I've updated everything to 3.1. Now included are individual left and right key-up and key-down macros for Shift, Ctrl, and Alt.

@Forum_account: I've updated the demo and fixed a bug or two, but I could not replicate the bug you described.
I am rewriting a portion of the demo, so I will look into that. While you're there, can you tell me if the image on the fourth map takes a while to load. I had an inexplicable two minute hang time before the image showed up.

Yes it does.

@Forum_account: I've updated the demo and fixed a bug or two, but I could not replicate the bug you described.

It seemed to only happen when I did exactly what I said: increase the framerate to 50 fps, open the 4th map, then move the mouse around. If I opened the 4th map before increasing the framerate, it was fine. It seems too weird to be a problem with the library and if other weird things are happening (ex: the 4th map control taking a long time to draw) then I'd just blame this quirk on BYOND =)
Yeah, it seems that the hang time is actually a bug involving the visibility of the window on startup. And I think I've pinned down the bug you've described. Assuming it's the same bug, if you are able to move the mouse from the second window's fourth map to any other map without passing the mouse over anything else, save for the border of the window, MOST of the time, MouseEntered() doesn't seem to be called at all. This happens on any of the frame rates too. I'll have to report these bugs soon.