diff --git a/code/__defines/flags.dm b/code/__defines/flags.dm
index 0bad588e678..7a8151ef7fd 100644
--- a/code/__defines/flags.dm
+++ b/code/__defines/flags.dm
@@ -32,6 +32,9 @@
/// This atom is queued for an overlay update.
#define ATOM_AWAITING_OVERLAY_UPDATE (1<<5)
+///The Reagent cannot be refilled
+#define ATOM_REAGENTS_NO_REFILL (1<<6)
+
/* -- /turf/var/turf_flags -- */
diff --git a/code/game/atoms.dm b/code/game/atoms.dm
index e63a02358aa..9e59e0f1ae3 100644
--- a/code/game/atoms.dm
+++ b/code/game/atoms.dm
@@ -129,6 +129,9 @@ var/global/list/pre_init_created_atoms // atom creation ordering means some stuf
/atom/proc/is_open_container()
return atom_flags & ATOM_REAGENTS_IS_OPEN
+/atom/proc/can_refill()
+ return atom_flags & ~ATOM_REAGENTS_NO_REFILL
+
/*//Convenience proc to see whether a container can be accessed in a certain way.
proc/can_subract_container()
diff --git a/code/modules/reagents/machinery/dispenser/reagent_tank.dm b/code/modules/reagents/machinery/dispenser/reagent_tank.dm
index bfe7dc58114..ae03e2999c9 100644
--- a/code/modules/reagents/machinery/dispenser/reagent_tank.dm
+++ b/code/modules/reagents/machinery/dispenser/reagent_tank.dm
@@ -9,10 +9,11 @@
density = 1
anchored = 0
pressure_resistance = 2*ONE_ATMOSPHERE
-
+ atom_flags = ATOM_REAGENTS_NO_REFILL
+ var/volume = 5000
+ var/has_hose = TRUE
var/obj/item/hose_connector/input/active/InputSocket
var/obj/item/hose_connector/output/active/OutputSocket
-
var/amount_per_transfer_from_this = 10
var/possible_transfer_amounts = list(10,25,50,100)
@@ -22,21 +23,20 @@
/obj/structure/reagent_dispensers/Destroy()
QDEL_NULL(InputSocket)
QDEL_NULL(OutputSocket)
-
- ..()
+ return ..()
/obj/structure/reagent_dispensers/Initialize()
- var/datum/reagents/R = new/datum/reagents(5000)
+ var/datum/reagents/R = new/datum/reagents(volume)
reagents = R
R.my_atom = src
if (!possible_transfer_amounts)
src.verbs -= /obj/structure/reagent_dispensers/verb/set_APTFT
- InputSocket = new(src)
- InputSocket.carrier = src
- OutputSocket = new(src)
- OutputSocket.carrier = src
-
+ if(has_hose)
+ InputSocket = new(src)
+ InputSocket.carrier = src
+ OutputSocket = new(src)
+ OutputSocket.carrier = src
. = ..()
/obj/structure/reagent_dispensers/examine(mob/user)
diff --git a/code/modules/reagents/reagent_containers/_reagent_containers.dm b/code/modules/reagents/reagent_containers/_reagent_containers.dm
index 322fe5389dc..85f8e3e251a 100644
--- a/code/modules/reagents/reagent_containers/_reagent_containers.dm
+++ b/code/modules/reagents/reagent_containers/_reagent_containers.dm
@@ -22,6 +22,9 @@
src.verbs -= /obj/item/reagent_containers/verb/set_APTFT
create_reagents(volume)
+/obj/item/reagent_containers/on_reagent_change()
+ update_icon()
+
/obj/item/reagent_containers/attack_self(mob/user as mob)
return
@@ -38,12 +41,14 @@
return 0
if(!target.reagents || !target.reagents.total_volume)
- to_chat(user, "[target] is empty.")
- return 1
+ if(target.can_refill())
+ return 0
+ else
+ to_chat(user, "[target] is empty.")
+ return 1
if(reagents && !reagents.get_free_space())
- to_chat(user, "[src] is full.")
- return 1
+ return 0
var/trans = target.reagents.trans_to_obj(src, target:amount_per_transfer_from_this)
to_chat(user, "You fill [src] with [trans] units of the contents of [target].")
@@ -116,8 +121,8 @@
feed_sound(user)
return TRUE
-/obj/item/reagent_containers/proc/standard_pour_into(var/mob/user, var/atom/target) // This goes into afterattack and yes, it's atom-level
- if(!target.is_open_container() || !target.reagents)
+/obj/item/reagent_containers/proc/standard_pour_into(var/mob/user, var/atom/target, var/pour_all = FALSE) // This goes into afterattack and yes, it's atom-level
+ if(!target.is_open_container() || !target.reagents || !target.can_refill())
return 0
if(!reagents || !reagents.total_volume)
@@ -128,6 +133,7 @@
to_chat(user, "[target] is full.")
return 1
- var/trans = reagents.trans_to(target, amount_per_transfer_from_this)
+ var/trans = pour_all ? reagents.total_volume : amount_per_transfer_from_this
+ trans = reagents.trans_to(target, trans)
to_chat(user, "You transfer [trans] units of the solution to [target].")
return 1
diff --git a/code/modules/reagents/reagent_containers/bidon.dm b/code/modules/reagents/reagent_containers/bidon.dm
new file mode 100644
index 00000000000..59667050583
--- /dev/null
+++ b/code/modules/reagents/reagent_containers/bidon.dm
@@ -0,0 +1,158 @@
+/obj/structure/reagent_dispensers/bidon
+ name = "bidon canister"
+ desc = "A canister for handling large volumes of chemicals."
+ icon = 'icons/obj/structures/bidon.dmi'
+ icon_state = "bidon"
+ possible_transfer_amounts = list(10, 30, 60, 120, 200, 300)
+ unacidable = TRUE
+ has_hose = FALSE
+ var/use_reagent_color = TRUE
+ var/fill_step = 10
+
+
+/obj/structure/reagent_dispensers/bidon/Initialize()
+ . = ..()
+ update_icon()
+
+
+/obj/structure/reagent_dispensers/bidon/update_icon()
+ cut_overlays()
+ if (!is_open_container())
+ add_overlay("[icon_state]_lid")
+ var/fill_state = ceil(round(reagents.total_volume / volume * 100, fill_step), 0, 100)
+ if (!fill_state)
+ return
+ if (use_reagent_color)
+ var/image/image = image(icon, icon_state = "[icon_state][fill_state]")
+ image.color = reagents.get_color()
+ add_overlay(image)
+ else
+ add_overlay("[icon_state][fill_state]")
+
+
+/obj/structure/reagent_dispensers/bidon/examine(mob/user, distance, infix, suffix)
+ . = ..()
+ if (distance > 5)
+ return
+ . += "The lid is [is_open_container() ? "off" : "on"]."
+ if (distance < 3)
+ . += "It holds [round(reagents.total_volume, 0.1)]/[volume] units of reagents."
+
+
+/obj/structure/reagent_dispensers/bidon/attack_hand(mob/living/user)
+ playsound(src, 'sound/items/trayhit2.ogg', 50, TRUE)
+ atom_flags ^= ATOM_REAGENTS_IS_OPEN
+ if (is_open_container())
+ to_chat(user, SPAN_ITALIC("You open the lid of \the [src]."))
+ else
+ to_chat(user, SPAN_ITALIC("You close the lid of \the [src]."))
+ update_icon()
+
+
+/obj/structure/reagent_dispensers/bidon/attackby(obj/item/item, mob/living/user)
+ if (!is_open_container() && istype(item, /obj/item/reagent_containers))
+ to_chat(user, SPAN_WARNING("Remove the lid first."))
+ return TRUE
+ return ..()
+
+
+/obj/structure/reagent_dispensers/bidon/stasis
+ name = "stasis bidon canister"
+ desc = "A canister for handling large volumes of chemicals. This one has a stasis field."
+ icon_state = "bidon_stasis"
+ atom_flags = ATOM_REAGENTS_SKIP_REACTIONS
+ use_reagent_color = FALSE
+ fill_step = 20
+ var/timer_seconds
+ var/timer_handle
+
+
+/obj/structure/reagent_dispensers/bidon/stasis/update_icon()
+ ..()
+ if (timer_handle)
+ add_overlay("timer_active")
+ else if (timer_seconds)
+ add_overlay("timer_idle")
+
+
+/obj/structure/reagent_dispensers/bidon/stasis/examine(mob/user, distance, infix, suffix)
+ . = ..()
+ if (distance > 5 || !timer_seconds)
+ return
+ var/timer_display = "a timer"
+ if (timer_handle)
+ timer_display = "an active timer"
+ if (distance > 3)
+ . += "It has [timer_display] attached."
+ else
+ . += "It has [timer_display] attached set for [timer_seconds] seconds."
+
+
+/obj/structure/reagent_dispensers/bidon/stasis/attackby(obj/item/item, mob/living/user)
+ if (istype(item, /obj/item/assembly/timer))
+ if (timer_seconds)
+ to_chat(user, SPAN_WARNING("\The [src] already has a timer attached."))
+ else if (user.unEquip(item))
+ var/obj/item/assembly/timer/timer = item
+ timer_seconds = timer.time
+ qdel(item)
+ playsound(src, 'sound/items/Screwdriver.ogg', 50, TRUE)
+ user.visible_message(
+ SPAN_ITALIC("\The [user] attaches \a [item] to \a [src]."),
+ SPAN_ITALIC("You attach \the [item] to \the [src]."),
+ SPAN_ITALIC("You hear metal clicking together."),
+ range = 5
+ )
+ return TRUE
+ if (item.is_screwdriver())
+ if (!timer_seconds)
+ to_chat(user, SPAN_WARNING("\The [src] doesn't have a timer attached."))
+ else if (timer_handle)
+ to_chat(user, SPAN_WARNING("\The [src]'s timer is active."))
+ else
+ user.visible_message(
+ SPAN_ITALIC("\The [user] removes the timer from \the [src]."),
+ SPAN_ITALIC("You remove the timer from \the [src]."),
+ SPAN_ITALIC("You hear metal clicking together."),
+ range = 5
+ )
+ playsound(src, item.usesound, 50, TRUE)
+ var/obj/item/assembly/timer/timer = new (loc)
+ user.put_in_hands(timer)
+ timer.time = timer_seconds
+ timer_seconds = 0
+ return TRUE
+ if (item.is_multitool())
+ if (!timer_seconds)
+ to_chat(user, SPAN_WARNING("\The [src] doesn't have a timer attached."))
+ return TRUE
+ else if (timer_handle)
+ deltimer(timer_handle)
+ timer_handle = null
+ user.visible_message(
+ SPAN_ITALIC("\The [user] disables the timer on \the [src]."),
+ SPAN_ITALIC("You disable the timer on \the [src]."),
+ SPAN_ITALIC("You hear soft beeping."),
+ range = 5
+ )
+ update_icon()
+ else
+ timer_handle = addtimer(CALLBACK(src, .proc/timer_end), timer_seconds SECONDS, TIMER_STOPPABLE)
+ user.visible_message(
+ SPAN_ITALIC("\The [user] activates the timer on \the [src]."),
+ SPAN_ITALIC("You activate the timer on \the [src]."),
+ SPAN_ITALIC("You hear soft beeping."),
+ range = 5
+ )
+ playsound(src, item.usesound, 50, TRUE)
+ update_icon()
+ return TRUE
+ return ..()
+
+
+/obj/structure/reagent_dispensers/bidon/stasis/proc/timer_end()
+ atom_flags &= ~(ATOM_REAGENTS_SKIP_REACTIONS)
+ reagents.handle_reactions()
+ atom_flags |= ATOM_REAGENTS_SKIP_REACTIONS
+ timer_handle = null
+ update_icon()
diff --git a/code/modules/reagents/reagent_containers/glass.dm b/code/modules/reagents/reagent_containers/glass.dm
index ad6877fd37f..ebf920734bb 100644
--- a/code/modules/reagents/reagent_containers/glass.dm
+++ b/code/modules/reagents/reagent_containers/glass.dm
@@ -106,6 +106,11 @@
for(var/type in can_be_placed_into) //Is it something it can be placed into?
if(istype(target, type))
return 1
+
+ //Disarm intent tries to empty the beaker
+ if(user.a_intent == I_DISARM && standard_pour_into(user, target, TRUE))
+ return TRUE
+
if(standard_dispenser_refill(user, target)) //Are they clicking a water tank/some dispenser?
return 1
if(standard_pour_into(user, target)) //Pouring into another beaker?
diff --git a/code/modules/research/designs/bio_devices.dm b/code/modules/research/designs/bio_devices.dm
index f3aa33845ff..7c6a26c6a88 100644
--- a/code/modules/research/designs/bio_devices.dm
+++ b/code/modules/research/designs/bio_devices.dm
@@ -59,3 +59,18 @@
build_path = /obj/item/analyzer/plant_analyzer
sort_string = "JAADA"
+/datum/design/item/biotech/bidon
+ desc = "A canister for handling large volumes of chemicals."
+ id = "bidon"
+ req_tech = list(TECH_MATERIAL = 4, TECH_BIO = 4)
+ materials = list(MAT_STEEL = 2000, "glass" = 1000)
+ build_path = /obj/structure/reagent_dispensers/bidon
+ sort_string = "JAADB"
+
+/datum/design/item/biotech/bidon_stasis
+ desc = "A stasis canister for handling large volumes of chemicals."
+ id = "bidon_stasis"
+ req_tech = list(TECH_MATERIAL = 6, TECH_BIO = 4, TECH_DATA = 5)
+ materials = list(MAT_STEEL = 2000, "glass" = 1000, MAT_SILVER = 100)
+ build_path = /obj/structure/reagent_dispensers/bidon/stasis
+ sort_string = "JAADC"
diff --git a/icons/obj/structures/bidon.dmi b/icons/obj/structures/bidon.dmi
new file mode 100644
index 00000000000..6dea9015bf7
Binary files /dev/null and b/icons/obj/structures/bidon.dmi differ
diff --git a/polaris.dme b/polaris.dme
index 7632f1ce87b..eba303d8ca4 100644
--- a/polaris.dme
+++ b/polaris.dme
@@ -3002,6 +3002,7 @@
#include "code\modules\reagents\reactions\instant\instant.dm"
#include "code\modules\reagents\reactions\instant\vox.dm"
#include "code\modules\reagents\reagent_containers\_reagent_containers.dm"
+#include "code\modules\reagents\reagent_containers\bidon.dm"
#include "code\modules\reagents\reagent_containers\blood_pack.dm"
#include "code\modules\reagents\reagent_containers\borghypo.dm"
#include "code\modules\reagents\reagent_containers\dropper.dm"