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"