ID:2266358
 
(See the best response by Ter13.)
Code:
client

Move ()

point

var/x
var/y

turf/Click (location, control, params)
..()
var/dest = new (x * world.icon_size, y * world.icon_size)
var/paramslist = params2list (params)
var/px = text2num (paramslist ["icon-x"])
var/py = text2num (paramlist ["icon-y"])

dest.x += px
dest.y += py

walk_to (usr, dest)


Problem description:
Well I have been searching around and studying the snippets about this but I can't understand the most of them. I don't need Pathfinding cause the walk_to intelligence is enough for my design. I just need to click on a turf and the player move to it.
I've tryed different methods and the best I got was from:
turf/Click (location, control, params)
..()
walk_to (usr, src)

But the player do not stay on top of the turf clicked. Its like it colide with the turf i'm clicking.

I saw an example using the client drag and other using complex pixelmovement calculation (i'm newb so it looks complex to me). And I tried to use locate (dest) but the player walks randomly and awkwardly.

What I need to know:
Whats the best aproach for this simple click movement? (Im using the step_size for speed and don't care if the movement is not pixel precise)

Why no Pathfinding?
Cause I'm using the visibility of the player like in roguelikes instead of opening the view of the world. Since you can't see behind a wall you cant click behind it so I don't need a code to find a better way around walls.

Why not copy/paste snippets?
The ones I found do things too complex for my own capabilities of using it. They normally are not for the same purpose I'm looking for. And I'm not copying codes that I don't understand completly, the ones I understand I rewrite it.

Thanks in advance.
L 
L1
Lx
Ly

Split params, text to string and point is a datum. Datums don't take procedures.

What's wrong with this?
turf/Click ()
..()
walk_to (usr, src)


!?
Best response
...God damn it delta.

Looks like OP's done a lot of copy/paste to try to make this work.

point is a datum. walk_to doesn't take datums as arguments. It takes atoms.

The logic of what you are doing is more or less sound, except for the fact that your px/py calculations are inaccurate.



mouse procs have a parameter called "screen-loc", which contains the pixel coordinates of the mouse cursor's location relative to the viewport from the bottom-left corner in "tile_x:pixel_x,tile_y:pixel_y" format. It's a text string, so we need to split it.

var/list/l = params2list(params) //convert the param string to a list.

var/list/l2 = splittext(l["screen-loc"],",") //split the screen coordinates at the comma.

//now we need to split the two strings in l2 into separate strings for processing.
var/list/lx = splittext(l2[1],":")
var/list/ly = splittext(l2[2],":")

//now, convert the text into numbers and do the math to get the on-screen coordinates of the mouse action.
var/sx = text2num(lx[1]) * TILE_WIDTH + text2num(lx[2]) - 1
var/sy = text2num(ly[1]) * TILE_HEIGHT + text2num(ly[2]) - 1



Then we can convert the screen location into a world location:

//client.bound_x/y/width/height can be read to get the coordinates of the game viewport.
var/wx = client.bound_x + sx
var/wy = client.bound_y + sy


That's how we get a correct global coordinate from a mouse proc. Should it be this hard? No. But it is. Doing it using icon-x/icon-y causes all kinds of problems with things like pixel offsets and the like, so it's got a tendency of being inaccurate.


Filtering clicks and performing the walk is easy by comparison.

Basically, we need to check to filter clicks by testing what control was clicked in, and whether the object is in the map. Then we use the above math to figure out where the user clicked on the map, then we need to walk the mob toward that location.

//this is just a little convenience function to get the screen loc from a coordinate string into absolute (pixel) coordinates.
//for convenience, the bottom-left of the map is TILE_WIDTH,TILE_HEIGHT, instead of zero. This simplifies a lot of math.
//This may sound stupid, but the first tile in BYOND is 1,1, which makes things harder for you arbitrarily. 0,0 was always a better first index.
//these could probably be client procs.

proc
screen2coord(client/client,param)
//split the location string into two parts
var/list/l = splittext(param,",")
//then split those two parts into two parts.
var/list/lx = splittext(l[1],":")
var/list/ly = splittext(l[2],":")

//store the coordinates in the first list we created and return it
l[1] = (text2num(lx[1])) * TILE_WIDTH + text2num(lx[2])-1
l[2] = (text2num(ly[1])) * TILE_HEIGHT + text2num(ly[2])-1
return l

screen2world(client/client,param)
var/list/l = splittext(param,",")
var/list/lx = splittext(l[1],":")
var/list/ly = splittext(l[2],":")

//store the coordinates in the first list we created and return it
l[1] = (text2num(lx[1])) * TILE_WIDTH + text2num(lx[2])-1 + client.bound_x
l[2] = (text2num(ly[1])) * TILE_HEIGHT + text2num(ly[2])-1 + client.bound_y
return l

client
var
obj/move_target //keep an object around to help our mob move places.

New()
//called when the client first connects to the world.
. = ..()
if(.)
//create an object to serve as the move target
move_target = new/obj() //this could probably do with some behavior expansion and its own type
/*PIXEL MOVEMENT MODE:
move_target.bounds = "1,1 to 1,1"*/


Del()
//destroy the move target on logout by setting its loc to null. It will garbage collect provided your code isn't garbage.
if(move_target)
move_target.loc = null
..()

//override client.Click() to inject behavior for clicks on the map.

Click(atom/object,atom/location,control,params)
if(control=="default.map1") //make sure we're clicking within the map control (The id of your map control might be different)
if(isturf(location) && mob.z && object.z==mob.z) //make sure the object is on the map, and on the same z layer as the player's mob.

//convert the parameter list and convert the screen location into world coordinates
var/list/paramslist = params2list(params)
var/list/point = screen2world(src,paramslist["screen-loc"])

//move the move_target to the tile that was clicked on
//this could probably be its own function
move_target.loc = locate(point[1]/TILE_WIDTH,point[2]/TILE_HEIGHT,mob.z)
/*PIXEL MOVEMENT MODE:
move_target.step_x = point[1]-move_target.x*TILE_WIDTH
move_target.step_y = point[2]-move_target.y*TILE_HEIGHT*/


//walk the player toward the move_target. You might want to add a movement delay to this, or ditch walk in favor of a handrolled loop later.
walk_towards(mob,move_target)

//invoke the supercall. This performs the default action of Click(), which is to call object.Click()
..()


The above is the tile movement variant of this code. Pixel movement variant will have a few very small changes to calculate the step offset for the move_target. I've commented those out.

You'll need to define TILE_WIDTH and TILE_HEIGHT for this to work, BTW. Don't forget that step.

This is essentially the minimal, *correct* way to move to a specific map location from a click. You can't rely on the object's coordinates plus the icon-x/y. This is visual data. The only reliable coordinates relative to the player's viewport and therefore the world have to come directly from the screen coordinates passed to the mouse functions.

I know this isn't the easiest method in the world, but it's what you have to understand in order to be able to handle this kind of thing correctly. BYOND has some major weaknesses as an engine. Mouse tracking is one of them. I've done my absolute best to overhaul this with one of my own private libraries.
(Just
So the parameter(s) return the mouse's location, not just within the world but the turf.
Good
Thanks a lot Ter13 *u*

I was really confused about these things.
In response to Rocetti
Rocetti wrote:
Thanks a lot Ter13 *u*

I was really confused about these things.

BYOND makes this harder than it should be. You should see how hard this was before Lummox added client.bounds:

http://www.byond.com/forum/?post=1930793
Ter13 wrote:
BYOND makes this harder than it should be. You should see how hard this was before Lummox added client.bounds:

http://www.byond.com/forum/?post=1930793

Yeah! I saw this example in my research and got really confused. But since it was a little out dated I believed that it had another way. So I started trying to test things up.

Hope someday there will be an feature of mouse movement and other easier ways to do somethings for gamestyles different from action rpg/classical roguelike (which i see is as the main focus of the engine, am I wrong?!)
In response to Rocetti
For pixel movement, I have libraries that let you get a vector to the mouse and move toward it pretty easily. I know this isn't the exact kind of movement you wanted, but that's not very far from this.



This is all the code that you need to write for the above:

This is in the .dme:
// This is required by a couple of these libraries.
#define TileWidth 32
#define TileHeight 32

// This is code generated when you check the boxes in the Lib folder.
// You'll have to download these first.
#include <kaiochao\mousebuttontracking\MouseButtons\MouseButtons.dme>
#include <kaiochao\mouseposition\MousePosition.dme>
#include <kaiochao\pixelpositions\PixelPositions.dme>
#include <kaiochao\shapes\shapes.dme> // This is just for the demo's icons.
#include <kaiochao\subpixelmovement\SubPixelMovement.dme>

This is specific to the demo:
world
fps = 60
maxx = 25
maxy = 25
turf = /turf/grid
mob = /mob/player

// Just a gray checkered grid.
turf/grid
icon_state = "rect"
color = "silver"

New()
..()
if((x + y) & 1)
color = "gray"

// Blue circle.
mob/player
icon_state = "oval"
color = "blue"

This adds the "move towards the mouse while the mouse button is held" functionality:
mob/player
icon_state = "oval"
color = "blue"

var
speed = 4 // Pixels per tick.

Login()
..()
spawn while(client)

// If the left mouse button is pressed:
if(client.mouse_buttons.IsPressed(client.MouseLeft))
var
// Vector from the player to the mouse.
to_mouse_x = client.mouse_position.WorldX() - CenterX()
to_mouse_y = client.mouse_position.WorldY() - CenterY()

if(to_mouse_x || to_mouse_y)
var
// Rescale to_mouse to velocity.
// In the same direction, but with magnitude = speed.
scale_to_speed = speed / sqrt(to_mouse_x ** 2 + to_mouse_y ** 2)
velocity_x = to_mouse_x * scale_to_speed
velocity_y = to_mouse_y * scale_to_speed

// Move by the velocity vector.
PixelMove(velocity_x, velocity_y)

sleep world.tick_lag

Note: I recommend using a global update loop instead of spawning an infinite loop in Login(). I did it this way here for simplicity, but it's less efficient and harder to profile/debug.

This is also pretty much the same as how gamepad analog stick movement works; just change the to_mouse vector to the analog stick input vector. For variable speed, multiply the input vector by speed and don't divide by the sqrt().
:D
Thats really cool! Good to have this on this post. Maybe I'll test some of this libraries to learn more. Thanks a lot.