ID:2423615
 
Given the code spans across several files, I will explain what will happen. If needed, I will post the full code if the problem itself isn't obvious.

When New is called:
animate(src, pixel_x = pixel_x + vel_x*lifetime, pixel_y = pixel_y + vel_y*lifetime, time = lifetime*0.5)


When the projectile is updated:
/obj/projectile/proc/update_projectile()

pixel_x_float += vel_x
pixel_y_float += vel_y

var/current_loc_x = x + floor(pixel_x_float / TILE_SIZE)
var/current_loc_y = y + floor(pixel_y_float / TILE_SIZE)

if(last_loc_x != current_loc_x || last_loc_x != current_loc_y)

current_loc = locate(current_loc_x,current_loc_y,z)

if(!is_turf(previous_loc))
on_hit(previous_loc)
return

if(!is_turf(src.loc))
on_hit(src.loc)
return

var/turf/new_turf = current_loc
var/turf/old_turf = previous_loc

if(!previous_loc || !current_loc)
on_hit(src)
return FALSE

if(new_turf.density)
on_hit(new_turf)
return

if(vel_y > 0)
if(old_turf.density_north)
on_hit(old_turf)
return
if(new_turf.density_south)
on_hit(new_turf)
return
else if(vel_y < 0)
if(old_turf.density_south)
on_hit(old_turf)
return
if(new_turf.density_north)
on_hit(new_turf)
return

if(vel_x > 0)
if(old_turf.density_east)
on_hit(old_turf)
return
if(new_turf.density_west)
on_hit(new_turf)
return
else if(vel_x < 0)
if(old_turf.density_west)
on_hit(old_turf)
return
if(new_turf.density_east)
on_hit(new_turf)
return

for(var/atom/A in new_turf.contents)
if(A == owner)
continue

if(A.density || is_mob(A))
if(on_hit(A))
return

previous_loc = current_loc


The terrible "master controller"
/world/proc/life()
set background = TRUE

active_subsystems = new(SS_ORDER_SIZE)

for(var/S in subtypesof(/datum/subsystem))
var/datum/subsystem/new_subsystem = new S
if(!new_subsystem.priority)
del(S)
continue
active_subsystems[new_subsystem.priority] = new_subsystem

spawn while(TRUE)
for(var/datum/subsystem/S in active_subsystems)
if(!S.tick_rate || S.next_run <= ticks)
if(!S.on_life())
del(S)
continue
S.next_run = ticks + S.tick_rate

curtime += TICK_LAG
ticks += 1
sleep(tick_lag)



So I attempted to make pixel-based projectiles but I'm having serious trouble getting them to work. They work perfectly on my end, however clients connected to my server cannot see the bullets for some reason.

The server's FPS is 60. The client's FPS is also 60.

When spawned, the following happens:
1. The icon itself is changed so that the bullet points in the direction it is traveling.
2. Animate is called. pixel_x is set to the x value of it's normalized velocity times it's lifetime in deciseconds, and pixel_y is set to the y value of it's normalized velocity times it's lifetime in deciseconds.
3. A Master Controller sends a signal to the bullet every tenth of a second to calculate it's collisons. The bullet's visible animation does not change during this.

Again, this works perfectly on my end however all clients cannot see the bullets most of the time. Usually they claim that the bullet appears several tiles in front of them, despite them having full visibility of where the bullet spawned and where the bullet. I can see other people's created bullets quite well as well.

So what is happening here? Connection issues? Bad code? Is there another method I should be using?
Quick tip before I go into my response:
Internally, fps and tick_lag are float values and when set they get converted to an integer tick speed value that is measured in milliseconds.

So might look something like this:
int tick_speed = (int)(1000 / fps)


So with that in mind, you want your FPS value to be a factor of 1000 or you'll lose some precision when the conversion happens. I personally like 40FPS for reasons not worth getting into but dealer's choice.


Now to the real problem you're having. It's probably a latency issue. The bullet is appearing several tiles in front of your players because by the time the client knows to render the animation, the bullet has already traveled a bit. It looks fine to you because, assuming you're playing on the same machine the server is running on, you have essentially zero latency between your own client and the server.

There's two solutions to your issue, one that I know will work and one that I haven't ever tried myself but should work in theory. I'll go over both so you can try out the theoretical one if you want.



I'll go with the theoretical solution first since it's shorter. The idea is that since latency is what is causing the issues for you, you just need to account for latency in your animate() call.

Byond doesn't have a built-in latency proc or var unfortunately but lucky for us our local Byond Guru Ter13 came up with a nice solution for that here: http://www.byond.com/ forum/?post=99653&page=2#comment21759302

He calls it ping but that's technically wrong, this solution gives you latency. Ping is round trip time which is generally around double what latency is but I digress.

Now you should be able to do something like this to fix your lag problem:
// When the projectile is created pass the client of the mob // that shoots the projectile in as an arg
// (unless the shooter is not controlled by a client)
/obj/projectile/New(client/C = null)
// Blah Blah
animate(src, pixel_x += (C ? (vel_x * (lifetime - (C.ping * 10)) : (vel_x * lifetime)), pixel_y += (C ? (vel_y * (lifetime - (C.ping * 10)) : (vel_y * lifetime)), time = lifetime * 0.5)

This felt gross to even type, no promises this will work at all but if anything is going to work at all with your current setup, it's probably this.



What I know will work is just completely scrapping the idea of using animate() or an update_projectile() proc. Instead, I'd just handle all projectile logic through the movement procs supplied by atom/movable.

It's not yet documented but as of the latest update (512), step_x, step_y, and step_size all accept non-integer values. Before this change it was preferable to create a custom movement system for pixel-precise projects because, even though step_size is essentially velocity, it was constrained to whole numbers only which made movement and collision detection clunky.

In addition to the step changes in 512, Cross(), Crossed(), Uncross(), and Uncrossed() are available to all atoms, not just movables.

With all those changes made in 512, using Byond's pixel movement system is easier, if not straight up better, than coming up with a custom one.

Using this method, instead of using animate() you could do something like this:

/client/Click(object, location, control, params)
// Blah blah blah
if (isturf(location) && mob && mob.can_shoot)
new /obj/projectile(mob.loc, get_dir(mob, location))
// Blah blah blah

/obj/projectile
var/range = 100 // This how many pixels the projectile can travel. The value of 100 is completely arbitrary.
// Other vars

/obj/projectile/New(direction)
shoot(direction)

/obj/projectile/proc/shoot(direction)
set waitfor = 0
while(range > 0 && loc && step(src, direction))
range -= step_size
sleep(world.tick_lag)