Files
2025-02-22 16:31:23 +01:00

308 lines
7.9 KiB
Plaintext

//Designed for things that need precision trajectories like projectiles.
//Don't use this for anything that you don't absolutely have to use this with (like projectiles!) because it isn't worth using a datum unless you need accuracy down to decimal places in pixels.
//You might see places where it does - 16 - 1. This is intentionally 17 instead of 16, because of how byond's tiles work and how not doing it will result in rounding errors like things getting put on the wrong turf.
#define RETURN_PRECISE_POSITION(A) new /datum/position(A)
#define RETURN_PRECISE_POINT(A) new /datum/point(A)
#define RETURN_POINT_VECTOR(ATOM, ANGLE, SPEED) {new /datum/point/vector(ATOM, null, null, null, null, ANGLE, SPEED)}
#define RETURN_POINT_VECTOR_INCREMENT(ATOM, ANGLE, SPEED, AMT) new /datum/point/vector(ATOM, null, null, null, null, ANGLE, SPEED, AMT)
/**
* Stores x/y/z and pixel_x/pixel_y
*/
/datum/position
var/x = 0
var/y = 0
var/z = 0
var/pixel_x = 0
var/pixel_y = 0
/datum/position/proc/valid()
return x && y && z && !isnull(pixel_x) && !isnull(pixel_y)
/datum/position/New(_x = 0, _y = 0, _z = 0, _pixel_x = 0, _pixel_y = 0) //first argument can also be a /datum/point.
if(istype(_x, /datum/point))
var/datum/point/P = _x
var/turf/T = P.return_turf()
_x = T.x
_y = T.y
_z = T.z
_pixel_x = P.return_px()
_pixel_y = P.return_py()
else if(istype(_x, /atom))
var/atom/A = _x
_x = A.x
_y = A.y
_z = A.z
_pixel_x = A.pixel_x
_pixel_y = A.pixel_y
x = _x
y = _y
z = _z
pixel_x = _pixel_x
pixel_y = _pixel_y
/datum/position/proc/return_turf()
return locate(x, y, z)
/datum/position/proc/return_px()
return pixel_x
/datum/position/proc/return_py()
return pixel_y
/datum/position/proc/return_point()
return new /datum/point(src)
// todo: shouldn't be global scope
/proc/point_midpoint_points(datum/point/a, datum/point/b) //Obviously will not support multiZ calculations! Same for the two below.
var/datum/point/created = new
created.x = a.x + (b.x - a.x) / 2
created.y = a.y + (b.y - a.y) / 2
created.z = a.z
return created
// todo: shouldn't be global scope
/proc/pixel_length_between_points(datum/point/a, datum/point/b)
return sqrt(((b.x - a.x) ** 2) + ((b.y - a.y) ** 2))
// todo: shouldn't be global scope
/**
* @return angle between A and B, as degrees **clockwise from north**
*/
/proc/angle_between_points(datum/point/a, datum/point/b)
return arctan((b.y - a.y), (b.x - a.x))
/**
* A precise point on the map.
*
* x/y are absolute pixels from map edge, so 1, 1 = the lower-left most pixel on the zlevel, not the center of turf (1,1)!
*/
/datum/point
var/x = 0
var/y = 0
var/z = 0
/datum/point/proc/valid()
return x && y && z
/datum/point/proc/copy_to(datum/point/p = new)
p.x = x
p.y = y
p.z = z
return p
// todo: get first of first argument wrapping, use to_point() on /datum/position and from_atom() / from_position() on /datum/point
/datum/point/New(_x, _y, _z, _pixel_x = 0, _pixel_y = 0) //first argument can also be a /datum/position or /atom.
if(istype(_x, /datum/position))
var/datum/position/P = _x
_x = P.x
_y = P.y
_z = P.z
_pixel_x = P.pixel_x
_pixel_y = P.pixel_y
else if(istype(_x, /atom))
var/atom/A = _x
_x = A.x
_y = A.y
_z = A.z
_pixel_x = A.pixel_x
_pixel_y = A.pixel_y
initialize_location(_x, _y, _z, _pixel_x, _pixel_y)
/datum/point/proc/initialize_location(tile_x, tile_y, tile_z, p_x = 0, p_y = 0)
if(!isnull(tile_x))
x = ((tile_x - 1) * WORLD_ICON_SIZE) + WORLD_ICON_SIZE / 2 + p_x + 1
if(!isnull(tile_y))
y = ((tile_y - 1) * WORLD_ICON_SIZE) + WORLD_ICON_SIZE / 2 + p_y + 1
if(!isnull(tile_z))
z = tile_z
/datum/point/proc/debug_out()
var/turf/T = return_turf()
return "\ref[src] aX [x] aY [y] aZ [z] pX [return_px()] pY [return_py()] mX [T.x] mY [T.y] mZ [T.z]"
/**
* angle is clockwise from north
*
* @return self
*/
/datum/point/proc/shift_in_projectile_angle(angle, distance)
x += sin(angle) * distance
y += cos(angle) * distance
return src
/**
* doesn't use set base pixel x/y
*
* if not on a turf, we return null
*/
/datum/point/proc/instantiate_movable_with_unmanaged_offsets(typepath, ...)
ASSERT(ispath(typepath, /atom/movable))
// todo: inline everything
var/turf/where = return_turf()
if(!where)
return
var/atom/movable/created = new typepath(arglist(list(where) + args.Copy(2)))
created.pixel_x = return_px()
created.pixel_y = return_py()
return created
/**
* return rounded pixel x
*/
/datum/point/proc/return_px()
// 1 = -15,
// 32 = +16
// we start at 16, 16
. = x % WORLD_ICON_SIZE
if(!.)
return WORLD_ICON_SIZE * 0.5
. -= WORLD_ICON_SIZE * 0.5
/**
* return rounded pixel y
*/
/datum/point/proc/return_py()
// 1 = -15,
// 32 = +16
// we start at 16, 16
. = y % WORLD_ICON_SIZE
if(!.)
return WORLD_ICON_SIZE * 0.5
. -= WORLD_ICON_SIZE * 0.5
/**
* return turf
*/
/datum/point/proc/return_turf()
return locate(
ceil(floor(x) / WORLD_ICON_SIZE),
ceil(floor(y) / WORLD_ICON_SIZE),
z,
)
/**
* extract closest in-bounds turf
*
* does not check for map transitions
*/
/datum/point/proc/clamped_return_turf()
return locate(
clamp(ceil(floor(x) / WORLD_ICON_SIZE), 1, world.maxx),
clamp(ceil(floor(y) / WORLD_ICON_SIZE), 1, world.maxy),
z,
)
/**
* return list(x, y, z)
*/
/datum/point/proc/return_coordinates() //[turf_x, turf_y, z]
return list(
ceil(floor(x) / WORLD_ICON_SIZE),
ceil(floor(y) / WORLD_ICON_SIZE),
z,
)
/datum/point/vector
/// Pixels per iteration
var/speed = 32
var/iteration = 0
var/angle = 0
/// Calculated x movement amounts to prevent having to do trig every step.
var/mpx = 0
/// Calculated y movement amounts to prevent having to do trig every step.
var/mpy = 0
var/starting_x = 0 //just like before, pixels from EDGE of map! This is set in initialize_location().
var/starting_y = 0
var/starting_z = 0
/datum/point/vector/New(_x, _y, _z, _pixel_x = 0, _pixel_y = 0, _angle, _speed, initial_increment = 0)
..()
initialize_trajectory(_speed, _angle)
if(initial_increment)
increment(initial_increment)
/datum/point/vector/initialize_location(tile_x, tile_y, tile_z, p_x = 0, p_y = 0)
. = ..()
starting_x = x
starting_y = y
starting_z = z
/datum/point/vector/copy_to(datum/point/vector/v = new)
..(v)
v.speed = speed
v.iteration = iteration
v.angle = angle
v.mpx = mpx
v.mpy = mpy
v.starting_x = starting_x
v.starting_y = starting_y
v.starting_z = starting_z
return v
/datum/point/vector/proc/initialize_trajectory(pixel_speed, new_angle)
if(!isnull(pixel_speed))
speed = pixel_speed
set_angle(new_angle)
/// Calculations use "byond angle" where north is 0 instead of 90, and south is 180 instead of 270.
/datum/point/vector/proc/set_angle(new_angle)
if(isnull(angle))
return
angle = new_angle
update_offsets()
/datum/point/vector/proc/update_offsets()
mpx = sin(angle) * speed
mpy = cos(angle) * speed
/datum/point/vector/proc/set_speed(new_speed)
if(isnull(new_speed) || speed == new_speed)
return
speed = new_speed
update_offsets()
/datum/point/vector/proc/increment(multiplier = 1)
iteration++
x += mpx * (multiplier)
y += mpy * (multiplier)
/datum/point/vector/proc/return_vector_after_increments(amount = 7, multiplier = 1, force_simulate = FALSE)
var/datum/point/vector/v = copy_to()
if(force_simulate)
for(var/i in 1 to amount)
v.increment(multiplier)
else
v.increment(multiplier * amount)
return v
/datum/point/vector/proc/on_z_change()
return
/datum/point/vector/processed // pixel_speed is per decisecond.
var/last_process = 0
var/last_move = 0
var/paused = FALSE
/datum/point/vector/processed/Destroy()
STOP_PROCESSING(SSprojectiles, src)
return ..()
/datum/point/vector/processed/proc/start()
last_process = world.time
last_move = world.time
START_PROCESSING(SSprojectiles, src)
/datum/point/vector/processed/process(delta_time)
if(paused)
last_move += world.time - last_process
last_process = world.time
return
var/needed_time = world.time - last_move
last_process = world.time
last_move = world.time
increment(needed_time / SSprojectiles.wait)