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


Problem description:

I'm not sure if this is a bug or what but if you shoot the beam, and then walk back and forth, sometimes damage doesn't apply its like its skipping and going through. This can be done at a lower FPS if needed. Also if you host with 2 people, and 1 person shoots it towards you and another does the back and forth same thing will happend.

How to replicate the issue:

Shoot Beam > walk left past it > Walk Right past it. Sometimes damage doesn't apply

Shoot Beam > Walk left past it > stop and not move > damage apply.


2015-07/Zasif-0001/projectile.rar

Best response
K.loc = locate(usr.x,usr.y+1,usr.z)

^Move isn't called when you manually locate stuff.

Move()

Damage is only triggered when the beam moves on top of the mob.

You also want to trigger damage on Crossed().

Basically, what's going on in your bug, is that you are calling the damage routine right before the head of the projectile leaves the tile. Since the projectile moves only once every 8 frames, there is a chance the player is going to move out of the way before Move() is called on that square, allowing them to zip right on through a projectile.

In order to fix this, you need to stop depending on searching behavior in Move(), and take advantage of work the built-in engine does for you. Basically, what I'm going to do to your snippet, is I'm going to define a few new procs that will allow you to detect all kinds of information regarding movements that you otherwise would not be able to access.

These functions are:

onEnter(): called on a movable when it tries to enter an atom. Return 1 to allow, 0 to disallow
onExit(): called on a movable when it tries to exit an atom. Return 1 to allow, 0 to disallow
onEntered(): called after a movable has succeeded in entering an atom. Does nothing by default.
onExited(): called after a movable has succeeded in exiting an atom. Does nothing by default.

onCross(): called when this movable tries to overlap another. 1 to allow 0 to disallow
onUncross(): called when this movable tries to stop overlapping another. 1 to allow 0 to disallow
onCrossed(): called when this movable starts overlapping another. Does nothing by default
onUncrossed(): called when this movable stops overlapping another. Does nothing by default

Bumped(): Called when a movable bumps into this atom.

I'm changing a lot of built-in behavior of the collision system, so pay attention to the consequences and adapt your code accordingly. They should be obvious.

Sure, you can fix it without these overrides, but your method requires a polling loop every time the projectile moves. Mine doesn't. No loops, perfect reliability, no skipping, no mistakes.

world
fps = 40

view = 8
obj

Tail
icon='beam.dmi'
icon_state = "tail"

layer = MOB_LAYER+1

New(atom/loc,Dir,Duration)
..()
spawn(8) //you are using spawn wrong. You need to put del(src) tabbed under spawn()
del(src)
turf
icon = 'turf.dmi'
mob
var/health
icon = 'person.dmi'
Login()
usr.loc = locate(1,1,1) //don't use usr in Login()

obj
beam
icon = 'beam.dmi'
icon_state = "head"
layer = 100
density = 0
Bump(A)
if(istype(A,/turf/))
var/turf/T = A
if(T.density)
del(src)
if(istype(A,/obj/)) //this should be an else if
del(src)
Move()
for(var/mob/M in src.loc) //
var/damage = rand(1,20)
M.health -= damage
world <<"[M] got hit for [damage] damage!"

var/K = new/obj/Tail(src.loc)

K:dir = src.dir
..() //you MUST ALWAYS return a value from Move()



mob
verb
Shoot_Beam()
var/obj/beam/K = new /obj/beam
if(src.dir == NORTH) //this should be a switch, not an if chain. Also, these four ifs should be else-ifs because otherwise it's incredibly inefficient. This is a bad habit you need to break.
K.loc = locate(usr.x,usr.y+1,usr.z) //You probably want to use Move()
if(src.dir == SOUTH)
K.loc = locate(usr.x,usr.y-1,usr.z)
if(src.dir == EAST)
K.loc = locate(usr.x+1,usr.y,usr.z)
if(src.dir == WEST)
K.loc = locate(usr.x-1,usr.y,usr.z)
K.dir = usr.dir
K.name=usr.name

walk(K,usr.dir,2) //This should probably be restructured.
spawn(16)
del(K)


I took the liberty of pointing out a few things you are doing that show some misunderstandings in how you write code. But it can all be streamlined quite a bit and your issue will be fixed easily by doing a few things with collision analogues.

//Just some quick analogues. Basically, these allow better projectile and collision programming a lot easier.
//onEnter() is called on a movable atom when it tries to enter a turf. This lets the mover have the final say in what happens during a movement, not just the turf.
//on-variants are called on the opposite object of where their non-on variants are called.
atom
proc
Bumped(atom/movable/O)

Enter(atom/movable/O,atom/oldloc)
. = O.onEnter(src,oldloc)

Exit(atom/movable/O,atom/newloc)
. = O.onExit(src,newloc)

Entered(atom/movable/O,atom/oldloc)
O.onEntered(src,oldloc)

Exited(atom/movable/O,atom/newloc)
O.onExited(src,newloc)

movable
proc
onEnter(atom/O,atom/oldloc)
. = !(density&&O.density)

onExit(atom/O,atom/newloc)
. = 1

onCross(atom/movable/O)
. = !(density&&O.density)

onUncross(atom/movable/O)
. = 1

onEntered(atom/movable/O,atom/oldloc)
onExited(atom/movable/O,atom/newloc)
onCrossed(atom/movable/O)
onUncrossed(atom/movable/O)

Cross(atom/movable/O)
. = O.onCross(src)

Uncross(atom/movable/O)
. = O.onUncross(src)

Crossed(atom/movable/O)
O.onCrossed(src)

Uncrossed(atom/movable/O)
O.onUncrossed(src)

Bump(atom/O)
O.Bumped(src)


Now that we've done all that, we can actually just make projectiles!

