diff --git a/code/_globalvars/lists/misc_lists.dm b/code/_globalvars/lists/misc_lists.dm index 04e24e33b78..7ea33c03f53 100644 --- a/code/_globalvars/lists/misc_lists.dm +++ b/code/_globalvars/lists/misc_lists.dm @@ -70,3 +70,6 @@ GLOBAL_LIST_EMPTY(seeds_cached_base64_icons) /// Assoc list of all RND techs with ID to name GLOBAL_LIST_EMPTY(rnd_tech_id_to_name) + +/// List of things that restrain teslas. +GLOBAL_LIST_EMPTY(tesla_containment) diff --git a/code/game/machinery/shieldgen.dm b/code/game/machinery/shieldgen.dm index 8f1a602c90e..9a3558c9ad7 100644 --- a/code/game/machinery/shieldgen.dm +++ b/code/game/machinery/shieldgen.dm @@ -13,11 +13,13 @@ . = ..() dir = pick(NORTH, SOUTH, EAST, WEST) recalculate_atmos_connectivity() + GLOB.tesla_containment += src /obj/machinery/shield/Destroy() opacity = FALSE density = FALSE recalculate_atmos_connectivity() + GLOB.tesla_containment -= src return ..() /obj/machinery/shield/Move() @@ -483,12 +485,14 @@ gen_secondary = B if(A && B) needs_power = TRUE + GLOB.tesla_containment += src /obj/machinery/shieldwall/Destroy() gen_primary?.remove_active_shield(src, dir) gen_secondary?.remove_active_shield(src, turn(dir, 180)) gen_primary = null gen_secondary = null + GLOB.tesla_containment -= src return ..() /obj/machinery/shieldwall/attack_hand(mob/user) diff --git a/code/modules/power/engines/singularity/containment_field.dm b/code/modules/power/engines/singularity/containment_field.dm index e217250c835..8f36181150b 100644 --- a/code/modules/power/engines/singularity/containment_field.dm +++ b/code/modules/power/engines/singularity/containment_field.dm @@ -19,12 +19,14 @@ COMSIG_ATOM_ENTERED = PROC_REF(on_atom_entered) ) AddElement(/datum/element/connect_loc, loc_connections) + GLOB.tesla_containment += src /obj/machinery/field/containment/Destroy() if(FG1)// These checks are mostly in case a field is spawned in by accident. FG1.fields -= src if(FG2) FG2.fields -= src + GLOB.tesla_containment -= src return ..() /obj/machinery/field/containment/attack_hand(mob/user) diff --git a/code/modules/power/engines/singularity/field_generator.dm b/code/modules/power/engines/singularity/field_generator.dm index 871c3dc1642..d10420963a4 100644 --- a/code/modules/power/engines/singularity/field_generator.dm +++ b/code/modules/power/engines/singularity/field_generator.dm @@ -18,8 +18,6 @@ field_generator energy level display #define FG_CHARGING 1 #define FG_ONLINE 2 -GLOBAL_LIST_EMPTY(field_generator_fields) - /obj/machinery/field/generator name = "Field Generator" desc = "A large thermal battery that projects a high amount of energy when powered." @@ -335,7 +333,6 @@ GLOBAL_LIST_EMPTY(field_generator_fields) CF.dir = field_dir fields += CF G.fields += CF - GLOB.field_generator_fields += CF for(var/mob/living/L in T) CF.Crossed(L, null) @@ -348,7 +345,6 @@ GLOBAL_LIST_EMPTY(field_generator_fields) clean_up = TRUE for(var/F in fields) qdel(F) - GLOB.field_generator_fields -= F for(var/CG in connected_gens) var/obj/machinery/field/generator/FG = CG diff --git a/code/modules/power/engines/tesla/energy_ball.dm b/code/modules/power/engines/tesla/energy_ball.dm index 4519935ddb9..7990d33a85c 100644 --- a/code/modules/power/engines/tesla/energy_ball.dm +++ b/code/modules/power/engines/tesla/energy_ball.dm @@ -31,9 +31,9 @@ var/energy_to_lower = -20 var/obj/singularity/energy_ball/parent_energy_ball /// Turf where the tesla will move to if it's loose - var/turf/target_turf - /// Direction we have to go to go towards the target turf - var/movement_dir + var/turf/movement_goal + /// The next spot the tesla will zoom to and stop at on its way to movement_goal + var/turf/move_target /// Variable that defines whether it has a field generator close enough var/has_close_field = FALSE /// How many tiles do we move per movement step? @@ -75,6 +75,9 @@ GLOB.poi_list -= src QDEL_LIST_CONTENTS(orbiting_balls) + movement_goal = null + move_target = null + GLOB.move_manager.stop_looping(src) return ..() /obj/singularity/energy_ball/admin_investigate_setup() @@ -110,112 +113,135 @@ . += "There are [length(orbiting_balls)] mini-balls orbiting it." /obj/singularity/energy_ball/proc/move_the_basket_ball() + if(!loc) + // Don't move while nowhere. + movement_goal = null + move_target = null + GLOB.move_manager.stop_looping(src) + return + has_close_field = FALSE - for(var/i in 1 to length(GLOB.field_generator_fields)) - var/temp_distance = get_dist(src, GLOB.field_generator_fields[i]) + for(var/i in 1 to length(GLOB.tesla_containment)) + var/temp_distance = get_dist(src, GLOB.tesla_containment[i]) if(temp_distance <= 15) has_close_field = TRUE break + if(has_close_field) + // We're in range of a containment field. Stop long-distance movement. + movement_goal = null + move_target = null + GLOB.move_manager.stop_looping(src) var/turf/T = get_step(src, pick(GLOB.alldirs)) if(can_move(T)) forceMove(T) - for(var/mob/living/carbon/C in loc) - dust_mobs(C) return - if(!target_turf || loc == target_turf) + if(loc == movement_goal) + // When reaching our target, clear it out, but wait for a moment before picking a new one. + movement_goal = null + return + + if(!movement_goal) + // If we don't have a target, pick one. find_the_basket() + move_target = null + + if(loc != move_target && !isnull(move_target)) return - var/turf/move_target = next_move_target() + // Pick a short-term goal. + move_target = next_move_target() + + if(move_target) + // Fire an energy beam at it, then start moving. + INVOKE_ASYNC(src, PROC_REF(arc_and_move)) + +/obj/singularity/energy_ball/proc/next_move_target() + if(!isturf(loc) || !isturf(movement_goal)) + return null + + var/x_diff = movement_goal.x - x + var/y_diff = movement_goal.y - y + if(abs(x_diff) <= steps_per_move && abs(y_diff) <= steps_per_move) + // We're close, go there directly. + return movement_goal + + var/target_x + var/target_y + if(abs(x_diff) >= abs(y_diff)) + // 8 tiles along X. + target_x = x + 8 * sign(x_diff) + // Scale back Y movement to match the difference in distance along the axes. + target_y = y + 8 * y_diff / abs(x_diff) + else + // 8 tiles along Y. + target_y = y + 8 * sign(y_diff) + // Scale back X movement to match the difference in distance along the axes. + target_x = x + 8 * x_diff / abs(y_diff) + + return locate(target_x, target_y, z) + +/obj/singularity/energy_ball/proc/arc_and_move() + if(!loc || !move_target) + // We went to nowhere before the INVOKE_ASYNC resolved. Abort, abort! + return + + // Initial beam. movement_beam(move_target, 1.5 SECONDS) sleep(0.5 SECONDS) - // MORE POWER + + // MORE POWER! movement_beam(move_target, 1 SECONDS) sleep(0.5 SECONDS) - GLOB.move_manager.home_onto(src, move_target, 0, 10) + + // Follow that arc! + GLOB.move_manager.stop_looping(src) + GLOB.move_manager.move_towards(src, move_target, 0.5, 10) /obj/singularity/energy_ball/proc/on_atom_entered(datum/source, atom/movable/entered) var/mob/living/living_entered = entered if(istype(living_entered)) living_entered.dust() -/datum/move_with_corner - var/turf/start - var/turf/end - var/turf/corner - var/pre_corner_x_unit - var/pre_corner_y_unit - var/pre_corner_dist - var/post_corner_x_unit - var/post_corner_y_unit - var/post_corner_dist - -/datum/move_with_corner/New(atom/start_in, atom/end_in) - start = start_in - end = end_in - pre_corner_x_unit = (end.x > start.x) ? 1 : -1 - pre_corner_y_unit = (end.y > start.y) ? 1 : -1 - if(start.x == end.x) - // Moving along y - corner = end - pre_corner_x_unit = 0 - pre_corner_dist = abs(start.y - end.y) - return - if(start.y == end.y) - // Moving along x - corner = end - pre_corner_y_unit = 0 - pre_corner_dist = abs(start.x - end.x) - return - - pre_corner_dist = min(abs(start.x-end.x), abs(start.y-end.y)) - corner = locate(start.x + pre_corner_x_unit * pre_corner_dist, start.y + pre_corner_y_unit * pre_corner_dist, start.z) - - if(start.x + pre_corner_x_unit * pre_corner_dist == end.x) - // x finished first, move along y - post_corner_x_unit = 0 - post_corner_dist = abs(start.y - end.y) - pre_corner_dist - else - // y finished first, move along x - post_corner_y_unit = 0 - post_corner_dist = abs(start.x - end.x) - pre_corner_dist - -/datum/move_with_corner/proc/get_early_end(dist) - if(dist <= pre_corner_dist) - return locate(start.x + pre_corner_x_unit * dist, start.y + pre_corner_y_unit * dist, start.z) - else if(dist == pre_corner_dist) - return corner - else if(dist >= pre_corner_dist + post_corner_dist) - return end - - var/remaining_dist = dist - pre_corner_dist - return locate(corner.x + remaining_dist * post_corner_x_unit, corner.y + remaining_dist * post_corner_y_unit, corner.z) - -/obj/singularity/energy_ball/proc/next_move_target() - var/datum/move_with_corner/path = new(loc, target_turf) - return path.get_early_end(steps_per_move) - /obj/singularity/energy_ball/proc/movement_beam(turf/move_target, duration) - var/datum/move_with_corner/path = new(loc, move_target) - - loc.Beam(path.corner, "lightning[rand(1, 12)]", 'icons/effects/effects.dmi', duration, INFINITY) - if(path.corner != move_target) - path.corner.Beam(move_target, "lightning[rand(1, 12)]", 'icons/effects/effects.dmi', duration, INFINITY) + loc.Beam(move_target, "lightning[rand(1, 12)]", 'icons/effects/effects.dmi', duration, INFINITY) /obj/singularity/energy_ball/proc/find_the_basket() var/area/where_to_move = pick(all_possible_areas) // Grabs a random area that isn't restricted var/turf/target_area_turfs = get_area_turfs(where_to_move) // Grabs the turfs from said area - target_turf = pick(target_area_turfs) // Grabs a single turf from the entire list + movement_goal = pick(target_area_turfs) // Grabs a single turf from the entire list /obj/singularity/energy_ball/Move(target, direction) - // Energy balls move through everything, make it a forceMove. if(miniball) return ..() + + if(!loc || !target) + // Don't move while nowhere. + return FALSE + + if((locate(/obj/machinery/shield) in target) || (locate(/obj/machinery/field/containment) in target)) + // We can't go that way, and we're now in range of a containment field. Stop long-distance movement. + movement_goal = null + move_target = null + GLOB.move_manager.stop_looping(src) + return FALSE + // Energy balls move through everything, make it a forceMove. forceMove(target, direction) return TRUE +/obj/singularity/energy_ball/forceMove(target) + . = ..() + if(miniball || !isturf(target)) + return + + for(var/mob/M in target) + dust_mobs(M) + +// Energy balls do not drift in space. +/obj/singularity/energy_ball/Process_Spacemove(movement_dir = 0, continuous_move = FALSE) + return TRUE + /obj/singularity/energy_ball/proc/handle_energy() if(energy >= energy_to_raise) energy_to_lower = energy_to_raise - 20 @@ -248,9 +274,6 @@ EB.parent_energy_ball = src EB.orbit(src, orbitsize, pick(FALSE, TRUE), rand(10, 25), pick(3, 4, 5, 6, 36), orbit_layer = EB.layer) -/obj/singularity/energy_ball/Bump(atom/A) - dust_mobs(A) - /obj/singularity/energy_ball/Bumped(atom/movable/AM) dust_mobs(AM) @@ -294,17 +317,15 @@ parent_energy_ball = null /obj/singularity/energy_ball/proc/dust_mobs(atom/A) - if(isliving(A)) - var/mob/living/L = A - if(L.incorporeal_move || L.status_flags & GODMODE) - return - if(!iscarbon(A)) + if(!isliving(A)) + return + var/mob/living/L = A + if(L.incorporeal_move || L.status_flags & GODMODE) return for(var/obj/machinery/power/grounding_rod/GR in orange(src, 2)) if(GR.anchored) return - var/mob/living/carbon/C = A - C.dust() + L.dust() /proc/tesla_zap(atom/source, zap_range = 3, power, zap_flags = ZAP_DEFAULT_FLAGS, list/shocked_targets = list()) if(QDELETED(source))