diff --git a/code/__DEFINES/robots.dm b/code/__DEFINES/robots.dm index e45ba3d2bb2..8da9a806493 100644 --- a/code/__DEFINES/robots.dm +++ b/code/__DEFINES/robots.dm @@ -4,11 +4,11 @@ //Bot defines, placed here so they can be read by other things! #define BOT_STEP_DELAY 4 //Delay between movemements -#define BOT_STEP_MAX_RETRIES 5 //Maximum times a bot will retry to step from its position +#define BOT_STEP_MAX_RETRIES 5 //Maximum times a bot will retry to step from its position #define DEFAULT_SCAN_RANGE 7 //default view range for finding targets. -//Mode defines +//Mode defines. If you add a new one make sure you update mode_name in /mob/living/simple_animal/bot #define BOT_IDLE 0 // idle #define BOT_HUNT 1 // found target, hunting #define BOT_PREP_ARREST 2 // at target, preparing to arrest @@ -27,6 +27,7 @@ #define BOT_NAV 15 // computing navigation #define BOT_WAIT_FOR_NAV 16 // waiting for nav computation #define BOT_NO_ROUTE 17 // no destination beacon found (or no route) +#define BOT_SHOWERSTANCE 18 // cleaning unhygienic humans //Bot types #define SEC_BOT (1<<0) // Secutritrons (Beepsky) and ED-209s @@ -36,6 +37,7 @@ #define MED_BOT (1<<4) // Medibots #define HONK_BOT (1<<5) // Honkbots & ED-Honks #define FIRE_BOT (1<<6) // Firebots +#define HYGIENE_BOT (1<<7) // Hygienebots //AI notification defines #define NEW_BORG 1 diff --git a/code/game/atoms.dm b/code/game/atoms.dm index f5845c25252..9ea7c421fe3 100644 --- a/code/game/atoms.dm +++ b/code/game/atoms.dm @@ -862,6 +862,22 @@ color = C return + +///Proc for being washed by a shower +/atom/proc/washed(var/atom/washer) + . = SEND_SIGNAL(src, COMSIG_COMPONENT_CLEAN_ACT, CLEAN_WEAK) + remove_atom_colour(WASHABLE_COLOUR_PRIORITY) + + var/datum/component/radioactive/healthy_green_glow = GetComponent(/datum/component/radioactive) + if(!healthy_green_glow || QDELETED(healthy_green_glow)) + return + var/strength = healthy_green_glow.strength + if(strength <= RAD_BACKGROUND_RADIATION) + qdel(healthy_green_glow) + return + healthy_green_glow.strength -= max(0, (healthy_green_glow.strength - (RAD_BACKGROUND_RADIATION * 2)) * 0.2) + + /** * call back when a var is edited on this atom * diff --git a/code/game/objects/effects/decals/cleanable.dm b/code/game/objects/effects/decals/cleanable.dm index b197c758c97..c6f8422c6cf 100644 --- a/code/game/objects/effects/decals/cleanable.dm +++ b/code/game/objects/effects/decals/cleanable.dm @@ -100,6 +100,9 @@ S.blood_state = blood_state update_icon() H.update_inv_shoes() +/atom/effect/decal/cleanable/washed(atom/washer) + . = ..() + qdel(src) /obj/effect/decal/cleanable/proc/can_bloodcrawl_in() if((blood_state != BLOOD_STATE_OIL) && (blood_state != BLOOD_STATE_NOT_BLOODY)) diff --git a/code/game/objects/structures/shower.dm b/code/game/objects/structures/shower.dm index 67298ec9b49..20e48a7efbb 100644 --- a/code/game/objects/structures/shower.dm +++ b/code/game/objects/structures/shower.dm @@ -98,102 +98,12 @@ wash_atom(AM) /obj/machinery/shower/proc/wash_atom(atom/A) - SEND_SIGNAL(A, COMSIG_COMPONENT_CLEAN_ACT, CLEAN_WEAK) + A.washed(src) reagents.reaction(A, TOUCH, reaction_volume) - if(isobj(A)) - wash_obj(A) - else if(isturf(A)) - wash_turf(A) - else if(isliving(A)) - wash_mob(A) + if(isliving(A)) check_heat(A) - contamination_cleanse(A) - -/obj/machinery/shower/proc/wash_obj(obj/O) - . = SEND_SIGNAL(O, COMSIG_COMPONENT_CLEAN_ACT, CLEAN_WEAK) - O.remove_atom_colour(WASHABLE_COLOUR_PRIORITY) - - -/obj/machinery/shower/proc/wash_turf(turf/tile) - SEND_SIGNAL(tile, COMSIG_COMPONENT_CLEAN_ACT, CLEAN_WEAK) - tile.remove_atom_colour(WASHABLE_COLOUR_PRIORITY) - for(var/obj/effect/E in tile) - if(is_cleanable(E)) - qdel(E) - - -/obj/machinery/shower/proc/wash_mob(mob/living/L) - SEND_SIGNAL(L, COMSIG_COMPONENT_CLEAN_ACT, CLEAN_WEAK) - L.remove_atom_colour(WASHABLE_COLOUR_PRIORITY) - SEND_SIGNAL(L, COMSIG_ADD_MOOD_EVENT, "shower", /datum/mood_event/nice_shower) - if(iscarbon(L)) - var/mob/living/carbon/M = L - . = TRUE - - for(var/obj/item/I in M.held_items) - wash_obj(I) - - if(M.back && wash_obj(M.back)) - M.update_inv_back(0) - - var/list/obscured = M.check_obscured_slots() - - if(M.head && wash_obj(M.head)) - M.update_inv_head() - - if(M.glasses && !(ITEM_SLOT_EYES in obscured) && wash_obj(M.glasses)) - M.update_inv_glasses() - - if(M.wear_mask && !(ITEM_SLOT_MASK in obscured) && wash_obj(M.wear_mask)) - M.update_inv_wear_mask() - - if(M.ears && !(HIDEEARS in obscured) && wash_obj(M.ears)) - M.update_inv_ears() - - if(M.wear_neck && !(ITEM_SLOT_NECK in obscured) && wash_obj(M.wear_neck)) - M.update_inv_neck() - - if(M.shoes && !(HIDESHOES in obscured) && wash_obj(M.shoes)) - M.update_inv_shoes() - - var/washgloves = FALSE - if(M.gloves && !(HIDEGLOVES in obscured)) - washgloves = TRUE - - if(ishuman(M)) - var/mob/living/carbon/human/H = M - - if(H.wear_suit && wash_obj(H.wear_suit)) - H.update_inv_wear_suit() - else if(H.w_uniform && wash_obj(H.w_uniform)) - H.update_inv_w_uniform() - - if(washgloves) - SEND_SIGNAL(H, COMSIG_COMPONENT_CLEAN_ACT, CLEAN_STRENGTH_BLOOD) - - if(!H.is_mouth_covered()) - H.lip_style = null - H.update_body() - - if(H.belt && wash_obj(H.belt)) - H.update_inv_belt() - else - SEND_SIGNAL(M, COMSIG_COMPONENT_CLEAN_ACT, CLEAN_STRENGTH_BLOOD) - else - SEND_SIGNAL(L, COMSIG_COMPONENT_CLEAN_ACT, CLEAN_STRENGTH_BLOOD) - -/obj/machinery/shower/proc/contamination_cleanse(atom/thing) - var/datum/component/radioactive/healthy_green_glow = thing.GetComponent(/datum/component/radioactive) - if(!healthy_green_glow || QDELETED(healthy_green_glow)) - return - var/strength = healthy_green_glow.strength - if(strength <= RAD_BACKGROUND_RADIATION) - qdel(healthy_green_glow) - return - healthy_green_glow.strength -= max(0, (healthy_green_glow.strength - (RAD_BACKGROUND_RADIATION * 2)) * 0.2) - /obj/machinery/shower/process() if(on) wash_atom(loc) diff --git a/code/modules/lighting/lighting_object.dm b/code/modules/lighting/lighting_object.dm index 3d84a0a81d9..e219f08e159 100644 --- a/code/modules/lighting/lighting_object.dm +++ b/code/modules/lighting/lighting_object.dm @@ -144,6 +144,9 @@ /atom/movable/lighting_object/onTransitZ() return +/atom/movable/lighting_object/washed(var/washer) + return + // Override here to prevent things accidentally moving around overlays. /atom/movable/lighting_object/forceMove(atom/destination, no_tp=FALSE, harderforce = FALSE) if(harderforce) diff --git a/code/modules/mob/living/carbon/carbon.dm b/code/modules/mob/living/carbon/carbon.dm index 9a731ef6b9b..a823dee108f 100644 --- a/code/modules/mob/living/carbon/carbon.dm +++ b/code/modules/mob/living/carbon/carbon.dm @@ -1038,3 +1038,33 @@ if(mood) if(mood.sanity < SANITY_UNSTABLE) return TRUE + +/mob/living/carbon/washed(var/atom/washer) + . = ..() + SEND_SIGNAL(src, COMSIG_ADD_MOOD_EVENT, "shower", /datum/mood_event/nice_shower) + + for(var/obj/item/I in held_items) + I.washed(washer) + + if(back) + update_inv_back(0) + + var/list/obscured = check_obscured_slots() + + if(head && head.washed(washer)) + update_inv_head() + + if(glasses && !(ITEM_SLOT_EYES in obscured) && glasses.washed(washer)) + update_inv_glasses() + + if(wear_mask && !(ITEM_SLOT_MASK in obscured && wear_mask.washed(washer))) + update_inv_wear_mask() + + if(ears && !(HIDEEARS in obscured) && ears.washed(washer)) + update_inv_ears() + + if(wear_neck && !(ITEM_SLOT_NECK in obscured) && wear_neck.washed(washer)) + update_inv_neck() + + if(shoes && !(HIDESHOES in obscured) && shoes.washed(washer)) + update_inv_shoes() diff --git a/code/modules/mob/living/carbon/human/human.dm b/code/modules/mob/living/carbon/human/human.dm index f9bbab9ad37..3924395d2e2 100644 --- a/code/modules/mob/living/carbon/human/human.dm +++ b/code/modules/mob/living/carbon/human/human.dm @@ -1087,6 +1087,24 @@ remove_movespeed_modifier(MOVESPEED_ID_DAMAGE_SLOWDOWN_FLYING) +/mob/living/carbon/human/washed(var/atom/washer) + . = ..() + if(wear_suit) + update_inv_wear_suit() + else if(w_uniform && w_uniform.washed(washer)) + update_inv_w_uniform() + + if(!is_mouth_covered()) + lip_style = null + update_body() + if(belt && belt.washed(washer)) + update_inv_belt() + + var/list/obscured = check_obscured_slots() + + if(gloves && !(HIDEGLOVES in obscured) && gloves.washed(washer)) + SEND_SIGNAL(src, COMSIG_COMPONENT_CLEAN_ACT, CLEAN_STRENGTH_BLOOD) + /mob/living/carbon/human/adjust_nutrition(var/change) //Honestly FUCK the oldcoders for putting nutrition on /mob someone else can move it up because holy hell I'd have to fix SO many typechecks if(HAS_TRAIT(src, TRAIT_NOHUNGER)) return FALSE diff --git a/code/modules/mob/living/living.dm b/code/modules/mob/living/living.dm index 2676de2a51d..29d38f40df8 100644 --- a/code/modules/mob/living/living.dm +++ b/code/modules/mob/living/living.dm @@ -1113,6 +1113,11 @@ //Mobs on Fire end +//Washing +/mob/living/washed(var/atom/washer) + . = ..() + SEND_SIGNAL(src, COMSIG_COMPONENT_CLEAN_ACT, CLEAN_STRENGTH_BLOOD) + // used by secbot and monkeys Crossed /mob/living/proc/knockOver(var/mob/living/carbon/C) if(C.key) //save us from monkey hordes diff --git a/code/modules/mob/living/simple_animal/bot/bot.dm b/code/modules/mob/living/simple_animal/bot/bot.dm index 89d4725e254..ada05cc9cfd 100644 --- a/code/modules/mob/living/simple_animal/bot/bot.dm +++ b/code/modules/mob/living/simple_animal/bot/bot.dm @@ -77,12 +77,12 @@ var/model = "" //The type of bot it is. var/bot_type = 0 //The type of bot it is, for radio control. var/data_hud_type = DATA_HUD_DIAGNOSTIC_BASIC //The type of data HUD the bot uses. Diagnostic by default. - //This holds text for what the bot is mode doing, reported on the remote bot control interface. + //This holds text for what the bot is mode doing, reported on the remote bot control interface. This is in order of the defines for the mode defines in robots.dm, in order var/list/mode_name = list("In Pursuit","Preparing to Arrest", "Arresting", \ "Beginning Patrol", "Patrolling", "Summoned by PDA", \ "Cleaning", "Repairing", "Proceeding to work site", "Healing", \ "Proceeding to AI waypoint", "Navigating to Delivery Location", "Navigating to Home", \ - "Waiting for clear path", "Calculating navigation path", "Pinging beacon network", "Unable to reach destination") + "Waiting for clear path", "Calculating navigation path", "Pinging beacon network", "Unable to reach destination", "Chasing filth") var/datum/atom_hud/data/bot_path/path_hud = new /datum/atom_hud/data/bot_path() var/path_image_icon = 'icons/mob/aibots.dmi' var/path_image_icon_state = "path_indicator" diff --git a/code/modules/mob/living/simple_animal/bot/construction.dm b/code/modules/mob/living/simple_animal/bot/construction.dm index fe24fa7ced8..c89748d6d1f 100644 --- a/code/modules/mob/living/simple_animal/bot/construction.dm +++ b/code/modules/mob/living/simple_animal/bot/construction.dm @@ -450,3 +450,43 @@ F.name = created_name qdel(I) qdel(src) + +//Get cleaned +/obj/item/bot_assembly/hygienebot + name = "incomplete hygienebot assembly" + desc = "Clear out the swamp once and for all" + icon = 'icons/obj/watercloset.dmi' + icon_state = "drone" + created_name = "Hygienebot" + +/obj/item/bot_assembly/hygienebot/attackby(obj/item/I, mob/user, params) + . = ..() + switch(build_step) + if(ASSEMBLY_FIRST_STEP) + if(I.tool_behaviour == TOOL_WELDER) + if(I.use_tool(src, user, 0, volume=40)) + add_overlay("hs_hole") + to_chat(user, "You weld a water hole in [src]!") + build_step++ + + if(ASSEMBLY_SECOND_STEP) + if(!can_finish_build(I, user)) + return + if(istype(I, /obj/item/stack/ducts)) + var/obj/item/stack/ducts/D = I + if(D.get_amount() < 1) + to_chat(user, "You need one fluid duct to finish [src]") + return + to_chat(user, "You start to pipe up [src]...") + if(do_after(user, 40, target = src)) + if(D.get_amount() >= 1) + D.use(1) + to_chat(user, "You pipe up [src].") + build_step++ + else + return + var/mob/living/simple_animal/bot/hygienebot/H = new(drop_location()) + H.name = created_name + qdel(I) + qdel(src) + return TRUE diff --git a/code/modules/mob/living/simple_animal/bot/hygienebot.dm b/code/modules/mob/living/simple_animal/bot/hygienebot.dm new file mode 100644 index 00000000000..321d4828ce6 --- /dev/null +++ b/code/modules/mob/living/simple_animal/bot/hygienebot.dm @@ -0,0 +1,254 @@ +//Cleanbot +/mob/living/simple_animal/bot/hygienebot + name = "\improper Hygienebot" + desc = "A flying cleaning robot, he'll chase down people who can't shower properly!" + icon = 'icons/obj/watercloset.dmi' + icon_state = "drone" + density = FALSE + anchored = FALSE + health = 100 + maxHealth = 100 + radio_key = /obj/item/encryptionkey/headset_service + radio_channel = RADIO_CHANNEL_SERVICE //Service + bot_type = HYGIENE_BOT + model = "Cleanbot" + bot_core_type = /obj/machinery/bot_core/hygienebot + window_id = "autoclean" + window_name = "Automatic Crew Cleaner X2" + pass_flags = PASSMOB + path_image_color = "#993299" + allow_pai = FALSE + layer = ABOVE_MOB_LAYER + + var/mob/living/carbon/human/target + var/currentspeed = 5 + var/washing = FALSE + var/mad = FALSE + var/last_found + var/oldtarget_name + + var/mutable_appearance/water_overlay + var/mutable_appearance/fire_overlay + +/mob/living/simple_animal/bot/hygienebot/Initialize() + . = ..() + update_icon() + var/datum/job/janitor/J = new/datum/job/janitor + access_card.access += J.get_access() + prev_access = access_card.access + +/mob/living/simple_animal/bot/hygienebot/explode() + walk_to(src,0) + visible_message("[src] blows apart in a foamy explosion!") + do_sparks(3, TRUE, src) + on = FALSE + new /obj/effect/particle_effect/foam(loc) + + ..() + +/mob/living/simple_animal/bot/hygienebot/Cross(atom/movable/AM) + . = ..() + if(washing) + do_wash(AM) + +/mob/living/simple_animal/bot/hygienebot/Crossed(atom/movable/AM) + . = ..() + if(washing) + do_wash(AM) + +/mob/living/simple_animal/bot/hygienebot/update_icon_state() + . = ..() + if(on) + icon_state = "drone-on" + else + icon_state = "drone" + + +/mob/living/simple_animal/bot/hygienebot/update_overlays() + . = ..() + if(on) + var/mutable_appearance/fire_overlay = mutable_appearance(icon,"flame") + . +=fire_overlay + + + if(washing) + var/mutable_appearance/water_overlay = mutable_appearance(icon, emagged ? "dronefire" : "dronewater") + . += water_overlay + + +/mob/living/simple_animal/bot/hygienebot/turn_off() + ..() + mode = BOT_IDLE + +/mob/living/simple_animal/bot/hygienebot/bot_reset() + ..() + target = null + oldtarget_name = null + walk_to(src,0) + last_found = world.time + + +/mob/living/simple_animal/bot/hygienebot/Crossed(atom/movable/AM) + . = ..() + if(washing) + do_wash(AM) + +/mob/living/simple_animal/bot/hygienebot/Moved() + . = ..() + if(washing && isturf(loc) && !emagged) + var/turf/open/OT = loc + OT.MakeSlippery(TURF_WET_WATER, min_wet_time = 10 SECONDS, wet_time_to_add = 5 SECONDS) + +/mob/living/simple_animal/bot/hygienebot/handle_automated_action() + if(!..()) + return + + if(washing) + do_wash(loc) + for(var/AM in loc) + do_wash(AM) + if(isopenturf(loc) && !emagged) + var/turf/open/tile = loc + tile.MakeSlippery(TURF_WET_WATER, min_wet_time = 10 SECONDS, wet_time_to_add = 5 SECONDS) + + switch(mode) + if(BOT_IDLE) // idle + walk_to(src,0) + look_for_lowhygiene() // see if any disgusting fucks are in range + if(!mode && auto_patrol) // still idle, and set to patrol + mode = BOT_START_PATROL // switch to patrol mode + + if(BOT_HUNT) // hunting for stinkman + // if can't reach stinkman for long enough, don't give up, try harder. + if(emagged) //lol fuck em up + currentspeed = 8 + start_washing() + mad = TRUE + else + switch(frustration) + if(0 to 4) + currentspeed = 5 + stop_washing() + mad = FALSE + if(4 to INFINITY) + currentspeed = 2.5 + start_washing() + mad = TRUE + if(target) + if(target.loc == loc && isturf(target.loc)) //LADIES AND GENTLEMAN WE GOTEM PREPARE TO DUMP + start_washing() + if(mad) + speak("Well about fucking time you degenerate", "Fucking finally", "Thank god, you finally stopped") + playsound(loc, 'sound/effects/hygienebot_angry.ogg', 60, 1) + mad = FALSE + mode = BOT_SHOWERSTANCE + else + var/turf/olddist = get_dist(src, target) + walk_to(src, target,0, currentspeed) + if(mad && prob(60)) + playsound(loc, 'sound/effects/hygienebot_angry.ogg', 60, 1) + speak(pick("Get back here you foul smelling fucker.", "If you don't get back here right now I'm going to give you a fucking vasectomy.", "STOP RUNNING OR I WILL CUT YOUR ARTERIES!", "Just fucking let me clean you you arsehole!", "STOP. RUNNING.", "Either you stop running or I will fucking drag you out of an airlock.", "I just want to fucking clean you you troglodyte.", "If you don't come back here I'll put a green cloud around you cunt.")) + if((get_dist(src, target)) >= (olddist)) + frustration++ + else + frustration = 0 + else + back_to_idle() + + if(BOT_SHOWERSTANCE) + if(check_purity(target)) + speak("Enjoy your clean and tidy day!") + playsound(loc, 'sound/effects/hygienebot_happy.ogg', 60, 1) + back_to_idle() + return + if(!target) + last_found = world.time + if(target.loc != loc && !isturf(target.loc)) + back_to_hunt() + + if(BOT_START_PATROL) + look_for_lowhygiene() + start_patrol() + + if(BOT_PATROL) + look_for_lowhygiene() + bot_patrol() + +/mob/living/simple_animal/bot/hygienebot/proc/back_to_idle() + mode = BOT_IDLE + walk_to(src,0) + target = null + frustration = 0 + last_found = world.time + stop_washing() + INVOKE_ASYNC(src, .proc/handle_automated_action) + +/mob/living/simple_animal/bot/hygienebot/proc/back_to_hunt() + frustration = 0 + mode = BOT_HUNT + stop_washing() + INVOKE_ASYNC(src, .proc/handle_automated_action) + +/mob/living/simple_animal/bot/hygienebot/proc/look_for_lowhygiene() + for (var/mob/living/carbon/human/H in view(7,src)) //Find the NEET + if((H.name == oldtarget_name) && (world.time < last_found + 100)) + continue + if(!check_purity(H)) //Theyre impure + target = H + oldtarget_name = H.name + speak("Unhygienic client found. Please stand still so I can clean you.") + playsound(loc, 'sound/effects/hygienebot_happy.ogg', 60, 1) + visible_message("[src] points at [H.name]!") + mode = BOT_HUNT + INVOKE_ASYNC(src, .proc/handle_automated_action) + break + else + continue + +/mob/living/simple_animal/bot/hygienebot/proc/start_washing() + washing = TRUE + update_icon() + +/mob/living/simple_animal/bot/hygienebot/proc/stop_washing() + washing = FALSE + update_icon() + + + +/mob/living/simple_animal/bot/hygienebot/get_controls(mob/user) + var/list/dat = list() + dat += hack(user) + dat += showpai(user) + dat += {" +Hygienebot X2 controls

