ID:2128785
 
Applies to:DM Language
Status: Open

Issue hasn't been assigned a status value.
In DM, tracking the position of the mouse is done by overriding the built-in mouse events to extract the position from the "screen-loc" parameter. These events only occur when the mouse moves over mouse-opaque regions.

Regions the mouse cannot be tracked:
1. The map background, which is that black (by default) void that you see, where there are no icons to be drawn e.g. past the edge of the world or by opacity.
2. Everywhere outside of the map control.



There is a decent workaround for untrackable region #1, which is used by Forum_account's Mouse Position library as well as my own Mouse and InputHandler libraries: catch the mouse with a screen object.
/* Catches the mouse over the map if nothing else does.
*/

obj/InputHandler_mouse_catcher
mouse_opacity = 2
icon = null
name = ""
plane = -100
layer = BACKGROUND_LAYER
screen_loc = "SOUTHWEST to NORTHEAST"

However, even this is not a perfect solution, because of SEE_PIXELS (which seems pretty useful otherwise):
DM Reference, "sight var (mob)":
SEE_PIXELS draws everything and then covers hidden turfs with blackness.

By "covering hidden turfs with blackness", it also "covers up" the mouse catcher, forcing the mouse to be ignored over that space.



There is one workaround that I know of for untrackable region #2, which is used by HDK Mouse And Keys: use JavaScript to track the mouse everywhere, even outside the game window. IIRC that was a security exploit and isn't actually supposed to work.

I wrote my own JavaScript script that uses edge mode for features requiring at least Internet Explorer 9.

What it does
When the mouse position over the browser changes, client.Topic() is called and provided with the position of the mouse relative to the screen and to the browser element, flipping the y-coordinate to be upward-positive and having an origin of (1, 1) in the screen/window by tradition.

It works except for a couple significant issues:
* It requires a browser control to cover the entire window, so you can't even see the map. Probably not a problem in the web client, which wouldn't require an extra browser control.
* It's in units of screen pixels instead of map pixels, but this can probably be easily accounted for.

The bare minimum for this feature
Essentially, the exact same mouse position information, without requiring the developer to create a skin with a window-filling, all-element-covering browser control.

Extra goodies that would be really nice to have
  • Coordinates relative to the client's camera. This would be calculated by mapping window coordinates to scaled map coordinates, etc.
  • Coordinates relative to the bottom-left of the world. I already compute this in my library by adding camera-relative coordinates to client.bound_x/y.
  • A client variable that enables mouse tracking, for the bandwidth-conscious, for mouse tracking to be disabled by default.
  • A client proc that is called when the mouse moves, only if mouse tracking is enabled. It should provide the coordinates as arguments; possibly along with the other MouseMove() arguments.
    Please do not put it in params, forcing us to use text2num() or some regex() to parse it.
    This would also allow for mouse-enter/exit events on individual elements.


The code itself:
Demo with source: https://puu.sh/qujer/2b4303d317.zip
client
var const
TOPIC_SRC = "src"
TOPIC_ACTION = "action"
TOPIC_ACTION_TRACK_MOUSE = "trackMouse"
TOPIC_MOUSE_SCREEN_X = "screenX"
TOPIC_MOUSE_SCREEN_Y = "screenY"
TOPIC_MOUSE_WINDOW_X = "windowX"
TOPIC_MOUSE_WINDOW_Y = "windowY"

var tmp
MousePosition/mouse_position

/MousePosition
var screen_x, screen_y, window_x, window_y

New()
..()
MousePosition_Initialize()

Topic(Href, HrefList[])
..()
if(HrefList[TOPIC_ACTION] == TOPIC_ACTION_TRACK_MOUSE)
mouse_position.screen_x = text2num(HrefList[TOPIC_MOUSE_SCREEN_X])
mouse_position.screen_y = text2num(HrefList[TOPIC_MOUSE_SCREEN_Y])
mouse_position.window_x = text2num(HrefList[TOPIC_MOUSE_WINDOW_X])
mouse_position.window_y = text2num(HrefList[TOPIC_MOUSE_WINDOW_Y])

