ID:2361708
 
(See the best response by Reformist.)
Code:
obj
tech
Manablast
icon='blast.dmi'
icon_state="energyblast"
Click()
var/turf/techloc = get_step(usr.loc,usr.dir)
var/obj/projectiles/manablast/M = new(techloc)
M.owner = usr
M.dir = usr.dir
M.Fire()


Problem description:
Using step_size set to 8 for mobs and objs (a default for a new project these days) looks very smooth, I prefer it to moving an entire tile. But I'm having a problem where the projectile is sometimes created ontop of the player instantly causing Bump() to be activated.
In the Fire() proc I have it set to walk(src,src.dir,1)
I would like to be able to create a new object in front of the usr with the correct amount of pixels so there's no accidental collision. What would be a good way to go about this?
Best response
If you're looking to create it in front of the person casting it, offset it by the person's step_x and step_y variables. However, this is just a first step. Unless there is a valid reason to allow people to collide with their own projectiles you should completely remove their ability to anyhow.
Thank you

I added

M.step_x = usr.step_x
M.step_y = usr.step_y

And that did center the projectile correctly. Still having problems with the players own projectile hitting them, which shouldn't ever happen unless the player runs around it and intentionally jumps back in front of it.
Would detecting the players direction then doing something like...

M.step_x = usr.step_x+16

be a good solution or would there be a better one?

And despite the hypothetical I posted above nothing in my project would benefit from the player being able to jump in front of their own attack
In response to Narmanb
Again, you can simply remove the ability for the projectile to collide with the person who created it.
I just back in to byond after a long hiatus thank you for your time!
Reformist's suggestion doesn't really fix the problem.

The problem is that you are creating the projectile in the wrong place because you aren't doing the necessary math.

To calculate the initial position of the projectile, you need to know four things:


1) The position of the player.

2) The dimensions of the player.

3) The dimensions of the projectile.

4) The direction of offset.

This little bit of code is gonna help us with #4:

var
//declare lookup tables for directional offsets
list/__dir2x = list(0,0,0,1,1,1,1,-1,
-1,-1,-1,0,0,0,0)
list/__dir2y = list(1,-1,0,0,1,-1,0,0,
1,-1,0,0,1,-1,0)
//declare storage variable for upcoming shortcuts
list/__xydir

//declare cleaner shortcuts for converting directions to offsets
#define dir2x(dir) ((__xydir=(dir&15)) >= 1 ? __dir2x[__xydir] : 0)
#define dir2y(dir) ((__xydir=(dir&15)) >= 1 ? __dir2y[__xydir] : 0)


For 1-3, we need to understand a few things:

The distance between the center points of two rectangles whose edges touch, and whose centers are aligned on a single axis is:

(Aw + Bw)/2.

Aw is the size of the axis of offset of one rectangle, and Bw is the size of the axis of offset of the other rectangle.

The center point of a rectangle can be figured out via:

Ax + Aw/2 , Ay + Ah/2, or half the width plus the x position of the lower left corner, half the height plus the y position of the lower left corner.

Seems pretty self explanatory, yeah?

To place the projectile, we need to know its location, but we only have its size. That means we need to work from the player's location and size to find the center, then offset by the size of the projectile to find the center, and then subtract half the projectile's size along the axis of offset to find the lower-left corner.

Again, we can make this easy for ourselves by setting up a handy shortcut:

#define TILE_WIDTH 32 //change this to suit your project's needs.
#define TILE_HEIGHT 32 //change this to suit your project's needs.

//the fact that these aren't defined under /atom is a major problem with DM. It makes code uglier. We can fix it with a simple hack.
atom
var
step_x = 0
step_y = 0
bound_x = 0
bound_y = 0
bound_width = TILE_WIDTH
bound_height = TILE_HEIGHT

var
turf/__loc_turf
__loc_step_x
__loc_step_y
proc
offset2loc(atom/ref,xoff,yoff)
__loc_step_x = ref.x * TILE_WIDTH + ref.step_x + xoff
__loc_step_y = ref.y * TILE_HEIGHT + ref.step_y + yoff
__loc_turf = locate(__loc_step_x / TILE_WIDTH, __loc_step_y / TILE_HEIGHT, ref.z)

if(__loc_turf)
__loc_step_x %= TILE_WIDTH
__loc_step_y %= TILE_HEIGHT
else
__loc_step_x = 0
__loc_step_y = 0

return __loc_turf


Now let's touch up your code a bit and work out how to calculate the offset of the desired location given the dimensions of the player and the projectile.

obj
tech
Manablast
icon='blast.dmi'
icon_state="energyblast"
Click()
var/obj/projectiles/manablast/M = new()

//break down the direction into normalized offsets
var/d = usr.dir, xfac = dir2x(d), yfac = dir2y(d)

//store the half-dimensions of the player and the projectile
var/uhw = usr.bound_width / 2, uhh = usr.bound_height / 2
var/phw = M.bound_width / 2, phh = M.bound_height / 2

//calculate the magnitude of the axis offsets,
var/xoff = xfac ? (uhw + phw) * xfac - (phw - uhw) : uhw - phw
var/yoff = yfac ? (uhh + phh) * yfac - (phh - uhh) : uhh - phh

M.owner = usr

//attempt to move to the desired location.
M.Move(offset2loc(usr,xoff,yoff),d,__loc_step_x,__loc_step_y)
M.Fire()


This code is ugly. And it's not something you want to jam into every single projectile you work with. Let's fix that by making a generalized helper function.

proc
projectile(type,mob/owner,atom/source,dir)
var/obj/projectiles/p = new type()

//break down the direction into normalized translation offsets
var/xfac = dir2x(dir), yfac = dir2y(dir)

//store the half-dimensions of the player and the projectile
var/uhw = source.bound_width / 2, uhh = source.bound_height / 2
var/phw = p.bound_width / 2, phh = p.bound_height / 2

//calculate the magnitude of translation
var/xoff = xfac ? (uhw + phw) * xfac - (phw - uhw) : uhw - phw
var/yoff = yfac ? (uhh + phh) * yfac - (phh - uhh) : uhh - phh

p.owner = owner

//attempt to move to the desired location.
p.Move(offset2loc(source,xoff,yoff),dir,__loc_step_x,__loc_step_y)

return p



Now let's go back and clean up:

obj
tech
Manablast
icon='blast.dmi'
icon_state="energyblast"
Click()
var/obj/projectiles/manablast/M = projectile(/obj/projectiles/manablast,usr,usr,usr.dir)
M.Fire()
Sorry it took me so long to reply.

And wow thank you that's extremely helpful. Works perfectly and the in depth explanation is appreciated, it'll definitely make my project more stream lined.

I do have one more question not sure if I should make a new thread.. I read the DM reference section about pixel movement. I think I understand setting boundary boxes, I tested it out with a very small object and i was about to move my character right up to it. Anyways, I used bounds_dist() in making an "Attack" command and it seems to function correctly but do you know if it calculates the distance from the center of the reference and target? If it measures from a corner I need to get it more accurate and not sure how to figure it out.

Anyways, thank you again for your help!
In response to Narmanb
bounds_dist() calculates distance from edges.
Ah alrighty thank you!