+Status: ["[on ? "On" : "Off"]"]
+Behaviour controls are [locked ? "locked" : "unlocked"]
+Maintenance panel is [open ? "opened" : "closed"]"} + + if(!locked || issilicon(user) || IsAdminGhost(user)) + dat += {"
Auto Patrol: ["[auto_patrol ? "On" : "Off"]"]"} + + return dat.Join("") + +/mob/living/simple_animal/bot/hygienebot/proc/check_purity(mob/living/L) + if(emagged && L.stat != DEAD) + return FALSE + + for(var/X in list(ITEM_SLOT_HEAD, ITEM_SLOT_MASK, ITEM_SLOT_ICLOTHING, ITEM_SLOT_OCLOTHING, ITEM_SLOT_FEET)) + + var/obj/item/I = L.get_item_by_slot(X) + if(I && HAS_BLOOD_DNA(I)) + return FALSE + return TRUE + +/mob/living/simple_animal/bot/hygienebot/proc/do_wash(atom/A) + if(emagged) + A.fire_act() + return //lol pranked no cleaning besides that + else + A.washed(src) + + + +/obj/machinery/bot_core/hygienebot + req_one_access = list(ACCESS_JANITOR, ACCESS_ROBOTICS) diff --git a/icons/obj/watercloset.dmi b/icons/obj/watercloset.dmi index 38a340ec15b..31e85ee7e9b 100644 Binary files a/icons/obj/watercloset.dmi and b/icons/obj/watercloset.dmi differ diff --git a/sound/effects/hygienebot_angry.ogg b/sound/effects/hygienebot_angry.ogg new file mode 100644 index 00000000000..d184cc38e21 Binary files /dev/null and b/sound/effects/hygienebot_angry.ogg differ diff --git a/sound/effects/hygienebot_happy.ogg b/sound/effects/hygienebot_happy.ogg new file mode 100644 index 00000000000..ca3b9159c37 Binary files /dev/null and b/sound/effects/hygienebot_happy.ogg differ diff --git a/tgstation.dme b/tgstation.dme index 2febe1c07f5..037fb907b3f 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -2235,6 +2235,7 @@ #include "code\modules\mob\living\simple_animal\bot\firebot.dm" #include "code\modules\mob\living\simple_animal\bot\floorbot.dm" #include "code\modules\mob\living\simple_animal\bot\honkbot.dm" +#include "code\modules\mob\living\simple_animal\bot\hygienebot.dm" #include "code\modules\mob\living\simple_animal\bot\medbot.dm" #include "code\modules\mob\living\simple_animal\bot\mulebot.dm" #include "code\modules\mob\living\simple_animal\bot\secbot.dm"