diff --git a/code/__defines/misc.dm b/code/__defines/misc.dm
index 4d5a639708f0..8198a65f2394 100644
--- a/code/__defines/misc.dm
+++ b/code/__defines/misc.dm
@@ -402,3 +402,9 @@
#define MM_ATTACK_RESULT_NONE 0
#define MM_ATTACK_RESULT_DEFLECTED BITFLAG(0)
#define MM_ATTACK_RESULT_BLOCKED BITFLAG(1)
+
+// Effectively a speed modifier for how fast pollen is produced by flowering plants. Pollen per second.
+// In theory, one pollen every 5 seconds (at time of writing)
+#define POLLEN_PER_SECOND 0.2
+#define POLLEN_PRODUCTION_MULT (POLLEN_PER_SECOND * (SSplants.wait / 10))
+#define MAX_POLLEN_PER_FLOWER 10
diff --git a/code/game/objects/items/devices/chameleonproj.dm b/code/game/objects/items/devices/chameleonproj.dm
index fc743829ceef..83f05a1b7de8 100644
--- a/code/game/objects/items/devices/chameleonproj.dm
+++ b/code/game/objects/items/devices/chameleonproj.dm
@@ -102,7 +102,7 @@
density = FALSE
anchored = TRUE
is_spawnable_type = FALSE
- movement_handlers = list(/datum/movement_handler/delay/chameleon_projector)
+ movement_handlers = list(/datum/movement_handler/delay/chameleon_projector = list(2.5 SECONDS))
var/obj/item/chameleon/master = null
/obj/effect/dummy/chameleon/Initialize(mapload, var/obj/item/chameleon/projector)
@@ -152,9 +152,6 @@
if(!my_turf.get_supporting_platform() && !(locate(/obj/structure/lattice) in loc))
disrupted()
-/datum/movement_handler/delay/chameleon_projector
- delay = 2.5 SECONDS
-
/datum/movement_handler/delay/chameleon_projector/MayMove(mob/mover, is_external)
return host.loc?.has_gravity() ? ..() : MOVEMENT_STOP
diff --git a/code/game/objects/structures/flora/plant.dm b/code/game/objects/structures/flora/plant.dm
index 9e44f55a2f3a..278a60e8f019 100644
--- a/code/game/objects/structures/flora/plant.dm
+++ b/code/game/objects/structures/flora/plant.dm
@@ -7,14 +7,20 @@
var/dead = FALSE
var/sampled = FALSE
var/datum/seed/plant
- var/harvestable
+ var/harvestable = 0 // Note that this is a counter, not a bool.
+ var/pollen = 0
/obj/structure/flora/plant/large
opacity = TRUE
density = TRUE
-/* Notes for future work moving logic off hydrotrays onto plants themselves:
/obj/structure/flora/plant/Process()
+ if(plant?.produces_pollen <= 0)
+ return PROCESS_KILL
+ if(pollen < MAX_POLLEN_PER_FLOWER)
+ pollen += plant.produces_pollen * POLLEN_PRODUCTION_MULT
+
+/* Notes for future work moving logic off hydrotrays onto plants themselves:
// check our immediate environment
// ask our environment for available reagents
// process the reagents
@@ -61,9 +67,13 @@
var/potency = plant.get_trait(TRAIT_POTENCY)
set_light(l_range = max(1, round(potency/10)), l_power = clamp(round(potency/30), 0, 1), l_color = plant.get_trait(TRAIT_BIOLUM_COLOUR))
update_icon()
- return ..()
+ . = ..()
+ if(plant?.produces_pollen && !is_processing)
+ START_PROCESSING(SSplants, src)
/obj/structure/flora/plant/Destroy()
+ if(is_processing)
+ STOP_PROCESSING(SSplants, src)
plant = null
. = ..()
@@ -147,3 +157,24 @@
/obj/structure/flora/plant/random_mushroom/Initialize()
plant = pick(get_mushroom_variants())
return ..()
+
+/obj/structure/flora/plant/random_flower
+ name = "flower"
+ color = COLOR_PINK
+ icon_state = "flower5"
+ is_spawnable_type = TRUE
+
+// Only contains roundstart plants, this is meant to be a mapping helper.
+/obj/structure/flora/plant/random_flower/proc/get_flower_variants()
+ var/static/list/flower_variants
+ if(isnull(flower_variants))
+ flower_variants = list()
+ for(var/plant in SSplants.seeds)
+ var/datum/seed/seed = SSplants.seeds[plant]
+ if(!isnull(seed?.name) && seed.produces_pollen)
+ flower_variants |= seed.name
+ return flower_variants
+
+/obj/structure/flora/plant/random_flower/Initialize()
+ plant = pick(get_flower_variants())
+ return ..()
diff --git a/code/game/objects/structures/flora/stump.dm b/code/game/objects/structures/flora/stump.dm
index 4911cd3a57e7..f13415593f7c 100644
--- a/code/game/objects/structures/flora/stump.dm
+++ b/code/game/objects/structures/flora/stump.dm
@@ -4,6 +4,7 @@
/obj/structure/flora/stump
name = "stump"
hitsound = 'sound/effects/hit_wood.ogg'
+ storage = /datum/storage/dead_tree
var/log_type = /obj/item/stack/material/log
/obj/structure/flora/stump/get_material_health_modifier()
diff --git a/code/game/objects/structures/flora/tree.dm b/code/game/objects/structures/flora/tree.dm
index 13dcf346bcc1..80eb4b09a9cf 100644
--- a/code/game/objects/structures/flora/tree.dm
+++ b/code/game/objects/structures/flora/tree.dm
@@ -114,6 +114,11 @@ var/global/list/christmas_trees = list()
icon_state = "tree_1"
protects_against_weather = FALSE
stump_type = /obj/structure/flora/stump/tree/dead
+ storage = /datum/storage/dead_tree
+
+/datum/storage/dead_tree
+ max_w_class = ITEM_SIZE_NORMAL
+ max_storage_space = ITEM_SIZE_SMALL * 5
/obj/structure/flora/tree/dead/random/init_appearance()
icon_state = "tree_[rand(1, 6)]"
diff --git a/code/modules/hydroponics/trays/tray_process.dm b/code/modules/hydroponics/trays/tray_process.dm
index a62c81a8d1e2..cd6d30d7a0ad 100644
--- a/code/modules/hydroponics/trays/tray_process.dm
+++ b/code/modules/hydroponics/trays/tray_process.dm
@@ -65,8 +65,9 @@
mutate((rand(100) < 15) ? 2 : 1)
mutation_level = 0
- if(pollen < 10)
- pollen += seed?.produces_pollen
+ if(pollen < MAX_POLLEN_PER_FLOWER)
+ pollen += seed?.produces_pollen * POLLEN_PRODUCTION_MULT
+ to_world("\ref[src] has pollen [pollen] ([seed?.produces_pollen] * [POLLEN_PRODUCTION_MULT])")
// Maintain tray nutrient and water levels.
if(seed.get_trait(TRAIT_REQUIRES_NUTRIENTS) && seed.get_trait(TRAIT_NUTRIENT_CONSUMPTION) > 0 && nutrilevel > 0 && prob(25))
diff --git a/code/modules/mob/living/living.dm b/code/modules/mob/living/living.dm
index 1e6497c10a04..e965e0fc84de 100644
--- a/code/modules/mob/living/living.dm
+++ b/code/modules/mob/living/living.dm
@@ -2005,3 +2005,6 @@ default behaviour is:
/mob/living/is_cloaked()
return has_mob_modifier(/decl/mob_modifier/cloaked)
+
+/mob/living/proc/is_playing_dead()
+ return stat || current_posture?.prone || (status_flags & FAKEDEATH)
diff --git a/code/modules/mob/mob_automove.dm b/code/modules/mob/mob_automove.dm
index f552d4f7c1e6..c74d72c44e97 100644
--- a/code/modules/mob/mob_automove.dm
+++ b/code/modules/mob/mob_automove.dm
@@ -25,7 +25,6 @@
/mob/failed_automove()
..()
stop_automove()
- _automove_target = null
return FALSE
/mob/start_automove(target, movement_type, datum/automove_metadata/metadata)
diff --git a/code/modules/mob/skills/skillset.dm b/code/modules/mob/skills/skillset.dm
index eef151360637..9da79495a218 100644
--- a/code/modules/mob/skills/skillset.dm
+++ b/code/modules/mob/skills/skillset.dm
@@ -47,7 +47,8 @@ var/global/list/all_skill_verbs
QDEL_NULL(NM) //Clean all nano_modules for simplicity.
QDEL_NULL(mob.skillset.NM)
QDEL_NULL_LIST(nm_viewing)
- QDEL_NULL_LIST(mob.skillset.nm_viewing)
+ if(mob.skillset)
+ QDEL_NULL_LIST(mob.skillset.nm_viewing)
on_levels_change()
//Called when a player is added as an antag and the antag datum processes the skillset.
diff --git a/maps/ministation/ministation-1.dmm b/maps/ministation/ministation-1.dmm
index 1ff78b8cade6..09ee082eafcb 100644
--- a/maps/ministation/ministation-1.dmm
+++ b/maps/ministation/ministation-1.dmm
@@ -8744,7 +8744,7 @@
/turf/floor/tiled,
/area/ministation/hall/e2)
"Oo" = (
-/obj/machinery/beehive,
+/obj/structure/apiary/mapped,
/turf/floor/fake_grass,
/area/ministation/hydro)
"Op" = (
diff --git a/mods/content/beekeeping/_beekeeping.dm b/mods/content/beekeeping/_beekeeping.dm
index d3b58be746d6..53ca84daf621 100644
--- a/mods/content/beekeeping/_beekeeping.dm
+++ b/mods/content/beekeeping/_beekeeping.dm
@@ -1,7 +1,13 @@
+#define FRAME_RESERVE_COST 30
+#define SWARM_AGITATION_PER_FRAME 25
+#define FRAME_MATERIAL_COST 20
+#define SWARM_GROWTH_COST 10
+#define FRAME_FILL_MATERIAL_COST 5
+#define HIVE_REPAIR_MATERIAL_COST 5
+
/decl/modpack/beekeeping
- name = "Beekeeping Content"
+ name = "Beekeeping and Insects Content"
/datum/storage/hopper/industrial/centrifuge/New()
..()
can_hold |= /obj/item/hive_frame
-
diff --git a/mods/content/beekeeping/_beekeeping.dme b/mods/content/beekeeping/_beekeeping.dme
index 75778704f368..92ee753943b5 100644
--- a/mods/content/beekeeping/_beekeeping.dme
+++ b/mods/content/beekeeping/_beekeeping.dme
@@ -3,10 +3,17 @@
// BEGIN_INCLUDE
#include "_beekeeping.dm"
#include "closets.dm"
-#include "hive_frame.dm"
#include "items.dm"
+#include "materials.dm"
#include "recipes.dm"
#include "trading.dm"
-#include "hives\_hive.dm"
+#include "hives\hive_extension.dm"
+#include "hives\hive_flora.dm"
+#include "hives\hive_frame.dm"
+#include "hives\hive_queen.dm"
+#include "hives\hive_structure.dm"
+#include "hives\hive_swarm.dm"
+#include "hives\insect_species\_insects.dm"
+#include "hives\insect_species\insects_pollinators.dm"
// END_INCLUDE
#endif
diff --git a/mods/content/beekeeping/closets.dm b/mods/content/beekeeping/closets.dm
index c39e70dbb457..daf63a942d5c 100644
--- a/mods/content/beekeeping/closets.dm
+++ b/mods/content/beekeeping/closets.dm
@@ -1,11 +1,11 @@
/obj/structure/closet/crate/hydroponics/beekeeping
name = "beekeeping crate"
- desc = "All you need to set up your own beehive."
+ desc = "All you need to set up your own beehive, except the beehive."
/obj/structure/closet/crate/hydroponics/beekeeping/Initialize()
. = ..()
- new /obj/item/beehive_assembly(src)
- new /obj/item/bee_smoker(src)
+ new /obj/item/stack/material/plank/mapped/wood/ten
+ new /obj/item/smoker(src)
new /obj/item/hive_frame/crafted(src)
new /obj/item/hive_frame/crafted(src)
new /obj/item/hive_frame/crafted(src)
diff --git a/mods/content/beekeeping/hive_frame.dm b/mods/content/beekeeping/hive_frame.dm
deleted file mode 100644
index e126ec1a7fa2..000000000000
--- a/mods/content/beekeeping/hive_frame.dm
+++ /dev/null
@@ -1,55 +0,0 @@
-/obj/item/hive_frame
- abstract_type = /obj/item/hive_frame
- icon_state = ICON_STATE_WORLD
- w_class = ITEM_SIZE_SMALL
- material_alteration = MAT_FLAG_ALTERATION_ALL
- chem_volume = 20
- var/destroy_on_centrifuge = FALSE
-
-/obj/item/hive_frame/on_reagent_change()
- . = ..()
- if(reagents?.total_volume)
- SetName("filled [initial(name)] ([reagents.get_primary_reagent_name()])")
- else
- SetName(initial(name))
- queue_icon_update()
-
-/obj/item/hive_frame/on_update_icon()
- . = ..()
- var/mesh_state = "[icon_state]-mesh"
- if(check_state_in_icon(mesh_state, icon))
- add_overlay(overlay_image(icon, mesh_state, COLOR_WHITE, RESET_COLOR))
- if(reagents?.total_volume)
- var/comb_state = "[icon_state]-comb"
- if(check_state_in_icon(comb_state, icon))
- add_overlay(overlay_image(icon, comb_state, reagents.get_color(), RESET_COLOR))
- compile_overlays()
-
-/obj/item/hive_frame/handle_centrifuge_process(obj/machinery/centrifuge/centrifuge)
- if(!(. = ..()))
- return
- if(reagents.total_volume)
- reagents.trans_to_holder(centrifuge.loaded_beaker.reagents, reagents.total_volume)
- for(var/obj/item/thing in contents)
- thing.dropInto(centrifuge.loc)
- if(destroy_on_centrifuge)
- for(var/atom/movable/thing in convert_matter_to_lumps())
- thing.dropInto(centrifuge.loc)
-
-// Crafted frame used in apiaries.
-/obj/item/hive_frame/crafted
- name = "hive frame"
- desc = "A wooden frame for insect hives that the workers will fill with products like honey."
- icon = 'mods/content/beekeeping/icons/frame.dmi'
- material = /decl/material/solid/organic/wood/oak
- material_alteration = MAT_FLAG_ALTERATION_ALL
-
-// TEMP until beewrite redoes hives.
-/obj/item/hive_frame/crafted/filled/Initialize()
- . = ..()
- new /obj/item/stack/material/bar/wax(src)
- update_icon()
-
-/obj/item/hive_frame/crafted/filled/populate_reagents()
- . = ..()
- reagents.add_reagent(/decl/material/liquid/nutriment/honey, reagents?.maximum_volume)
diff --git a/mods/content/beekeeping/hives/_hive.dm b/mods/content/beekeeping/hives/_hive.dm
deleted file mode 100644
index 1ace43bbe1cf..000000000000
--- a/mods/content/beekeeping/hives/_hive.dm
+++ /dev/null
@@ -1,168 +0,0 @@
-/obj/machinery/beehive
- name = "apiary"
- icon = 'mods/content/beekeeping/icons/beekeeping.dmi'
- icon_state = "beehive-0"
- desc = "A wooden box designed specifically to house our buzzling buddies. Far more efficient than traditional hives. Just insert a frame and a queen, close it up, and you're good to go!"
- density = TRUE
- anchored = TRUE
- layer = BELOW_OBJ_LAYER
-
- var/closed = 0
- var/bee_count = 0 // Percent
- var/smoked = 0 // Timer
- var/honeycombs = 0 // Percent
- var/frames = 0
- var/maxFrames = 5
-
-/obj/machinery/beehive/Initialize()
- . = ..()
- update_icon()
-
-/obj/machinery/beehive/on_update_icon()
- overlays.Cut()
- icon_state = "beehive-[closed]"
- if(closed)
- overlays += "lid"
- if(frames)
- overlays += "empty[frames]"
- if(honeycombs >= 100)
- overlays += "full[round(honeycombs / 100)]"
- if(!smoked)
- switch(bee_count)
- if(1 to 20)
- overlays += "bees1"
- if(21 to 40)
- overlays += "bees2"
- if(41 to 60)
- overlays += "bees3"
- if(61 to 80)
- overlays += "bees4"
- if(81 to 100)
- overlays += "bees5"
-
-/obj/machinery/beehive/get_examine_strings(mob/user, distance, infix, suffix)
- . = ..()
- if(!closed)
- . += "The lid is open."
-
-/obj/machinery/beehive/attackby(var/obj/item/used_item, var/mob/user)
- if(IS_CROWBAR(used_item))
- closed = !closed
- user.visible_message("\The [user] [closed ? "closes" : "opens"] \the [src].", "You [closed ? "close" : "open"] \the [src].")
- update_icon()
- return TRUE
- else if(IS_WRENCH(used_item))
- anchored = !anchored
- user.visible_message("\The [user] [anchored ? "wrenches" : "unwrenches"] \the [src].", "You [anchored ? "wrench" : "unwrench"] \the [src].")
- return TRUE
- else if(istype(used_item, /obj/item/bee_smoker))
- if(closed)
- to_chat(user, "You need to open \the [src] with a crowbar before smoking the bees.")
- return TRUE
- user.visible_message("\The [user] smokes the bees in \the [src].", "You smoke the bees in \the [src].")
- smoked = 30
- update_icon()
- return TRUE
- else if(istype(used_item, /obj/item/hive_frame/crafted))
- if(closed)
- to_chat(user, "You need to open \the [src] with a crowbar before inserting \the [used_item].")
- return TRUE
- if(frames >= maxFrames)
- to_chat(user, "There is no place for an another frame.")
- return TRUE
- var/obj/item/hive_frame/crafted/H = used_item
- if(H.reagents?.total_volume)
- to_chat(user, "\The [used_item] is full with beeswax and honey, empty it in the extractor first.")
- return TRUE
- ++frames
- user.visible_message("\The [user] loads \the [used_item] into \the [src].", "You load \the [used_item] into \the [src].")
- update_icon()
- qdel(used_item)
- return TRUE
- else if(istype(used_item, /obj/item/bee_pack))
- var/obj/item/bee_pack/B = used_item
- if(B.full && bee_count)
- to_chat(user, "\The [src] already has bees inside.")
- return TRUE
- if(!B.full && bee_count < 90)
- to_chat(user, "\The [src] is not ready to split.")
- return TRUE
- if(!B.full && !smoked)
- to_chat(user, "Smoke \the [src] first!")
- return TRUE
- if(closed)
- to_chat(user, "You need to open \the [src] with a crowbar before moving the bees.")
- return TRUE
- if(B.full)
- user.visible_message("\The [user] puts the queen and the bees from \the [used_item] into \the [src].", "You put the queen and the bees from \the [used_item] into \the [src].")
- bee_count = 20
- B.empty()
- else
- user.visible_message("\The [user] puts bees and larvae from \the [src] into \the [used_item].", "You put bees and larvae from \the [src] into \the [used_item].")
- bee_count /= 2
- B.fill()
- update_icon()
- return TRUE
- else if(istype(used_item, /obj/item/scanner/plant))
- to_chat(user, "Scan result of \the [src]...")
- to_chat(user, "Beehive is [bee_count ? "[round(bee_count)]% full" : "empty"].[bee_count > 90 ? " Colony is ready to split." : ""]")
- if(frames)
- to_chat(user, "[frames] frames installed, [round(honeycombs / 100)] filled.")
- if(honeycombs < frames * 100)
- to_chat(user, "Next frame is [round(honeycombs % 100)]% full.")
- else
- to_chat(user, "No frames installed.")
- if(smoked)
- to_chat(user, "The hive is smoked.")
- return TRUE
- else if(IS_SCREWDRIVER(used_item))
- if(bee_count)
- to_chat(user, "You can't dismantle \the [src] with these bees inside.")
- return TRUE
- to_chat(user, "You start dismantling \the [src]...")
- playsound(loc, 'sound/items/Screwdriver.ogg', 50, 1)
- if(do_after(user, 30, src))
- user.visible_message("\The [user] dismantles \the [src].", "You dismantle \the [src].")
- new /obj/item/beehive_assembly(loc)
- qdel(src)
- return TRUE
- return FALSE // this should probably not be a machine, so don't do any component interactions
-
-/obj/machinery/beehive/physical_attack_hand(var/mob/user)
- if(closed)
- return FALSE
- . = TRUE
- if(honeycombs < 100)
- to_chat(user, "There are no filled honeycombs.")
- return
- if(!smoked && bee_count)
- to_chat(user, "The bees won't let you take the honeycombs out like this, smoke them first.")
- return
- user.visible_message("\The [user] starts taking the honeycombs out of \the [src].", "You start taking the honeycombs out of \the [src]...")
- while(honeycombs >= 100 && do_after(user, 30, src))
- new /obj/item/hive_frame/crafted/filled(loc)
- honeycombs -= 100
- --frames
- update_icon()
- if(honeycombs < 100)
- to_chat(user, "You take all filled honeycombs out.")
-
-/obj/machinery/beehive/Process()
- if(closed && !smoked && bee_count)
- pollinate_flowers()
- update_icon()
- smoked = max(0, smoked - 1)
- if(!smoked && bee_count)
- bee_count = min(bee_count * 1.005, 100)
- update_icon()
-
-/obj/machinery/beehive/proc/pollinate_flowers()
- var/coef = bee_count / 100
- var/trays = 0
- for(var/obj/machinery/portable_atmospherics/hydroponics/H in view(7, src))
- if(H.seed && !H.dead)
- H.plant_health += 0.05 * coef
- if(H.pollen >= 1)
- H.pollen--
- trays++
- honeycombs = min(honeycombs + 0.1 * coef * min(trays, 5), frames * 100)
diff --git a/mods/content/beekeeping/hives/hive_extension.dm b/mods/content/beekeeping/hives/hive_extension.dm
new file mode 100644
index 000000000000..c315dd59e2ef
--- /dev/null
+++ b/mods/content/beekeeping/hives/hive_extension.dm
@@ -0,0 +1,218 @@
+/datum/extension/insect_hive
+ base_type = /datum/extension/insect_hive
+ expected_type = /obj/structure
+ flags = EXTENSION_FLAG_IMMEDIATE
+ /// The species of insect that made this hive.
+ var/decl/insect_species/holding_species
+ /// References to our current swarm effects gathering for the hive.
+ var/list/swarms
+ var/current_health = 100
+ var/material = 10
+ var/raw_reserves = 0
+ /// Tracker for the last world.time that a frame was removed.
+ var/frame_last_removed = 0
+ /// Tracker for time that smoke will wear off.
+ var/smoked_until = 0
+
+/datum/extension/insect_hive/New(datum/holder, _species_decl)
+ ..()
+ holding_species = istype(_species_decl, /decl/insect_species) ? _species_decl : GET_DECL(_species_decl)
+ if(!istype(holding_species))
+ CRASH("Insect hive extension instantiated with invalid insect species: '[_species_decl]'.")
+ START_PROCESSING(SSprocessing, src)
+
+/datum/extension/insect_hive/Destroy()
+ STOP_PROCESSING(SSprocessing, src)
+ if(length(swarms))
+ for(var/obj/effect/insect_swarm/swarm as anything in swarms)
+ swarm.owner = null
+ swarms = null
+ var/atom/movable/hive = holder
+ if(istype(hive) && !QDELETED(hive))
+ hive.queue_icon_update()
+ return ..()
+
+/datum/extension/insect_hive/Process()
+ if(world.time < smoked_until)
+ return
+ holding_species.process_hive(src)
+ create_hive_products()
+
+/datum/extension/insect_hive/proc/handle_item_interaction(mob/user, obj/item/item)
+ return FALSE
+
+/datum/extension/insect_hive/proc/drop_nest(atom/drop_loc)
+ if(!isatom(drop_loc))
+ return
+ // handle some kind of physical hive dropping here
+ remove_extension(holder, /datum/extension/insect_hive)
+ if(!QDELETED(src))
+ qdel(src)
+
+/datum/extension/insect_hive/proc/get_nest_condition()
+ switch(current_health)
+ if(0, 10)
+ return "dying"
+ if(10, 30)
+ return "struggling"
+ if(30, 60)
+ return "sickly"
+ if(60, 90)
+ return null
+ return "thriving"
+
+/datum/extension/insect_hive/proc/get_nest_name()
+ return holding_species?.nest_name
+
+/datum/extension/insect_hive/proc/examined(mob/user, show_detail)
+ var/nest_descriptor = get_nest_condition()
+ if(nest_descriptor)
+ to_chat(user, SPAN_NOTICE("It contains \a [nest_descriptor] [get_nest_name()]."))
+ else
+ to_chat(user, SPAN_NOTICE("It contains \a [get_nest_name()]."))
+
+/datum/extension/insect_hive/proc/frame_removed(obj/item/frame)
+ frame_last_removed = world.time
+ if(world.time >= smoked_until && length(swarms) > 0)
+ if(isatom(holder))
+ var/atom/hive = holder
+ hive.visible_message(SPAN_DANGER("The buzzing from \the [holder] intensifies."))
+ for(var/obj/effect/insect_swarm/swarm as anything in swarms)
+ swarm.swarm_agitation = min(100, swarm.swarm_agitation + SWARM_AGITATION_PER_FRAME)
+
+/datum/extension/insect_hive/proc/try_hand_harvest(mob/user, obj/item/structure)
+ if(istype(structure) && !structure.storage)
+ var/obj/item/hive_frame/frame = locate() in structure
+ if(frame)
+ frame.dropInto(get_turf(structure))
+ if(istype(user))
+ user.put_in_hands(frame)
+ return TRUE
+ return FALSE
+
+/datum/extension/insect_hive/proc/try_tool_harvest(mob/user, obj/item/tool)
+ return FALSE
+
+/datum/extension/insect_hive/proc/swarm_destroyed(obj/effect/insect_swarm/swarm)
+ return
+
+/datum/extension/insect_hive/proc/swarm_at_hive()
+ for(var/atom/movable/swarm as anything in swarms)
+ if(get_turf(swarm) == get_turf(holder))
+ return swarm
+
+/datum/extension/insect_hive/proc/has_material(amt)
+ return amt <= material
+
+/datum/extension/insect_hive/proc/consume_material(amt)
+ if(has_material(amt))
+ material = clamp(material-amt, 0, 100)
+ return TRUE
+ return FALSE
+
+/datum/extension/insect_hive/proc/add_material(amt)
+ material = clamp(material+amt, 0, 100)
+ return TRUE
+
+/datum/extension/insect_hive/proc/add_reserves(amt)
+ raw_reserves = clamp(raw_reserves+amt, 0, 100)
+ return TRUE
+
+/datum/extension/insect_hive/proc/has_reserves(amt, raw_reserves_only = TRUE)
+ if(raw_reserves >= amt)
+ return TRUE
+ if(raw_reserves_only)
+ return FALSE
+ var/reserve = 0
+ for(var/obj/item/frame in holder)
+ reserve += frame.reagents?.total_volume
+ if(reserve >= amt)
+ return TRUE
+ return FALSE
+
+/datum/extension/insect_hive/proc/consume_reserves(amt, raw_reserves_only = TRUE)
+ if(!has_reserves(amt, raw_reserves_only))
+ return FALSE
+ if(raw_reserves >= amt)
+ raw_reserves -= amt
+ return TRUE
+ if(raw_reserves_only)
+ return FALSE
+ amt -= raw_reserves
+ raw_reserves = 0
+ for(var/obj/item/frame in holder)
+ if(!frame.reagents?.total_volume)
+ continue
+ var/consume = min(amt, frame.reagents.total_volume)
+ frame.reagents.remove_any(consume)
+ amt -= consume
+ if(amt <= 0)
+ return TRUE
+ return FALSE
+
+/datum/extension/insect_hive/proc/adjust_health(amt)
+ current_health = clamp(current_health + amt, 0, 100)
+ if(current_health <= 0)
+ var/atom/movable/hive = holder
+ hive.visible_message(SPAN_DANGER("\The [holding_species.nest_name] sags and collapses."))
+ remove_extension(holder, base_type)
+
+/datum/extension/insect_hive/proc/create_hive_products()
+
+ var/atom/movable/hive = holder
+ if(!istype(hive) || !holding_species)
+ return TRUE
+
+ if(!swarm_at_hive()) // nobody home to do the work
+ return TRUE
+
+ // Naturally build up enough material for a new frame (or repairs).
+ if(!has_material(FRAME_MATERIAL_COST))
+ add_material(1)
+
+ // Damaged hives cannot produce combs or honey.
+ if(current_health < 100)
+ if(consume_material(HIVE_REPAIR_MATERIAL_COST))
+ adjust_health(rand(3,5))
+ return TRUE
+
+ if(!has_reserves(FRAME_RESERVE_COST))
+ return TRUE
+
+ var/list/holder_contents = hive.get_contained_external_atoms()
+ for(var/obj/item/hive_frame/frame in holder_contents)
+ if(!frame.reagents || (frame.reagents.total_volume >= frame.reagents.maximum_volume))
+ continue
+ var/fill_cost = REAGENTS_FREE_SPACE(frame.reagents)
+ if(has_material(FRAME_FILL_MATERIAL_COST) && has_reserves(fill_cost))
+ consume_material(FRAME_FILL_MATERIAL_COST)
+ consume_reserves(fill_cost)
+ holding_species.fill_hive_frame(frame)
+ return TRUE
+
+ var/obj/item/native_frame = holding_species.native_frame_type
+ var/native_frame_size = initial(native_frame.w_class)
+ var/space_left = hive.storage.max_storage_space
+ for(var/obj/item/thing in hive.get_stored_inventory())
+ space_left -= thing.w_class
+ if(space_left < native_frame_size)
+ return
+
+ // Put a timer check on this to avoid a hive filling up with combs the moment you take 2 frames out.
+ if(world.time > (frame_last_removed + 2 MINUTES) && space_left >= native_frame_size && consume_material(FRAME_MATERIAL_COST))
+ // Frames start empty, and will be filled next run.
+ // Native 'frames' (combs) are bigger than crafted ones and aren't reusable.
+ new native_frame(holder, holding_species.produce_material)
+ hive.storage.update_ui_after_item_insertion()
+
+/datum/extension/insect_hive/proc/get_total_swarm_intensity()
+ . = 0
+ for(var/obj/effect/insect_swarm/swarm as anything in swarms)
+ . += swarm.swarm_intensity
+
+/datum/extension/insect_hive/proc/smoked_by(mob/user, atom/source, smoke_time = 10 SECONDS)
+ smoked_until = max(smoked_until, world.time + smoke_time)
+ // this is a little weird due to telekinetic bee smoking but so it goes
+ for(var/obj/effect/insect_swarm/swarm as anything in swarms)
+ swarm.was_smoked(max(0, smoked_until-world.time))
+ return TRUE
diff --git a/mods/content/beekeeping/hives/hive_flora.dm b/mods/content/beekeeping/hives/hive_flora.dm
new file mode 100644
index 000000000000..d6bc3a50c34f
--- /dev/null
+++ b/mods/content/beekeeping/hives/hive_flora.dm
@@ -0,0 +1,40 @@
+/obj/structure/flora
+ /// Percentage chance of trying to spawn an insect hive here, if appropriate.
+ var/insect_hive_chance = 20
+
+/obj/structure/flora/Initialize(ml, _mat, _reinf_mat)
+ . = ..()
+ if(insect_hive_chance && length(get_supported_insects()))
+ return INITIALIZE_HINT_LATELOAD
+
+/obj/structure/flora/LateInitialize()
+ ..()
+ if(prob(insect_hive_chance) && !has_extension(src, /datum/extension/insect_hive))
+ var/list/insects = get_supported_insects()
+ if(length(insects))
+ insects = insects.Copy() // don't mutate the static list.
+ for(var/species_type in insects)
+ var/decl/insect_species/species = GET_DECL(species_type)
+ if(!species.can_spawn_in_flora(src))
+ insects -= species_type
+ if(length(insects))
+ set_extension(src, /datum/extension/insect_hive, pickweight(insects))
+ update_icon()
+
+// Insect species that can hive in this flora.
+/obj/structure/flora/proc/get_supported_insects()
+ return
+
+/obj/structure/flora/tree/get_supported_insects()
+ var/static/list/_insects = list(
+ /decl/insect_species/honeybees = 10,
+ ///decl/insect_species/wasps = 1
+ )
+ return _insects
+
+/obj/structure/flora/stump/get_supported_insects()
+ var/static/list/_insects = list(
+ /decl/insect_species/honeybees = 10,
+ ///decl/insect_species/wasps = 1
+ )
+ return _insects
diff --git a/mods/content/beekeeping/hives/hive_frame.dm b/mods/content/beekeeping/hives/hive_frame.dm
new file mode 100644
index 000000000000..c3a4cdceb5de
--- /dev/null
+++ b/mods/content/beekeeping/hives/hive_frame.dm
@@ -0,0 +1,91 @@
+/obj/item/hive_frame
+ abstract_type = /obj/item/hive_frame
+ icon_state = ICON_STATE_WORLD
+ w_class = ITEM_SIZE_SMALL
+ material_alteration = MAT_FLAG_ALTERATION_ALL
+ chem_volume = 20
+ var/destroy_on_centrifuge = FALSE
+
+/obj/item/hive_frame/on_reagent_change()
+ . = ..()
+ if(reagents?.total_volume)
+ SetName("filled [initial(name)] ([reagents.get_primary_reagent_name()])")
+ else
+ SetName(initial(name))
+ queue_icon_update()
+
+/obj/item/hive_frame/on_update_icon()
+ . = ..()
+ var/mesh_state = "[icon_state]-mesh"
+ if(check_state_in_icon(mesh_state, icon))
+ add_overlay(overlay_image(icon, mesh_state, COLOR_WHITE, RESET_COLOR))
+ if(reagents?.total_volume)
+ var/comb_state = "[icon_state]-comb"
+ if(check_state_in_icon(comb_state, icon))
+ add_overlay(overlay_image(icon, comb_state, reagents.get_color(), RESET_COLOR))
+ compile_overlays()
+
+/obj/item/hive_frame/handle_centrifuge_process(obj/machinery/centrifuge/centrifuge)
+ if(!(. = ..()))
+ return
+ if(reagents.total_volume)
+ reagents.trans_to_holder(centrifuge.loaded_beaker.reagents, reagents.total_volume)
+ for(var/obj/item/thing in contents)
+ thing.dropInto(centrifuge.loc)
+ if(destroy_on_centrifuge)
+ for(var/atom/movable/thing in convert_matter_to_lumps())
+ thing.dropInto(centrifuge.loc)
+
+/obj/item/hive_frame/honey/populate_reagents()
+ . = ..()
+ var/decl/insect_species/bees = GET_DECL(/decl/insect_species/honeybees)
+ bees.fill_hive_frame(src)
+
+/obj/item/hive_frame/forceMove(atom/dest)
+ var/atom/old_loc = loc
+ . = ..()
+ if(. && istype(old_loc))
+ check_hive_loc(old_loc)
+
+/obj/item/hive_frame/Move()
+ var/atom/old_loc = loc
+ . = ..()
+ if(. && istype(old_loc))
+ check_hive_loc(old_loc)
+
+/obj/item/hive_frame/proc/check_hive_loc(atom/check_loc)
+ var/datum/extension/insect_hive/hive = get_extension(check_loc, /datum/extension/insect_hive)
+ if(istype(hive) && loc != hive.holder)
+ hive.frame_removed(src)
+
+// Crafted frame used in apiaries.
+/obj/item/hive_frame/crafted
+ name = "hive frame"
+ desc = "A wooden frame for insect hives that the workers will fill with products like honey."
+ icon = 'mods/content/beekeeping/icons/frame.dmi'
+ material = /decl/material/solid/organic/wood/oak
+
+// Raw version of honeycomb for wild hives.
+/obj/item/hive_frame/comb
+ name = "comb"
+ icon = 'mods/content/beekeeping/icons/comb.dmi'
+ material = /decl/material/solid/organic/wax
+ destroy_on_centrifuge = TRUE
+ material_alteration = MAT_FLAG_ALTERATION_COLOR
+ is_spawnable_type = FALSE
+ w_class = ITEM_SIZE_NORMAL // Larger than crafted frames, because you should use crafted frames in your hive.
+
+/obj/item/hive_frame/comb/Initialize(ml, material_key, decl/insect_species/spawning_hive)
+ . = ..()
+ if(istype(spawning_hive))
+ SetName(spawning_hive.native_frame_name)
+ desc = spawning_hive.native_frame_desc
+ spawning_hive.fill_hive_frame(src)
+
+// Comb subtype for mapping and debugging.
+/obj/item/hive_frame/comb/honey
+ is_spawnable_type = TRUE
+ color = COLOR_GOLD
+
+/obj/item/hive_frame/comb/honey/Initialize(ml, material_key)
+ return ..(ml, material_key, GET_DECL(/decl/insect_species/honeybees))
diff --git a/mods/content/beekeeping/hives/hive_queen.dm b/mods/content/beekeeping/hives/hive_queen.dm
new file mode 100644
index 000000000000..599b487d243e
--- /dev/null
+++ b/mods/content/beekeeping/hives/hive_queen.dm
@@ -0,0 +1,23 @@
+/obj/item/bee_pack
+ name = "bee pack"
+ desc = "Contains a queen bee and some worker bees. Everything you'll need to start a hive!"
+ icon = 'mods/content/beekeeping/icons/bee_pack.dmi'
+ material = /decl/material/solid/organic/plastic
+ var/contains_insects = /decl/insect_species/honeybees
+
+/obj/item/bee_pack/Initialize()
+ . = ..()
+ update_icon()
+
+/obj/item/bee_pack/on_update_icon()
+ . = ..()
+ if(contains_insects)
+ add_overlay("[icon_state]-full")
+ else
+ add_overlay("[icon_state]-empty")
+
+/obj/item/bee_pack/proc/empty()
+ SetName("empty [initial(name)]")
+ desc = "A stasis pack for moving bees. It's empty."
+ contains_insects = null
+ update_icon()
diff --git a/mods/content/beekeeping/hives/hive_structure.dm b/mods/content/beekeeping/hives/hive_structure.dm
new file mode 100644
index 000000000000..855f9e42c7b8
--- /dev/null
+++ b/mods/content/beekeeping/hives/hive_structure.dm
@@ -0,0 +1,86 @@
+/obj/structure/attackby(obj/item/used_item, mob/user)
+ if((. = ..()))
+ return
+ var/datum/extension/insect_hive/hive = get_extension(src, /datum/extension/insect_hive)
+ if(istype(hive) && hive.handle_item_interaction(user, used_item))
+ return TRUE
+
+/obj/structure/attack_hand(mob/user)
+ if(has_extension(src, /datum/extension/insect_hive))
+ var/datum/extension/insect_hive/hive = get_extension(src, /datum/extension/insect_hive)
+ if(hive.try_hand_harvest(user, src))
+ return TRUE
+ return ..()
+
+/obj/structure/attackby(obj/item/used_item, mob/user)
+ if(has_extension(src, /datum/extension/insect_hive))
+ var/datum/extension/insect_hive/hive = get_extension(src, /datum/extension/insect_hive)
+ if(hive.try_tool_harvest(user, used_item))
+ return TRUE
+ return ..()
+
+/obj/structure/examined_by(mob/user, distance, infix, suffix)
+ . = ..()
+ var/datum/extension/insect_hive/hive = get_extension(src, /datum/extension/insect_hive)
+ if(istype(hive))
+ hive.examined(user, (distance <= 1))
+
+/atom/physically_destroyed(var/skip_qdel)
+ var/datum/extension/insect_hive/hive = get_extension(src, /datum/extension/insect_hive)
+ hive?.drop_nest(loc)
+ return ..()
+
+/obj/structure/dismantle_structure(mob/user)
+ var/datum/extension/insect_hive/hive = get_extension(src, /datum/extension/insect_hive)
+ hive?.drop_nest(loc)
+ return ..()
+
+// 'proper' nest structure for building and mapping
+/obj/structure/apiary
+ name = "apiary"
+ desc = "An artificial hive for raising insects, like bees, and harvesting products like honey."
+ icon = 'mods/content/beekeeping/icons/apiary.dmi'
+ icon_state = ICON_STATE_WORLD
+ density = TRUE
+ anchored = TRUE
+ storage = /datum/storage/apiary
+ material_alteration = MAT_FLAG_ALTERATION_ALL
+ material = /decl/material/solid/organic/wood/oak
+ color = /decl/material/solid/organic/wood/oak::color
+ obj_flags = OBJ_FLAG_ANCHORABLE
+ tool_interaction_flags = (TOOL_INTERACTION_ANCHOR | TOOL_INTERACTION_DECONSTRUCT)
+
+/obj/structure/apiary/CanPass(atom/movable/mover, turf/target, height=0, air_group=0)
+ return air_group || height == 0 || !density || (istype(mover) && mover.checkpass(PASS_FLAG_TABLE))
+
+/obj/structure/apiary/attackby(obj/item/used_item, mob/user)
+
+ if(istype(used_item, /obj/item/bee_pack))
+ var/datum/extension/insect_hive/hive = get_extension(src, /datum/extension/insect_hive)
+ if(istype(hive))
+ to_chat(user, SPAN_WARNING("\The [src] already contains \a [hive.holding_species.nest_name]."))
+ return TRUE
+ var/obj/item/bee_pack/pack = used_item
+ if(!pack.contains_insects)
+ to_chat(user, SPAN_WARNING("\The [pack] is empty!"))
+ return TRUE
+ user.visible_message(SPAN_NOTICE("\The [user] transfers the contents of \the [pack] into \the [src]."))
+ set_extension(src, /datum/extension/insect_hive, pack.contains_insects)
+ pack.empty()
+ return TRUE
+
+ . = ..()
+
+/datum/storage/apiary
+ can_hold = list(/obj/item/hive_frame)
+ max_w_class = ITEM_SIZE_NORMAL
+ max_storage_space = ITEM_SIZE_SMALL * 5 // Five regular frames.
+
+/obj/structure/apiary/mapped/Initialize(ml, _mat, _reinf_mat)
+ . = ..()
+ for(var/_ = 1 to 5)
+ new /obj/item/hive_frame/crafted(src)
+
+/obj/structure/apiary/mapped/bees/Initialize(ml, _mat, _reinf_mat)
+ set_extension(src, /datum/extension/insect_hive, /decl/insect_species/honeybees)
+ . = ..()
diff --git a/mods/content/beekeeping/hives/hive_swarm.dm b/mods/content/beekeeping/hives/hive_swarm.dm
new file mode 100644
index 000000000000..084ef4461d84
--- /dev/null
+++ b/mods/content/beekeeping/hives/hive_swarm.dm
@@ -0,0 +1,369 @@
+/obj/effect/insect_swarm
+ anchored = TRUE
+ is_spawnable_type = FALSE
+ icon_state = "0"
+ gender = NEUTER
+ default_pixel_z = 8
+ layer = ABOVE_HUMAN_LAYER
+ pass_flags = PASS_FLAG_TABLE
+ movement_handlers = list(/datum/movement_handler/delay/insect_swarm = list(1 SECOND))
+
+ /// Current movement target for automove (ie. hive, flowers or victim)
+ VAR_PRIVATE/atom/move_target
+ /// Reference to our owning hive.
+ var/datum/extension/insect_hive/owner
+ /// Reference to our insect archetype.
+ var/decl/insect_species/insect_type
+ /// A counter for disturbances to the hive or this swarm, causes them to sting people.
+ var/swarm_agitation = 0
+ /// Percentage value; if it drops to 0, the swarm will be destroyed.
+ var/swarm_intensity = 1
+ /// Cooldown timer for next tick.
+ VAR_PRIVATE/next_work = 0
+ /// Time that smoke will wear off.
+ var/smoked_until = 0
+
+/datum/movement_handler/delay/insect_swarm/DoMove(direction, mob/mover, is_external)
+ ..()
+ step(host, direction)
+ return MOVEMENT_HANDLED
+
+/obj/effect/insect_swarm/debug/Initialize(mapload)
+ . = ..(mapload, _insect_type = /decl/insect_species/honeybees)
+
+/obj/effect/insect_swarm/Initialize(mapload, _insect_type, _hive)
+ . = ..()
+ insect_type = istype(_insect_type, /decl/insect_species) ? _insect_type : GET_DECL(_insect_type)
+ owner = _hive
+ if(!istype(insect_type))
+ PRINT_STACK_TRACE("Insect swarm created with invalid insect type: '[_insect_type]'")
+ return INITIALIZE_HINT_QDEL
+ if(!istype(owner))
+ PRINT_STACK_TRACE("Insect swarm created with invalid hive: '[owner]'")
+ return INITIALIZE_HINT_QDEL
+ update_transform()
+ update_swarm()
+ LAZYDISTINCTADD(owner.swarms, src)
+ START_PROCESSING(SSobj, src)
+
+/obj/effect/insect_swarm/Destroy()
+ if(owner)
+ owner.swarm_destroyed(src)
+ LAZYREMOVE(owner.swarms, src)
+ owner = null
+ stop_automove()
+ STOP_PROCESSING(SSobj, src)
+ return ..()
+
+// Resolves the current swarm amount to a coarser value used for icon state selection.
+/obj/effect/insect_swarm/proc/get_swarm_state()
+ return ceil((swarm_intensity / insect_type.max_swarm_intensity) * insect_type.max_swarm_state)
+
+/obj/effect/insect_swarm/on_update_icon()
+ . = ..()
+ color = insect_type.swarm_color
+ icon = insect_type.swarm_icon
+ icon_state = num2text(get_swarm_state())
+ if(is_smoked())
+ icon_state = "[icon_state]_smoked"
+
+/obj/effect/insect_swarm/update_transform()
+ . = ..()
+ // Some icon variation via transform.
+ if(prob(75))
+ var/matrix/swarm_transform = transform || matrix()
+ swarm_transform.Turn(pick(90, 180, 270))
+ transform = swarm_transform
+
+/obj/effect/insect_swarm/proc/update_swarm()
+ update_icon()
+ if(get_swarm_state() == 1)
+ SetName(insect_type.name_singular)
+ desc = insect_type.insect_desc
+ gender = NEUTER
+ else
+ SetName(insect_type.name_plural)
+ desc = insect_type.swarm_desc
+ gender = PLURAL
+
+/obj/effect/insect_swarm/proc/is_agitated()
+ return QDELETED(owner) || (swarm_agitation > 0 && !is_smoked())
+
+/obj/effect/insect_swarm/proc/find_sting_target()
+ for(var/mob/living/victim in view(7, src))
+ if(victim.simulated && !victim.is_playing_dead())
+ return victim
+
+/obj/effect/insect_swarm/proc/merge(obj/effect/insect_swarm/other_swarm)
+
+ // If we can fit into one swarm, just merge us together.
+ var/total_intensity = swarm_intensity + other_swarm.swarm_intensity
+ if(total_intensity <= insect_type.max_swarm_intensity)
+ swarm_intensity = total_intensity
+ swarm_agitation = max(swarm_agitation, other_swarm.swarm_agitation)
+ update_swarm()
+ qdel(other_swarm)
+ return
+
+ // Otherwise equalize between swarms.
+ swarm_intensity = floor(total_intensity / 2)
+ other_swarm.swarm_intensity = total_intensity - swarm_intensity
+ swarm_agitation = max(swarm_agitation, other_swarm.swarm_agitation)
+ other_swarm.swarm_agitation = max(swarm_agitation, other_swarm.swarm_agitation)
+ update_swarm()
+ other_swarm.update_swarm()
+
+/obj/effect/insect_swarm/Move()
+ . = ..()
+ // Swarms from the same hive in the same loc merge together.
+ if(. && loc && !QDELETED(src))
+ try_consolidate_swarms()
+
+/obj/effect/insect_swarm/Process()
+
+ // Swarms on a loc should try to merge if possible.
+ try_consolidate_swarms()
+ if(QDELETED(src))
+ return
+
+ // Swarms with no hive gradually decay to nothing.
+ if(!owner)
+ adjust_swarm_intensity(-(rand(1,3)))
+ if(QDELETED(src))
+ return
+
+ if(!move_target || !(move_target in view(5, src)))
+ stop_automove()
+
+ if(is_smoked())
+ return
+
+ // Angry swarms move with purpose.
+ if(is_agitated())
+ swarm_agitation = max(0, swarm_agitation-1)
+ if(!ismob(move_target))
+ var/mob/new_move_target = find_sting_target()
+ if(istype(new_move_target))
+ move_target = new_move_target
+ if(move_target)
+ start_automove(move_target)
+ if(insect_type.sting_amount || insect_type.sting_reagent)
+ insect_type.try_sting(src, loc)
+ return
+
+ // Large swarms split if they aren't agitated.
+ if(swarm_can_split() && isturf(loc))
+ var/turf/our_turf = loc
+ for(var/turf/swarm_turf as anything in RANGE_TURFS(our_turf, 1))
+ if(swarm_turf == loc || !swarm_turf.CanPass(src))
+ continue
+ var/new_intensity = round(swarm_intensity/2)
+ var/obj/effect/insect_swarm/new_swarm = new type(swarm_turf, insect_type, owner)
+ new_swarm.swarm_intensity = new_intensity
+ new_swarm.swarm_agitation = swarm_agitation
+ new_swarm.update_swarm()
+ swarm_intensity -= new_intensity
+ update_swarm()
+ break
+
+ // Sting people, if we are so inclined.
+ if(insect_type.sting_amount || insect_type.sting_reagent)
+ insect_type.try_sting(src, loc)
+
+ // Hive behavior is dictated by the hive.
+ if(owner)
+ handle_hive_behavior()
+ return
+
+ // If we're not agitated and don't have a hive, we probably shouldn't be pathing somewhere.
+ stop_automove()
+
+ // Idle swarms with no hive just wander around.
+ if(prob(5))
+ SelfMove(pick(global.alldirs))
+
+/obj/effect/insect_swarm/proc/is_first_swarm_at_hive()
+ var/atom/movable/hive = owner?.holder
+ if(!isturf(hive?.loc) || loc != hive.loc)
+ return FALSE
+ if(length(owner?.swarms) == 1)
+ return TRUE
+ for(var/obj/effect/insect_swarm/swarm in hive.loc)
+ if(swarm == src)
+ return TRUE
+ if(swarm in owner.swarms)
+ break
+ return FALSE
+
+/obj/effect/insect_swarm/failed_automove()
+ ..()
+ stop_automove()
+ return FALSE
+
+/obj/effect/insect_swarm/get_automove_target(datum/automove_metadata/metadata)
+ return move_target
+
+/obj/effect/insect_swarm/stop_automove()
+ move_target = null
+ . = ..()
+
+/obj/effect/insect_swarm/can_do_automated_move(variant_move_delay)
+ return !is_smoked()
+
+/obj/effect/insect_swarm/start_automove(target, movement_type, datum/automove_metadata/metadata)
+ move_target = target
+ . = ..()
+
+/obj/effect/insect_swarm/proc/handle_hive_behavior()
+
+ var/atom/movable/hive = owner?.holder
+ if(!isturf(loc))
+ // We've just been created; shunt us out onto the turf.
+ if(loc == hive)
+ dropInto(hive.loc)
+ else
+ return
+
+ // If we are the first (or only) of our owner swarms in the loc, and we aren't needed, we don't move. Hive needs workers.
+ if(owner?.has_reserves(FRAME_RESERVE_COST))
+ if(is_first_swarm_at_hive())
+ stop_automove()
+ return
+ if(!hive_has_swarm() && loc != hive.loc)
+ start_automove(owner.holder)
+ return
+
+ do_work()
+
+/obj/effect/insect_swarm/proc/do_work()
+ stop_automove()
+ if(prob(25))
+ var/step_dir = pick(global.alldirs)
+ if(get_dist(owner.holder, get_step(loc, step_dir)) <= 2)
+ SelfMove(step_dir)
+
+/obj/effect/insect_swarm/proc/hive_has_swarm()
+ var/atom/movable/hive = owner?.holder
+ if(!isturf(hive?.loc))
+ return FALSE
+ for(var/obj/item/swarm as anything in owner.swarms)
+ if(swarm.loc == hive.loc)
+ return TRUE
+ return FALSE
+
+/obj/effect/insect_swarm/proc/adjust_swarm_intensity(amount)
+ var/old_intensity = swarm_intensity
+ swarm_intensity = clamp(swarm_intensity + amount, 0, insect_type.max_swarm_intensity)
+ if(old_intensity != swarm_intensity)
+ if(swarm_intensity <= 0)
+ qdel(src)
+ else
+ update_swarm()
+
+/obj/effect/insect_swarm/proc/can_grow()
+ // higher swarm intensity is only seen during agitated states when they converge on a victim and merge.
+ return swarm_intensity < insect_type.max_swarm_growth_intensity
+
+/obj/effect/insect_swarm/proc/can_merge()
+ return swarm_intensity < (is_agitated() ? insect_type.max_swarm_intensity : insect_type.max_swarm_growth_intensity)
+
+/obj/effect/insect_swarm/proc/swarm_can_split()
+ return !is_agitated() && swarm_intensity > insect_type.max_swarm_growth_intensity
+
+/obj/effect/insect_swarm/proc/try_consolidate_swarms()
+ if(!can_merge())
+ return
+ for(var/obj/effect/insect_swarm/other_swarm in loc)
+ if(other_swarm == src || !other_swarm.can_merge() || other_swarm.owner != owner || other_swarm.insect_type != insect_type)
+ continue
+ merge(other_swarm)
+ return
+
+/obj/effect/insect_swarm/DoMove(direction, mob/mover, is_external)
+ . = ..()
+ to_world("swarm tried to move: [.]")
+
+/obj/effect/insect_swarm/pollinator
+ var/pollen = 0
+
+/obj/effect/insect_swarm/pollinator/do_work()
+
+ // Have a rest/do some work.
+ if(world.time < next_work)
+ return
+
+ // Unload pollen into hive.
+ if(pollen)
+ if(loc == get_turf(owner.holder))
+ owner.add_reserves(pollen)
+ pollen = 0
+ next_work = world.time + 5 SECONDS
+ stop_automove()
+ else
+ start_automove(owner.holder)
+ return
+
+ // Move to flowers.
+ if(move_target)
+ if(get_turf(move_target) == loc || !(move_target in view(src, 7)))
+ move_target = null
+ stop_automove()
+ else
+ start_automove(move_target)
+ return
+
+ // Harvest from flowers in our loc.
+ for(var/obj/machinery/portable_atmospherics/hydroponics/flower in loc)
+ if(!flower.pollen)
+ continue
+ if(flower.seed && !flower.dead)
+ flower.plant_health += rand(3, 5)
+ flower.check_plant_health()
+ pollen += flower.pollen
+ flower.pollen = 0
+ next_work = world.time + 5 SECONDS
+ stop_automove()
+ return
+
+ // Same logic for flora. TODO unify these when seeds are rewritten to be less bespoke.
+ for(var/obj/structure/flora/plant/flower in loc)
+ if(!flower.pollen)
+ continue
+ pollen += flower.pollen
+ flower.pollen = 0
+ next_work = world.time + 5 SECONDS
+ stop_automove()
+ return
+
+ // Find a flower.
+ var/list/all_potential_targets = list()
+ for(var/thing in view(src, 7))
+ if(istype(thing, /obj/machinery/portable_atmospherics/hydroponics))
+ var/obj/machinery/portable_atmospherics/hydroponics/flower = thing
+ if(flower.pollen)
+ all_potential_targets += flower
+ else if(istype(thing, /obj/structure/flora/plant))
+ var/obj/structure/flora/plant/flower = thing
+ if(flower.pollen)
+ all_potential_targets += flower
+
+ var/closest_dist
+ var/atom/closest_target
+ for(var/atom/thing as anything in shuffle(all_potential_targets))
+ var/next_dist = get_dist(src, thing)
+ if(isnull(closest_target) || next_dist < closest_dist)
+ closest_target = thing
+ closest_dist = next_dist
+
+ if(closest_target)
+ start_automove(closest_target)
+ else
+ start_automove(owner.holder)
+
+/obj/effect/insect_swarm/proc/was_smoked(smoke_time = 10 SECONDS)
+ smoked_until = max(smoked_until, world.time + smoke_time)
+ swarm_agitation = round(swarm_agitation * 0.75)
+ addtimer(CALLBACK(src, TYPE_PROC_REF(/atom, update_icon), TRUE), smoke_time, (TIMER_UNIQUE|TIMER_OVERRIDE))
+
+/obj/effect/insect_swarm/proc/is_smoked()
+ return world.time < smoked_until
\ No newline at end of file
diff --git a/mods/content/beekeeping/hives/insect_species/_insects.dm b/mods/content/beekeeping/hives/insect_species/_insects.dm
new file mode 100644
index 000000000000..d380f6e2e80d
--- /dev/null
+++ b/mods/content/beekeeping/hives/insect_species/_insects.dm
@@ -0,0 +1,189 @@
+/decl/insect_species
+ abstract_type = /decl/insect_species
+
+ // Descriptive strings for individual insects and swarms.
+ var/name_singular
+ var/name_plural
+ var/insect_desc
+
+ // Vars for nest description and products.
+ var/nest_name
+ var/list/produce_reagents
+ var/decl/material/produce_material
+ var/produce_material_amount = 1
+ var/native_frame_name = "comb"
+ var/native_frame_desc = "A wax comb from an insect nest."
+ var/native_frame_type = /obj/item/hive_frame/comb
+
+ // Visual appearance and behavior of swarms.
+ var/swarm_desc
+ var/swarm_color = COLOR_BROWN
+ var/swarm_icon = 'mods/content/beekeeping/icons/swarm.dmi'
+ var/swarm_type = /obj/effect/insect_swarm
+ var/max_swarm_growth_intensity = 50
+ var/max_swarm_intensity = 100
+ var/max_swarm_state = 6
+
+ // Venom delivered by swarms whens stinging a victim.
+ var/sting_reagent
+ var/sting_amount
+
+/decl/insect_species/Initialize()
+ if(produce_material)
+ produce_material = GET_DECL(produce_material)
+ return ..()
+
+/decl/insect_species/validate()
+ . = ..()
+
+ if(!name_singular)
+ . += "no singular name set"
+ if(!name_plural)
+ . += "no plural name set"
+ if(!nest_name)
+ . += "no nest name set"
+ if(!insect_desc)
+ . += "no insect desc set"
+
+ if(swarm_type)
+ if(!ispath(swarm_type, /obj/effect/insect_swarm))
+ . += "invalid swarm path (must be /obj/effect/insect_swarm or subtype): '[swarm_type]'"
+ if(!swarm_desc)
+ . += "no swarm description set"
+
+ if(produce_reagents)
+ if(!length(produce_reagents) || !islist(produce_reagents))
+ . += "empty or non-list produce_reagents"
+ else
+ var/total = 0
+ for(var/reagent in produce_reagents)
+ if(!ispath(reagent, /decl/material))
+ . += "non-material produce_reagents entry '[reagent]'"
+ continue
+ var/amt = produce_reagents[reagent]
+ if(!isnum(amt) || amt <= 0)
+ . += "non-numerical or 0 produce_reagents value: '[reagent]', '[amt]'"
+ total += amt
+ if(total != 1)
+ . += "produce_reagents weighting does not sum to 1: '[total]'"
+
+ if(produce_material)
+ if(!isnum(produce_material_amount) || produce_material_amount <= 0)
+ . += "non-numeric or zero produce amount: '[produce_material_amount]'"
+ if(!istype(produce_material, /decl/material))
+ . += "non-material product material type: '[produce_material]'"
+
+ if(!swarm_icon)
+ . += "null swarm icon"
+ else
+ for(var/i = 0 to max_swarm_state)
+ var/check_state = num2text(i)
+ if(!check_state_in_icon(check_state, swarm_icon))
+ . += "missing active icon_state '[check_state]'"
+ check_state = "[check_state]_smoked"
+ if(!check_state_in_icon(check_state, swarm_icon))
+ . += "missing smoked icon_state '[check_state]'"
+
+/decl/insect_species/proc/fill_hive_frame(obj/item/frame)
+
+ if(!istype(frame) || QDELETED(frame))
+ return FALSE
+
+ var/frame_space = REAGENTS_FREE_SPACE(frame.reagents)
+ if(frame_space <= 0)
+ return FALSE
+
+ if(frame.reagents?.maximum_volume && length(produce_reagents))
+ var/reagent_split = max(1, floor(min(REAGENTS_FREE_SPACE(frame.reagents), 20) / length(produce_reagents)))
+ for(var/reagent in produce_reagents)
+ frame.reagents.add_reagent(reagent, max(1, (reagent_split * produce_reagents[reagent])), defer_update = TRUE)
+ frame.reagents.handle_update()
+ if(produce_material && (frame.material != produce_material) && !(locate(/obj/item/stack/material/lump) in frame))
+ for(var/atom/movable/thing in produce_material.create_object(frame, produce_material_amount, /obj/item/stack/material/lump))
+ thing.forceMove(frame)
+ return TRUE
+
+/decl/insect_species/proc/try_sting(obj/effect/insect_swarm/swarm, atom/loc)
+ if(!istype(swarm) || QDELETED(swarm) || !istype(loc))
+ return FALSE
+ // If we're agitated, always sting. Otherwise, % chance equal to a quarter of our overall swarm intensity.
+ if(!swarm.is_agitated() && !prob(max(1, round(swarm.swarm_intensity/4))))
+ return FALSE
+ var/base_sting_chance = (sting_amount * clamp(round(swarm.swarm_intensity/10), 1, 10))
+ var/sting_mult = swarm.is_agitated() ? max(base_sting_chance, 65) : base_sting_chance
+ for(var/mob/living/victim in loc)
+ if(!victim.simulated || victim.stat || victim.current_posture?.prone)
+ continue
+ var/datum/reagents/injected_reagents = victim.get_injected_reagents()
+ var/obj/item/organ/external/affecting = victim.get_organ(pick(global.all_limb_tags))
+ if(!affecting || BP_IS_PROSTHETIC(affecting) || BP_IS_CRYSTAL(affecting))
+ continue
+ if(injected_reagents && victim.can_inject(victim, affecting.organ_tag))
+ to_chat(victim, SPAN_DANGER("\A [swarm] stings you [sting_mult <= sting_amount * 2 ? "" : "multiple times"] on your [affecting.name]!"))
+ injected_reagents.add_reagent(sting_reagent, sting_mult)
+ affecting.add_pain(sting_mult)
+ . = TRUE
+
+/decl/insect_species/proc/can_spawn_in_flora(var/obj/structure/flora)
+
+ // Territory range.
+ for(var/obj/structure/flora/plant in view(flora, 7))
+ if(has_extension(plant, /datum/extension/insect_hive))
+ return FALSE
+
+ // Food source.
+ for(var/obj/machinery/portable_atmospherics/hydroponics/flower in view(flora, 7))
+ if(flower.seed?.produces_pollen)
+ return TRUE
+
+ for(var/obj/structure/flora/plant/flower in view(flora, 7))
+ if(flower.plant?.produces_pollen)
+ return TRUE
+
+
+ return FALSE
+
+/decl/insect_species/proc/process_hive(datum/extension/insect_hive/hive_metadata)
+
+ // Sanity check.
+ var/atom/movable/hive = hive_metadata.holder
+ if(!istype(hive) || !swarm_type || !istype(hive_metadata))
+ return
+
+ // Make sure we always have at least one swarm.
+ if(!length(hive_metadata.swarms))
+ new swarm_type(hive, src, hive_metadata)
+
+ // Reduce swarms if we have too many.
+ var/swarm_intensity = hive_metadata.get_total_swarm_intensity()
+ if(swarm_intensity > max_swarm_intensity && length(hive_metadata.swarms))
+ var/obj/effect/insect_swarm/swarm = hive_metadata.swarms[1]
+ swarm.adjust_swarm_intensity(-(swarm_intensity-max_swarm_intensity))
+ return
+
+ // Try to grow an existing swarm until we're at our max.
+ if(hive_metadata.has_reserves(SWARM_GROWTH_COST) && length(hive_metadata.swarms))
+ for(var/obj/effect/insect_swarm/swarm as anything in hive_metadata.swarms)
+ if(swarm.can_grow() && hive_metadata.consume_reserves(SWARM_GROWTH_COST))
+ swarm.adjust_swarm_intensity(min(max_swarm_growth_intensity-swarm_intensity, rand(3,5)))
+ return
+
+ // If we have sufficient filled combs, create a new swarm. Otherwise, expand a swarm.
+ if(hive.loc && hive_metadata.has_reserves(SWARM_GROWTH_COST))
+
+ var/obj/effect/insect_swarm/swarm
+ for(var/obj/effect/insect_swarm/check_swarm as anything in hive_metadata.swarms)
+ if(check_swarm.loc == hive.loc && check_swarm.can_grow())
+ swarm = check_swarm
+ break
+
+ if(!swarm)
+ var/comb_count = 0
+ for(var/obj/item/hive_frame/frame in hive)
+ if(frame.reagents && frame.reagents.total_volume >= frame.reagents.maximum_volume)
+ comb_count++
+ if(length(hive_metadata.swarms) < comb_count)
+ swarm = new swarm_type(hive.loc, src, hive_metadata)
+
+ if(!QDELETED(swarm) && istype(swarm) && hive_metadata.consume_reserves(SWARM_GROWTH_COST))
+ swarm.adjust_swarm_intensity(min((max_swarm_growth_intensity-swarm_intensity), rand(3,5)))
diff --git a/mods/content/beekeeping/hives/insect_species/insects_pollinators.dm b/mods/content/beekeeping/hives/insect_species/insects_pollinators.dm
new file mode 100644
index 000000000000..83aeb86f8462
--- /dev/null
+++ b/mods/content/beekeeping/hives/insect_species/insects_pollinators.dm
@@ -0,0 +1,28 @@
+/decl/insect_species/honeybees
+ name_singular = "honeybee"
+ name_plural = "honeybees"
+ nest_name = "beehive"
+ native_frame_name = "honeycomb"
+ native_frame_desc = "A lattice of hexagonal wax cells usually filled with honey."
+ native_frame_type = /obj/item/hive_frame/comb
+ swarm_desc = "A swarm of buzzing honeybees."
+ insect_desc = "A single buzzing honeybee."
+ swarm_color = COLOR_GOLD
+ swarm_type = /obj/effect/insect_swarm/pollinator
+ sting_reagent = /decl/material/liquid/bee_venom
+ sting_amount = 1
+ produce_reagents = list(/decl/material/liquid/nutriment/honey = 1)
+ produce_material = /decl/material/solid/organic/wax
+
+/*
+/decl/insect_species/wasps
+ name_singular = "wasp"
+ name_plural = "wasps"
+ nest_name = "wasp hive"
+ swarm_desc = "A swarm of humming wasps."
+ insect_desc = "A solitary wasp."
+ sting_reagent = /decl/material/liquid/cyanide
+ sting_amount = 5
+ swarm_color = COLOR_BRONZE
+ swarm_type = /obj/effect/insect_swarm/pollinator // tarantula hunter...
+*/
\ No newline at end of file
diff --git a/mods/content/beekeeping/icons/apiary.dmi b/mods/content/beekeeping/icons/apiary.dmi
new file mode 100644
index 000000000000..7ad80a4408f3
Binary files /dev/null and b/mods/content/beekeeping/icons/apiary.dmi differ
diff --git a/mods/content/beekeeping/icons/apiary_bees_etc.dmi b/mods/content/beekeeping/icons/apiary_bees_etc.dmi
deleted file mode 100644
index 130120bb08fc..000000000000
Binary files a/mods/content/beekeeping/icons/apiary_bees_etc.dmi and /dev/null differ
diff --git a/mods/content/beekeeping/icons/bee_pack.dmi b/mods/content/beekeeping/icons/bee_pack.dmi
new file mode 100644
index 000000000000..fe68dad8b4b7
Binary files /dev/null and b/mods/content/beekeeping/icons/bee_pack.dmi differ
diff --git a/mods/content/beekeeping/icons/beehive.dmi b/mods/content/beekeeping/icons/beehive.dmi
new file mode 100644
index 000000000000..9b58ec19c1b7
Binary files /dev/null and b/mods/content/beekeeping/icons/beehive.dmi differ
diff --git a/mods/content/beekeeping/icons/beekeeping.dmi b/mods/content/beekeeping/icons/beekeeping.dmi
deleted file mode 100644
index 569122d4a58a..000000000000
Binary files a/mods/content/beekeeping/icons/beekeeping.dmi and /dev/null differ
diff --git a/mods/content/beekeeping/icons/comb.dmi b/mods/content/beekeeping/icons/comb.dmi
new file mode 100644
index 000000000000..edb611c93ae4
Binary files /dev/null and b/mods/content/beekeeping/icons/comb.dmi differ
diff --git a/mods/content/beekeeping/icons/smoker.dmi b/mods/content/beekeeping/icons/smoker.dmi
index 5109ca468e5e..e6b14a8576cd 100644
Binary files a/mods/content/beekeeping/icons/smoker.dmi and b/mods/content/beekeeping/icons/smoker.dmi differ
diff --git a/mods/content/beekeeping/icons/swarm.dmi b/mods/content/beekeeping/icons/swarm.dmi
new file mode 100644
index 000000000000..e55d936c1f15
Binary files /dev/null and b/mods/content/beekeeping/icons/swarm.dmi differ
diff --git a/mods/content/beekeeping/items.dm b/mods/content/beekeeping/items.dm
index 2c15fa75cd04..f520e5085eab 100644
--- a/mods/content/beekeeping/items.dm
+++ b/mods/content/beekeeping/items.dm
@@ -1,47 +1,34 @@
-/obj/item/beehive_assembly
- name = "beehive assembly"
- desc = "Contains everything you need to build a beehive."
- icon = 'mods/content/beekeeping/icons/apiary_bees_etc.dmi'
- icon_state = "apiary"
- material = /decl/material/solid/organic/wood/oak
-
-/obj/item/beehive_assembly/attack_self(var/mob/user)
- to_chat(user, "You start assembling \the [src]...")
- if(do_after(user, 30, src))
- user.visible_message("\The [user] constructs a beehive.", "You construct a beehive.")
- new /obj/machinery/beehive(get_turf(user))
- qdel(src)
-
-/obj/item/bee_smoker
- name = "bee smoker"
- desc = "A device used to calm down bees before harvesting honey."
+/obj/item/smoker
+ name = "smoker"
+ desc = "A device used to calm insects down before harvesting from a hive."
icon = 'mods/content/beekeeping/icons/smoker.dmi'
icon_state = ICON_STATE_WORLD
w_class = ITEM_SIZE_SMALL
material = /decl/material/solid/metal/steel
-/obj/item/bee_pack
- name = "bee pack"
- desc = "Contains a queen bee and some worker bees. Everything you'll need to start a hive!"
- icon = 'mods/content/beekeeping/icons/beekeeping.dmi'
- icon_state = "beepack"
- material = /decl/material/solid/organic/plastic
- var/full = 1
+// TODO: consume reagents or charges? Unnecessary complexity?
+/obj/item/smoker/resolve_attackby(atom/A, mob/user, click_params)
+
+ if(!user.check_dexterity(get_required_attack_dexterity(user, A)))
+ return TRUE
+
+ var/smoked = FALSE
+ if(has_extension(A, /datum/extension/insect_hive))
+ var/datum/extension/insect_hive/hive = get_extension(A, /datum/extension/insect_hive)
+ if(hive.smoked_by(user, A))
+ smoked = TRUE
-/obj/item/bee_pack/Initialize()
- . = ..()
- overlays += "beepack-full"
+ if(!smoked && isturf(A))
+ for(var/obj/effect/insect_swarm/swarm in A)
+ swarm.was_smoked()
+ smoked = TRUE
-/obj/item/bee_pack/proc/empty()
- full = 0
- name = "empty bee pack"
- desc = "A stasis pack for moving bees. It's empty."
- overlays.Cut()
- overlays += "beepack-empty"
+ if(smoked)
+ var/turf/smoked_turf = get_turf(A)
+ if(smoked_turf)
+ playsound(smoked_turf, 'sound/effects/refill.ogg', 25, 1)
+ user.visible_message(SPAN_NOTICE("\The [user] douses \the [A] in smoke from \the [src]."))
+ new /obj/effect/effect/smoke(smoked_turf, 2 SECONDS)
+ return TRUE
-/obj/item/bee_pack/proc/fill()
- full = initial(full)
- SetName(initial(name))
- desc = initial(desc)
- overlays.Cut()
- overlays += "beepack-full"
+ return ..()
diff --git a/mods/content/beekeeping/materials.dm b/mods/content/beekeeping/materials.dm
new file mode 100644
index 000000000000..496d5f9b394c
--- /dev/null
+++ b/mods/content/beekeeping/materials.dm
@@ -0,0 +1,20 @@
+/decl/material/liquid/bee_venom
+ name = "bee venom"
+ uid = "liquid_venom_bee"
+ lore_text = "An irritant used by bees to drive off predators."
+ taste_description = "noxious bitterness"
+ color = "#d7d891"
+ heating_products = list(
+ /decl/material/liquid/denatured_toxin = 1
+ )
+ heating_point = 100 CELSIUS
+ heating_message = "becomes clear."
+ taste_mult = 1.2
+ metabolism = REM * 0.25
+ exoplanet_rarity_plant = MAT_RARITY_UNCOMMON
+ exoplanet_rarity_gas = MAT_RARITY_EXOTIC
+
+/decl/material/liquid/bee_venom/affect_blood(mob/living/M, removed, datum/reagents/holder)
+ . = ..()
+ if(istype(M))
+ M.adjustHalLoss(max(1, ceil(removed * 10)))
diff --git a/mods/content/beekeeping/recipes.dm b/mods/content/beekeeping/recipes.dm
index 8494617d3477..3d8515113826 100644
--- a/mods/content/beekeeping/recipes.dm
+++ b/mods/content/beekeeping/recipes.dm
@@ -1,6 +1,5 @@
-/decl/stack_recipe/planks/beehive_assembly
- result_type = /obj/item/beehive_assembly
- category = "furniture"
+/decl/stack_recipe/planks/furniture/apiary
+ result_type = /obj/structure/apiary
/decl/stack_recipe/planks/beehive_frame
- result_type = /obj/item/hive_frame/crafted
+ result_type = /obj/item/hive_frame/crafted
diff --git a/mods/content/beekeeping/trading.dm b/mods/content/beekeeping/trading.dm
index 45009265f1bf..c63d01f412c2 100644
--- a/mods/content/beekeeping/trading.dm
+++ b/mods/content/beekeeping/trading.dm
@@ -1,15 +1,15 @@
/datum/trader/trading_beacon/manufacturing/New()
- LAZYSET(possible_trading_items, /obj/item/bee_pack, TRADER_THIS_TYPE)
- LAZYSET(possible_trading_items, /obj/item/bee_smoker, TRADER_THIS_TYPE)
- LAZYSET(possible_trading_items, /obj/item/beehive_assembly, TRADER_THIS_TYPE)
- LAZYSET(possible_trading_items, /obj/item/hive_frame/crafted, TRADER_THIS_TYPE)
+ LAZYSET(possible_trading_items, /obj/item/bee_pack, TRADER_THIS_TYPE)
+ LAZYSET(possible_trading_items, /obj/item/smoker, TRADER_THIS_TYPE)
+ LAZYSET(possible_trading_items, /obj/item/hive_frame/crafted, TRADER_THIS_TYPE)
+ LAZYSET(possible_trading_items, /obj/item/stack/material/plank/mapped/wood/ten, TRADER_THIS_TYPE)
..()
/decl/hierarchy/supply_pack/hydroponics/bee_keeper
name = "Equipment - Beekeeping"
contains = list(
- /obj/item/beehive_assembly,
- /obj/item/bee_smoker,
+ /obj/item/stack/material/plank/mapped/wood/ten,
+ /obj/item/smoker,
/obj/item/hive_frame/crafted = 5,
/obj/item/bee_pack
)