diff --git a/code/game/machinery/bots/cprbot.dm b/code/game/machinery/bots/cprbot.dm new file mode 100644 index 000000000000..44c2f85976a1 --- /dev/null +++ b/code/game/machinery/bots/cprbot.dm @@ -0,0 +1,196 @@ +/obj/structure/machinery/bot/cprbot + name = "CPRbot" + desc = "Designed for urgent medical intervention, this CPRbot offers high-tech support in a compact form." + icon = 'icons/obj/structures/machinery/aibots.dmi' + icon_state = "cprbot0" + density = FALSE + anchored = FALSE + health = 100 + maxhealth = 100 + req_access = list(ACCESS_MARINE_MEDBAY) + var/const/search_radius = 10 + var/const/cprbot_proximity_check_radius = 2 // Radius to check for nearby CPRbots + var/processing = TRUE + var/mob/living/carbon/human/target = null + var/cpr_cooldown = 0 + var/path = list() // Path for movement logic + var/currently_healing = 0 + var/iff_signal = FACTION_MARINE // IFF signal to check for valid targets + var/list/medical_facts = list( + "Did you know? The human heart beats over 100,000 times a day.", + "Fun fact: Blood makes up about 7% of your body's weight.", + "Medical trivia: Your brain uses 20% of the oxygen you breathe.", + "Laughter really can increase your pain tolerance.", + "Did you know? The human skeleton is made up of 206 bones.", + "Fun fact: The average adult human body contains about 5 liters of blood.", + "Medical trivia: The human body has around 37.2 trillion cells.", + "The skin is the largest organ in the human body.", + "Did you know? The liver can regenerate itself if a portion is removed.", + "Fun fact: Your sense of smell is closely linked to your memory." + "The only muscle that never tires is that heart." + "Did you know? not breathing can lead to a premature cessation of life!" + ) + var/list/idle_messages = list( + "Stay still, I'm assessing the situation.", + "Just a routine check-up, don't worry.", + "Scanning the area for any casualties.", + "I’m ready to save lives, one compression at a time.", + "I hope everyone is feeling alright today!" + "It's not magic it's CPR Buddy 9000!" + "I should have been a plastic surgeon." + "What kind of medbay is this, everyone’s dropping like flies" + "Each breath a day keeps me at bay!" + "I sense a disturbance in my circuit board. as of a million people stopped breathing and were suddenly silent." + ) + var/motivational_message = "Live! Live! Don't die on me now!" + var/list/has_said_to_patient = list() // Track which patients have been warned + var/last_message_time = 0 // Tracks the last time a message was spoken + +/obj/structure/machinery/bot/cprbot/New() + ..() + spawn(5) // Wait for 5 seconds after spawning before starting initialization + src.initialize_cprbot() + +/obj/structure/machinery/bot/cprbot/proc/initialize_cprbot() + while (processing && health > 0) + src.find_and_move_to_patient() + if (target && world.time >= cpr_cooldown) + src.perform_cpr(target) + src.random_message() // Check if it's time to send a random message + sleep(2) // Slower processing loop, moves once every 2 seconds + +/obj/structure/machinery/bot/cprbot/proc/random_message() + if (world.time >= last_message_time + 600) // At least 1 minute (600 deciseconds) cooldown + if (currently_healing) + src.speak(motivational_message) + else + if (prob(50)) + src.speak(pick(medical_facts)) + else + src.speak(pick(idle_messages)) + last_message_time = world.time // Update the last message time + +/obj/structure/machinery/bot/cprbot/proc/speak(message) + if (!processing || !message) + return + visible_message("[src] beeps, \"[message]\"") + +/obj/structure/machinery/bot/cprbot/proc/find_and_move_to_patient() + var/list/humans = list() + for (var/mob/living/carbon/human/H in range(search_radius)) + if (H.stat == DEAD && H.check_tod() && H.is_revivable() && H.get_target_lock(iff_signal) && !src.has_nearby_cprbot(H)) + humans += H + + if (humans.len > 0) + target = src.get_nearest(humans) + if (target && !has_said_to_patient.Find(target)) + visible_message("[target] is injured! I'm coming!") + has_said_to_patient += target + src.move_to_target(target) + else + target = null + +/obj/structure/machinery/bot/cprbot/proc/has_nearby_cprbot(mob/living/carbon/human/H) + // Check if there are any other CPRbots within a two-tile radius of the target + for (var/obj/structure/machinery/bot/cprbot/nearby_cprbot in range(H, cprbot_proximity_check_radius)) + if (nearby_cprbot != src) // Ignore self + return TRUE + return FALSE + +/obj/structure/machinery/bot/cprbot/proc/get_nearest(list/humans) + var/mob/living/carbon/human/nearest = null + var/distance = search_radius + 1 + + for (var/mob/living/carbon/human/H in humans) + var/d = get_dist(src, H) + if (d < distance) + nearest = H + distance = d + + return nearest + +/obj/structure/machinery/bot/cprbot/proc/move_to_target(mob/living/carbon/human/H) + if (H) + var/pathfinding_result = AStar(src.loc, get_turf(H), /turf/proc/CardinalTurfsWithAccess, /turf/proc/Distance, 0, 30) + if (length(pathfinding_result) == 0) + // No reachable path to the target, so stop looking for this patient + target = null + return + path = pathfinding_result + + // Begin moving towards the target if a path exists + if (get_dist(src, H) > 1) + if (length(path) > 0) + step_to(src, path[1]) + path -= path[1] + spawn(10) // Moves every 10 ticks (approximately 1 second) + if (length(path)) + step_to(src, path[1]) + path -= path[1] + else + currently_healing = 1 + else + // No valid target, stop looking + target = null + + +/obj/structure/machinery/bot/cprbot/proc/perform_cpr(mob/living/carbon/human/H) + if (!H || H.stat != DEAD || !H.is_revivable() || !ishuman_strict(H)) + target = null + icon_state = "cprbot0" + currently_healing = 0 + return + + if (get_dist(src, H) > 1) + src.move_to_target(H) + return + + icon_state = "cprbot_active" + H.revive_grace_period += 4 SECONDS + cpr_cooldown = world.time + 7 SECONDS + H.visible_message(SPAN_NOTICE("[src] automatically performs CPR on [H].")) + H.visible_message(SPAN_DANGER("Currently performing CPR on [H] do not intervene!")) + currently_healing = 1 + + spawn(0.5) + while (H.is_revivable() && H.stat == DEAD && get_dist(src, H) <= 1) + sleep(0.5) + icon_state = "cprbot0" + currently_healing = 0 + +/obj/structure/machinery/bot/cprbot/proc/self_destruct(mob/living/carbon/human/user = null) + var/obj/item/cprbot_item/I = new /obj/item/cprbot_item(src.loc) + + if (user) + if (!user.put_in_active_hand(I)) + if (!user.put_in_inactive_hand(I)) + I.forceMove(src.loc) + else + I.forceMove(src.loc) + + qdel(src) + +/obj/structure/machinery/bot/cprbot/attack_hand(mob/user as mob) + if (..()) + return TRUE + + SEND_SIGNAL(user, COMSIG_LIVING_ATTACKHAND_HUMAN, src) + + if (user != src) + visible_message(SPAN_DANGER("[user] begins to undeploy [src]!")) + src.self_destruct(user) + return TRUE + +/obj/structure/machinery/bot/cprbot/explode() + src.on = 0 + src.visible_message(SPAN_DANGER("[src] blows apart!"), null, null, 1) + var/turf/Tsec = get_turf(src) + + new /obj/item/cprbot_broken(Tsec) + + var/datum/effect_system/spark_spread/s = new /datum/effect_system/spark_spread + s.set_up(3, 1, src) + s.start() + qdel(src) + return + diff --git a/code/game/machinery/vending/vendor_types/crew/synthetic.dm b/code/game/machinery/vending/vendor_types/crew/synthetic.dm index 240155176efa..841a146898ba 100644 --- a/code/game/machinery/vending/vendor_types/crew/synthetic.dm +++ b/code/game/machinery/vending/vendor_types/crew/synthetic.dm @@ -411,6 +411,7 @@ GLOBAL_LIST_INIT(cm_vending_synth_tools, list( list("Maintenance Jack", 15, /obj/item/maintenance_jack, null, VENDOR_ITEM_REGULAR), list("Portable Dialysis Machine", 15, /obj/item/tool/portadialysis, null, VENDOR_ITEM_REGULAR), list("Telescopic Baton", 15, /obj/item/weapon/telebaton, null, VENDOR_ITEM_REGULAR), + list("CPR Buddy 9k", 15, /obj/item/cprbot_item, null, VENDOR_ITEM_REGULAR), )) //------------EXPERIMENTAL TOOL KITS--------------- diff --git a/code/game/objects/items/cprbot.dm b/code/game/objects/items/cprbot.dm new file mode 100644 index 000000000000..32c8c2345cf4 --- /dev/null +++ b/code/game/objects/items/cprbot.dm @@ -0,0 +1,112 @@ +/obj/item/cprbot_item + name = "CPRbot" + desc = "A compact CPRbot 9000 asemply" + icon = 'icons/obj/structures/machinery/aibots.dmi' + icon_state = "cprbot" + w_class = SIZE_MEDIUM + var/deployment_path = /obj/structure/machinery/bot/cprbot + +/obj/item/cprbot_item/proc/deploy_cprbot(mob/user, atom/location) + if(!user || !location) + return + // Attempt to delete the item first + qdel(src) + world << "Deleting item [src]. Deploying CPRbot at [location]." + + // Proceed with the CPRbot deployment + var/obj/structure/machinery/bot/cprbot/R = new deployment_path(location) + if(R) + R.add_fingerprint(user) + world << "CPRbot deployed successfully at [location]." + else + world << "Failed to deploy CPRbot at [location]." + +/obj/item/cprbot_item/attack_self(mob/user) + if (..()) + return TRUE + if(user) + deploy_cprbot(user, user.loc) + +/obj/item/cprbot_item/afterattack(atom/target, mob/user, proximity) + if(proximity && isturf(target)) + var/turf/T = target + if(!T.density) + deploy_cprbot(user, T) + +/obj/item/cprbot_broken + name = "CPRbot" + desc = "A compact CPRbot 9000 asemply it appears to be in bad shape" + icon = 'icons/obj/structures/machinery/aibots.dmi' + icon_state = "cprbot_broken" + w_class = SIZE_MEDIUM + +/obj/item/cprbot_broken/attackby(obj/item/W, mob/living/user) + if(iswelder(W)) + if(!HAS_TRAIT(W, TRAIT_TOOL_BLOWTORCH)) + to_chat(user, SPAN_WARNING("You need a stronger blowtorch!")) + return + + var/obj/item/tool/weldingtool/WT = W + if(!WT.isOn()) + to_chat(user, SPAN_WARNING("\The [WT] needs to be on!")) + return + + if(!WT.remove_fuel(5, user)) // Ensure the welder has enough fuel to operate + to_chat(user, SPAN_NOTICE("You need more welding fuel to complete this task.")) + return + + playsound(src, 'sound/items/Welder.ogg', 25, 1) + + if(!do_after(user, 10 * user.get_skill_duration_multiplier(SKILL_CONSTRUCTION), INTERRUPT_ALL | BEHAVIOR_IMMOBILE, BUSY_ICON_BUILD)) + return + + var/obj/item/cprbot_item/I = new /obj/item/cprbot_item(src.loc) + + if(user) + if(!user.put_in_active_hand(I)) + if(!user.put_in_inactive_hand(I)) + I.forceMove(src.loc) + else + I.forceMove(src.loc) + +/obj/item/cprbot_broken/attackby(obj/item/W, mob/living/user) + if(iswelder(W)) + if(!HAS_TRAIT(W, TRAIT_TOOL_BLOWTORCH)) + to_chat(user, SPAN_WARNING("You need a stronger blowtorch!")) + return + + var/obj/item/tool/weldingtool/WT = W + if(!WT.isOn()) + to_chat(user, SPAN_WARNING("\The [WT] needs to be on!")) + return + + if(!WT.remove_fuel(5, user)) // Ensure enough fuel is available + to_chat(user, SPAN_NOTICE("You need more welding fuel to complete this task.")) + return + + playsound(src, 'sound/items/Welder.ogg', 25, 1) + + if(!do_after(user, 10 * user.get_skill_duration_multiplier(SKILL_CONSTRUCTION), INTERRUPT_ALL | BEHAVIOR_IMMOBILE, BUSY_ICON_BUILD)) + return + + // Create the repaired item + var/obj/item/cprbot_item/I = new /obj/item/cprbot_item(src.loc) + + // Check if the broken item is in the user's hand + var/hand_was_active = user.get_active_hand() == src + var/hand_was_inactive = user.get_inactive_hand() == src + + // Remove the broken item + qdel(src) + + // Attempt to place the new item into the user's hands + if (hand_was_active) + if (!user.put_in_active_hand(I)) + I.forceMove(user.loc) // Place it at user's location if hands are full + else if (hand_was_inactive) + if (!user.put_in_inactive_hand(I)) + I.forceMove(user.loc) // Place it at user's location if hands are full + else + I.forceMove(user.loc) // Place at the original location if not in hand + + diff --git a/colonialmarines.dme b/colonialmarines.dme index 48996a93ec30..f4a7f188cd9a 100644 --- a/colonialmarines.dme +++ b/colonialmarines.dme @@ -904,6 +904,7 @@ #include "code\game\machinery\atmoalter\scrubber.dm" #include "code\game\machinery\bots\bots.dm" #include "code\game\machinery\bots\cleanbot.dm" +#include "code\game\machinery\bots\cprbot.dm" #include "code\game\machinery\bots\floorbot.dm" #include "code\game\machinery\bots\medbot.dm" #include "code\game\machinery\bots\mulebot.dm" @@ -1088,6 +1089,7 @@ #include "code\game\objects\items\contraband.dm" #include "code\game\objects\items\cosmetics.dm" #include "code\game\objects\items\cpr_dummy.dm" +#include "code\game\objects\items\cprbot.dm" #include "code\game\objects\items\disks.dm" #include "code\game\objects\items\fulton.dm" #include "code\game\objects\items\gift_wrappaper.dm" diff --git a/icons/obj/structures/machinery/aibots.dmi b/icons/obj/structures/machinery/aibots.dmi index 18cf52cb43be..1fecfe73b336 100644 Binary files a/icons/obj/structures/machinery/aibots.dmi and b/icons/obj/structures/machinery/aibots.dmi differ