diff --git a/_maps/shuttles/emergency_tranquility.dmm b/_maps/shuttles/emergency_tranquility.dmm index b2a15c5df11..5458e59c843 100644 --- a/_maps/shuttles/emergency_tranquility.dmm +++ b/_maps/shuttles/emergency_tranquility.dmm @@ -716,7 +716,7 @@ /turf/open/floor/grass, /area/shuttle/escape) "nQ" = ( -/mob/living/simple_animal/bot/hygienebot, +/mob/living/basic/bot/hygienebot, /turf/open/floor/wood, /area/shuttle/escape) "nV" = ( diff --git a/_maps/shuttles/ruin_cyborg_mothership.dmm b/_maps/shuttles/ruin_cyborg_mothership.dmm index 7188b8bd3c0..5093dfcc7db 100644 --- a/_maps/shuttles/ruin_cyborg_mothership.dmm +++ b/_maps/shuttles/ruin_cyborg_mothership.dmm @@ -215,17 +215,6 @@ /obj/structure/lattice/catwalk, /turf/open/space/basic, /area/shuttle/ruin/cyborg_mothership) -"mG" = ( -/obj/structure/table, -/obj/item/surgery_tray/full, -/obj/effect/turf_decal/bot, -/obj/structure/sink/directional/east, -/obj/item/toy/figure/borg{ - pixel_x = 7; - pixel_y = 12 - }, -/turf/open/floor/iron/dark, -/area/shuttle/ruin/cyborg_mothership) "mN" = ( /obj/machinery/conveyor{ dir = 4; @@ -584,11 +573,6 @@ /obj/structure/cable, /turf/open/floor/iron/showroomfloor, /area/shuttle/ruin/cyborg_mothership) -"Fe" = ( -/mob/living/simple_animal/bot/hygienebot, -/obj/machinery/camera/directional/south, -/turf/open/floor/iron/showroomfloor, -/area/shuttle/ruin/cyborg_mothership) "FQ" = ( /obj/structure/lattice, /mob/living/basic/hivebot/range, @@ -654,6 +638,11 @@ /mob/living/basic/hivebot/mechanic, /turf/template_noop, /area/shuttle/ruin/cyborg_mothership) +"JS" = ( +/mob/living/basic/bot/hygienebot, +/obj/machinery/camera/directional/south, +/turf/open/floor/iron/showroomfloor, +/area/shuttle/ruin/cyborg_mothership) "Ks" = ( /obj/structure/cable, /obj/machinery/conveyor/inverted{ @@ -722,6 +711,17 @@ /obj/structure/cable, /turf/open/floor/circuit/airless, /area/shuttle/ruin/cyborg_mothership) +"Oq" = ( +/obj/structure/table, +/obj/item/surgery_tray/full, +/obj/effect/turf_decal/bot, +/obj/structure/sink/directional/east, +/obj/item/toy/figure/borg{ + pixel_x = 7; + pixel_y = 12 + }, +/turf/open/floor/iron/dark, +/area/shuttle/ruin/cyborg_mothership) "Ou" = ( /obj/machinery/camera/directional/west, /obj/structure/cable, @@ -1161,7 +1161,7 @@ zZ zZ mN iN -mG +Oq zZ kz kz @@ -1186,7 +1186,7 @@ fB zZ Ey Sd -Fe +JS yF HM yF diff --git a/code/__DEFINES/ai/bot_keys.dm b/code/__DEFINES/ai/bot_keys.dm index 25467e45485..5cf2e4263d4 100644 --- a/code/__DEFINES/ai/bot_keys.dm +++ b/code/__DEFINES/ai/bot_keys.dm @@ -15,6 +15,8 @@ #define BB_RADIO_CHANNEL "radio_channel" ///list of unreachable things we will temporarily ignore #define BB_TEMPORARY_IGNORE_LIST "temporary_ignore_list" +///Last thing we attempted to reach +#define BB_LAST_ATTEMPTED_PATHING "last_attempted_pathing" // medbot keys ///the patient we must heal @@ -55,3 +57,17 @@ #define BB_ACID_SPRAY_TARGET "acid_spray_target" ///key that holds trash we will burn #define BB_HUNTABLE_TRASH "huntable_trash" + +//hygienebots +///key that holds our threats +#define BB_WASH_THREATS "wash_threats" +///key that holds speech when we find our target +#define BB_WASH_FOUND "wash_found" +///key that holds speech when we cleaned our target +#define BB_WASH_DONE "wash_done" +///key that holds target we will wash +#define BB_WASH_TARGET "wash_target" +///key that holds how frustrated we are when target is running away +#define BB_WASH_FRUSTRATION "wash_frustration" +///key that holds cooldown after we finish cleaning something, so we dont immediately run off to patrol +#define BB_POST_CLEAN_COOLDOWN "post_clean_cooldown" diff --git a/code/datums/components/crafting/robot.dm b/code/datums/components/crafting/robot.dm index e0c6b4ecd3a..326c58d50c4 100644 --- a/code/datums/components/crafting/robot.dm +++ b/code/datums/components/crafting/robot.dm @@ -131,7 +131,7 @@ /datum/crafting_recipe/hygienebot name = "Hygienebot" - result = /mob/living/simple_animal/bot/hygienebot + result = /mob/living/basic/bot/hygienebot reqs = list( /obj/item/bot_assembly/hygienebot = 1, /obj/item/stack/ducts = 1, diff --git a/code/modules/mob/living/basic/bots/_bots.dm b/code/modules/mob/living/basic/bots/_bots.dm index 5151ec116fc..f8b273804ea 100644 --- a/code/modules/mob/living/basic/bots/_bots.dm +++ b/code/modules/mob/living/basic/bots/_bots.dm @@ -819,8 +819,13 @@ GLOBAL_LIST_INIT(command_strings, list( diag_hud_set_botmode() /mob/living/basic/bot/proc/after_attacked(datum/source, atom/attacker, attack_flags) + SIGNAL_HANDLER + if(attack_flags & ATTACKER_DAMAGING_ATTACK) do_sparks(number = 5, cardinal_only = TRUE, source = src) /mob/living/basic/bot/spawn_gibs(drop_bitflags = NONE) new /obj/effect/gibspawner/robot(drop_location(), src) + +/mob/living/basic/bot/proc/on_bot_movement(atom/movable/source, atom/oldloc, dir, forced) + return diff --git a/code/modules/mob/living/basic/bots/bot_ai.dm b/code/modules/mob/living/basic/bots/bot_ai.dm index bb8d77eb64b..802b66ef48d 100644 --- a/code/modules/mob/living/basic/bots/bot_ai.dm +++ b/code/modules/mob/living/basic/bots/bot_ai.dm @@ -23,6 +23,10 @@ BB_PREVIOUS_BEACON_TARGET, BB_BOT_SUMMON_TARGET, ) + ///how many times we tried to reach the target + var/current_pathing_attempts = 0 + ///if we cant reach it after this many attempts, add it to our ignore list + var/max_pathing_attempts = 25 /datum/ai_controller/basic_controller/bot/TryPossessPawn(atom/new_pawn) . = ..() @@ -70,8 +74,15 @@ var/datum/target = blackboard[key] if(QDELETED(target)) return FALSE - if(!can_reach_target(target, distance)) + var/datum/last_attempt = blackboard[BB_LAST_ATTEMPTED_PATHING] + if(last_attempt != target) + current_pathing_attempts = 0 + set_blackboard_key(BB_LAST_ATTEMPTED_PATHING, target) + else + current_pathing_attempts++ + if(current_pathing_attempts >= max_pathing_attempts || !can_reach_target(target, distance)) clear_blackboard_key(key) + clear_blackboard_key(BB_LAST_ATTEMPTED_PATHING) set_blackboard_key_assoc_lazylist(BB_TEMPORARY_IGNORE_LIST, target, TRUE) return FALSE return TRUE @@ -84,7 +95,7 @@ /datum/ai_behavior/manage_unreachable_list behavior_flags = AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION - action_cooldown = 15 SECONDS + action_cooldown = 45 SECONDS /datum/ai_behavior/manage_unreachable_list/perform(seconds_per_tick, datum/ai_controller/controller, list_key) . = ..() diff --git a/code/modules/mob/living/basic/bots/bot_hud.dm b/code/modules/mob/living/basic/bots/bot_hud.dm index 4ed97fe32f7..c4001473f94 100644 --- a/code/modules/mob/living/basic/bots/bot_hud.dm +++ b/code/modules/mob/living/basic/bots/bot_hud.dm @@ -90,9 +90,13 @@ hud.add_atom_to_hud(src) ///proc that handles moving along the bot's drawn path -/mob/living/basic/bot/proc/handle_loop_movement(atom/movable/source) +/mob/living/basic/bot/proc/handle_loop_movement(atom/movable/source, atom/oldloc, dir, forced) SIGNAL_HANDLER + handle_hud_path() + on_bot_movement(source, oldloc, dir, forced) + +/mob/living/basic/bot/proc/handle_hud_path() if(client || !length(current_pathed_turfs) || isnull(ai_controller)) return @@ -106,7 +110,6 @@ if(target_image) animate(target_image, alpha = 0, time = 0.3 SECONDS) current_pathed_turfs -= our_turf - return ///proc that handles deleting the bot's drawn path when needed /mob/living/basic/bot/proc/clear_path_hud() diff --git a/code/modules/mob/living/basic/bots/cleanbot/cleanbot.dm b/code/modules/mob/living/basic/bots/cleanbot/cleanbot.dm index c19ba69c5ce..8a8050b9afc 100644 --- a/code/modules/mob/living/basic/bots/cleanbot/cleanbot.dm +++ b/code/modules/mob/living/basic/bots/cleanbot/cleanbot.dm @@ -94,7 +94,6 @@ /obj/effect/decal/cleanable/glass, /obj/effect/decal/cleanable/vomit, /obj/effect/decal/cleanable/wrapping, - /obj/effect/decal/remains, )) ///blood we can clean var/static/list/cleanable_blood = typecacheof(list( @@ -111,6 +110,7 @@ var/static/list/huntable_trash = typecacheof(list( /obj/item/trash, /obj/item/food/deadmouse, + /obj/effect/decal/remains, )) ///drawings we hunt var/static/list/cleanable_drawings = typecacheof(list(/obj/effect/decal/cleanable/crayon)) diff --git a/code/modules/mob/living/basic/bots/cleanbot/cleanbot_ai.dm b/code/modules/mob/living/basic/bots/cleanbot/cleanbot_ai.dm index 8007f56f789..5a992c1cc75 100644 --- a/code/modules/mob/living/basic/bots/cleanbot/cleanbot_ai.dm +++ b/code/modules/mob/living/basic/bots/cleanbot/cleanbot_ai.dm @@ -1,4 +1,5 @@ #define BOT_CLEAN_PATH_LIMIT 15 +#define POST_CLEAN_COOLDOWN 5 SECONDS /datum/ai_controller/basic_controller/bot/cleanbot blackboard = list( @@ -51,7 +52,6 @@ /datum/ai_planning_subtree/cleaning_subtree/SelectBehaviors(datum/ai_controller/basic_controller/bot/cleanbot/controller, seconds_per_tick) if(controller.reachable_key(BB_CLEAN_TARGET, BOT_CLEAN_PATH_LIMIT)) - controller.clear_blackboard_key(BB_BEACON_TARGET) controller.queue_behavior(/datum/ai_behavior/execute_clean, BB_CLEAN_TARGET) return SUBTREE_RETURN_FINISH_PLANNING @@ -128,6 +128,7 @@ /datum/ai_behavior/execute_clean/finish_action(datum/ai_controller/controller, succeeded, target_key, targeting_strategy_key, hiding_location_key) . = ..() + controller.set_blackboard_key(BB_POST_CLEAN_COOLDOWN, POST_CLEAN_COOLDOWN + world.time) var/atom/target = controller.blackboard[target_key] if(QDELETED(target) || is_type_in_typecache(target, controller.blackboard[BB_HUNTABLE_TRASH])) return @@ -186,7 +187,7 @@ /datum/ai_planning_subtree/find_patrol_beacon/cleanbot /datum/ai_planning_subtree/find_patrol_beacon/cleanbot/SelectBehaviors(datum/ai_controller/basic_controller/bot/controller, seconds_per_tick) - if(controller.blackboard_key_exists(BB_CLEAN_TARGET)) + if(controller.blackboard[BB_POST_CLEAN_COOLDOWN] >= world.time) return return ..() @@ -214,3 +215,4 @@ controller.clear_blackboard_key(BB_ACTIVE_PET_COMMAND) #undef BOT_CLEAN_PATH_LIMIT +#undef POST_CLEAN_COOLDOWN diff --git a/code/modules/mob/living/basic/bots/hygienebot/hygienebot.dm b/code/modules/mob/living/basic/bots/hygienebot/hygienebot.dm new file mode 100644 index 00000000000..1d802ca9358 --- /dev/null +++ b/code/modules/mob/living/basic/bots/hygienebot/hygienebot.dm @@ -0,0 +1,132 @@ +#define WASH_PERIOD 3 SECONDS + +/mob/living/basic/bot/hygienebot + name = "\improper Hygienebot" + desc = "A flying cleaning robot, he'll chase down people who can't shower properly!" + icon = 'icons/mob/silicon/aibots.dmi' + icon_state = "hygienebot" + base_icon_state = "hygienebot" + pass_flags = PASSMOB | PASSFLAPS | PASSTABLE + layer = MOB_UPPER_LAYER + density = FALSE + anchored = FALSE + health = 100 + maxHealth = 100 + path_image_color = "#80dae7" + maints_access_required = list(ACCESS_ROBOTICS, ACCESS_JANITOR) + radio_key = /obj/item/encryptionkey/headset_service + radio_channel = RADIO_CHANNEL_SERVICE + bot_type = HYGIENE_BOT + additional_access = /datum/id_trim/job/janitor + hackables = "cleaning service protocols" + ai_controller = /datum/ai_controller/basic_controller/bot/hygienebot + + ///are we currently washing someone? + var/washing = FALSE + ///Visual overlay of the bot spraying water. + var/static/mutable_appearance/water_overlay = mutable_appearance('icons/mob/silicon/aibots.dmi', "hygienebot-water") + ///Visual overlay of the bot commiting warcrimes. + var/static/mutable_appearance/fire_overlay = mutable_appearance('icons/mob/silicon/aibots.dmi', "hygienebot-fire") + ///announcements we say when we find a target + var/static/list/found_announcements = list( + HYGIENEBOT_VOICED_UNHYGIENIC = 'sound/voice/hygienebot/unhygienicclient.ogg', + ) + ///announcements we say when the target keeps moving away + var/static/list/threat_announcements = list( + HYGIENEBOT_VOICED_THREAT_AIRLOCK = 'sound/voice/hygienebot/dragyouout.ogg', + HYGIENEBOT_VOICED_FOUL_SMELL = 'sound/voice/hygienebot/foulsmelling.ogg', + HYGIENEBOT_VOICED_TROGLODYTE = 'sound/voice/hygienebot/troglodyte.ogg', + HYGIENEBOT_VOICED_GREEN_CLOUD = 'sound/voice/hygienebot/greencloud.ogg', + HYGIENEBOT_VOICED_ARSEHOLE = 'sound/voice/hygienebot/letmeclean.ogg', + HYGIENEBOT_VOICED_THREAT_ARTERIES = 'sound/voice/hygienebot/cutarteries.ogg', + HYGIENEBOT_VOICED_STOP_RUNNING = 'sound/voice/hygienebot/stoprunning.ogg', + ) + ///announcements we say after we have cleaned our target + var/static/list/cleaned_announcements = list( + HYGIENEBOT_VOICED_FUCKING_FINALLY = 'sound/voice/hygienebot/finally.ogg', + HYGIENEBOT_VOICED_THANK_GOD = 'sound/voice/hygienebot/thankgod.ogg', + HYGIENEBOT_VOICED_DEGENERATE = 'sound/voice/hygienebot/degenerate.ogg', + ) + +/mob/living/basic/bot/hygienebot/Initialize(mapload) + . = ..() + update_appearance(UPDATE_ICON) + + generate_ai_speech() + + var/static/list/loc_connections = list( + COMSIG_ATOM_ENTERED = PROC_REF(on_entered), + ) + AddElement(/datum/element/connect_loc, loc_connections) + var/static/list/hat_offsets = list(1, 1) + AddElement(/datum/element/hat_wearer, offsets = hat_offsets) + + ADD_TRAIT(src, TRAIT_SPRAY_PAINTABLE, INNATE_TRAIT) + RegisterSignal(src, COMSIG_HOSTILE_PRE_ATTACKINGTARGET, PROC_REF(on_attack)) + +/mob/living/basic/bot/hygienebot/explode() + var/datum/effect_system/fluid_spread/foam/foam = new + foam.set_up(2, holder = src, location = loc) + foam.start() + return ..() + +/mob/living/basic/bot/hygienebot/generate_speak_list() + var/static/list/finalized_speak_list = (found_announcements + threat_announcements + cleaned_announcements) + return finalized_speak_list + +/mob/living/basic/bot/hygienebot/update_icon_state() + . = ..() + icon_state = "[base_icon_state][bot_mode_flags & BOT_MODE_ON ? "-on" : ""]" + + +/mob/living/basic/bot/hygienebot/update_overlays() + . = ..() + if(bot_mode_flags & BOT_MODE_ON) + . += mutable_appearance(icon, "hygienebot-flame") + + if(!washing) + return + + . += (bot_access_flags & BOT_COVER_EMAGGED) ? fire_overlay : water_overlay + +/mob/living/basic/bot/hygienebot/proc/on_entered(datum/source, atom/movable/movable) + SIGNAL_HANDLER + if(!washing) + return + commence_wash(movable) + +/mob/living/basic/bot/hygienebot/proc/on_attack(datum/source, atom/target) + SIGNAL_HANDLER + . = COMPONENT_HOSTILE_NO_ATTACK + if(washing) + return + set_washing_mode(new_mode = TRUE) + for(var/atom/to_wash in loc) + commence_wash(to_wash) + addtimer(CALLBACK(src, PROC_REF(set_washing_mode), FALSE), WASH_PERIOD) + +/mob/living/basic/bot/hygienebot/proc/set_washing_mode(new_mode) + washing = new_mode + update_appearance(UPDATE_OVERLAYS) + +/mob/living/basic/bot/hygienebot/proc/commence_wash(atom/target) + if(bot_access_flags & BOT_COVER_EMAGGED) + target.fire_act() + return + target.wash(CLEAN_WASH) + +/mob/living/basic/bot/hygienebot/on_bot_movement(atom/movable/source, atom/oldloc, dir, forced) + + if(!washing || !isturf(loc)) + return + + for(var/mob/living/carbon/human in loc) + commence_wash(human) + + +/mob/living/basic/bot/hygienebot/proc/generate_ai_speech() + ai_controller.set_blackboard_key(BB_WASH_FOUND, found_announcements) + ai_controller.set_blackboard_key(BB_WASH_THREATS, threat_announcements) + ai_controller.set_blackboard_key(BB_WASH_DONE, cleaned_announcements) + +#undef WASH_PERIOD diff --git a/code/modules/mob/living/basic/bots/hygienebot/hygienebot_ai.dm b/code/modules/mob/living/basic/bots/hygienebot/hygienebot_ai.dm new file mode 100644 index 00000000000..2d2bc27079f --- /dev/null +++ b/code/modules/mob/living/basic/bots/hygienebot/hygienebot_ai.dm @@ -0,0 +1,142 @@ +#define BOT_FRUSTRATION_LIMIT 8 + +/datum/ai_controller/basic_controller/bot/hygienebot + blackboard = list( + BB_SALUTE_MESSAGES = list( + "salutes", + "nods in appreciation towards", + ), + BB_WASH_FRUSTRATION = 0, + ) + planning_subtrees = list( + /datum/ai_planning_subtree/manage_unreachable_list, + /datum/ai_planning_subtree/respond_to_summon, + /datum/ai_planning_subtree/wash_people, + /datum/ai_planning_subtree/salute_authority, + /datum/ai_planning_subtree/find_patrol_beacon, + ) + reset_keys = list( + BB_WASH_TARGET, + BB_BEACON_TARGET, + BB_PREVIOUS_BEACON_TARGET, + BB_BOT_SUMMON_TARGET, + ) + +/datum/ai_controller/basic_controller/bot/hygienebot/TryPossessPawn(atom/new_pawn) + . = ..() + if(. & AI_CONTROLLER_INCOMPATIBLE) + return + RegisterSignal(new_pawn, COMSIG_AI_BLACKBOARD_KEY_CLEARED(BB_WASH_TARGET), PROC_REF(reset_anger)) + +/datum/ai_controller/basic_controller/bot/hygienebot/proc/reset_anger() + SIGNAL_HANDLER + + set_blackboard_key(BB_WASH_FRUSTRATION, 0) + + +/datum/ai_planning_subtree/wash_people + +/datum/ai_planning_subtree/wash_people/SelectBehaviors(datum/ai_controller/basic_controller/bot/controller, seconds_per_tick) + var/mob/living/basic/bot/bot_pawn = controller.pawn + + var/atom/wash_target = controller.blackboard[BB_WASH_TARGET] + if(QDELETED(wash_target)) + controller.queue_behavior(/datum/ai_behavior/find_valid_wash_targets, BB_WASH_TARGET, bot_pawn.bot_access_flags) + return + + if(get_dist(bot_pawn, wash_target) < 9) + controller.queue_behavior(/datum/ai_behavior/wash_target, BB_WASH_TARGET) + return SUBTREE_RETURN_FINISH_PLANNING + + controller.clear_blackboard_key(BB_WASH_TARGET) //delete if too far + +/datum/ai_behavior/find_valid_wash_targets + action_cooldown = 5 SECONDS + +/datum/ai_behavior/find_valid_wash_targets/perform(seconds_per_tick, datum/ai_controller/controller, target_key, our_access_flags) + . = ..() + var/list/ignore_list = controller.blackboard[BB_TEMPORARY_IGNORE_LIST] + var/atom/found_target + for(var/mob/living/carbon/human/wash_potential in oview(5, controller.pawn)) + + if(found_target) + break + + if(isnull(wash_potential.mind) || wash_potential.stat != CONSCIOUS) + continue + + if(LAZYACCESS(ignore_list, wash_potential)) + continue + + if(our_access_flags & BOT_COVER_EMAGGED) + controller.set_blackboard_key_assoc_lazylist(BB_TEMPORARY_IGNORE_LIST, wash_potential, TRUE) + found_target = wash_potential + break + + for(var/atom/clothing in wash_potential.get_equipped_items()) + if(GET_ATOM_BLOOD_DNA_LENGTH(clothing)) + found_target = wash_potential + break + + if(isnull(found_target)) + finish_action(controller, succeeded = FALSE) + return + + controller.set_blackboard_key(target_key, found_target) + finish_action(controller, succeeded = TRUE) + + + +/datum/ai_behavior/find_valid_wash_targets/finish_action(datum/ai_controller/controller, succeeded, target_key) + . = ..() + if(!succeeded) + return + var/datum/action/cooldown/bot_announcement/announcement = controller.blackboard[BB_ANNOUNCE_ABILITY] + announcement.announce(pick(controller.blackboard[BB_WASH_FOUND])) + +/datum/ai_behavior/wash_target + behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION | AI_BEHAVIOR_MOVE_AND_PERFORM + required_distance = 0 + action_cooldown = 1 SECONDS + +/datum/ai_behavior/wash_target/setup(datum/ai_controller/controller, target_key) + . = ..() + var/atom/target = controller.blackboard[target_key] + if(QDELETED(target)) + return FALSE + set_movement_target(controller, target) + +/datum/ai_behavior/wash_target/perform(seconds_per_tick, datum/ai_controller/basic_controller/bot/controller, target_key) + . = ..() + var/mob/living/carbon/human/unclean_target = controller.blackboard[target_key] + var/mob/living/basic/living_pawn = controller.pawn + if(QDELETED(unclean_target)) + finish_action(controller, FALSE, target_key) + return + + if(living_pawn.loc == get_turf(unclean_target)) + living_pawn.melee_attack(unclean_target) + finish_action(controller, TRUE, target_key) + return + + var/frustration_count = controller.blackboard[BB_WASH_FRUSTRATION] + controller.set_blackboard_key(BB_WASH_FRUSTRATION, frustration_count + 1) + finish_action(controller, FALSE, target_key) + +/datum/ai_behavior/wash_target/finish_action(datum/ai_controller/controller, succeeded, target_key) + . = ..() + var/datum/action/cooldown/bot_announcement/announcement = controller.blackboard[BB_ANNOUNCE_ABILITY] + + if(succeeded) + if(controller.blackboard[BB_WASH_FRUSTRATION] > 0) + announcement.announce(pick(controller.blackboard[BB_WASH_DONE])) + controller.clear_blackboard_key(target_key) + return + + if(controller.blackboard[BB_WASH_FRUSTRATION] < BOT_FRUSTRATION_LIMIT) + return + + announcement.announce(pick(controller.blackboard[BB_WASH_THREATS])) + controller.set_blackboard_key(BB_WASH_FRUSTRATION, 0) + +#undef BOT_FRUSTRATION_LIMIT diff --git a/code/modules/mob/living/basic/bots/medbot/medbot_ai.dm b/code/modules/mob/living/basic/bots/medbot/medbot_ai.dm index 8d8e3efbfe3..ceb333b1755 100644 --- a/code/modules/mob/living/basic/bots/medbot/medbot_ai.dm +++ b/code/modules/mob/living/basic/bots/medbot/medbot_ai.dm @@ -16,6 +16,7 @@ BB_PREVIOUS_BEACON_TARGET, BB_BOT_SUMMON_TARGET, ) + ai_traits = PAUSE_DURING_DO_AFTER /datum/ai_movement/jps/bot/medbot @@ -185,7 +186,7 @@ return can_see(source, patient, radius) /datum/ai_behavior/announce_patient - action_cooldown = 30 SECONDS + action_cooldown = 3 MINUTES behavior_flags = AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION /datum/ai_behavior/announce_patient/perform(seconds_per_tick, datum/ai_controller/basic_controller/bot/controller, target_key) diff --git a/code/modules/mob/living/simple_animal/bot/construction.dm b/code/modules/mob/living/simple_animal/bot/construction.dm index 7ef55e8357c..9591aaed69c 100644 --- a/code/modules/mob/living/simple_animal/bot/construction.dm +++ b/code/modules/mob/living/simple_animal/bot/construction.dm @@ -552,8 +552,8 @@ to_chat(user, span_notice("You start to pipe up [src]...")) if(do_after(user, 40, target = src) && D.use(1)) to_chat(user, span_notice("You pipe up [src].")) - var/mob/living/simple_animal/bot/hygienebot/H = new(drop_location()) - H.name = created_name + var/mob/living/basic/bot/hygienebot/new_bot = new(drop_location()) + new_bot.name = created_name qdel(src) if(I.tool_behaviour == TOOL_SCREWDRIVER) //deconstruct new /obj/item/assembly/prox_sensor(Tsec) diff --git a/code/modules/mob/living/simple_animal/bot/hygienebot.dm b/code/modules/mob/living/simple_animal/bot/hygienebot.dm deleted file mode 100644 index 0db59e2518c..00000000000 --- a/code/modules/mob/living/simple_animal/bot/hygienebot.dm +++ /dev/null @@ -1,253 +0,0 @@ -//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/mob/silicon/aibots.dmi' - icon_state = "hygienebot" - base_icon_state = "hygienebot" - pass_flags = PASSMOB | PASSFLAPS | PASSTABLE - layer = MOB_UPPER_LAYER - density = FALSE - anchored = FALSE - health = 100 - maxHealth = 100 - - maints_access_required = list(ACCESS_ROBOTICS, ACCESS_JANITOR) - radio_key = /obj/item/encryptionkey/headset_service - radio_channel = RADIO_CHANNEL_SERVICE //Service - bot_mode_flags = ~BOT_MODE_CAN_BE_SAPIENT - bot_type = HYGIENE_BOT - hackables = "cleaning service protocols" - path_image_color = "#993299" - - automated_announcements = list( - HYGIENEBOT_VOICED_UNHYGIENIC = 'sound/voice/hygienebot/unhygienicclient.ogg', - HYGIENEBOT_VOICED_ENJOY_DAY = 'sound/voice/hygienebot/cleanandtidy.ogg', - HYGIENEBOT_VOICED_THREAT_AIRLOCK = 'sound/voice/hygienebot/dragyouout.ogg', - HYGIENEBOT_VOICED_FOUL_SMELL = 'sound/voice/hygienebot/foulsmelling.ogg', - HYGIENEBOT_VOICED_TROGLODYTE = 'sound/voice/hygienebot/troglodyte.ogg', - HYGIENEBOT_VOICED_GREEN_CLOUD = 'sound/voice/hygienebot/greencloud.ogg', - HYGIENEBOT_VOICED_ARSEHOLE = 'sound/voice/hygienebot/letmeclean.ogg', - HYGIENEBOT_VOICED_THREAT_ARTERIES = 'sound/voice/hygienebot/cutarteries.ogg', - HYGIENEBOT_VOICED_STOP_RUNNING = 'sound/voice/hygienebot/stoprunning.ogg', - HYGIENEBOT_VOICED_FUCKING_FINALLY = 'sound/voice/hygienebot/finally.ogg', - HYGIENEBOT_VOICED_THANK_GOD = 'sound/voice/hygienebot/thankgod.ogg', - HYGIENEBOT_VOICED_DEGENERATE = 'sound/voice/hygienebot/degenerate.ogg', - ) - - ///The human target the bot is trying to wash. - var/mob/living/carbon/human/target - ///The mob's current speed, which varies based on how long the bot chases it's target. - var/currentspeed = 5 - ///Is the bot currently washing it's target/everything else that crosses it? - var/washing = FALSE - ///Have the target evaded the bot for long enough that it will swear at it like kirk did to kahn? - var/mad = FALSE - ///The last time that the previous/current target was found. - var/last_found - ///Name of the previous target the bot was pursuing. - var/oldtarget_name - ///Visual overlay of the bot spraying water. - var/mutable_appearance/water_overlay - ///Visual overlay of the bot commiting warcrimes. - var/mutable_appearance/fire_overlay - -/mob/living/simple_animal/bot/hygienebot/Initialize(mapload) - . = ..() - update_appearance(UPDATE_ICON) - - // Doing this hurts my soul, but simplebot access reworks are for another day. - var/datum/id_trim/job/jani_trim = SSid_access.trim_singletons_by_path[/datum/id_trim/job/janitor] - access_card.add_access(jani_trim.access + jani_trim.wildcard_access) - prev_access = access_card.access.Copy() - var/static/list/loc_connections = list( - COMSIG_ATOM_ENTERED = PROC_REF(on_entered), - ) - AddElement(/datum/element/connect_loc, loc_connections) - - ADD_TRAIT(src, TRAIT_SPRAY_PAINTABLE, INNATE_TRAIT) - -/mob/living/simple_animal/bot/hygienebot/explode() - var/datum/effect_system/fluid_spread/foam/foam = new - foam.set_up(2, holder = src, location = loc) - foam.start() - - return ..() - -/mob/living/simple_animal/bot/hygienebot/proc/on_entered(datum/source, atom/movable/AM) - SIGNAL_HANDLER - if(washing) - do_wash(AM) - -/mob/living/simple_animal/bot/hygienebot/update_icon_state() - . = ..() - icon_state = "[base_icon_state][bot_mode_flags & BOT_MODE_ON ? "-on" : null]" - - -/mob/living/simple_animal/bot/hygienebot/update_overlays() - . = ..() - if(bot_mode_flags & BOT_MODE_ON) - . += mutable_appearance(icon, "hygienebot-flame") - - if(washing) - . += mutable_appearance(icon, bot_cover_flags & BOT_COVER_EMAGGED ? "hygienebot-fire" : "hygienebot-water") - - -/mob/living/simple_animal/bot/hygienebot/turn_off() - ..() - mode = BOT_IDLE - -/mob/living/simple_animal/bot/hygienebot/bot_reset() - ..() - target = null - oldtarget_name = null - SSmove_manager.stop_looping(src) - last_found = world.time - -/mob/living/simple_animal/bot/hygienebot/handle_automated_action() - if(!..()) - return - - if(washing) - do_wash(loc) - for(var/AM in loc) - if (AM == src) - continue - do_wash(AM) - if(isopenturf(loc) && !(bot_cover_flags & BOT_COVER_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 - SSmove_manager.stop_looping(src) - look_for_lowhygiene() // see if any disgusting fucks are in range - if(!mode && bot_mode_flags & BOT_MODE_AUTOPATROL) // still idle, and set to patrol - mode = BOT_START_PATROL // switch to patrol mode - - if(BOT_HUNT) // hunting for stinkman - if(bot_cover_flags & BOT_COVER_EMAGGED) //lol fuck em up - currentspeed = 3.5 - start_washing() - mad = TRUE - else - switch(frustration) - if(0 to 4) - currentspeed = 5 - mad = FALSE - if(5 to INFINITY) - currentspeed = 2.5 - mad = TRUE - if(target && !check_purity(target)) - if(target.loc == loc && isturf(target.loc)) //LADIES AND GENTLEMAN WE GOTEM PREPARE TO DUMP - start_washing() - if(mad) - var/static/list/relief = list( - HYGIENEBOT_VOICED_FUCKING_FINALLY, - HYGIENEBOT_VOICED_THANK_GOD, - HYGIENEBOT_VOICED_DEGENERATE, - ) - speak(pick(relief)) - playsound(loc, 'sound/effects/hygienebot_angry.ogg', 60, 1) //i think it should still make robot noises too - mad = FALSE - mode = BOT_SHOWERSTANCE - else - stop_washing() - var/olddist = get_dist(src, target) - if(olddist > 20 || frustration > 100) // Focus on something else - back_to_idle() - return - SSmove_manager.move_to(src, target, 0, currentspeed) - if(mad && prob(min(frustration * 2, 60))) - var/static/list/threats = list( - HYGIENEBOT_VOICED_THREAT_AIRLOCK, - HYGIENEBOT_VOICED_FOUL_SMELL, - HYGIENEBOT_VOICED_TROGLODYTE, - HYGIENEBOT_VOICED_GREEN_CLOUD, - HYGIENEBOT_VOICED_ARSEHOLE, - HYGIENEBOT_VOICED_THREAT_ARTERIES, - HYGIENEBOT_VOICED_STOP_RUNNING, - ) - speak(pick(threats)) - playsound(loc, 'sound/effects/hygienebot_angry.ogg', 60, 1) - if((get_dist(src, target)) >= olddist) - frustration++ - else - frustration = 0 - else - back_to_idle() - - if(BOT_SHOWERSTANCE) - if(check_purity(target)) - speak(HYGIENEBOT_VOICED_ENJOY_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 - SSmove_manager.stop_looping(src) - target = null - frustration = 0 - last_found = world.time - stop_washing() - INVOKE_ASYNC(src, PROC_REF(handle_automated_action)) - -/mob/living/simple_animal/bot/hygienebot/proc/back_to_hunt() - frustration = 0 - mode = BOT_HUNT - stop_washing() - INVOKE_ASYNC(src, PROC_REF(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(HYGIENEBOT_VOICED_UNHYGIENIC) - playsound(loc, 'sound/effects/hygienebot_happy.ogg', 60, 1) - visible_message("[src] points at [H.name]!") - mode = BOT_HUNT - INVOKE_ASYNC(src, PROC_REF(handle_automated_action)) - break - else - continue - -/mob/living/simple_animal/bot/hygienebot/proc/start_washing() - washing = TRUE - update_appearance() - -/mob/living/simple_animal/bot/hygienebot/proc/stop_washing() - washing = FALSE - update_appearance() - -/mob/living/simple_animal/bot/hygienebot/proc/check_purity(mob/living/L) - if((bot_cover_flags & BOT_COVER_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 && GET_ATOM_BLOOD_DNA_LENGTH(I)) - return FALSE - return TRUE - -/mob/living/simple_animal/bot/hygienebot/proc/do_wash(atom/A) - if(bot_cover_flags & BOT_COVER_EMAGGED) - A.fire_act() //lol pranked no cleaning besides that - else - A.wash(CLEAN_WASH) diff --git a/code/modules/unit_tests/simple_animal_freeze.dm b/code/modules/unit_tests/simple_animal_freeze.dm index 5a090a59a93..f47fc72fbf3 100644 --- a/code/modules/unit_tests/simple_animal_freeze.dm +++ b/code/modules/unit_tests/simple_animal_freeze.dm @@ -8,7 +8,6 @@ /mob/living/simple_animal/bot, /mob/living/simple_animal/bot/firebot, /mob/living/simple_animal/bot/floorbot, - /mob/living/simple_animal/bot/hygienebot, /mob/living/simple_animal/bot/mulebot, /mob/living/simple_animal/bot/mulebot/paranormal, /mob/living/simple_animal/bot/secbot, diff --git a/tgstation.dme b/tgstation.dme index b4b43389d62..b05d7d677cf 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -4577,6 +4577,8 @@ #include "code\modules\mob\living\basic\bots\cleanbot\cleanbot.dm" #include "code\modules\mob\living\basic\bots\cleanbot\cleanbot_abilities.dm" #include "code\modules\mob\living\basic\bots\cleanbot\cleanbot_ai.dm" +#include "code\modules\mob\living\basic\bots\hygienebot\hygienebot.dm" +#include "code\modules\mob\living\basic\bots\hygienebot\hygienebot_ai.dm" #include "code\modules\mob\living\basic\bots\medbot\medbot.dm" #include "code\modules\mob\living\basic\bots\medbot\medbot_ai.dm" #include "code\modules\mob\living\basic\clown\clown.dm" @@ -4994,7 +4996,6 @@ #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\mulebot.dm" #include "code\modules\mob\living\simple_animal\bot\secbot.dm" #include "code\modules\mob\living\simple_animal\bot\SuperBeepsky.dm"