Thankfully, BYOND 512 and 513 have freed us from the majority of this problem. With operator overloading, we can now take matters into our own hands and free ourselves from the majority of the boilerplate that infects working with the mouse without having to parse information we don't want to use.
Let's open by setting up some initial defines. If you already have values like this in your project, you can omit these.
#ifndef TILE_WIDTH
#define TILE_WIDTH 32
#endif
#ifndef TILE_HEIGHT
#define TILE_HEIGHT 32
#endif
I hate dealing with the screen-loc parameter. We can take a lot of the work out of it by declaring a utility proc:
var/regex/__rgx_sloc = regex(@"[:,]","g")
proc
parse_screen_loc(sloc)
var/list/l = splittext(sloc,__rgx_sloc)
switch(l.len)
if(5)
return list((text2num(l[2])-1) * TILE_WIDTH + text2num(l[3]),(text2num(l[4])-1) * TILE_HEIGHT + text2num(l[5]),l[1])
if(4)
return list((text2num(l[1])-1) * TILE_WIDTH + text2num(l[2]),(text2num(l[3])-1) * TILE_HEIGHT + text2num(l[4]),null)
else
return list(0,0,null)
Next, let's create a mouse_params datum. What we're going to do, is wrap the param string in a datum. We're going to use this datum to make working with the string a lot easier. yes, this will involve some extra overhead. However, if both the client and the atom involved in a mouse proc need to read the paramslist, we are getting cost-savings by working with a mutable datum rather than an immutable string and two separate lists. We are also saving time by generalizing common tasks you will have to do to work with the params string.
#define MOUSE_NONE 0
#define MOUSE_BUTTON1 1
#define MOUSE_BUTTON2 2
#define MOUSE_BUTTON3 4
#define MOUSE_BUTTONS 7
#define MOUSE_SHIFT 8
#define MOUSE_CTRL 16
#define MOUSE_ALT 32
#define MOUSE_MODIFIERS 56
#define MOUSE_NOT_DIRTY 0
#define MOUSE_PARAMS_DIRTY 1
#define MOUSE_VIS_DIRTY 2
#define MOUSE_SCREEN_DIRTY 4
#define MOUSE_ICON_DIRTY 8
#define MOUSE_BUTTONS_DIRTY 16
#define MOUSE_DIRTY 31
mouse_params
var
string
params
screen_pos
vis_pos
icon_pos
buttons = MOUSE_NONE
dirty_flags = MOUSE_DIRTY
New(str)
string = str
dirty_flags = MOUSE_DIRTY
..()
proc
operator[](idx)
//TODO
operator[]=(idx,value)
//TODO
This is the basic structure of the mouse_params datum. As you can see, we added some procs called operator[] and operator[]=. This is a BYOND 512 feature that allows you to change how using operators on a datum works. Since we overrode [] and []=, we're going to be interacting with this object as though it were an associative list, for the most part.
The dirty_flags variable is important to how this object is going to function. We want to make some of the values easier to work with. I don't like using text2num() everywhere, or parsing screen-locs when working with mouse procs. I don't like hunting for which buttons are on or off, so I've created some storage spaces for storing more useful versions of these parameters. dirty_flags will tell you what has been parsed and what has not, so that we can parse what's needed when we need it. This saves a little bit of processing when we don't need all the data, and it costs a little extra when we do need all of the data, but again, the cost savings overall will be significant because most of the time, we're discarding unneeded data rather than working with every parameter.
Let's take a look at how we should implement these objects in the client:
client
MouseDown(atom/object,atom/location,control,params)
params = new/mouse_params(params)
..()
That's it. You need to implement this behavior in any Mouse proc your project actually uses. MouseDown(), MouseUp(), Click(), DoubleClick(), MouseDrag(), MouseMove(), MouseWheel(), MouseDrop(), MouseEnter(), and MouseExit().
So let's loop back to actually implementing the operator overloads one at a time:
proc
operator[](idx)
if(dirty_flags&MOUSE_PARAMS_DIRTY && idx!="params")
params = params2list(string)
dirty_flags &= ~MOUSE_PARAMS_DIRTY
switch(idx)
if("params")
return string
if("params-list")
return params
if("screen-loc","left","middle","right","ctrl","shift","alt","drag-cell","drop-cell","drag","link")
return params[idx]
if("screen-pos")
if(dirty_flags&MOUSE_SCREEN_DIRTY)
screen_pos = parse_screen_loc(params["screen-loc"])
dirty_flags &= ~MOUSE_SCREEN_DIRTY
return screen_pos
if("vis-pos","vis-x","vis-y")
if(dirty_flags&MOUSE_VIS_DIRTY)
var/vx = params["vis-x"], vy = params["vis-y"], list/l
if(!vx||!vy)
if(dirty_flags&MOUSE_ICON_DIRTY)
vx = params["icon-x"]; vy = params["icon-y"]
l = list(text2num(vx),text2num(vy))
dirty_flags &= ~MOUSE_ICON_DIRTY
else
l = icon_pos
vis_pos = l||(l = list(text2num(vx),text2num(vy)))
dirty_flags &= ~MOUSE_VIS_DIRTY
switch(idx)
if("vis-pos")
return vis_pos
if("vis-x")
return vis_pos[1]
if("vis-y")
return vis_pos[2]
if("icon-pos","icon-x","icon-y")
if(dirty_flags&MOUSE_ICON_DIRTY)
icon_pos = list(text2num(params["icon-x"]),text2num(params["icon-y"]))
dirty_flags &= ~MOUSE_ICON_DIRTY
switch(idx)
if("icon-pos")
return icon_pos
if("icon-x")
return icon_pos[1]
if("icon-y")
return icon_pos[2]
if("buttons")
if(dirty_flags&MOUSE_BUTTONS_DIRTY)
var/b = 0
if(params["left"])
b += MOUSE_BUTTON1
if(params["middle"])
b += MOUSE_BUTTON3
if(params["right"])
b += MOUSE_BUTTON2
if(params["shift"])
b += MOUSE_SHIFT
if(params["ctrl"])
b += MOUSE_CTRL
if(params["alt"])
b += MOUSE_ALT
buttons = b
dirty_flags &= ~MOUSE_BUTTONS_DIRTY
return buttons
else
return null
This proc will handle both the parsing and formatting of the demanded data available in the param string. Notice how anywhere we parse data, we turn off the dirty_flag associated with that data, so we only parse the demanded data a single time.
Now let's implement index assignment, just for parity. We don't really need to do this, but you never know.
operator[]=(idx,value)
switch(idx)
if("params","params-list")
if(istext(value))
string = value
dirty_flags = MOUSE_DIRTY
else
params = value
dirty_flags = MOUSE_DIRTY & ~MOUSE_PARAMS_DIRTY
if("screen-loc")
if(dirty_flags & MOUSE_PARAMS_DIRTY)
params = params2list(string)
dirty_flags &= ~MOUSE_PARAMS_DIRTY
dirty_flags |= MOUSE_SCREEN_DIRTY
params[idx] = value
if("left","middle","right","ctrl","shift","alt")
if(dirty_flags & MOUSE_PARAMS_DIRTY)
params = params2list(params)
dirty_flags &= ~MOUSE_PARAMS_DIRTY
dirty_flags |= MOUSE_BUTTONS_DIRTY
params[idx] = value
if("drag-cell","drop-cell","drag","link")
if(dirty_flags & MOUSE_PARAMS_DIRTY)
params = params2list(params)
dirty_flags &= ~MOUSE_PARAMS_DIRTY
params[idx] = value
if("vis-x","vis-y")
if(dirty_flags & MOUSE_PARAMS_DIRTY)
params = params2list(params)
dirty_flags &= ~MOUSE_PARAMS_DIRTY
dirty_flags |= MOUSE_VIS_DIRTY
params[idx] = "[value]"
if("vis-pos")
if(dirty_flags & MOUSE_PARAMS_DIRTY)
params = params2list(params)
dirty_flags &= ~MOUSE_PARAMS_DIRTY
dirty_flags &= ~MOUSE_VIS_DIRTY
vis_pos = value
params["vis-x"] = "[value[1]]"
params["vis-y"] = "[value[2]]"
if("icon-x","icon-y")
if(dirty_flags & MOUSE_ICON_DIRTY)
params = params2list(params)
dirty_flags &= ~MOUSE_PARAMS_DIRTY
dirty_flags |= MOUSE_ICON_DIRTY
params[idx] = "[value]"
if("icon-pos")
if(dirty_flags & MOUSE_PARAMS_DIRTY)
params = params2list(params)
dirty_flags &= ~MOUSE_PARAMS_DIRTY
dirty_flags &= ~MOUSE_ICON_DIRTY
icon_pos = value
params["icon-x"] = "[value[1]]"
params["icon-y"] = "[value[2]]"
else
return value
return value
This one's pretty repetitive, and actually is likely unnecessary, but it can't hurt to define it.
Now I hear you: "But Ter, I already do things differently, and I'm used to it. Why should I spend all the time changing?" I have an answer for you: You don't have to:
proc
params2listx(str)
return params2list(str)
var/__paramstring
#define params2list(x) (istype((__paramstring=(x)),/mouse_params) ? (__paramstring = __paramstring["params-list"]) : params2listx(__paramstring))
Put this define anywhere after mouse_params and her operator overloads are declared, and now we can treat a param string and a mouse_params datum as the same thing. Yes, this will generate a little extra overhead until you migrate away from calling params2list() on mouse procs, but it's a good temporary fix while you implement the new behavior a bit at a time.
So with all of that done, let's take a look at what this allows you to do:
We can now quickly figure out what mouse buttons have been pressed without complex if-else chains:
turf
Click(atom/location,control,params)
switch(params["buttons"] & MOUSE_BUTTONS)
if(MOUSE_BUTTON1)
world.log << "left click"
if(MOUSE_BUTTON2)
world.log << "right click"
if(MOUSE_BUTTON3)
world.log << "middle click"
We can also do the same with mouse-modifiers:
turf
Click(atom/location,control,params)
switch(params["buttons"] & MOUSE_MODIFIERS)
if(MOUSE_SHIFT)
world.log << "shift held"
if(MOUSE_CTRL)
world.log << "ctrl held"
if(MOUSE_ALT)
world.log << "alt held"
if(MOUSE_SHIFT|MOUSE_CTRL)
world.log << "ctrl+shift held"
if(MOUSE_SHIFT|MOUSE_ALT)
world.log << "alt+shift held"
if(MOUSE_SHIFT|MOUSE_ALT|MOUSE_CTRL)
world.log << "ctrl+alt+shift held"
We can now get the icon pos as a number directly:
turf
Click(atom/location,control,params)
world.log << "[params["icon-pos"][1]] [params["icon-pos"][2]]"
//or:
world.log << "[params["icon-x"]] [params["icon-y"]]"
//or:
var/list/l = params["icon-pos"]
world.log << "[l[1]] [l[2]]"
Same for vis pos
turf
Click(atom/location,control,params)
world.log << "[params["vis-pos"][1]] [params["vis-pos"][2]]"
//or:
world.log << "[params["vis-x"]] [params["vis-y"]]"
//or:
var/list/l = params["vis-pos"]
world.log << "[l[1]] [l[2]]"
We can also get the screen-loc in pixel coordinates easily:
turf
Click(atom/location,control,params)
var/list/l = params["screen-pos"]
world.log << "[l[1]] [[l[2]]"
You can get the params string:
turf
Click(atom/location,control,params)
world.log << params["params"]
You can also get the params list:
turf
Click(atom/location,control,params)
var/list/l = params["params-list"]
Every other property can be accessed the same way.
In addition, in the case where you access any of the variables of the params, it will persist down the call chain, so it does not need to be re-parsed by overrides further up, so it will reduce wasted cycles handling data that's already been handled and discarded.
Once the mouse proc ends, the param datum is discarded automatically. No fuss, no muss.