diff --git a/code/__DEFINES/robots.dm b/code/__DEFINES/robots.dm
index 9c813052419..0820d632473 100644
--- a/code/__DEFINES/robots.dm
+++ b/code/__DEFINES/robots.dm
@@ -35,6 +35,7 @@
#define CLEAN_BOT (1<<3) // Cleanbots
#define MED_BOT (1<<4) // Medibots
#define HONK_BOT (1<<5) // Honkbots & ED-Honks
+#define FIRE_BOT (1<<6) // Firebots
//AI notification defines
#define NEW_BORG 1
diff --git a/code/game/objects/items/extinguisher.dm b/code/game/objects/items/extinguisher.dm
index 5a2b2c9013d..6496b6991fe 100644
--- a/code/game/objects/items/extinguisher.dm
+++ b/code/game/objects/items/extinguisher.dm
@@ -124,10 +124,13 @@
if (target.loc == user)
return
//TODO; Add support for reagents in water.
+
if(refilling)
refilling = FALSE
return
if (!safety)
+
+
if (src.reagents.total_volume < 1)
to_chat(usr, "\The [src] is empty!")
return
diff --git a/code/modules/crafting/recipes.dm b/code/modules/crafting/recipes.dm
index 2c41bad53ec..46b889e8fd1 100644
--- a/code/modules/crafting/recipes.dm
+++ b/code/modules/crafting/recipes.dm
@@ -200,6 +200,16 @@
time = 40
category = CAT_ROBOT
+/datum/crafting_recipe/Firebot
+ name = "Firebot"
+ result = /mob/living/simple_animal/bot/firebot
+ reqs = list(/obj/item/extinguisher = 1,
+ /obj/item/bodypart/r_arm/robot = 1,
+ /obj/item/assembly/prox_sensor = 1,
+ /obj/item/clothing/head/hardhat/red = 1)
+ time = 40
+ category = CAT_ROBOT
+
/datum/crafting_recipe/improvised_pneumatic_cannon //Pretty easy to obtain but
name = "Pneumatic Cannon"
result = /obj/item/pneumatic_cannon/ghetto
diff --git a/code/modules/mob/living/simple_animal/bot/construction.dm b/code/modules/mob/living/simple_animal/bot/construction.dm
index 62d8a9eef19..313593f2c7a 100644
--- a/code/modules/mob/living/simple_animal/bot/construction.dm
+++ b/code/modules/mob/living/simple_animal/bot/construction.dm
@@ -505,3 +505,45 @@
to_chat(user, "You unbolt [src]'s energy swords")
for(var/IS in 1 to swordamt)
new /obj/item/melee/transforming/energy/sword/saber(Tsec)
+
+
+
+// Fire extinguisher + borg arm = firebot assembly
+/obj/item/extinguisher/attackby(obj/O, mob/user, params)
+ if(istype(O, /obj/item/bodypart/l_arm/robot) || istype(O, /obj/item/bodypart/r_arm/robot))
+ to_chat(user, "You add [O] to [src].")
+ qdel(O)
+ qdel(src)
+ user.put_in_hands(new /obj/item/bot_assembly/firebot)
+ else
+ ..()
+
+//Firebot Assembly
+/obj/item/bot_assembly/firebot
+ name = "incomplete firebot assembly"
+ desc = "A fire extinguisher with an arm attached to it."
+ icon_state = "firebot_arm"
+ created_name = "Firebot"
+
+/obj/item/bot_assembly/firebot/attackby(obj/item/I, mob/user, params)
+ ..()
+ switch(build_step)
+ if(ASSEMBLY_FIRST_STEP)
+ if(istype(I, /obj/item/clothing/head/hardhat/red))
+ if(!user.temporarilyRemoveItemFromInventory(I))
+ return
+ to_chat(user,"You add the [I] to [src]!")
+ icon_state = "firebot_helmet"
+ desc = "An incomplete firebot assembly with a fire helmet."
+ qdel(I)
+ build_step++
+
+ if(ASSEMBLY_SECOND_STEP)
+ if(isprox(I))
+ if(!can_finish_build(I, user))
+ return
+ to_chat(user, "You add the [I] to [src]! Beep Boop!")
+ var/mob/living/simple_animal/bot/firebot/F = new(drop_location())
+ F.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
new file mode 100644
index 00000000000..30c4c9a091e
--- /dev/null
+++ b/code/modules/mob/living/simple_animal/bot/firebot.dm
@@ -0,0 +1,301 @@
+//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/aibots.dmi'
+ icon_state = "firebot"
+ density = FALSE
+ anchored = FALSE
+ health = 25
+ maxHealth = 25
+ spacewalk = TRUE
+
+ radio_key = /obj/item/encryptionkey/headset_eng
+ radio_channel = RADIO_CHANNEL_ENGINEERING
+ bot_type = FIRE_BOT
+ model = "Firebot"
+ bot_core = /obj/machinery/bot_core/firebot
+ window_id = "autoextinguisher"
+ window_name = "Mobile Fire Extinguisher v1.0"
+ path_image_color = "#FFA500"
+
+ 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
+ var/foam_cooldown = 0
+
+ var/extinguish_people = TRUE
+ var/extinguish_fires = TRUE
+ var/stationary_mode = FALSE
+
+/mob/living/simple_animal/bot/firebot/Initialize()
+ . = ..()
+ update_icon()
+ var/datum/job/engineer/J = new/datum/job/engineer
+ access_card.access += J.get_access()
+ prev_access = access_card.access
+
+ internal_ext = new /obj/item/extinguisher(src)
+ internal_ext.safety = FALSE
+ internal_ext.precision = TRUE
+ internal_ext.max_water = INFINITY
+ internal_ext.Initialize()
+
+/mob/living/simple_animal/bot/firebot/turn_on()
+ . = ..()
+ update_icon()
+
+/mob/living/simple_animal/bot/firebot/turn_off()
+ ..()
+ update_icon()
+
+/mob/living/simple_animal/bot/firebot/bot_reset()
+ ..()
+ target_fire = null
+ old_target_fire = null
+ ignore_list = list()
+ anchored = FALSE
+ update_icon()
+
+/mob/living/simple_animal/bot/firebot/proc/soft_reset()
+ path = list()
+ target_fire = null
+ mode = BOT_IDLE
+ last_found = world.time
+ update_icon()
+
+/mob/living/simple_animal/bot/firebot/set_custom_texts()
+ text_hack = "You corrupt [name]'s safety protocols."
+ text_dehack = "You detect errors in [name] and reset his programming."
+ text_dehack_fail = "[name] is not responding to reset commands!"
+
+/mob/living/simple_animal/bot/firebot/get_controls(mob/user)
+ var/dat
+ dat += hack(user)
+ dat += showpai(user)
+ dat += "Mobile Fire Extinguisher v1.0
"
+ dat += "Status: [on ? "On" : "Off"]
"
+ dat += "Maintenance panel panel is [open ? "opened" : "closed"]
"
+
+ dat += "Behaviour controls are [locked ? "locked" : "unlocked"]
"
+ if(!locked || issilicon(user) || IsAdminGhost(user))
+ dat += "Extinguish Fires: [extinguish_fires ? "Yes" : "No"]
"
+ dat += "Extinguish People: [extinguish_people ? "Yes" : "No"]
"
+ dat += "Patrol Station: [auto_patrol ? "Yes" : "No"]
"
+ dat += "Stationary Mode: [stationary_mode ? "Yes" : "No"]
"
+
+ return dat
+
+/mob/living/simple_animal/bot/firebot/emag_act(mob/user)
+ ..()
+ if(emagged == 2)
+ if(user)
+ to_chat(user, "[src] buzzes and beeps.")
+ audible_message("[src] buzzes oddly!")
+ playsound(src, "sparks", 75, TRUE)
+ if(user)
+ old_target_fire = user
+ extinguish_fires = FALSE
+ extinguish_people = TRUE
+
+ internal_ext.chem = "clf3" //Refill the internal extinguisher with liquid fire
+ internal_ext.power = 3
+ internal_ext.precision = FALSE
+ internal_ext.Initialize()
+
+/mob/living/simple_animal/bot/firebot/Topic(href, href_list)
+ if(..())
+ return TRUE
+
+ switch(href_list["operation"])
+ if("extinguish_fires")
+ extinguish_fires = !extinguish_fires
+ if("extinguish_people")
+ extinguish_people = !extinguish_people
+ if("stationary_mode")
+ stationary_mode = !stationary_mode
+
+ update_controls()
+ update_icon()
+
+/mob/living/simple_animal/bot/firebot/proc/is_burning(atom/target)
+ if(ismob(target))
+ var/mob/living/M = target
+ if(M.on_fire || (emagged == 2 && !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/list/messagevoice = list("No fires detected." = 'sound/voice/firebot/nofires.ogg',
+ "Only you can prevent station fires." = 'sound/voice/firebot/onlyyou.ogg',
+ "Temperature nominal." = 'sound/voice/firebot/tempnominal.ogg',
+ "Keep it cool." = 'sound/voice/firebot/keepitcool.ogg')
+ var/message = pick(messagevoice)
+ speak(message)
+ playsound(src, messagevoice[message], 50)
+
+ // 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)
+
+ if(extinguish_people)
+ target_fire = scan(/mob/living, old_target_fire, scan_range) // Scan for burning humans first
+
+ if(target_fire == null && extinguish_fires)
+ target_fire = scan(/turf/open, 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) <= (emagged == 2 ? 1 : 2))) // Make the bot spray water from afar when not emagged
+ if((speech_cooldown + SPEECH_INTERVAL) < world.time)
+ if(ishuman(target_fire))
+ speak("Stop, drop and roll!")
+ playsound(src, "sound/voice/firebot/stopdropnroll.ogg", 50, 0)
+ else
+ speak("Extinguishing!")
+ playsound(src, "sound/voice/firebot/extinguishing.ogg", 50, 0)
+ 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, get_turf(target_fire), /turf/proc/Distance_cardinal, 0, 30, 1, id=access_card)
+ 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(auto_patrol && !target_fire)
+ if(mode == BOT_IDLE || mode == BOT_START_PATROL)
+ start_patrol()
+
+ if(mode == BOT_PATROL)
+ bot_patrol()
+
+
+//Look for burning people or turfs around the bot
+/mob/living/simple_animal/bot/firebot/process_scan(atom/scan_target)
+ var/result
+
+ if(scan_target == src)
+ return result
+
+ if(is_burning(scan_target))
+ if((detected_cooldown + DETECTED_VOICE_INTERVAL) < world.time)
+ speak("Fire detected!")
+ playsound(src, "sound/voice/firebot/detected.ogg", 50, 0)
+ detected_cooldown = world.time
+ result = scan_target
+
+ return result
+
+/mob/living/simple_animal/bot/firebot/temperature_expose(datum/gas_mixture/air, temperature, volume)
+ if((temperature > T0C + 200 || temperature < BODYTEMP_COLD_DAMAGE_LIMIT) && foam_cooldown + FOAM_INTERVAL < world.time)
+ new /obj/effect/particle_effect/foam/firefighting(loc)
+ foam_cooldown = world.time
+ ..()
+
+/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.afterattack(target, user, null)
+
+/mob/living/simple_animal/bot/firebot/update_icon()
+ if(!on)
+ icon_state = "firebot0"
+ return
+ if(IsStun() || IsParalyzed())
+ icon_state = "firebots1"
+ else if(stationary_mode) //Bot has yellow light to indicate stationary mode.
+ icon_state = "firebots1"
+ else
+ icon_state = "firebot1"
+
+
+/mob/living/simple_animal/bot/firebot/explode()
+ on = FALSE
+ visible_message("[src] blows apart!")
+
+ var/atom/Tsec = drop_location()
+
+ new /obj/item/assembly/prox_sensor(Tsec)
+ new /obj/item/clothing/head/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)
+
+ if(prob(50))
+ drop_part(robot_arm, Tsec)
+
+ do_sparks(3, TRUE, src)
+ ..()
+
+/obj/machinery/bot_core/firebot
+ req_one_access = list(ACCESS_CONSTRUCTION, ACCESS_ROBOTICS)
+
+#undef SPEECH_INTERVAL
+#undef DETECTED_VOICE_INTERVAL
+#undef FOAM_INTERVAL
+
diff --git a/icons/mob/aibots.dmi b/icons/mob/aibots.dmi
index 913a0dff02e..f4049abc416 100644
Binary files a/icons/mob/aibots.dmi and b/icons/mob/aibots.dmi differ
diff --git a/sound/voice/firebot/detected.ogg b/sound/voice/firebot/detected.ogg
new file mode 100644
index 00000000000..e5acfa10c7f
Binary files /dev/null and b/sound/voice/firebot/detected.ogg differ
diff --git a/sound/voice/firebot/extinguishing.ogg b/sound/voice/firebot/extinguishing.ogg
new file mode 100644
index 00000000000..d7b44b7fce3
Binary files /dev/null and b/sound/voice/firebot/extinguishing.ogg differ
diff --git a/sound/voice/firebot/keepitcool.ogg b/sound/voice/firebot/keepitcool.ogg
new file mode 100644
index 00000000000..e04c94337a4
Binary files /dev/null and b/sound/voice/firebot/keepitcool.ogg differ
diff --git a/sound/voice/firebot/nofires.ogg b/sound/voice/firebot/nofires.ogg
new file mode 100644
index 00000000000..962f18d1358
Binary files /dev/null and b/sound/voice/firebot/nofires.ogg differ
diff --git a/sound/voice/firebot/onlyyou.ogg b/sound/voice/firebot/onlyyou.ogg
new file mode 100644
index 00000000000..e8eb3cdf2ee
Binary files /dev/null and b/sound/voice/firebot/onlyyou.ogg differ
diff --git a/sound/voice/firebot/stopdropnroll.ogg b/sound/voice/firebot/stopdropnroll.ogg
new file mode 100644
index 00000000000..9e6b1c1bb60
Binary files /dev/null and b/sound/voice/firebot/stopdropnroll.ogg differ
diff --git a/sound/voice/firebot/tempnominal.ogg b/sound/voice/firebot/tempnominal.ogg
new file mode 100644
index 00000000000..9eaa984cd6a
Binary files /dev/null and b/sound/voice/firebot/tempnominal.ogg differ
diff --git a/tgstation.dme b/tgstation.dme
index 2a3d30ff27e..77419b97c64 100755
--- a/tgstation.dme
+++ b/tgstation.dme
@@ -2072,6 +2072,7 @@
#include "code\modules\mob\living\simple_animal\bot\cleanbot.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\honkbot.dm"
#include "code\modules\mob\living\simple_animal\bot\medbot.dm"