src << output({"<tt>Mouse Position:<ul>
<li>Screen X:
[mouse_position.screen_x]</li>
<li>Screen Y:
[mouse_position.screen_y]</li>
<li>Window X:
[mouse_position.window_x]</li>
<li>Window Y:
[mouse_position.window_y]</li></ul></tt>"}, "mouse_tracker:output")

proc
MousePosition_Initialize()
mouse_position = new /MousePosition
src << output({"\
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge">
</head>
<body>
<div id=output></div>
<script>
var lastX, lastY;
addEventListener("mousemove", function(e) {
if(e.screenX != lastX || e.screenY != lastY) {
lastX = e.screenX;
lastY = e.screenY;
window.location = "byond://?\
[TOPIC_SRC]=\ref[src];\
[TOPIC_ACTION]=[TOPIC_ACTION_TRACK_MOUSE];\
[TOPIC_MOUSE_SCREEN_X]="+(1 + e.screenX)+";\
[TOPIC_MOUSE_SCREEN_Y]="+(window.screen.height - e.screenY)+";\
[TOPIC_MOUSE_WINDOW_X]="+(1 + e.clientX)+";\
[TOPIC_MOUSE_WINDOW_Y]="+(window.innerHeight - e.clientY)+";\
";
}
});
function output(html) {
document.getElementById('output').innerHTML = html;
}
</script>
</body>
</html>
"}
, "mouse_tracker")
I'm also still experiencing issues where certain mouse button presses cause zero mouse movement events to be called,
e.g.
1. double-click and hold, don't move the mouse
2. move the camera
3. move the mouse; no mouse events (i.e. MouseDrag) will be called until buttons are released.
I'm pretty sure I reported this bug already, though.

In general, it'd be great if we could check whether a certain button (keyboard button, mouse button, gamepad button) is being pressed, like what my InputHandler tries to provide but can't due to that bug.
To emphasize, the blackness added by SEE_PIXELS creates void space that covers HUD objects and does not trigger any mouse events, so there are situations where the mouse's position over the map is impossible to track as far as I know, unless you layer the mouse catcher over the map.

One other thing I tried was to make the mouse catcher's plane change depending on if your mouse is detected or not. That is, on client/MouseExited(), the mouse catcher's plane is set to 100; on MouseEntered(), its plane is set to -100. This almost solves it; it probably wouldn't work well over a network, and it misses every other frame because the mouse catcher has to flicker in order to know when the mouse re-enters the map. This isn't really a nice workaround, though.
This is the same with SEE_BLACKNESS. I would really like to see this feature added. I can't think of any workarounds, and Kaiochao seems to have the same issue. I can definitely say this would be a very useful feature and I'd love to see it in 512 or even sooner.

We're using a similar system to Kaiochao for our "click catcher" and SEE_BLACKNESS is practically incompatible with it - when you click blackness usually, we'd catch it.

I can definitely say with certainty that this feature would be very beneficial.
As a long time upstanding space station 13 player, I believe this feature is currently the most important problem that we need to have rectified and implemented into Byond.

In response to DrCelt
That's a good point, DrCelt. Thanks for bringing that up.

I'm still interested in pursuing something to improve mouse tracking, so this thread is still on my radar.
In response to Lummox JR
Thanks Lummox. This really is the last hurdle as far as I can see to allowing SEE_BLACKNESS to be used on our server, which personally I can't wait to use. I donated and bought membership to show my appreciation for you adding SEE_BLACKNESS in the first place.

Here's hoping we see it in 512 or sooner because I'm excited about the possibility of using it in action.
Well, I have accidentally found a solution to the blackness problem. Came about while I was experimenting with using a PLANE_MASTER for mouse_opacity tests.

world
maxx = 22
maxy = 22
maxz = 1

var
MasterScreen/MS = new()

MasterScreen
parent_type = /obj
appearance_flags = PLANE_MASTER
screen_loc = "1,1"
mouse_opacity = 2

turf
icon = 'Test.dmi'
icon_state = "Grass"

mob
icon = 'Test.dmi'
sight = SEE_PIXELS
Login()
..()

client
New()
..()
screen += MS
MouseMove(object, location, control, params)
var/list/p = params2list(params)
world << p["screen-loc"]


If you use the PLANE_MASTER appearance flag and, it will make the whole map screen mouse interactable

Edit: Turns out setting the plane to a high value makes it incompatible with default object clicking. Leaving the plane at the default setting (0), however, makes it work with any object and even in the black area.