diff --git a/code/__HELPERS/unsorted.dm b/code/__HELPERS/unsorted.dm index 2ada9d04ea6..763ccf1a790 100644 --- a/code/__HELPERS/unsorted.dm +++ b/code/__HELPERS/unsorted.dm @@ -1901,20 +1901,6 @@ GLOBAL_DATUM_INIT(dview_mob, /mob/dview, new) LAZYSET(modifiers, "icon-y", "[(click_turf_py - click_turf.pixel_y) + ((click_turf_y - click_turf.y) * world.icon_size)]") return click_turf -/proc/params2turf(scr_loc, turf/origin, client/C) - if(!scr_loc) - return null - var/tX = splittext(scr_loc, ",") - var/tY = splittext(tX[2], ":") - var/tZ = origin.z - tY = tY[1] - tX = splittext(tX[1], ":") - tX = tX[1] - var/list/actual_view = getviewsize(C ? C.view : world.view) - tX = clamp(origin.x + text2num(tX) - round(actual_view[1] / 2) - 1, 1, world.maxx) - tY = clamp(origin.y + text2num(tY) - round(actual_view[2] / 2) - 1, 1, world.maxy) - return locate(tX, tY, tZ) - /proc/CallAsync(datum/source, proctype, list/arguments) set waitfor = FALSE return call(source, proctype)(arglist(arguments)) diff --git a/code/_onclick/click.dm b/code/_onclick/click.dm index 9e497ce2443..437425a29e3 100644 --- a/code/_onclick/click.dm +++ b/code/_onclick/click.dm @@ -500,10 +500,10 @@ var/mob/living/carbon/C = usr C.swap_hand() else - var/turf/T = params2turf(modifiers["screen-loc"], get_turf(usr)) - params += "&catcher=1" - if(T) - T.Click(location, control, params) + var/turf/click_turf = parse_caught_click_modifiers(modifiers, get_turf(usr.client ? usr.client.eye : usr), usr.client) + if(click_turf) + modifiers["catcher"] = TRUE + click_turf.Click(location, control, list2params(modifiers)) . = 1 #undef MAX_SAFE_BYOND_ICON_SCALE_TILES diff --git a/code/datums/spells/alien_spells/neurotoxin_spit.dm b/code/datums/spells/alien_spells/neurotoxin_spit.dm index 1f24877e54c..9ac2ac72f4f 100644 --- a/code/datums/spells/alien_spells/neurotoxin_spit.dm +++ b/code/datums/spells/alien_spells/neurotoxin_spit.dm @@ -29,7 +29,7 @@ neurotoxin.current = get_turf(user) neurotoxin.original = target neurotoxin.firer = user - neurotoxin.preparePixelProjectile(target, get_turf(target), user) + neurotoxin.preparePixelProjectile(target, user) neurotoxin.fire() user.newtonian_move(get_dir(U, T)) diff --git a/code/datums/spells/wizard_spells.dm b/code/datums/spells/wizard_spells.dm index 775b4ee6404..aa5579428aa 100644 --- a/code/datums/spells/wizard_spells.dm +++ b/code/datums/spells/wizard_spells.dm @@ -376,7 +376,7 @@ FB.current = get_turf(user) FB.original = target FB.firer = user - FB.preparePixelProjectile(target, get_turf(target), user) + FB.preparePixelProjectile(target, user) FB.fire() user.newtonian_move(get_dir(U, T)) diff --git a/code/game/mecha/equipment/weapons/weapons.dm b/code/game/mecha/equipment/weapons/weapons.dm index 2ceee1338aa..59762a09548 100644 --- a/code/game/mecha/equipment/weapons/weapons.dm +++ b/code/game/mecha/equipment/weapons/weapons.dm @@ -50,7 +50,7 @@ spread = round((rand() - 0.5) * variance) else spread = round((i / projectiles_per_shot - 0.5) * variance) - A.preparePixelProjectile(target, targloc, chassis.occupant, params, spread) + A.preparePixelProjectile(target, chassis.occupant, params2list(params), spread) chassis.use_power(energy_drain) projectiles-- diff --git a/code/modules/mining/equipment/kinetic_crusher.dm b/code/modules/mining/equipment/kinetic_crusher.dm index 3778aac3286..9c13db2b3ff 100644 --- a/code/modules/mining/equipment/kinetic_crusher.dm +++ b/code/modules/mining/equipment/kinetic_crusher.dm @@ -119,7 +119,8 @@ for(var/t in trophies) var/obj/item/crusher_trophy/T = t T.on_projectile_fire(D, user) - D.preparePixelProjectile(target, get_turf(target), user, clickparams) + var/modifiers = params2list(clickparams) + D.preparePixelProjectile(target, user, modifiers) D.firer = user D.firer_source_atom = src D.hammer_synced = src diff --git a/code/modules/mob/living/simple_animal/hostile/hostile.dm b/code/modules/mob/living/simple_animal/hostile/hostile.dm index 0d104ab1d01..3e728d2a858 100644 --- a/code/modules/mob/living/simple_animal/hostile/hostile.dm +++ b/code/modules/mob/living/simple_animal/hostile/hostile.dm @@ -406,7 +406,7 @@ if(AIStatus != AI_ON)//Don't want mindless mobs to have their movement screwed up firing in space newtonian_move(get_dir(targeted_atom, targets_from)) P.original = targeted_atom - P.preparePixelProjectile(targeted_atom, get_turf(targeted_atom), src) + P.preparePixelProjectile(targeted_atom, startloc) P.fire() return P diff --git a/code/modules/mob/living/simple_animal/hostile/megafauna/colossus.dm b/code/modules/mob/living/simple_animal/hostile/megafauna/colossus.dm index dc02055627a..996d6c7c618 100644 --- a/code/modules/mob/living/simple_animal/hostile/megafauna/colossus.dm +++ b/code/modules/mob/living/simple_animal/hostile/megafauna/colossus.dm @@ -111,7 +111,7 @@ Difficulty: Very Hard . = ..() move_to_delay = initial(move_to_delay) -/mob/living/simple_animal/hostile/megafauna/colossus/OpenFire() +/mob/living/simple_animal/hostile/megafauna/colossus/OpenFire(atom/target_atom) anger_modifier = clamp(((maxHealth - health)/50),0,20) ranged_cooldown = world.time + 120 @@ -122,7 +122,7 @@ Difficulty: Very Hard if(2) random_shots() if(3) - blast() + blast(target_atom) if(4) alternating_dir_shots() return @@ -139,7 +139,7 @@ Difficulty: Very Hard move_to_delay = initial(move_to_delay) if(health <= maxHealth / (enraged ? 10 : 9) && final_available) //One time use final attack. Want to make it not get skipped as much on base colossus, but a little easier to skip on enraged as it can be used multiple times - final_attack() + final_attack(target_atom) if(!enraged) final_available = FALSE else if(prob(20 + anger_modifier)) //Major attack @@ -148,7 +148,7 @@ Difficulty: Very Hard random_shots() else if(prob(70)) - blast() + blast(target_atom) else alternating_dir_shots() @@ -208,17 +208,25 @@ Difficulty: Very Hard SLEEP_CHECK_DEATH(1) icon_state = initial(icon_state) -/mob/living/simple_animal/hostile/megafauna/colossus/proc/shoot_projectile(turf/marker, set_angle) - if(!isnum(set_angle) && (!marker || marker == loc)) - return +/mob/living/simple_animal/hostile/megafauna/colossus/proc/shoot_projectile(atom/target_atom, set_angle) var/turf/startloc = get_turf(src) + var/turf/endloc = get_turf(target_atom) + + if(!startloc || !endloc || endloc == loc) + return + var/obj/item/projectile/P = new /obj/item/projectile/colossus(startloc) - P.preparePixelProjectile(marker, marker, startloc) + P.preparePixelProjectile(endloc, startloc) P.firer = src P.firer_source_atom = src + if(target) - P.original = target - P.fire(set_angle) + P.original = target_atom + + if(isnum(set_angle)) + P.fire(set_angle) + else + P.fire() /mob/living/simple_animal/hostile/megafauna/colossus/proc/random_shots(do_sleep = TRUE) ranged_cooldown = world.time + 30 @@ -231,17 +239,20 @@ Difficulty: Very Hard if(prob(enraged ? 10 : 5)) shoot_projectile(T) -/mob/living/simple_animal/hostile/megafauna/colossus/proc/blast(set_angle, do_sleep = TRUE) +/mob/living/simple_animal/hostile/megafauna/colossus/proc/blast(atom/target_atom, set_angle, do_sleep = TRUE) ranged_cooldown = world.time + 20 if(do_sleep) telegraph(BLAST) SLEEP_CHECK_DEATH(enraged ? 0.75 SECONDS : 1.5 SECONDS) - var/turf/target_turf = get_turf(target) + + var/turf/target_turf = get_turf(target_atom) playsound(src, 'sound/magic/clockwork/invoke_general.ogg', 200, TRUE, 2) newtonian_move(get_dir(target_turf, src)) var/angle_to_target = get_angle(src, target_turf) + if(isnum(set_angle)) angle_to_target = set_angle + var/static/list/colossus_shotgun_shot_angles = list(12.5, 7.5, 2.5, -2.5, -7.5, -12.5) for(var/i in colossus_shotgun_shot_angles) shoot_projectile(target_turf, angle_to_target + i) @@ -262,7 +273,7 @@ Difficulty: Very Hard if(mode) say("[mode]") -/mob/living/simple_animal/hostile/megafauna/colossus/proc/final_attack() +/mob/living/simple_animal/hostile/megafauna/colossus/proc/final_attack(atom/target_atom) icon_state = "eva_attack" say("PERISH MORTAL!") telegraph() @@ -273,7 +284,7 @@ Difficulty: Very Hard for(var/i in 1 to 20) if(finale_counter > 4) telegraph() - blast(do_sleep = FALSE) + blast(target_atom, do_sleep = FALSE) if(finale_counter > 1) finale_counter-- diff --git a/code/modules/mob/living/simple_animal/hostile/mining/elites/herald.dm b/code/modules/mob/living/simple_animal/hostile/mining/elites/herald.dm index bf637c1fa3e..6ee74c49b94 100644 --- a/code/modules/mob/living/simple_animal/hostile/mining/elites/herald.dm +++ b/code/modules/mob/living/simple_animal/hostile/mining/elites/herald.dm @@ -128,7 +128,7 @@ var/turf/startloc = get_turf(src) if(!is_teleshot) var/obj/item/projectile/H = new /obj/item/projectile/herald(startloc) - H.preparePixelProjectile(marker, marker, src) + H.preparePixelProjectile(marker, startloc) H.firer = src H.firer_source_atom = src if(target) @@ -139,7 +139,7 @@ shoot_projectile(marker, set_angle - 15, FALSE, FALSE) else var/obj/item/projectile/H = new /obj/item/projectile/herald/teleshot(startloc) - H.preparePixelProjectile(marker, marker, startloc) + H.preparePixelProjectile(marker, startloc) H.firer = src H.firer_source_atom = src if(target) diff --git a/code/modules/mod/modules/modules_engineering.dm b/code/modules/mod/modules/modules_engineering.dm index f87ae33c3a5..f02d8a9e029 100644 --- a/code/modules/mod/modules/modules_engineering.dm +++ b/code/modules/mod/modules/modules_engineering.dm @@ -120,7 +120,7 @@ var/obj/item/projectile/tether = new /obj/item/projectile/tether(get_turf(mod.wearer)) tether.original = target tether.firer = mod.wearer - tether.preparePixelProjectile(target, get_turf(target), mod.wearer) + tether.preparePixelProjectile(target, mod.wearer) tether.fire() playsound(src, 'sound/weapons/batonextend.ogg', 25, TRUE) INVOKE_ASYNC(tether, TYPE_PROC_REF(/obj/item/projectile/tether, make_chain)) diff --git a/code/modules/mod/modules/modules_supply.dm b/code/modules/mod/modules/modules_supply.dm index f4744a6cdcb..f0c31a29955 100644 --- a/code/modules/mod/modules/modules_supply.dm +++ b/code/modules/mod/modules/modules_supply.dm @@ -470,7 +470,7 @@ var/obj/item/projectile/bomb = new /obj/item/projectile/bullet/reusable/mining_bomb(get_turf(mod.wearer)) bomb.original = target bomb.firer = mod.wearer - bomb.preparePixelProjectile(target, get_turf(target), mod.wearer) + bomb.preparePixelProjectile(target, mod.wearer) bomb.fire() playsound(src, 'sound/weapons/grenadelaunch.ogg', 75, TRUE) drain_power(use_power_cost) diff --git a/code/modules/projectiles/firing.dm b/code/modules/projectiles/firing.dm index fe839a0eee7..3e02f5fafaf 100644 --- a/code/modules/projectiles/firing.dm +++ b/code/modules/projectiles/firing.dm @@ -8,8 +8,8 @@ spread = round((rand() - 0.5) * distro) else //Smart spread spread = round((i / pellets - 0.5) * distro) - if(!throw_proj(target, targloc, user, params, spread)) - return 0 + if(isnull(throw_proj(target, targloc, user, params, spread, firer_source_atom))) + return FALSE if(i > 1) newshot() if(click_cooldown_override) @@ -36,68 +36,133 @@ reagents.trans_to(BB, reagents.total_volume) //For chemical darts/bullets qdel(reagents) -/obj/item/ammo_casing/proc/throw_proj(atom/target, turf/targloc, mob/living/user, params, spread) - var/turf/curloc = get_turf(user) +/obj/item/ammo_casing/proc/throw_proj(atom/target, turf/targloc, mob/living/user, params, spread, atom/firer_source_atom) + var/turf/curloc = get_turf(firer_source_atom) if(!istype(targloc) || !istype(curloc) || !BB) - return 0 + return BB.ammo_casing = src if(target && get_dist(user, target) <= 1) //Point blank shot must always hit BB.prehit(target) target.bullet_act(BB, BB.def_zone) QDEL_NULL(BB) - return 1 + return TRUE if(targloc == curloc) if(target) //if the target is right on our location we go straight to bullet_act() BB.prehit(target) target.bullet_act(BB, BB.def_zone) QDEL_NULL(BB) - return 1 + return TRUE + + var/modifiers = params2list(params) + BB.preparePixelProjectile(target, user, modifiers, spread) - BB.preparePixelProjectile(target, targloc, user, params, spread) if(BB) BB.fire() BB = null - return 1 + + return TRUE /obj/item/ammo_casing/proc/spread(turf/target, turf/current, distro) var/dx = abs(target.x - current.x) var/dy = abs(target.y - current.y) return locate(target.x + round(gaussian(0, distro) * (dy+2)/8, 1), target.y + round(gaussian(0, distro) * (dx+2)/8, 1), target.z) -/obj/item/projectile/proc/preparePixelProjectile(atom/target, turf/targloc, mob/living/user, params, spread) - var/turf/curloc = get_turf(user) - loc = get_turf(user) - starting = get_turf(user) - current = curloc - yo = targloc.y - curloc.y - xo = targloc.x - curloc.x +/** + * Aims the projectile at a target. + * + * Must be passed at least one of a target or a list of click parameters. + * If only passed the click modifiers the source atom must be a mob with a client. + * + * Arguments: + * - [target][/atom]: (Optional) The thing that the projectile will be aimed at. + * - [source][/atom]: The initial location of the projectile or the thing firing it. + * - [modifiers][/list]: (Optional) A list of click parameters to apply to this operation. + * - deviation: (Optional) How the trajectory should deviate from the target in degrees. + * - //Spread is FORCED! + */ +/obj/item/projectile/proc/preparePixelProjectile(atom/target, atom/source, list/modifiers = null, deviation = 0) + if(!(isnull(modifiers) || islist(modifiers))) + stack_trace("WARNING: Projectile [type] fired with non-list modifiers, likely was passed click params.") + modifiers = null - if(params) - var/list/mouse_control = params2list(params) - if(mouse_control["icon-x"]) - p_x = text2num(mouse_control["icon-x"]) - if(mouse_control["icon-y"]) - p_y = text2num(mouse_control["icon-y"]) - if(mouse_control["screen-loc"]) - //Split screen-loc up into X+Pixel_X and Y+Pixel_Y - var/list/screen_loc_params = splittext(mouse_control["screen-loc"], ",") + var/turf/source_loc = get_turf(source) + var/turf/target_loc = get_turf(target) + if(isnull(source_loc)) + stack_trace("WARNING: Projectile [type] fired from nullspace.") + qdel(src) + return FALSE - //Split X+Pixel_X up into list(X, Pixel_X) - var/list/screen_loc_X = splittext(screen_loc_params[1],":") + trajectory_ignore_forcemove = TRUE + forceMove(source_loc) + trajectory_ignore_forcemove = FALSE - //Split Y+Pixel_Y up into list(Y, Pixel_Y) - var/list/screen_loc_Y = splittext(screen_loc_params[2],":") - var/x = (text2num(screen_loc_X[1]) - 1) * world.icon_size + text2num(screen_loc_X[2]) - var/y = (text2num(screen_loc_Y[1]) - 1) * world.icon_size + text2num(screen_loc_Y[2]) + starting = source_loc + pixel_x = source.pixel_x + pixel_y = source.pixel_y + original = target + if(length(modifiers)) + var/list/calculated = calculate_projectile_angle_and_pixel_offsets(source, target_loc && target, modifiers) - //Calculate the "resolution" of screen based on client's view and world's icon size. This will work if the user can view more tiles than average. - var/list/screenview = getviewsize(user.client.view) + p_x = calculated[2] + p_y = calculated[3] + set_angle(calculated[1] + deviation) + return TRUE - var/ox = round((screenview[1] * world.icon_size) / 2) - user.client.pixel_x //"origin" x - var/oy = round((screenview[2] * world.icon_size) / 2) - user.client.pixel_y //"origin" y - var/angle = ATAN2(y - oy, x - ox) - Angle = angle - if(spread) - Angle += spread + if(target_loc) + yo = target_loc.y - source_loc.y + xo = target_loc.x - source_loc.x + set_angle(get_angle(src, target_loc) + deviation) + return TRUE + + stack_trace("WARNING: Projectile [type] fired without a target or mouse parameters to aim with.") + qdel(src) + return FALSE + +/** + * Calculates the pixel offsets and angle that a projectile should be launched at. + * + * Arguments: + * - [source][/atom]: The thing that the projectile is being shot from. + * - [target][/atom]: (Optional) The thing that the projectile is being shot at. + * - If this is not provided the source atom must be a mob with a client. + * - [modifiers][/list]: A list of click parameters used to modify the shot angle. + */ +/proc/calculate_projectile_angle_and_pixel_offsets(atom/source, atom/target, modifiers) + var/angle = 0 + var/p_x = LAZYACCESS(modifiers, ICON_X) ? text2num(LAZYACCESS(modifiers, ICON_X)) : world.icon_size / 2 // ICON_(X|Y) are measured from the bottom left corner of the icon. + var/p_y = LAZYACCESS(modifiers, ICON_Y) ? text2num(LAZYACCESS(modifiers, ICON_Y)) : world.icon_size / 2 // This centers the target if modifiers aren't passed. + + var/mob/user = source + if(ismob(user) && user?.client && LAZYACCESS(modifiers, SCREEN_LOC)) + //Split screen-loc up into X+Pixel_X and Y+Pixel_Y + var/list/screen_loc_params = splittext(LAZYACCESS(modifiers, SCREEN_LOC), ",") + + //Split X+Pixel_X up into list(X, Pixel_X) + var/list/screen_loc_X = splittext(screen_loc_params[1],":") + + //Split Y+Pixel_Y up into list(Y, Pixel_Y) + var/list/screen_loc_Y = splittext(screen_loc_params[2],":") + var/x = (text2num(screen_loc_X[1]) - 1) * world.icon_size + text2num(screen_loc_X[2]) + var/y = (text2num(screen_loc_Y[1]) - 1) * world.icon_size + text2num(screen_loc_Y[2]) + + //Calculate the "resolution" of screen based on client's view and world's icon size. This will work if the user can view more tiles than average. + var/list/screenview = getviewsize(user.client.view) + + var/ox = round((screenview[1] * world.icon_size) / 2) - user.client.pixel_x //"origin" x + var/oy = round((screenview[2] * world.icon_size) / 2) - user.client.pixel_y //"origin" y + angle = ATAN2(y - oy, x - ox) + + return list(angle, p_x, p_y) + + if(!target) + CRASH("Can't make trajectory calculations without a target or click modifiers and a client.") + + var/turf/source_loc = get_turf(source) + var/turf/target_loc = get_turf(target) + var/dx = ((target_loc.x - source_loc.x) * world.icon_size) + (target.pixel_x - source.pixel_x) + (p_x - (world.icon_size / 2)) + var/dy = ((target_loc.y - source_loc.y) * world.icon_size) + (target.pixel_y - source.pixel_y) + (p_y - (world.icon_size / 2)) + + angle = ATAN2(dy, dx) + return list(angle, p_x, p_y) diff --git a/code/modules/projectiles/guns/misc/blastcannon.dm b/code/modules/projectiles/guns/misc/blastcannon.dm index 19cc8adbee2..44a6ca8aa93 100644 --- a/code/modules/projectiles/guns/misc/blastcannon.dm +++ b/code/modules/projectiles/guns/misc/blastcannon.dm @@ -92,7 +92,7 @@ message_admins("Blast wave fired from [ADMIN_COORDJMP(starting)] ([get_area_name(user, TRUE)]) at [ADMIN_COORDJMP(targturf)] ([target.name]) by [key_name_admin(user)] with power [heavy]/[medium]/[light].") log_game("Blast wave fired from ([starting.x], [starting.y], [starting.z]) ([get_area_name(user, TRUE)]) at ([target.x], [target.y], [target.z]) ([target]) by [key_name(user)] with power [heavy]/[medium]/[light].") var/obj/item/projectile/blastwave/BW = new(loc, heavy, medium, light) - BW.preparePixelProjectile(target, get_turf(target), user, params, 0) + BW.preparePixelProjectile(target, get_turf(src), params2list(params), 0) BW.fire() /obj/item/projectile/blastwave diff --git a/code/modules/projectiles/projectile_base.dm b/code/modules/projectiles/projectile_base.dm index ac8f9b91abf..0e25287c36f 100644 --- a/code/modules/projectiles/projectile_base.dm +++ b/code/modules/projectiles/projectile_base.dm @@ -481,13 +481,16 @@ /obj/item/projectile/set_angle(new_angle) ..() + Angle = new_angle - trajectory.set_angle(new_angle) + if(trajectory) + trajectory.set_angle(new_angle) if(has_been_fired && hitscan && isloc(loc) && (loc != last_angle_set_hitscan_store)) last_angle_set_hitscan_store = loc var/datum/point/point_cache = new (src) point_cache = trajectory.copy_to() store_hitscan_collision(point_cache) + return TRUE /obj/item/projectile/proc/set_angle_centered(new_angle) set_angle(new_angle)