diff --git a/_maps/RandomRuins/SpaceRuins/infested_frigate.dmm b/_maps/RandomRuins/SpaceRuins/infested_frigate.dmm index 542f64d7abd..1976a33a4ad 100644 --- a/_maps/RandomRuins/SpaceRuins/infested_frigate.dmm +++ b/_maps/RandomRuins/SpaceRuins/infested_frigate.dmm @@ -1980,7 +1980,7 @@ /turf/open/floor/mineral/plastitanium, /area/ruin/space/has_grav/infested_frigate) "Fs" = ( -/mob/living/simple_animal/bot/firebot{ +/mob/living/basic/bot/firebot{ faction = list("syndicate","xenomorph"); name = "Mr. Squirtsky"; desc = "He seems much happier with the new management!" diff --git a/_maps/RandomZLevels/SnowCabin.dmm b/_maps/RandomZLevels/SnowCabin.dmm index 2451c557312..ff38749b206 100644 --- a/_maps/RandomZLevels/SnowCabin.dmm +++ b/_maps/RandomZLevels/SnowCabin.dmm @@ -2052,7 +2052,7 @@ /turf/open/floor/wood/freezing, /area/awaymission/cabin/lumbermill) "jV" = ( -/mob/living/simple_animal/bot/firebot, +/mob/living/basic/bot/firebot, /turf/open/floor/wood/freezing, /area/awaymission/cabin/lumbermill) "jW" = ( diff --git a/_maps/map_files/Birdshot/birdshot.dmm b/_maps/map_files/Birdshot/birdshot.dmm index e830561e589..919d8346ee5 100644 --- a/_maps/map_files/Birdshot/birdshot.dmm +++ b/_maps/map_files/Birdshot/birdshot.dmm @@ -70054,7 +70054,7 @@ /turf/open/floor/iron/smooth, /area/station/engineering/break_room) "xfB" = ( -/mob/living/simple_animal/bot/firebot, +/mob/living/basic/bot/firebot, /obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer4, /obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer2, /obj/machinery/holopad, diff --git a/code/__DEFINES/ai/bot_keys.dm b/code/__DEFINES/ai/bot_keys.dm index 05c0b7aff18..c7285d18aca 100644 --- a/code/__DEFINES/ai/bot_keys.dm +++ b/code/__DEFINES/ai/bot_keys.dm @@ -109,6 +109,18 @@ DEFINE_BITFIELD(honkbot_flags, list( ///key that holds our honk ability #define BB_HONK_ABILITY "honk_ability" +//firebot keys +///things we can extinguish +#define BB_FIREBOT_CAN_EXTINGUISH "can_extinguish" +///the target we will extinguish +#define BB_FIREBOT_EXTINGUISH_TARGET "extinguish_target" +///lines we say when we detect a fire +#define BB_FIREBOT_FIRE_DETECTED_LINES "fire_detected_lines" +///lines we say when we are idle +#define BB_FIREBOT_IDLE_LINES "idle_lines" +///lines we say when we are emagged +#define BB_FIREBOT_EMAGGED_LINES "emagged_lines" + //vibebots ///key that holds our partying ability #define BB_VIBEBOT_PARTY_ABILITY "party_ability" diff --git a/code/__DEFINES/robots.dm b/code/__DEFINES/robots.dm index bcca92e6060..393e0f7b5f7 100644 --- a/code/__DEFINES/robots.dm +++ b/code/__DEFINES/robots.dm @@ -244,6 +244,21 @@ DEFINE_BITFIELD(medical_mode_flags, list( "MEDBOT_TIPPED_MODE" = MEDBOT_TIPPED_MODE, )) +///Whether we are stationary or not +#define FIREBOT_STATIONARY_MODE (1<<0) +///If we will extinguish people +#define FIREBOT_EXTINGUISH_PEOPLE (1<<1) +///if we will extinguish turfs on flames +#define FIREBOT_EXTINGUISH_FLAMES (1<<2) + +DEFINE_BITFIELD(firebot_mode_flags, list( + "FIREBOT_STATIONARY_MODE" = FIREBOT_STATIONARY_MODE, + "FIREBOT_EXTINGUISH_PEOPLE" = FIREBOT_EXTINGUISH_PEOPLE, + "FIREBOT_EXTINGUISH_FLAMES" = FIREBOT_EXTINGUISH_FLAMES, +)) + + + //cleanBOT defines on what to clean #define CLEANBOT_CLEAN_BLOOD (1<<0) #define CLEANBOT_CLEAN_TRASH (1<<1) @@ -284,6 +299,9 @@ DEFINE_BITFIELD(janitor_mode_flags, list( #define FIREBOT_VOICED_ONLY_YOU "Only you can prevent station fires." #define FIREBOT_VOICED_TEMPERATURE_NOMINAL "Temperature nominal." #define FIREBOT_VOICED_KEEP_COOL "Keep it cool." +#define FIREBOT_VOICED_CANDLE_TIP "Keep candles near curtains for cozy night lights!" +#define FIREBOT_VOICED_ELECTRIC_FIRE "Keep full buckets of water near outlets in case of an electric fire!" +#define FIREBOT_VOICED_FUEL_TIP "Pouring fuel on fire makes it burn out faster!" #define HYGIENEBOT_VOICED_UNHYGIENIC "Unhygienic client found. Please stand still so I can clean you." #define HYGIENEBOT_VOICED_ENJOY_DAY "Enjoy your clean and tidy day!" diff --git a/code/datums/ai/basic_mobs/targeting_strategies/basic_targeting_strategy.dm b/code/datums/ai/basic_mobs/targeting_strategies/basic_targeting_strategy.dm index 14f0d032079..07b544bc0a2 100644 --- a/code/datums/ai/basic_mobs/targeting_strategies/basic_targeting_strategy.dm +++ b/code/datums/ai/basic_mobs/targeting_strategies/basic_targeting_strategy.dm @@ -137,3 +137,10 @@ /datum/targeting_strategy/basic/same_faction/faction_check(mob/living/living_mob, mob/living/the_target) return !..() // inverts logic to ONLY target mobs that share a faction + +/datum/targeting_strategy/basic/allow_turfs + +/datum/targeting_strategy/basic/allow_turfs/can_attack(mob/living/living_mob, atom/the_target, vision_range) + if(isturf(the_target)) + return TRUE + return ..() diff --git a/code/datums/components/crafting/robot.dm b/code/datums/components/crafting/robot.dm index 2398a8b54fb..d3f7a349e12 100644 --- a/code/datums/components/crafting/robot.dm +++ b/code/datums/components/crafting/robot.dm @@ -107,7 +107,7 @@ /datum/crafting_recipe/firebot name = "Firebot" - result = /mob/living/simple_animal/bot/firebot + result = /mob/living/basic/bot/firebot reqs = list( /obj/item/extinguisher = 1, /obj/item/bodypart/arm/right/robot = 1, diff --git a/code/modules/mob/living/basic/bots/bot_ai.dm b/code/modules/mob/living/basic/bots/bot_ai.dm index 93d53d6e521..b7cd5dcabd3 100644 --- a/code/modules/mob/living/basic/bots/bot_ai.dm +++ b/code/modules/mob/living/basic/bots/bot_ai.dm @@ -276,3 +276,17 @@ /datum/ai_behavior/bot_search/proc/valid_target(datum/ai_controller/basic_controller/bot/controller, atom/my_target) return TRUE + +///behavior to make our bot talk +/datum/ai_behavior/bot_speech + action_cooldown = 5 SECONDS + behavior_flags = AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION + +/datum/ai_behavior/bot_speech/perform(seconds_per_tick, datum/ai_controller/controller, list/list_to_pick_from, announce_key) + var/datum/action/cooldown/bot_announcement/announcement = controller.blackboard[announce_key] + + if(isnull(announcement) || !length(list_to_pick_from)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + + announcement.announce(pick(list_to_pick_from)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED diff --git a/code/modules/mob/living/basic/bots/firebot/firebot.dm b/code/modules/mob/living/basic/bots/firebot/firebot.dm new file mode 100644 index 00000000000..140948b81c0 --- /dev/null +++ b/code/modules/mob/living/basic/bots/firebot/firebot.dm @@ -0,0 +1,169 @@ +#define FOAM_INTERVAL 5 SECONDS + +/mob/living/basic/bot/firebot + name = "\improper Firebot" + desc = "A little fire extinguishing bot. He looks rather anxious." + icon = 'icons/mob/silicon/aibots.dmi' + icon_state = "firebot1" + light_color = "#8cffc9" + light_power = 0.8 + + req_access = list(ACCESS_ROBOTICS, ACCESS_CONSTRUCTION) + radio_key = /obj/item/encryptionkey/headset_eng + radio_channel = RADIO_CHANNEL_ENGINEERING + bot_type = FIRE_BOT + additional_access = /datum/id_trim/job/station_engineer + hackables = "fire safety protocols" + path_image_color = "#FFA500" + possessed_message = "You are a firebot! Protect the station from fires to the best of your ability!" + ai_controller = /datum/ai_controller/basic_controller/bot/firebot + ///our inbuilt fire extinguisher + var/obj/item/extinguisher/internal_ext + + ///Flags firebots use to decide how they function. + var/firebot_mode_flags = FIREBOT_EXTINGUISH_PEOPLE | FIREBOT_EXTINGUISH_FLAMES + //Selections: FIREBOT_STATIONARY_MODE | FIREBOT_EXTINGUISH_PEOPLE | FIREBOT_EXTINGUISH_FLAMES + ///cooldown before we release foam all over + COOLDOWN_DECLARE(foam_cooldown) + + +/mob/living/basic/bot/firebot/generate_speak_list() + var/static/list/idle_lines = list( + FIREBOT_VOICED_NO_FIRES = 'sound/voice/firebot/nofires.ogg', + FIREBOT_VOICED_ONLY_YOU = 'sound/voice/firebot/onlyyou.ogg', + FIREBOT_VOICED_TEMPERATURE_NOMINAL = 'sound/voice/firebot/tempnominal.ogg', + FIREBOT_VOICED_KEEP_COOL = 'sound/voice/firebot/keepitcool.ogg', + ) + var/static/list/fire_detected_lines = list( + FIREBOT_VOICED_FIRE_DETECTED = 'sound/voice/firebot/detected.ogg', + FIREBOT_VOICED_STOP_DROP = 'sound/voice/firebot/stopdropnroll.ogg', + FIREBOT_VOICED_EXTINGUISHING = 'sound/voice/firebot/extinguishing.ogg', + ) + var/static/list/emagged_lines = list( + FIREBOT_VOICED_CANDLE_TIP = 'sound/voice/firebot/candle_tip.ogg', + FIREBOT_VOICED_ELECTRIC_FIRE = 'sound/voice/firebot/electric_fire_tip.ogg', + FIREBOT_VOICED_FUEL_TIP = 'sound/voice/firebot/gasoline_tip.ogg' + ) + ai_controller.set_blackboard_key(BB_FIREBOT_EMAGGED_LINES, emagged_lines) + ai_controller.set_blackboard_key(BB_FIREBOT_IDLE_LINES, idle_lines) + ai_controller.set_blackboard_key(BB_FIREBOT_FIRE_DETECTED_LINES, fire_detected_lines) + return idle_lines + fire_detected_lines + +/mob/living/basic/bot/firebot/Initialize(mapload) + . = ..() + ADD_TRAIT(src, TRAIT_SPACEWALK, INNATE_TRAIT) + update_appearance(UPDATE_ICON) + var/static/list/things_to_extinguish = typecacheof(list(/mob/living/carbon)) + ai_controller.set_blackboard_key(BB_FIREBOT_CAN_EXTINGUISH, things_to_extinguish) + create_extinguisher() + AddElement(/datum/element/atmos_sensitive, mapload) + +/mob/living/basic/bot/firebot/Destroy() + QDEL_NULL(internal_ext) + return ..() + +/mob/living/basic/bot/firebot/bot_reset(bypass_ai_reset) + . = ..() + create_extinguisher() + +/mob/living/basic/bot/firebot/proc/create_extinguisher() + internal_ext = new /obj/item/extinguisher(src) + internal_ext.safety = FALSE + internal_ext.precision = TRUE + internal_ext.max_water = INFINITY + internal_ext.refill() + +/mob/living/basic/bot/firebot/melee_attack(atom/attacked_atom, list/modifiers, ignore_cooldown = FALSE) + use_extinguisher(attacked_atom) + +/mob/living/basic/bot/firebot/RangedAttack(atom/attacked_atom, list/modifiers) + use_extinguisher(attacked_atom) + +/mob/living/basic/bot/firebot/proc/use_extinguisher(atom/attacked_atom) + if(!(bot_mode_flags & BOT_MODE_ON)) + return + spray_water(attacked_atom) + +/mob/living/basic/bot/firebot/emag_act(mob/user, obj/item/card/emag/emag_card) + . = ..() + if(!(bot_access_flags & BOT_COVER_EMAGGED)) + return + + to_chat(user, span_warning("You enable the very ironically named \"fighting with fire\" mode, and disable the targeting safeties.")) // heheehe. funny + + audible_message(span_danger("[src] buzzes oddly!")) + playsound(src, SFX_SPARKS, 75, TRUE, SHORT_RANGE_SOUND_EXTRARANGE) + + internal_ext.chem = /datum/reagent/clf3 //Refill the internal extinguisher with liquid fire + internal_ext.power = 3 + internal_ext.safety = FALSE + internal_ext.precision = FALSE + internal_ext.max_water = INFINITY + internal_ext.refill() + return TRUE + +// Variables sent to TGUI +/mob/living/basic/bot/firebot/ui_data(mob/user) + var/list/data = ..() + if(!(bot_access_flags & BOT_COVER_LOCKED) || HAS_SILICON_ACCESS(user)) + data["custom_controls"]["extinguish_fires"] = firebot_mode_flags & FIREBOT_EXTINGUISH_FLAMES + data["custom_controls"]["extinguish_people"] = firebot_mode_flags & FIREBOT_EXTINGUISH_PEOPLE + data["custom_controls"]["stationary_mode"] = firebot_mode_flags & FIREBOT_STATIONARY_MODE + return data + +// Actions received from TGUI +/mob/living/basic/bot/firebot/ui_act(action, params) + . = ..() + if(. || (bot_access_flags & BOT_COVER_LOCKED && !HAS_SILICON_ACCESS(usr))) + return + + switch(action) + if("extinguish_fires") + firebot_mode_flags ^= FIREBOT_EXTINGUISH_FLAMES + if("extinguish_people") + firebot_mode_flags ^= FIREBOT_EXTINGUISH_PEOPLE + if("stationary_mode") + firebot_mode_flags ^= FIREBOT_STATIONARY_MODE + update_appearance() + +/mob/living/basic/bot/firebot/should_atmos_process(datum/gas_mixture/air, exposed_temperature) + return (exposed_temperature > T0C + 200 || exposed_temperature < BODYTEMP_COLD_DAMAGE_LIMIT) + +/mob/living/basic/bot/firebot/atmos_expose(datum/gas_mixture/air, exposed_temperature) + if(!COOLDOWN_FINISHED(src, foam_cooldown)) + return + var/datum/effect_system/fluid_spread/foam/firefighting/foam = new + foam.set_up(3, holder = src, location = loc) + foam.start() + COOLDOWN_START(src, foam_cooldown, FOAM_INTERVAL) + +/mob/living/basic/bot/firebot/proc/spray_water(atom/attacked_atom) + if(firebot_mode_flags & FIREBOT_STATIONARY_MODE) + flick("firebots_use", src) + else + flick("firebot1_use", src) + internal_ext?.melee_attack_chain(src, attacked_atom) + +/mob/living/basic/bot/firebot/update_icon_state() + . = ..() + if(!(bot_mode_flags & BOT_MODE_ON)) + icon_state = "firebot0" + return + if(IsStun() || IsParalyzed() || (firebot_mode_flags & FIREBOT_STATIONARY_MODE)) //Bot has yellow light to indicate stationary mode. + icon_state = "firebots1" + return + icon_state = "firebot1" + +/mob/living/basic/bot/firebot/explode() + var/turf/my_turf = drop_location() + + new /obj/item/assembly/prox_sensor(my_turf) + new /obj/item/clothing/head/utility/hardhat/red(my_turf) + + if(isopenturf(my_turf)) + var/turf/open/open_turf = my_turf + open_turf.MakeSlippery(TURF_WET_WATER, min_wet_time = 10 SECONDS, wet_time_to_add = 5 SECONDS) + + return ..() + +#undef FOAM_INTERVAL diff --git a/code/modules/mob/living/basic/bots/firebot/firebot_ai.dm b/code/modules/mob/living/basic/bots/firebot/firebot_ai.dm new file mode 100644 index 00000000000..31127ecdfb4 --- /dev/null +++ b/code/modules/mob/living/basic/bots/firebot/firebot_ai.dm @@ -0,0 +1,132 @@ +#define ANNOUNCEMENT_TIMER 10 SECONDS + +/datum/ai_controller/basic_controller/bot/firebot + blackboard = list( + BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic/allow_turfs, + BB_UNREACHABLE_LIST_COOLDOWN = 45 SECONDS, + ) + planning_subtrees = list( + /datum/ai_planning_subtree/respond_to_summon, + /datum/ai_planning_subtree/manage_unreachable_list, + /datum/ai_planning_subtree/extinguishing_people, + /datum/ai_planning_subtree/extinguishing_turfs, + /datum/ai_planning_subtree/salute_authority, + /datum/ai_planning_subtree/firebot_speech, + /datum/ai_planning_subtree/find_patrol_beacon, + ) + reset_keys = list( + BB_FIREBOT_EXTINGUISH_TARGET, + BB_BEACON_TARGET, + BB_PREVIOUS_BEACON_TARGET, + BB_BOT_SUMMON_TARGET, + ) + ///cooldown until we announce a fire again + COOLDOWN_DECLARE(announcement_cooldown) + +/datum/ai_controller/basic_controller/bot/firebot/TryPossessPawn(atom/new_pawn) + . = ..() + if(. & AI_CONTROLLER_INCOMPATIBLE) + return + RegisterSignal(new_pawn, COMSIG_AI_BLACKBOARD_KEY_SET(BB_FIREBOT_EXTINGUISH_TARGET), PROC_REF(on_target_found)) + +///say a silly line whenever we find someone on fire +/datum/ai_controller/basic_controller/bot/firebot/proc/on_target_found() + SIGNAL_HANDLER + if(!COOLDOWN_FINISHED(src, announcement_cooldown)) + return + + var/datum/action/cooldown/bot_announcement/announcement = blackboard[BB_ANNOUNCE_ABILITY] + if(isnull(announcement)) + return + + var/list/lines = blackboard[BB_FIREBOT_FIRE_DETECTED_LINES] + if(!length(lines)) + return + INVOKE_ASYNC(announcement, TYPE_PROC_REF(/datum/action/cooldown/bot_announcement, announce), pick(lines)) + COOLDOWN_START(src, announcement_cooldown, ANNOUNCEMENT_TIMER) + + +///subtree for extinguishing people +/datum/ai_planning_subtree/extinguishing_people + +/datum/ai_planning_subtree/extinguishing_people/SelectBehaviors(datum/ai_controller/basic_controller/bot/controller, seconds_per_tick) + if(controller.blackboard_key_exists(BB_FIREBOT_EXTINGUISH_TARGET)) + controller.queue_behavior(/datum/ai_behavior/basic_melee_attack/interact_once/extinguish, BB_FIREBOT_EXTINGUISH_TARGET, BB_TARGETING_STRATEGY) + return SUBTREE_RETURN_FINISH_PLANNING + + var/mob/living/basic/bot/firebot/living_bot = controller.pawn + var/range = living_bot.firebot_mode_flags & FIREBOT_STATIONARY_MODE ? 1 : 5 + + if(living_bot.firebot_mode_flags & FIREBOT_EXTINGUISH_PEOPLE) + controller.queue_behavior(/datum/ai_behavior/bot_search/people_on_fire, BB_FIREBOT_EXTINGUISH_TARGET, controller.blackboard[BB_FIREBOT_CAN_EXTINGUISH], range) + +///behavior for finding people on fire +/datum/ai_behavior/bot_search/people_on_fire + +/datum/ai_behavior/bot_search/people_on_fire/valid_target(datum/ai_controller/basic_controller/bot/controller, mob/living/my_target) + var/mob/living/basic/bot/living_bot = controller.pawn + return (my_target.on_fire || (living_bot.bot_access_flags & BOT_COVER_EMAGGED)) + +///subtree for finding turfs to extinguish +/datum/ai_planning_subtree/extinguishing_turfs + +/datum/ai_planning_subtree/extinguishing_turfs/SelectBehaviors(datum/ai_controller/basic_controller/bot/controller, seconds_per_tick) + if(controller.blackboard_key_exists(BB_FIREBOT_EXTINGUISH_TARGET)) + return + + var/mob/living/basic/bot/firebot/living_bot = controller.pawn + var/should_bypass_blacklist = living_bot.firebot_mode_flags & FIREBOT_STATIONARY_MODE + + if(living_bot.firebot_mode_flags & FIREBOT_EXTINGUISH_FLAMES) + controller.queue_behavior(/datum/ai_behavior/search_burning_turfs, BB_FIREBOT_EXTINGUISH_TARGET, should_bypass_blacklist) + +///behavior to find burning turfs +/datum/ai_behavior/search_burning_turfs + action_cooldown = 2 SECONDS + behavior_flags = AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION + +/datum/ai_behavior/search_burning_turfs/perform(seconds_per_tick, datum/ai_controller/basic_controller/bot/controller, target_key, bypass_add_blacklist = FALSE) + var/mob/living/living_pawn = controller.pawn + var/list/ignore_list = controller.blackboard[BB_TEMPORARY_IGNORE_LIST] + + for(var/turf/possible_turf as anything in RANGE_TURFS(5, living_pawn)) + if(QDELETED(living_pawn)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + if(!isopenturf(possible_turf)) + continue + var/turf/open/open_turf = possible_turf + if(!open_turf.active_hotspot) + continue + if(LAZYACCESS(ignore_list, possible_turf)) + continue + if(controller.set_if_can_reach(target_key, possible_turf, bypass_add_to_blacklist = bypass_add_blacklist)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED + + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + +///behavior to extinguish mobs or turfs +/datum/ai_behavior/basic_melee_attack/interact_once/extinguish + +/datum/ai_behavior/basic_melee_attack/interact_once/extinguish/finish_action(datum/ai_controller/basic_controller/bot/controller, succeeded, target_key, targeting_strategy_key, hiding_location_key) + var/atom/target = controller.blackboard[BB_FIREBOT_EXTINGUISH_TARGET] + var/mob/living/basic/bot/living_bot = controller.pawn + + //if we couldnt path, or we successfully burnt someone, ignore them for a bit! + if(!succeeded || (isliving(target) && (living_bot.bot_access_flags & BOT_COVER_EMAGGED))) + controller.set_blackboard_key_assoc_lazylist(BB_TEMPORARY_IGNORE_LIST, target, TRUE) + + return ..() + +///subtree to make us say funny idle lines +/datum/ai_planning_subtree/firebot_speech + ///chance we spout lines + var/speech_prob = 3 + +/datum/ai_planning_subtree/firebot_speech/SelectBehaviors(datum/ai_controller/basic_controller/bot/controller, seconds_per_tick) + if(controller.blackboard[BB_FIREBOT_EXTINGUISH_TARGET] || !SPT_PROB(speech_prob, seconds_per_tick)) + return + var/mob/living/basic/bot/living_bot = controller.pawn + var/list/idle_lines = (living_bot.bot_access_flags & BOT_COVER_EMAGGED) ? controller.blackboard[BB_FIREBOT_EMAGGED_LINES] : controller.blackboard[BB_FIREBOT_IDLE_LINES] + controller.queue_behavior(/datum/ai_behavior/bot_speech, idle_lines, BB_ANNOUNCE_ABILITY) + +#undef ANNOUNCEMENT_TIMER diff --git a/code/modules/mob/living/simple_animal/bot/construction.dm b/code/modules/mob/living/simple_animal/bot/construction.dm index a349ca64995..80e2b8c0c83 100644 --- a/code/modules/mob/living/simple_animal/bot/construction.dm +++ b/code/modules/mob/living/simple_animal/bot/construction.dm @@ -494,8 +494,8 @@ if(!can_finish_build(I, user)) return to_chat(user, span_notice("You add the [I] to [src]! Beep Boop!")) - var/mob/living/simple_animal/bot/firebot/F = new(drop_location()) - F.name = created_name + var/mob/living/basic/bot/firebot/firebot = new(drop_location()) + firebot.name = created_name qdel(I) qdel(src) diff --git a/code/modules/mob/living/simple_animal/bot/firebot.dm b/code/modules/mob/living/simple_animal/bot/firebot.dm deleted file mode 100644 index db883e9141b..00000000000 --- a/code/modules/mob/living/simple_animal/bot/firebot.dm +++ /dev/null @@ -1,320 +0,0 @@ -//Firebot - -#define SPEECH_INTERVAL 300 // Time between idle speeches -#define DETECTED_VOICE_INTERVAL 300 // Time between fire detected callouts -#define FOAM_INTERVAL 50 // Time between deployment of fire fighting foam - -/mob/living/simple_animal/bot/firebot - name = "\improper Firebot" - desc = "A little fire extinguishing bot. He looks rather anxious." - icon = 'icons/mob/silicon/aibots.dmi' - icon_state = "firebot1" - light_color = "#8cffc9" - light_power = 0.8 - density = FALSE - anchored = FALSE - health = 25 - maxHealth = 25 - - req_one_access = list(ACCESS_ROBOTICS, ACCESS_CONSTRUCTION) - radio_key = /obj/item/encryptionkey/headset_eng - radio_channel = RADIO_CHANNEL_ENGINEERING - bot_type = FIRE_BOT - hackables = "fire safety protocols" - path_image_color = "#FFA500" - possessed_message = "You are a firebot! Protect the station from fires to the best of your ability!" - - automated_announcements = list( - FIREBOT_VOICED_FIRE_DETECTED = 'sound/voice/firebot/detected.ogg', - FIREBOT_VOICED_STOP_DROP = 'sound/voice/firebot/stopdropnroll.ogg', - FIREBOT_VOICED_EXTINGUISHING = 'sound/voice/firebot/extinguishing.ogg', - FIREBOT_VOICED_NO_FIRES = 'sound/voice/firebot/nofires.ogg', - FIREBOT_VOICED_ONLY_YOU = 'sound/voice/firebot/onlyyou.ogg', - FIREBOT_VOICED_TEMPERATURE_NOMINAL = 'sound/voice/firebot/tempnominal.ogg', - FIREBOT_VOICED_KEEP_COOL = 'sound/voice/firebot/keepitcool.ogg', - ) - - var/atom/target_fire - var/atom/old_target_fire - - var/obj/item/extinguisher/internal_ext - - var/last_found = 0 - - var/speech_cooldown = 0 - var/detected_cooldown = 0 - COOLDOWN_DECLARE(foam_cooldown) - - var/extinguish_people = TRUE - var/extinguish_fires = TRUE - var/stationary_mode = FALSE - -/mob/living/simple_animal/bot/firebot/Initialize(mapload) - . = ..() - ADD_TRAIT(src, TRAIT_SPACEWALK, INNATE_TRAIT) - update_appearance(UPDATE_ICON) - - // Doing this hurts my soul, but simplebot access reworks are for another day. - var/datum/id_trim/job/engi_trim = SSid_access.trim_singletons_by_path[/datum/id_trim/job/station_engineer] - access_card.add_access(engi_trim.access + engi_trim.wildcard_access) - prev_access = access_card.access.Copy() - - create_extinguisher() - AddElement(/datum/element/atmos_sensitive, mapload) - -/mob/living/simple_animal/bot/firebot/Destroy() - QDEL_NULL(internal_ext) - return ..() - -/mob/living/simple_animal/bot/firebot/bot_reset() - create_extinguisher() - -/mob/living/simple_animal/bot/firebot/proc/create_extinguisher() - internal_ext = new /obj/item/extinguisher(src) - internal_ext.safety = FALSE - internal_ext.precision = TRUE - internal_ext.max_water = INFINITY - internal_ext.refill() - -/mob/living/simple_animal/bot/firebot/UnarmedAttack(atom/A, proximity_flag, list/modifiers) - if(!(bot_mode_flags & BOT_MODE_ON)) - return - if(!can_unarmed_attack()) - return - if(internal_ext) - internal_ext.interact_with_atom(A, src, modifiers) - else - return ..() - -/mob/living/simple_animal/bot/firebot/RangedAttack(atom/A, proximity_flag, list/modifiers) - if(!(bot_mode_flags & BOT_MODE_ON)) - return - if(internal_ext) - internal_ext.interact_with_atom(A, src, modifiers) - else - return ..() - -/mob/living/simple_animal/bot/firebot/turn_on() - . = ..() - update_appearance() - -/mob/living/simple_animal/bot/firebot/turn_off() - ..() - update_appearance() - -/mob/living/simple_animal/bot/firebot/bot_reset() - ..() - target_fire = null - old_target_fire = null - set_anchored(FALSE) - update_appearance() - -/mob/living/simple_animal/bot/firebot/proc/soft_reset() - path = list() - target_fire = null - mode = BOT_IDLE - last_found = world.time - update_appearance() - -/mob/living/simple_animal/bot/firebot/emag_act(mob/user, obj/item/card/emag/emag_card) - . = ..() - if(!(bot_cover_flags & BOT_COVER_EMAGGED)) - return - - to_chat(user, span_warning("You enable the very ironically named \"fighting with fire\" mode, and disable the targeting safeties.")) // heheehe. funny - - audible_message(span_danger("[src] buzzes oddly!")) - playsound(src, SFX_SPARKS, 75, TRUE, SHORT_RANGE_SOUND_EXTRARANGE) - if(user) - old_target_fire = user - extinguish_fires = FALSE - extinguish_people = TRUE - - internal_ext = new /obj/item/extinguisher(src) - internal_ext.chem = /datum/reagent/clf3 //Refill the internal extinguisher with liquid fire - internal_ext.power = 3 - internal_ext.safety = FALSE - internal_ext.precision = FALSE - internal_ext.max_water = INFINITY - internal_ext.refill() - return TRUE - -// Variables sent to TGUI -/mob/living/simple_animal/bot/firebot/ui_data(mob/user) - var/list/data = ..() - if(!(bot_cover_flags & BOT_COVER_LOCKED) || HAS_SILICON_ACCESS(user)) - data["custom_controls"]["extinguish_fires"] = extinguish_fires - data["custom_controls"]["extinguish_people"] = extinguish_people - data["custom_controls"]["stationary_mode"] = stationary_mode - return data - -// Actions received from TGUI -/mob/living/simple_animal/bot/firebot/ui_act(action, params) - . = ..() - if(. || (bot_cover_flags & BOT_COVER_LOCKED && !HAS_SILICON_ACCESS(usr))) - return - - switch(action) - if("extinguish_fires") - extinguish_fires = !extinguish_fires - if("extinguish_people") - extinguish_people = !extinguish_people - if("stationary_mode") - stationary_mode = !stationary_mode - update_appearance() - -/mob/living/simple_animal/bot/firebot/proc/is_burning(atom/target) - if(ismob(target)) - var/mob/living/M = target - if(M.on_fire || (bot_cover_flags & BOT_COVER_EMAGGED && !M.on_fire)) - return TRUE - - else if(isturf(target)) - var/turf/open/T = target - if(T.active_hotspot) - return TRUE - - return FALSE - -/mob/living/simple_animal/bot/firebot/handle_automated_action() - if(!..()) - return - - if(IsStun() || IsParalyzed()) - old_target_fire = target_fire - target_fire = null - mode = BOT_IDLE - return - - if(prob(1) && target_fire == null) - var/static/list/idle_line = list( - FIREBOT_VOICED_NO_FIRES, - FIREBOT_VOICED_ONLY_YOU, - FIREBOT_VOICED_TEMPERATURE_NOMINAL, - FIREBOT_VOICED_KEEP_COOL, - ) - speak(pick(idle_line)) - - // Couldn't reach the target, reset and try again ignoring the old one - if(frustration > 8) - old_target_fire = target_fire - soft_reset() - - // We extinguished our target or it was deleted - if(QDELETED(target_fire) || !is_burning(target_fire) || isdead(target_fire)) - target_fire = null - var/scan_range = (stationary_mode ? 1 : DEFAULT_SCAN_RANGE) - - var/list/things_to_extinguish = list() - if(extinguish_people) - things_to_extinguish += list(/mob/living) - - if(target_fire == null && extinguish_fires) - things_to_extinguish += list(/turf/open) - - target_fire = scan(things_to_extinguish, old_target_fire, scan_range) // Scan for burning turfs second - old_target_fire = target_fire - - // Target reached ENGAGE WATER CANNON - if(target_fire && (get_dist(src, target_fire) <= (bot_cover_flags & BOT_COVER_EMAGGED ? 1 : 2))) // Make the bot spray water from afar when not emagged - if((speech_cooldown + SPEECH_INTERVAL) < world.time) - if(ishuman(target_fire)) - speak(FIREBOT_VOICED_STOP_DROP) - else - speak(FIREBOT_VOICED_EXTINGUISHING) - speech_cooldown = world.time - - flick("firebot1_use", src) - spray_water(target_fire, src) - - soft_reset() - - // Target ran away - else if(target_fire && path.len && (get_dist(target_fire,path[path.len]) > 2)) - path = list() - mode = BOT_IDLE - last_found = world.time - - else if(target_fire && stationary_mode) - soft_reset() - return - - if(target_fire && (get_dist(src, target_fire) > 2)) - - path = get_path_to(src, target_fire, max_distance=30, mintargetdist=1, access=access_card.GetAccess()) - mode = BOT_MOVING - if(!path.len) - soft_reset() - - if(path.len > 0 && target_fire) - if(!bot_move(path[path.len])) - old_target_fire = target_fire - soft_reset() - return - - // We got a target but it's too far away from us - if(path.len > 8 && target_fire) - frustration++ - - if(bot_mode_flags & BOT_MODE_AUTOPATROL && !target_fire) - switch(mode) - if(BOT_IDLE, BOT_START_PATROL) - start_patrol() - if(BOT_PATROL) - bot_patrol() - - -//Look for burning people or turfs around the bot -/mob/living/simple_animal/bot/firebot/process_scan(atom/scan_target) - if(!is_burning(scan_target)) - return null - - if((detected_cooldown + DETECTED_VOICE_INTERVAL) < world.time) - speak(FIREBOT_VOICED_FIRE_DETECTED) - detected_cooldown = world.time - return scan_target - -/mob/living/simple_animal/bot/firebot/should_atmos_process(datum/gas_mixture/air, exposed_temperature) - return (exposed_temperature > T0C + 200 || exposed_temperature < BODYTEMP_COLD_DAMAGE_LIMIT) - -/mob/living/simple_animal/bot/firebot/atmos_expose(datum/gas_mixture/air, exposed_temperature) - if(COOLDOWN_FINISHED(src, foam_cooldown)) - var/datum/effect_system/fluid_spread/foam/firefighting/foam = new - foam.set_up(3, holder = src, location = loc) - foam.start() - COOLDOWN_START(src, foam_cooldown, FOAM_INTERVAL) - -/mob/living/simple_animal/bot/firebot/proc/spray_water(atom/target, mob/user) - if(stationary_mode) - flick("firebots_use", user) - else - flick("firebot1_use", user) - internal_ext.interact_with_atom(target, src) - -/mob/living/simple_animal/bot/firebot/update_icon_state() - . = ..() - if(!(bot_mode_flags & BOT_MODE_ON)) - icon_state = "firebot0" - return - if(IsStun() || IsParalyzed() || stationary_mode) //Bot has yellow light to indicate stationary mode. - icon_state = "firebots1" - return - icon_state = "firebot1" - - -/mob/living/simple_animal/bot/firebot/explode() - var/atom/Tsec = drop_location() - - new /obj/item/assembly/prox_sensor(Tsec) - new /obj/item/clothing/head/utility/hardhat/red(Tsec) - - var/turf/T = get_turf(Tsec) - - if(isopenturf(T)) - var/turf/open/theturf = T - theturf.MakeSlippery(TURF_WET_WATER, min_wet_time = 10 SECONDS, wet_time_to_add = 5 SECONDS) - return ..() - -#undef SPEECH_INTERVAL -#undef DETECTED_VOICE_INTERVAL -#undef FOAM_INTERVAL diff --git a/code/modules/unit_tests/simple_animal_freeze.dm b/code/modules/unit_tests/simple_animal_freeze.dm index 706fe290ccc..267e610c066 100644 --- a/code/modules/unit_tests/simple_animal_freeze.dm +++ b/code/modules/unit_tests/simple_animal_freeze.dm @@ -6,7 +6,6 @@ // If you are refactoring a simple_animal, REMOVE it from this list var/list/allowed_types = list( /mob/living/simple_animal/bot, - /mob/living/simple_animal/bot/firebot, /mob/living/simple_animal/bot/floorbot, /mob/living/simple_animal/bot/mulebot, /mob/living/simple_animal/bot/mulebot/paranormal, diff --git a/sound/voice/firebot/candle_tip.ogg b/sound/voice/firebot/candle_tip.ogg new file mode 100644 index 00000000000..0f29f18e3f9 Binary files /dev/null and b/sound/voice/firebot/candle_tip.ogg differ diff --git a/sound/voice/firebot/electric_fire_tip.ogg b/sound/voice/firebot/electric_fire_tip.ogg new file mode 100644 index 00000000000..29910024b19 Binary files /dev/null and b/sound/voice/firebot/electric_fire_tip.ogg differ diff --git a/sound/voice/firebot/gasoline_tip.ogg b/sound/voice/firebot/gasoline_tip.ogg new file mode 100644 index 00000000000..838c0d267e6 Binary files /dev/null and b/sound/voice/firebot/gasoline_tip.ogg differ diff --git a/tgstation.dme b/tgstation.dme index 95898ab0e9d..0ea8dd2e156 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -4659,6 +4659,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\firebot\firebot.dm" +#include "code\modules\mob\living\basic\bots\firebot\firebot_ai.dm" #include "code\modules\mob\living\basic\bots\honkbots\honkbot.dm" #include "code\modules\mob\living\basic\bots\honkbots\honkbot_abilities.dm" #include "code\modules\mob\living\basic\bots\honkbots\honkbot_ai.dm" @@ -5114,7 +5116,6 @@ #include "code\modules\mob\living\simple_animal\bot\bot_announcement.dm" #include "code\modules\mob\living\simple_animal\bot\construction.dm" #include "code\modules\mob\living\simple_animal\bot\ed209bot.dm" -#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\mulebot.dm" #include "code\modules\mob\living\simple_animal\bot\secbot.dm" diff --git a/tools/UpdatePaths/Scripts/83861_firebots.txt b/tools/UpdatePaths/Scripts/83861_firebots.txt new file mode 100644 index 00000000000..152220c38b2 --- /dev/null +++ b/tools/UpdatePaths/Scripts/83861_firebots.txt @@ -0,0 +1 @@ +/mob/living/simple_animal/bot/firebot/@SUBTYPES : /mob/living/basic/bot/firebot/@SUBTYPES{@OLD} \ No newline at end of file