ID:2811101
 
(See the best response by Lummox JR.)
Okay, so I've tried half a dozen ways of doing this and can't seem to find a method that isn't janked. Part of me hoped that it would come to me as I described the problem...

Summary:

I am making a top-down turn-based game, and in the process of creating an "aiming" module which has to be pretty modular, depending on the kind of weapon being aimed. Overall, not too complicated - You move a "reticle" obj around, which is pointed to when you fire the weapon.

I ran into the issue when trying to highlight any tiles between you and the reticle (In a straight, single-tile path), AND highlight any tiles beyond the reticle that would be in the path of the projectile if it can penetrate your target. It would ideally show all possible tiles the projectile could path through extending out to the maximum range.

No problem in the cardinal directions, but when aiming at an intermediate angle, I can't figure out a good solution to keep a consistant line.

One method I tried was to keep "doubling" the difference in x&y between you and the reticle until it is outside the weapons range, then just do some pathing between you and that point - But this fails if you are too close to the edge of the map.

I've tried using using arctan to get a ratio between x-steps and y-steps, and then alternate, but continue to run into math issues.

Is there a simple solution to this kind of problem I am overlooking? Any ideas about the best way to come at the problem?



Code:
The aiming proc for weapons that can be manually aimed:
proc
GimbalAim(mob/M,,D,comp/C) // M: Player, D: The direction being passed, C: The weapon being used
if(M.reticle && M.aiming == C) //If you are already aiming this weapon using a reticle
step(M.reticle,D) //Move the reticle in the input direction
if(C.indirect == 0) //If the projectile paths straight to/through your reticle
ExtendPath(M,,D,C) //Draw the path extending from you to the reticle and beyond -- Currectly empty because I can't find a functional way of even doing this
else //Otherwise, just the reticle is fine (For "Mortar" style weapons)
return
else //Otherwise, create a reticle and set this weapon as being aimed
M.reticle = new/obj/reticle(M.loc)
M.aiming = C


Best response
If the projectile only has tile-based collisions, meaning you don't have to worry about it occupying more than one tile at any point along the way, you can use Bresenham's line algorithm.

It would look something like this:

proc/BresenhamLine(turf/start, turf/end)
var/dx = end.x - start.x, dy = end.y - start.y
var/ax = abs(dx), ay = abs(dy)
var/maindir, sidedir, len, inc
if(ax >= ay)
maindir = dx > 0 ? EAST : WEST
sidedir = dy > 0 ? NORTH : SOUTH
len = ax+1; inc = ay ? ay+1 : 0
else
maindir = dy > 0 ? NORTH : SOUTH
sidedir = dx > 0 ? EAST : WEST
len = ay+1; inc = ax ? ax+1 : 0
. = list(start)
var/acc = len/2
var/turf/T = start
for(var/n=len, --n,)
T = get_step(T, maindir)
if((acc += inc) > len)
acc -= len
T = get_step(T, sidedir)
. += T
In response to Lummox JR
Oh!~ Cool to see I was on the right track at least - My best attempt looked pretty similar to this, but was getting really messy - I think I can sort it out with that! Thanks so much dude!
In response to Lummox JR
this was my last attempt before things got kinda whack - was struggling with division by zero errors, and the like.

proc
ExtendPath(mob/M,QWER,D,comp/C)
if(M.reticle && get_dist(M,M.reticle) < C.range) //If the reticle is inside the weapons range
var
dx = M.reticle.x - M.x
dy = M.reticle.y - M.y
tx = dx //temp var x
ty = dy //temp var y
dirx // East or West
diry // North or South
obj/ret_end = new(M.loc)
switch(dx)
if(1 to 1000) dirx = 1
if(-1000 to -1) dirx = -1
else
dirx = 0
switch(dy)
if(1 to 1000) diry = 1
if(-1000 to -1) diry = -1
else
diry = 0
for(var/i = 0, i < C.range, i++)
if(abs(tx) == abs(ty))
ret_end.x += dirx
ret_end.y += diry
if(abs(tx) > abs(ty))
ret_end.x += dirx
tx--
if(tx < 0) tx = dx
else if(abs(ty) > abs(tx))
ret_end.y += diry
ty--
if(ty < 0) ty = dy
In response to Lummox JR
Okay! Finally got it working perfectly! I had to make a few modifications to get it to extend the full length of the weapons range, and I had to remove the "+1's" for the len and inc definitions though.

This is what it looked like in the end:

proc
ExtendPath(mob/M,turf/start,turf/end,comp/C)
var/dx = end.x - start.x , dy = end.y - start.y
var/ax = abs(dx) , ay = abs(dy)
var/maindir , sidedir , len , inc
for(var/turf/T in M.AimAt) //Cleaning up any earlier lines using a list I was already using in a different kind of aiming
T.overlays -= 'highlight.dmi' //Change this to modify an image object
if(ax >= ay)
maindir = dx > 0 ? EAST : WEST
sidedir = dy > 0 ? NORTH : SOUTH
len = ax; inc = ay
else if(ay > ax)
maindir = dy > 0 ? NORTH : SOUTH
sidedir = dx > 0 ? EAST : WEST
len = ay; inc = ax
M.AimAt = list(start) //Changed the "." to an external list so any previous lines can be cleared
var/acc = len/2 //Halfway point to the main length ??
var/turf/T = start
world<<"_________"
for(var/n=C.range, --n,)
world<<"n=[n] , acc=[acc] , inc=[inc] , len=[len]"
T = get_step(T, maindir)
if((acc += inc) > len)
acc -= len
T = get_step(T, sidedir)
T.overlays+='highlight.dmi' //Change this to an image object
M.AimAt += T