mob
Login()
if(!loc) //if this is an initial login
loc = locate(1,1,1)
proc
takeDamage(dmg)
health -= dmg
world << "[src] got hit for [dmg] damage!"

verb
Shoot_Beam()
new/obj/beam(get_step(usr,usr.dir),usr.dir,usr.name,16)

obj
tail
icon = 'beam.dmi'
icon_state = "tail"

//again, pay attention to the parameters
New(loc,Dir,Duration=8)
..()
dir = Dir
spawn(Duration)
del src

beam
icon = 'beam.dmi'
icon_state = "head"
layer = 100
density = 0
var
tail_type = /obj/tail
tail_duration = 8

//pay attention to the parameters
New(atom/Loc,Dir,Name,Duration,Tail=/obj/tail,TailDuration=8)
name = Name
tail_type = Tail
tail_duration = TailDuration
Move(Loc,Dir)
if(loc) //if we were created in a valid location, start moving and delete ourselves when time expires
walk(src,dir,2)
spawn(Duration)
del src

//if we bumped something... Which shouldn't really be happening with a non-dense projectile
Bump(atom/o)
if(isturf(o))
if(o.density)
del src
else if(isobj(o))
del src

//called every time this object moves
Move(atom/NewLoc,Dir=0,step_x=0,step_y=0)
var/turf/oldloc = loc
. = ..()
if(.) //if the move was successful, create a new tail at the old location
new tail_type(oldloc,dir,tail_duration)

//called when this object is stepped on by an object
Crossed(mob/m)
if(istype(m))
m.takeDamage(rand(1,20)) //we've made this into a proc

//called when this object steps on top of an object
onCrossed(mob/m)
if(istype(m))
m.takeDamage(rand(1,20))
It's not really the code I use, but it does have same effect, was wondering about another thing, diagonal tails how would that work with tiled movement, stacking another layer of tails on top of the current tail using pixel_x/y?
The trick is to actually stretch each tile slightly to account for the length of the hypotenuse.

Basically, we can calculate the length of the diagonal beam:

TILE_DIAG = sqrt(TILE_WIDTH**2 + TILE_HEIGHT**2)


Assuming you use a 32x32 tile size, we're looking at:

TILE_DIAG = sqrt(1024 + 1024)
TILE_DIAG = sqrt(2048)
TILE_DIAG = 45.255 (approx, rounded up)

Next, we need to stretch the beam given the ratio of its length by the target length. The scaling factor should be:

45.255 / 32

Or approximately 1.4142

In order to apply a diagonal direction to a beam, we'd want to use transform.

var/matrix/m = matrix()
m.Scale(1.4142,1)
m.Turn(-45)
trail.transform = m


Now, we can actually speed this up a LOT if we precalculate our matrices and jam them into a global list:

var
list/dir_matrix = list(matrix(0,-1,0,1,0,0),matrix(0,1,0,-1,0,0),null,matrix(1,0,0,0,1,0),
matrix(0.99999,-0.707107,0,0.99999,0.707107,0),matrix(0.99999,0.707107,0,-0.99999,0.707107,0),null,
matrix(-1,0,0,0,-1,0),matrix(-0.99999,-0.707107,0,0.99999,-0.707107,0),matrix(-0.99999,0.707107,0,-0.99999,-0.707107,0))


Now we just update our tail and beam objects...

obj
tail
icon = 'beam.dmi'
icon_state = "tail"

//again, pay attention to the parameters
New(loc,Dir,Duration=8)
..()
dir = Dir
transform = dir_matrix[dir]
spawn(Duration)
del src

beam
icon = 'beam.dmi'
icon_state = "head"
layer = 100
density = 0
var
tail_type = /obj/tail
tail_duration = 8

//pay attention to the parameters
New(atom/Loc,Dir,Name,Duration,Tail=/obj/tail,TailDuration=8)
name = Name
tail_type = Tail
tail_duration = TailDuration
Move(Loc,Dir)
transform = dir_matrix[dir]
if(loc) //if we were created in a valid location, start moving and delete ourselves when time expires
walk(src,dir,2)
spawn(Duration)
del src


Now, here's the added benefit of this. You can remove the directions from the beam.dmi file. You only need a single direction, the east-facing one for each icon_state. Transform gives you the other 7 directions for free.
god this is so sexy
In response to Ter13
Ter, You should make a lib or a snippet sunday about tiled/pixel projectiles. :X
Ter, You should make a lib or a snippet sunday about tiled/pixel projectiles. :X

I actually have a library for pixel movement projectiles. It costs $150 a non-exclusive, non-transferable license.

I could be convinced to convert it to tiled projectiles that can move in full 360 degree angles, but I'd have to charge the original license fee plus another $50 for the additional work. (again, non-exclusive, non-transferable)

Projectiles are sort of my bread and butter. It's what I've done the most work with in the last year.
In response to Zasif
I'm pretty sure there are a few already from when pixel movement wasn't built-in. However, the solution of "stretching the beam to connect diagonally" has nothing to do with pixel projectiles.
In response to Kaiochao
Not really what I was suggesting, was more about a demo 1 - tiled movement demo 2 - pixel movemnt. :P
However, the solution of "stretching the beam to connect diagonally" has nothing to do with pixel projectiles.

Yep, it's pretty much unnecessary in a pixel beam system. It's actually easier if you are in pixel mode already anyway just to calculate the sin/cos values for the direction of the beam's travel and plop down objects without bothing to scale them. Stretching is pretty much irrelevant at a pixel level. It's useful for the tiled approach, but not so much the pixel approach.
Anyway, cheers, happy to be of service.
If the tail doesn't show up, could there be an issue with something else in the project that makes the tail not show up?