From 7a994fffba29a3fe456b5debfae337c5cee20480 Mon Sep 17 00:00:00 2001 From: Jacquerel Date: Wed, 3 Dec 2025 20:37:35 +0000 Subject: [PATCH] Pathfinder Module no longer pathfinds (#94261) ## About The Pull Request ![dreamseeker_9pYVx5xTE0](https://github.com/user-attachments/assets/86695b34-deb1-4307-83c8-ecd81b1b6b28) Removes the "pathfinding through the halls" function of the MODsuit pathfinder module in favour of having it "fly" to your location in the same manner as cargo pods (by getting inside an invisible cargo pod). If it's inside a locked crate or locker when this happens it will break the door on the way out, so be careful where you leave it. ## Why It's Good For The Game Mostly because nobody uses the pathfinder module. Pathfinding through the corridors is _cool_ on a conceptual level but it's not very practical, _especially_ because it means that it has a good chance of not functioning at all if you are on a station with Z levels. This method means that it will always arrive in the same amount of time (quite fast, but not fast enough that it will arrive before someone can shoot you) regardless of where you are when you suddenly realise that you need spaceproofing rather than drip. Also Fikou asked me to do it. ## Changelog :cl: balance: The Captain's MODSuit pathfinder module now flies it directly to their location rather than physically traversing the halls of the station. /:cl: --- code/__DEFINES/ai/ai_blackboard.dm | 2 - code/__DEFINES/dcs/signals/signals_object.dm | 2 + code/datums/ai/movement/ai_movement_jps.dm | 3 - code/datums/ai/objects/mod.dm | 48 ------- code/game/machinery/botlaunchpad.dm | 13 +- code/modules/cargo/supplypod.dm | 36 +++++- code/modules/mod/modules/module_pathfinder.dm | 117 +++++++++++------- tgstation.dme | 1 - 8 files changed, 105 insertions(+), 117 deletions(-) delete mode 100644 code/datums/ai/objects/mod.dm diff --git a/code/__DEFINES/ai/ai_blackboard.dm b/code/__DEFINES/ai/ai_blackboard.dm index cbef7194bc9..3e9a65027af 100644 --- a/code/__DEFINES/ai/ai_blackboard.dm +++ b/code/__DEFINES/ai/ai_blackboard.dm @@ -176,8 +176,6 @@ #define BB_MOD_TARGET "BB_mod_target" ///The module the AI was created from #define BB_MOD_MODULE "BB_mod_module" -///Range for a MOD AI controller. -#define MOD_AI_RANGE 200 ///Only target mobs with these traits #define BB_TARGET_ONLY_WITH_TRAITS "BB_target_only_with_traits" diff --git a/code/__DEFINES/dcs/signals/signals_object.dm b/code/__DEFINES/dcs/signals/signals_object.dm index b66d92b8df7..487872df053 100644 --- a/code/__DEFINES/dcs/signals/signals_object.dm +++ b/code/__DEFINES/dcs/signals/signals_object.dm @@ -226,6 +226,8 @@ #define COMSIG_MULTITOOL_REMOVE_BUFFER "multitool_remove_buffer" ///from [/obj/effect/mine/proc/triggermine]: #define COMSIG_MINE_TRIGGERED "minegoboom" +///from [/obj/structure/closet/supplypod/proc/handleReturnAfterDeparting]: +#define COMSIG_SUPPLYPOD_RETURNING "supplypodgohome" ///from [/obj/structure/closet/supplypod/proc/preOpen]: #define COMSIG_SUPPLYPOD_LANDED "supplypodgoboom" diff --git a/code/datums/ai/movement/ai_movement_jps.dm b/code/datums/ai/movement/ai_movement_jps.dm index 3781dd7f5dc..1153833a31e 100644 --- a/code/datums/ai/movement/ai_movement_jps.dm +++ b/code/datums/ai/movement/ai_movement_jps.dm @@ -53,6 +53,3 @@ /datum/ai_movement/jps/bot/travel_to_beacon maximum_length = AI_BOT_PATH_LENGTH max_pathing_attempts = 10 - -/datum/ai_movement/jps/modsuit - maximum_length = MOD_AI_RANGE diff --git a/code/datums/ai/objects/mod.dm b/code/datums/ai/objects/mod.dm deleted file mode 100644 index 46b41adb1c8..00000000000 --- a/code/datums/ai/objects/mod.dm +++ /dev/null @@ -1,48 +0,0 @@ -/// An AI controller for the MODsuit pathfinder module. It's activated by module and attaches itself to the user. -/datum/ai_controller/mod - blackboard = list( - BB_MOD_TARGET, - BB_MOD_MODULE, - ) - can_idle = FALSE - max_target_distance = MOD_AI_RANGE //a little spicy but its one specific item that summons it, and it doesn't run otherwise - ai_movement = /datum/ai_movement/jps/modsuit - ///ID card generated from the suit's required access. Used for pathing. - var/obj/item/card/id/advanced/id_card - -/datum/ai_controller/mod/TryPossessPawn(atom/new_pawn) - if(!istype(new_pawn, /obj/item/mod/control)) - return AI_CONTROLLER_INCOMPATIBLE - var/obj/item/mod/control/mod = new_pawn - id_card = new /obj/item/card/id/advanced/simple_bot() - if(length(mod.req_access)) - id_card.set_access(mod.req_access) - return ..() //Run parent at end - -/datum/ai_controller/mod/UnpossessPawn(destroy) - QDEL_NULL(id_card) - return ..() //Run parent at end - -/datum/ai_controller/mod/SelectBehaviors(seconds_per_tick) - current_behaviors = list() - if(blackboard[BB_MOD_TARGET] && blackboard[BB_MOD_MODULE]) - queue_behavior(/datum/ai_behavior/mod_attach) - -/datum/ai_controller/mod/get_access() - return id_card.GetAccess() - -/datum/ai_behavior/mod_attach - behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT|AI_BEHAVIOR_MOVE_AND_PERFORM - -/datum/ai_behavior/mod_attach/perform(seconds_per_tick, datum/ai_controller/controller) - if(!controller.pawn.Adjacent(controller.blackboard[BB_MOD_TARGET])) - return AI_BEHAVIOR_DELAY - var/obj/item/mod/module/pathfinder/module = controller.blackboard[BB_MOD_MODULE] - module.attach(controller.blackboard[BB_MOD_TARGET]) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - -/datum/ai_behavior/mod_attach/finish_action(datum/ai_controller/controller, succeeded) - . = ..() - controller.clear_blackboard_key(BB_MOD_TARGET) - var/obj/item/mod/module/pathfinder/module = controller.blackboard[BB_MOD_MODULE] - module.end_recall(succeeded) diff --git a/code/game/machinery/botlaunchpad.dm b/code/game/machinery/botlaunchpad.dm index e044bb1e296..9cd9c647acc 100644 --- a/code/game/machinery/botlaunchpad.dm +++ b/code/game/machinery/botlaunchpad.dm @@ -48,7 +48,7 @@ launched_bot = WEAKREF(possible_bot) podspawn(list( "target" = get_turf(src), - "path" = /obj/structure/closet/supplypod/botpod, + "path" = /obj/structure/closet/supplypod/transport/botpod, "style" = /datum/pod_style/seethrough, "reverse_dropoff_coords" = list(reverse_turf.x, reverse_turf.y, reverse_turf.z) )) @@ -66,15 +66,6 @@ var/mob/living/simple_animal/bot/simple_bot = our_bot simple_bot.call_bot(src, get_turf(src)) -/obj/structure/closet/supplypod/botpod - style = /datum/pod_style/seethrough - explosionSize = list(0,0,0,0) - reversing = TRUE +/obj/structure/closet/supplypod/transport/botpod reverse_option_list = list("Mobs"=TRUE,"Objects"=FALSE,"Anchored"=FALSE,"Underfloor"=FALSE,"Wallmounted"=FALSE,"Floors"=FALSE,"Walls"=FALSE,"Mecha"=FALSE) - delays = list(POD_TRANSIT = 0, POD_FALLING = 0, POD_OPENING = 0, POD_LEAVING = 0) - reverse_delays = list(POD_TRANSIT = 15, POD_FALLING = 10, POD_OPENING = 0, POD_LEAVING = 0) - custom_rev_delay = TRUE - effectQuiet = TRUE leavingSound = 'sound/vehicles/rocketlaunch.ogg' - close_sound = null - pod_flags = FIRST_SOUNDS diff --git a/code/modules/cargo/supplypod.dm b/code/modules/cargo/supplypod.dm index 2488942a9f2..73875c011f7 100644 --- a/code/modules/cargo/supplypod.dm +++ b/code/modules/cargo/supplypod.dm @@ -71,6 +71,19 @@ bluespace = TRUE explosionSize = list(0,0,0,0) +/// Quick setup for if you want a pod that transports a specific object somewhere and makes it look like it is flying away +/obj/structure/closet/supplypod/transport + style = /datum/pod_style/seethrough + specialised = TRUE + explosionSize = list(0,0,0,0) + reversing = TRUE + delays = list(POD_TRANSIT = 0, POD_FALLING = 0, POD_OPENING = 0, POD_LEAVING = 0) + reverse_delays = list(POD_TRANSIT = 15, POD_FALLING = 10, POD_OPENING = 0, POD_LEAVING = 0) + custom_rev_delay = TRUE + effectQuiet = TRUE + close_sound = null + pod_flags = FIRST_SOUNDS + /obj/structure/closet/supplypod/podspawn/deathmatch desc = "A blood-red styled drop pod." specialised = TRUE @@ -300,6 +313,7 @@ if (custom_rev_delay) delays = reverse_delays backToNonReverseIcon() + SEND_SIGNAL(src, COMSIG_SUPPLYPOD_RETURNING) var/turf/return_turf = locate(reverse_dropoff_coords[1], reverse_dropoff_coords[2], reverse_dropoff_coords[3]) new /obj/effect/pod_landingzone(return_turf, src) @@ -308,9 +322,9 @@ var/list/boom = explosionSize resistance_flags = initial(resistance_flags) set_density(TRUE) //Density is originally false so the pod doesn't block anything while it's still falling through the air - AddComponent(/datum/component/pellet_cloud, projectile_type=shrapnel_type, magnitude=shrapnel_magnitude) if(effectShrapnel) - SEND_SIGNAL(src, COMSIG_SUPPLYPOD_LANDED) + AddComponent(/datum/component/pellet_cloud, projectile_type=shrapnel_type, magnitude=shrapnel_magnitude) + SEND_SIGNAL(src, COMSIG_SUPPLYPOD_LANDED) for (var/mob/living/target_living in turf_underneath) if (iscarbon(target_living)) //If effectLimb is true (which means we pop limbs off when we hit people): if (effectLimb) @@ -364,6 +378,7 @@ moveToNullspace() addtimer(CALLBACK(src, PROC_REF(open_pod), benis), delays[POD_OPENING]) //After the opening delay passes, we use the open proc from this supplyprod while referencing the contents of the "holder", in this case the gondolapod mob else if (ispath(style, /datum/pod_style/seethrough)) + transform = matrix() open_pod(src) else addtimer(CALLBACK(src, PROC_REF(open_pod), src), delays[POD_OPENING]) //After the opening delay passes, we use the open proc from this supplypod, while referencing this supplypod's contents @@ -415,7 +430,8 @@ if (!holder) return take_contents(holder) - playsound(holder, close_sound, soundVolume*0.75, TRUE, -3) + if (close_sound) + playsound(holder, close_sound, soundVolume*0.75, TRUE, -3) holder.setClosed() addtimer(CALLBACK(src, PROC_REF(preReturn), holder), delays[POD_LEAVING] * 0.2) //Start to leave a bit after closing for cinematic effect @@ -658,7 +674,11 @@ stack_trace("Pod landingzone effect created with no pod") return INITIALIZE_HINT_QDEL transform = matrix() * 1.5 - animate(src, transform = matrix()*0.01, time = pod.delays[POD_TRANSIT]+pod.delays[POD_FALLING]) + var/arrival_time = pod.delays[POD_TRANSIT] + pod.delays[POD_FALLING] + if (arrival_time > 0) + animate(src, transform = matrix()*0.01, time = arrival_time) + else + alpha = 0 /obj/effect/pod_landingzone //This is the object that forceMoves the supplypod to its location name = "Landing Zone Indicator" @@ -685,7 +705,11 @@ if (!pod.effectStealth) helper = new (drop_location(), pod) alpha = 255 - animate(src, transform = matrix().Turn(90), time = pod.delays[POD_TRANSIT]+pod.delays[POD_FALLING]) + var/arrival_time = pod.delays[POD_TRANSIT] + pod.delays[POD_FALLING] + if (arrival_time > 0) + animate(src, transform = matrix().Turn(90), time = arrival_time) + else + alpha = 0 if (single_order) if (istype(single_order, /datum/supply_order)) var/datum/supply_order/SO = single_order @@ -701,7 +725,7 @@ if(pod.effectStun) //If effectStun is true, stun any mobs caught on this pod_landingzone until the pod gets a chance to hit them for (var/mob/living/target_living in get_turf(src)) target_living.Stun(pod.delays[POD_TRANSIT]+10, ignore_canstun = TRUE)//you ain't goin nowhere, kid. - if (pod.delays[POD_TRANSIT] + pod.delays[POD_FALLING] < pod.fallingSoundLength) + if (arrival_time < pod.fallingSoundLength) pod.fallingSoundLength = 3 //The default falling sound is a little long, so if the landing time is shorter than the default falling sound, use a special, shorter default falling sound pod.fallingSound = 'sound/items/weapons/mortar_whistle.ogg' var/soundStartTime = pod.delays[POD_TRANSIT] - pod.fallingSoundLength + pod.delays[POD_FALLING] diff --git a/code/modules/mod/modules/module_pathfinder.dm b/code/modules/mod/modules/module_pathfinder.dm index 4b948d6ed07..e9497e48e82 100644 --- a/code/modules/mod/modules/module_pathfinder.dm +++ b/code/modules/mod/modules/module_pathfinder.dm @@ -1,3 +1,5 @@ +#define PATHFINDER_PRE_ANIMATE_TIME (2 SECONDS) + ///Pathfinder - Can fly the suit from a long distance to an implant installed in someone. /obj/item/mod/module/pathfinder name = "MOD pathfinder module" @@ -24,6 +26,8 @@ var/image/jet_icon /// Allow suit activation - Lets this module be recalled from the MOD. var/allow_suit_activation = FALSE // I'm not here to argue about balance + /// Are we currently travelling? + var/in_transit = FALSE /obj/item/mod/module/pathfinder/Initialize(mapload) @@ -88,7 +92,7 @@ return balloon_alert(activator, "implanted") if(!(activator == mod.wearer)) // someone else implanted you - balloon_alert(mod.wearer, "pathfinder MOD tracker implanted!") + balloon_alert(mod.wearer, "tracker implanted!") playsound(src, 'sound/effects/spray.ogg', 30, TRUE, -6) /obj/item/mod/module/pathfinder/proc/attach(mob/living/user) @@ -101,64 +105,81 @@ return mod.quick_deploy(user) human_user.update_action_buttons(TRUE) - balloon_alert(human_user, "[mod] attached") playsound(mod, 'sound/machines/ping.ogg', 50, TRUE) drain_power(use_energy_cost) /obj/item/mod/module/pathfinder/proc/recall(mob/recaller) if(!implant) - balloon_alert(recaller, "no target implant!") + balloon_alert(recaller, "no implant!") return FALSE if(recaller != implant.imp_in && !allow_suit_activation) // No pAI recalling - balloon_alert(recaller, "sector safety regulations prevent MOD-side recalling!") + balloon_alert(recaller, "invalid user!") return FALSE if(mod.open) balloon_alert(recaller, "cover open!") return FALSE - if(mod.ai_controller) - balloon_alert(recaller, "already moving!") + if(in_transit) + balloon_alert(recaller, "suit in transit!") return FALSE if(ismob(get_atom_on_turf(mod))) - balloon_alert(recaller, "already on someone!") + balloon_alert(recaller, "already worn!") return FALSE - if(mod.z != implant.imp_in.z || get_dist(implant.imp_in, mod) > MOD_AI_RANGE) - balloon_alert(recaller, "too far!") - return FALSE - var/datum/ai_controller/mod_ai = new /datum/ai_controller/mod(mod) - mod.ai_controller = mod_ai - mod_ai.set_movement_target(type, implant.imp_in) - mod_ai.set_blackboard_key(BB_MOD_TARGET, implant.imp_in) - mod_ai.set_blackboard_key(BB_MOD_MODULE, src) - mod.interaction_flags_item &= ~INTERACT_ITEM_ATTACK_HAND_PICKUP - mod.AddElement(/datum/element/movetype_handler) - ADD_TRAIT(mod, TRAIT_MOVE_FLYING, MOD_TRAIT) - animate(mod, 0.2 SECONDS, pixel_x = base_pixel_y, pixel_y = base_pixel_y) - mod.add_overlay(jet_icon) - RegisterSignal(mod, COMSIG_MOVABLE_MOVED, PROC_REF(on_move)) + + in_transit = TRUE + animate(mod, 0.5 SECONDS, pixel_x = base_pixel_y, pixel_y = base_pixel_y) + mod.Shake(pixelshiftx = 1, pixelshifty = 1, duration = PATHFINDER_PRE_ANIMATE_TIME) + addtimer(CALLBACK(src, PROC_REF(do_recall), recaller), PATHFINDER_PRE_ANIMATE_TIME, TIMER_DELETE_ME) + balloon_alert(recaller, "suit recalled") if(!(recaller == mod.wearer)) balloon_alert(mod.wearer, "suit recalled") return TRUE -/obj/item/mod/module/pathfinder/proc/on_move(atom/movable/source, atom/old_loc, dir, forced) - SIGNAL_HANDLER - - var/matrix/mod_matrix = matrix() - mod_matrix.Turn(get_angle(source, implant.imp_in)) - source.transform = mod_matrix - -/obj/item/mod/module/pathfinder/proc/end_recall(successful = TRUE) - if(!mod) +/// Pod-transport the suit to its owner +/obj/item/mod/module/pathfinder/proc/do_recall(mob/recaller) + var/container = get_atom_on_turf(mod) + if(ismob(container)) + balloon_alert(recaller, "launch interrupted!") return - QDEL_NULL(mod.ai_controller) - mod.interaction_flags_item |= INTERACT_ITEM_ATTACK_HAND_PICKUP - REMOVE_TRAIT(mod, TRAIT_MOVE_FLYING, MOD_TRAIT) - mod.RemoveElement(/datum/element/movetype_handler) + + if(iscloset(container)) + var/obj/structure/closet/closet = container + if (!closet.opened) + if (!closet.open()) + playsound(closet, 'sound/effects/bang.ogg', vol = 50, vary = TRUE) + closet.bust_open() + + + mod.add_overlay(jet_icon) + playsound(mod, 'sound/vehicles/rocketlaunch.ogg', vol = 80, vary = FALSE) + var/turf/land_target = get_turf(implant.imp_in) + var/obj/structure/closet/supplypod/pod = podspawn(list( + "target" = get_turf(mod), + "path" = /obj/structure/closet/supplypod/transport/module_pathfinder, + "reverse_dropoff_coords" = list(land_target.x, land_target.y, land_target.z), + )) + + pod.insert(mod, pod) + RegisterSignal(pod, COMSIG_SUPPLYPOD_RETURNING, PROC_REF(pod_takeoff)) + + if (istype(container, /obj/machinery/suit_storage_unit)) + var/obj/machinery/suit_storage_unit/storage = container + storage.locked = FALSE + storage.open_machine() + +/// Track when pod has taken off so we don't falsely report the initial landing +/obj/item/mod/module/pathfinder/proc/pod_takeoff(datum/pod) + SIGNAL_HANDLER + RegisterSignal(pod, COMSIG_SUPPLYPOD_LANDED, PROC_REF(pod_landed)) + +/// When the pod landed, we can recall again +/obj/item/mod/module/pathfinder/proc/pod_landed() + SIGNAL_HANDLER + in_transit = FALSE mod.cut_overlay(jet_icon) - mod.transform = matrix() - UnregisterSignal(mod, COMSIG_MOVABLE_MOVED) - if(!successful) - balloon_alert(implant.imp_in, "suit lost connection!") + playsound(mod, 'sound/items/handling/toolbox/toolbox_drop.ogg', vol = 80, vary = FALSE) + if (implant?.imp_in?.Adjacent(src)) + INVOKE_ASYNC(src, PROC_REF(attach), implant.imp_in) // ########### // THE INPLANT @@ -173,8 +194,6 @@ /// The pathfinder module we are linked to. var/obj/item/mod/module/pathfinder/module - - /obj/item/implant/mod/Initialize(mapload) . = ..() if(!istype(loc, /obj/item/mod/module/pathfinder)) @@ -182,8 +201,6 @@ module = loc /obj/item/implant/mod/Destroy() - if(module?.mod?.ai_controller) - module.end_recall(successful = FALSE) module = null return ..() @@ -192,7 +209,6 @@ Name: Nakamura Engineering Pathfinder Implant
\ Implant Details: Allows for the recall of a Modular Outerwear Device by the implant owner at any time.
" - /datum/action/item_action/mod_recall name = "Recall MOD" desc = "Recall a MODsuit anyplace, anytime." @@ -205,7 +221,7 @@ COOLDOWN_DECLARE(recall_cooldown) /datum/action/item_action/mod_recall/New(Target) - ..() + . = ..() if(!istype(Target, /obj/item/implant/mod)) qdel(src) return @@ -215,5 +231,14 @@ if(!COOLDOWN_FINISHED(src, recall_cooldown)) implant.balloon_alert(owner, "on cooldown!") return - if(implant.module.recall(owner)) // change this - COOLDOWN_START(src, recall_cooldown, 15 SECONDS) + if(implant.module.recall(owner)) + implant.balloon_alert(owner, "suit incoming...") + COOLDOWN_START(src, recall_cooldown, 5 SECONDS) + +/// Special pod subtype we use just to make insertion check easy +/obj/structure/closet/supplypod/transport/module_pathfinder + +/obj/structure/closet/supplypod/transport/module_pathfinder/insertion_allowed(atom/to_insert) + return istype(to_insert, /obj/item/mod/control) + +#undef PATHFINDER_PRE_ANIMATE_TIME diff --git a/tgstation.dme b/tgstation.dme index 3d73e7cf7f4..acbea823808 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -1041,7 +1041,6 @@ #include "code\datums\ai\movement\ai_movement_complete_stop.dm" #include "code\datums\ai\movement\ai_movement_dumb.dm" #include "code\datums\ai\movement\ai_movement_jps.dm" -#include "code\datums\ai\objects\mod.dm" #include "code\datums\ai\objects\vending_machines\vending_machine_behaviors.dm" #include "code\datums\ai\objects\vending_machines\vending_machine_controller.dm" #include "code\datums\ai\robot_customer\robot_customer_behaviors.dm"