ID:2059226
 
(See the best response by Ter13.)
Code:
client
show_popup_menus = 0
var
image/cursor

MouseDown(atom/a, turf/t, control, params)
params = params2list(params)
if("right" in params)
src.RightClick(t, text2num(params["icon-x"]), text2num(params["icon-y"]))
else
return ..()

proc
RightClick(atom/movable/o, icon_x, icon_y)
if(src.cursor)
del src.cursor

if(!o)
return

if(istype(o))
icon_x += o.step_x + o.pixel_x
icon_y += o.step_y + o.pixel_y
o = o.loc

src.cursor = image('mover.dmi', o)
o.pixel_x = icon_x - 16
o.pixel_y = icon_y - 16
src << src.cursor


Problem description:

Reference: https://gyazo.com/055c6b9f252239d8198934a6352d00b3
Turf moves when clicked by the cursor.
o.pixel_x = icon_x - 16
o.pixel_y = icon_y - 16

Because you are changing the turf's pixel_x and y. if you right click on the turf.
In response to Ter13
Well I overlooked that, but another question I have is when I click the same exact spot twice, the cursor moves, how do I prevent that?

https://gyazo.com/fc9ed3bc6f2ed03523c8b42789386692
In response to Neimo
You're not clicking the same spot twice if the turf's icon is moving beneath a cursor that isn't moving...

I'd just make a new obj for your menu and move that to the mouse when you need it. Still use the image object.
In response to Kaiochao
I'm not understanding that, I should create a obj?
Best response
The cursor is moving because your calculations for cursor position are wrong.

Some new features have made their way into the language that can make what you are doing a bit more reliable.

#define TILE_WIDTH 32
#define TILE_HEIGHT 32

#define CURSOR_WIDTH 32
#define CURSOR_HEIGHT 32

#define clamp(v,l,h) max(min(v,l),h)

var
regex_mouseloc = regex_mloc = regex("\[:,]")
client
var
image/cursor

New()
. = ..()
if(.) //if we successfully logged into a mob
cursor = new/image() //initialize the cursor
cursor.icon = 'mover.dmi'
images += cursor
Del()
cursor.loc = null //make sure the cursor gets deleted by setting loc to null
..()

MouseDown(atom/a, turf/t, control, params)
params = params2list(params)
if(t) //if the object is on the map
var/list/p = params2list(params)
if(p["right"])
var/list/l = splittext(p["screen-loc"],regex_mouseloc) //process the screen location of the click into a list of 4 raw numbers
//using client.bounds, we can now calculate the map location of the click in pixel coordinates
var/px = (text2num(l[1])-1)*TILE_WIDTH + text2num(l[2])-1 + bound_x
var/py = (text2num(l[3])-1)*TILE_HEIGHT + text2num(l[4])-1 + bound_y
//move the cursor
MoveCursor(px,py,eye:z)
return
return ..()

proc
MoveCursor(wx,wy,z)
if(z)
//calculate tile within map borders so the click is always accurate
var/tx = clamp(round(wx/TILE_WIDTH+1),1,world.maxx)
var/ty = clamp(round(wy/TILE_HEIGHT+1),1,world.maxy)
//find the remaining pixel offset
wx -= tx*TILE_HEIGHT
wy -= ty*TILE_WIDTH
cursor.loc = locate(tx,ty,eye.z) //move the cursor into position
cursor.pixel_x = wx + CURSOR_WIDTH/2 //center the cursor's pixel coordinates the the pixel clicked on
cursor.pixel_y = wy + CURSOR_HEIGHT/2
else
cursor.loc = null


The above example uses the correct math and doesn't rely on icon-x and icon-y. Icon-x and icon-y should not be considered reliable in the context of mouse procs that do not specifically involve an atom's visual appearance.

Recently, I requested that Lummox add client.bounds to the language. He did so almost immediately. Using client.bounds allows you to do a much more accurate job of calculating the global coordinates that the player is interacting with.

As for splittext() and regex(), these are new text handling functions introduced in the most recent versions of BYOND. splittext() now makes it much less of a pain in the ass to work with screen_loc-based values by splitting up a string into multiple substrings using a delimeter. regex() allows you to perform complex text processing very easily if you understand the syntax, which is a bit of a godsend.

Regex is a bit of an advanced topic, but the regex pattern I'm using is very simple. It simply splits the text anywhere it finds ";" or ",". The rest of the work happens from good ol' text2num() to convert the text into numbers.

I also removed the bit where you delete the cursor every time you move it. Manually deleting objects that are still referenced by other objects is slow. The bigger your world is, the less you can get away with it. Since every client needs to have a cursor, we should initialize it when the client is created or when the client first requires it. When we don't need it, we just move it to null. When the client logs out, we make sure its location is null and it will delete itself once the client no longer exists. Much better practice.