From 7dbdd8c392a5d39a78330456d7df58d3c0261c96 Mon Sep 17 00:00:00 2001 From: MistakeNot4892 Date: Mon, 22 Sep 2025 10:51:08 +1000 Subject: [PATCH 1/7] Rewriting beehives and beehavior. --- maps/ministation/ministation-1.dmm | 2 +- mods/content/beekeeping/_beekeeping.dm | 3 +- mods/content/beekeeping/_beekeeping.dme | 11 +- mods/content/beekeeping/closets.dm | 4 +- mods/content/beekeeping/hives/_hive.dm | 168 --------- .../beekeeping/hives/hive_extension.dm | 195 +++++++++++ mods/content/beekeeping/hives/hive_flora.dm | 40 +++ .../beekeeping/{ => hives}/hive_frame.dm | 47 ++- mods/content/beekeeping/hives/hive_queen.dm | 23 ++ .../beekeeping/hives/hive_structure.dm | 83 +++++ mods/content/beekeeping/hives/hive_swarm.dm | 322 ++++++++++++++++++ .../hives/insect_species/_insects.dm | 174 ++++++++++ .../insect_species/insects_pollinators.dm | 26 ++ mods/content/beekeeping/icons/apiary.dmi | Bin 0 -> 345 bytes .../beekeeping/icons/apiary_bees_etc.dmi | Bin 3301 -> 0 bytes mods/content/beekeeping/icons/bee_pack.dmi | Bin 0 -> 366 bytes mods/content/beekeeping/icons/beehive.dmi | Bin 0 -> 319 bytes mods/content/beekeeping/icons/beekeeping.dmi | Bin 5053 -> 0 bytes mods/content/beekeeping/icons/comb.dmi | Bin 0 -> 1077 bytes mods/content/beekeeping/icons/smoker.dmi | Bin 1019 -> 480 bytes mods/content/beekeeping/icons/swarm.dmi | Bin 0 -> 1016 bytes mods/content/beekeeping/items.dm | 40 --- mods/content/beekeeping/materials.dm | 20 ++ mods/content/beekeeping/recipes.dm | 7 +- mods/content/beekeeping/trading.dm | 10 +- 25 files changed, 940 insertions(+), 235 deletions(-) delete mode 100644 mods/content/beekeeping/hives/_hive.dm create mode 100644 mods/content/beekeeping/hives/hive_extension.dm create mode 100644 mods/content/beekeeping/hives/hive_flora.dm rename mods/content/beekeeping/{ => hives}/hive_frame.dm (51%) create mode 100644 mods/content/beekeeping/hives/hive_queen.dm create mode 100644 mods/content/beekeeping/hives/hive_structure.dm create mode 100644 mods/content/beekeeping/hives/hive_swarm.dm create mode 100644 mods/content/beekeeping/hives/insect_species/_insects.dm create mode 100644 mods/content/beekeeping/hives/insect_species/insects_pollinators.dm create mode 100644 mods/content/beekeeping/icons/apiary.dmi delete mode 100644 mods/content/beekeeping/icons/apiary_bees_etc.dmi create mode 100644 mods/content/beekeeping/icons/bee_pack.dmi create mode 100644 mods/content/beekeeping/icons/beehive.dmi delete mode 100644 mods/content/beekeeping/icons/beekeeping.dmi create mode 100644 mods/content/beekeeping/icons/comb.dmi create mode 100644 mods/content/beekeeping/icons/swarm.dmi create mode 100644 mods/content/beekeeping/materials.dm 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..471217ac52d6 100644 --- a/mods/content/beekeeping/_beekeeping.dm +++ b/mods/content/beekeeping/_beekeeping.dm @@ -1,7 +1,6 @@ /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..9caf9bb459c8 100644 --- a/mods/content/beekeeping/closets.dm +++ b/mods/content/beekeeping/closets.dm @@ -1,10 +1,10 @@ /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/stack/material/plank/mapped/wood/ten new /obj/item/bee_smoker(src) new /obj/item/hive_frame/crafted(src) new /obj/item/hive_frame/crafted(src) 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..d02839f4d40b --- /dev/null +++ b/mods/content/beekeeping/hives/hive_extension.dm @@ -0,0 +1,195 @@ +/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 ticks remaning since we were last smoked. + var/smoked_out = 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(smoked_out > 0) + smoked_out-- + 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) + return + +/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(!smoked_out) + for(var/obj/effect/insect_swarm/swarm in swarms) + swarm.swarm_agitation = min(100, swarm.swarm_agitation + 5) + +/datum/extension/insect_hive/proc/try_hand_harvest(mob/user) + 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(20)) + add_material(1) + + // Damaged hives cannot produce combs or honey. + if(current_health < 100) + if(consume_material(5)) + adjust_health(rand(3,5)) + return TRUE + + if(!has_reserves(20)) + 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(consume_material(5) && 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(20)) + // 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 diff --git a/mods/content/beekeeping/hives/hive_flora.dm b/mods/content/beekeeping/hives/hive_flora.dm new file mode 100644 index 000000000000..e6091c3610bd --- /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/hive_frame.dm b/mods/content/beekeeping/hives/hive_frame.dm similarity index 51% rename from mods/content/beekeeping/hive_frame.dm rename to mods/content/beekeeping/hives/hive_frame.dm index e126ec1a7fa2..50df27ad12b3 100644 --- a/mods/content/beekeeping/hive_frame.dm +++ b/mods/content/beekeeping/hives/hive_frame.dm @@ -1,7 +1,7 @@ /obj/item/hive_frame - abstract_type = /obj/item/hive_frame - icon_state = ICON_STATE_WORLD - w_class = ITEM_SIZE_SMALL + 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 @@ -36,20 +36,45 @@ 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/Move() + var/datum/extension/insect_hive/hive = get_extension(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 - 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() +// 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/crafted/filled/populate_reagents() +/obj/item/hive_frame/comb/Initialize(ml, material_key, decl/insect_species/spawning_hive) . = ..() - reagents.add_reagent(/decl/material/liquid/nutriment/honey, reagents?.maximum_volume) + 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..f7586ad6eec4 --- /dev/null +++ b/mods/content/beekeeping/hives/hive_structure.dm @@ -0,0 +1,83 @@ +/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)) + 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)) + +/obj/structure/dismantle_structure(mob/user) + if(isatom(loc)) + var/datum/extension/insect_hive/hive = get_extension(src, /datum/extension/insect_hive) + if(istype(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..2e733a96b138 --- /dev/null +++ b/mods/content/beekeeping/hives/hive_swarm.dm @@ -0,0 +1,322 @@ +/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) + + /// 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 + /// if more states are added to swarm.dmi, increase this + var/const/MAX_SWARM_STATE = 6 + /// Cooldown timer for next tick. + VAR_PRIVATE/next_work = 0 + +/datum/movement_handler/delay/insect_swarm + delay = 1 SECOND + +/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 + color = insect_type.swarm_color + icon = insect_type.swarm_icon + 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 ..() + +/obj/effect/insect_swarm/proc/update_swarm() + icon_state = num2text(ceil((swarm_intensity / insect_type.max_swarm_intensity) * MAX_SWARM_STATE)) + if(icon_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 + + // Some icon variation via transform. + if(prob(75)) + var/matrix/swarm_transform = matrix() + swarm_transform.Turn(pick(90, 180, 270)) + +/obj/effect/insect_swarm/proc/is_agitated() + return QDELETED(owner) || swarm_agitation > 0 + +/obj/effect/insect_swarm/proc/find_sting_target() + for(var/mob/living/victim in view(7, src)) + if(!victim.simulated || victim.stat || victim.current_posture?.prone) + continue + if(victim.isSynthetic()) + continue + 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() + + // Angry swarms move with purpose. + if(is_agitated()) + swarm_agitation = max(0, swarm_agitation-1) + if(!move_target) + move_target = find_sting_target() + if(move_target) + start_automove(move_target) + 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/get_automove_target(datum/automove_metadata/metadata) + return move_target + +/obj/effect/insect_swarm/stop_automove() + SHOULD_CALL_PARENT(FALSE) + move_target = null + //. = ..() // TODO work out why they're not automoving + walk(src, 0) + +/obj/effect/insect_swarm/start_automove(target, movement_type, datum/automove_metadata/metadata) + SHOULD_CALL_PARENT(FALSE) + move_target = target + //. = ..() // TODO work out why they're not automoving + if(move_target) + walk_to(src, move_target, 0, 7) + else + walk(src, 0) + +/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?.raw_reserves >= 15) + 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/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 + + // Find a flower. + var/closest_dist + var/atom/closest_target + for(var/obj/machinery/portable_atmospherics/hydroponics/flower in view(src, 7)) + if(!flower.pollen) + continue + var/next_dist = get_dist(src, closest_target) + if(isnull(closest_dist) || next_dist < closest_dist) + closest_target = flower + closest_dist = next_dist + + if(closest_target) + start_automove(closest_target) + else + start_automove(owner.holder) 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..013e720ae593 --- /dev/null +++ b/mods/content/beekeeping/hives/insect_species/_insects.dm @@ -0,0 +1,174 @@ +/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 + + // 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]'" + +/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/sting_mult = sting_amount * clamp(round(swarm.swarm_intensity/10), 1, 10) + 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)) + injected_reagents.add_reagent(sting_reagent, sting_mult) + affecting.add_pain(sting_mult) + if(sting_mult <= sting_amount * 2) + to_chat(victim, SPAN_DANGER("You are stung on your [affecting.name] by \a [swarm]!")) + else + to_chat(victim, SPAN_DANGER("You are stung multiple times on your [affecting.name] by \a [swarm]!")) + . = 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 + + 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(5) && 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(5)) + 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(5)) + + 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(5)) + 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..0f9b924e62fe --- /dev/null +++ b/mods/content/beekeeping/hives/insect_species/insects_pollinators.dm @@ -0,0 +1,26 @@ +/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... diff --git a/mods/content/beekeeping/icons/apiary.dmi b/mods/content/beekeeping/icons/apiary.dmi new file mode 100644 index 0000000000000000000000000000000000000000..7ad80a4408f3f53bf80d2991b201ac53255a1200 GIT binary patch literal 345 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 diff --git a/mods/content/beekeeping/icons/bee_pack.dmi b/mods/content/beekeeping/icons/bee_pack.dmi new file mode 100644 index 0000000000000000000000000000000000000000..fe68dad8b4b7b784528b38dea0b402b09a99fae4 GIT binary patch literal 366 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0J3?w7mbKU|exd5LK*8>L*Xr`G?I$Q%3*s){B zB`@dyUyI#M6gxUPfKqQ#KSu*47)yfuf*Bm1-ADs+Dyu>wN?cNllZ!G7N;32F7#J$% z1cwzAm45#cT=4Ph6D@CDt#fD22X6>9xM=*~kW=W z2U{2ZKQ;Y0!?BF;slHbwFC2)R&3N5mGIM5w%7cT8s~euO-DuV0;O1~`mufsSDCboFyt I=akR{0DX&+ApigX literal 0 HcmV?d00001 diff --git a/mods/content/beekeeping/icons/beehive.dmi b/mods/content/beekeeping/icons/beehive.dmi new file mode 100644 index 0000000000000000000000000000000000000000..9b58ec19c1b74072539d0b269117bffdb25863f9 GIT binary patch literal 319 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 LF0}L^{MG*ef&6Bj diff --git a/mods/content/beekeeping/icons/comb.dmi b/mods/content/beekeeping/icons/comb.dmi new file mode 100644 index 0000000000000000000000000000000000000000..edb611c93ae4cfa8c7a3c51219b16f0b8858a17e GIT binary patch literal 1077 zcmV-51j_q~P)V=-0C=2r%DoDJAQ(pB+3zY|YA*U)UBU>5x`#?jp%PT;_8XcS+RlL|Mcegq zgcu}U+6U`Sa5Q;A?6xWzaMTqsqwQwmJ0VU{gCn_}-&I0PSs%br5?lRr=(oQrTih}K z(G=Y%AO=Yf@ZToKT(=aW000AcNkl^8@84&$*)NSoL?)Fc`E- zrP7;3BJqpI-tHCs~&JUa!|4jYhVnX}>lajn~O! zvKoy>yKeS&Re)g_uq+Fjropl-Twh-!6bd00i(xPrAP@*391f$`>*46=2=#jXX^9`_ zvzn%vmSwRwH#Z1{LVw0$vD?95uoege`r&Z6-|O|5!^1;!9{!q}H%XEN01!nHj^lt3 zf^<5KcDoJJG_kd{gLYPb@!?xS)H>PR6df+pJkZvZE zVRv_TyKece=Kw`f#)e@)k|d;3DG)+Xuh(H&76>5_MG=$NCn^?;&o&DvigGr?7YHHU z2R=&(>58IgJ;D!vZ{N8lK}nKe7zXzD_u)7WR8c6N5m%*;tiNr#7r zz`(%3z`!&NX{7)F00DGTPE!Ct=GbNc005JER9JLGWpiV4X>fFDZ*Bkpc$`yKaB_9` z^iy#0_2eo`Eh^5;&r`5fFwryM;w;ZhDainGjE%TBGg33tGk;1ToZ^zil2jm5sXV_Z zCq;>iGbOXA7|1u|;!G<_%uR)`;i@u9a})FOGgB0j@>42xi*xcz;*(NyN)Y-?h}KsW zpOKiCLXOTHygHQ?T>V_YK>z@Og;3&NQFc85007iUL_t(&f$f+}4#F@DMC%3n7^&d^ zh=Xv0cEJ(2|9=%oS2)N@aHc}4n73-u=E?oZqf8SaJEAE;>*adK3J_IHaK1gTTgXpP zFsovM3s^zgDnJZrt6*n=galFqA0NUTDkv90u2-B;LF~0Uf*4Z%h1gRggb+f0hHT|L2S)=0bii!ZRFPJD0$iyd|1gL}4`WG^R#5KHoZm^|R-g}&gO2i&~yg}^(E z2uvOr64*R|Z4nVd2q8ygECN`;3O3NO2w(+E|C%<80OZ)b0Dm$Nm$EhwRsaA107*qo IM6N<$f`7cmv;Y7A literal 1019 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H1|$#LC7uRSv#LTON?cNllZ!G7N;32F7#J$% z%ssuA>yUv&>qE<(UC!3we_qaV4f1OHCg$a}h;zwLg-)^e_ZBwqSg~U+^ZVe(cfMw| z$cXH}J;m0QW6N&a^ws7^9UqiDITKl5nrglCMUrB`+9^#dEsX6_Ib__KcD2>_F?7aFV0|T>+ zr;B4q#hkZybo-PXW!gU0=Z7uYafpMZ{YH=sL!*vo#$~pHn#UHTP1HEo@G2odTTrG! z`a$*4W$LF>#AhwC*SPn5dXGfLB(IMp9|c!UwmHwcYhS4?LrFKo5y1uvCINSbM;s0^ zj2+4hg$Efc-gkdm|7ZWc#D%)|o#meF)puJw@$2u}**ZT8t^+YTOJGB>lN>74Q2TL|7@B5{d1gxT;Ljp48Hp;*A7^Uznz{im04r@ z_w3^udjHSuJk+Ihi$jb3!Cw7KCVQ)VW-Tj<=X_o%SNh@NewH2gHeP-(r?^%1qnW^; z&<~qWoNx9^xBn)(;L~x3&8PdD;@>}3PMGtKi($R`ukQ>MJm7F)QRu@AEW(0|?n_;3 zds_5!r?A%PP0R286fyD(Ymh$WdAF{n#CD3;sltDSUK>;n?t&TQ9Asjmz5J!^WMUEug} zheAxVfl24n^zQO=rM4}88<{{U39A}3!I&%d91o>f`@8E{S5({YoArWOV40VxhkWBx-co9ECzaln6fAg}Pcw5$4JIeu>n;AS^{an^LB{Ts5PVTgY diff --git a/mods/content/beekeeping/icons/swarm.dmi b/mods/content/beekeeping/icons/swarm.dmi new file mode 100644 index 0000000000000000000000000000000000000000..1d8ea6f1b1ae8e237aaaefaf5352ad590422c65c GIT binary patch literal 1016 zcmVV=-0C=2J zR&a84_w-Y6@%7{?OD!tS%+FJ>RWQ*r;NmRLOex6#a*U0*I5Sc+(=$pSoZ^zil2jm5 z$v}yVGbOXA7|1r{;!G<_%uR)`;VKNVt1ttKrsgD80+~iSaEQ=mgk75@R&D5-jmfjq zggkAg1VIpP=E)`bg$(EWxzFaRs2vf)T9ilpwumJRE7-|%>5mY;1#v)<|-QZ z?Z|);!<5D>7emjJaG);mUL>xr3`Q>=44Ts^k_1Tb{>d<1-dOSwJhG$OCBi z33*DnTR8cE zW(1R{(<5Yh3|70j(MBpjZ@r9{GxgFa0>h~}7S&HrMK2+v`cpQ@mG|o4U1&7fb_^!> zB7flXLdJQVrgMGKC#latr(he^sia*+p$pqY4;e2r6Myx_-%2A;Q;fjoMKr8!KQs5V z%h7LB_3o0mff5Qc(1(`gXifcPuZIhrpCeRP?3KXMioXYou 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." @@ -19,29 +5,3 @@ 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 - -/obj/item/bee_pack/Initialize() - . = ..() - overlays += "beepack-full" - -/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" - -/obj/item/bee_pack/proc/fill() - full = initial(full) - SetName(initial(name)) - desc = initial(desc) - overlays.Cut() - overlays += "beepack-full" 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..f79cceb6cc50 100644 --- a/mods/content/beekeeping/trading.dm +++ b/mods/content/beekeeping/trading.dm @@ -1,14 +1,14 @@ /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/bee_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/stack/material/plank/mapped/wood/ten, /obj/item/bee_smoker, /obj/item/hive_frame/crafted = 5, /obj/item/bee_pack From 099368110a39b1a6ccb319fe727f2d735557de96 Mon Sep 17 00:00:00 2001 From: MistakeNot4892 Date: Mon, 22 Sep 2025 11:10:25 +1000 Subject: [PATCH 2/7] Reimplementing bee smoking. --- mods/content/beekeeping/closets.dm | 2 +- .../beekeeping/hives/hive_extension.dm | 16 ++++++--- mods/content/beekeeping/hives/hive_swarm.dm | 9 ++++- mods/content/beekeeping/items.dm | 33 +++++++++++++++++-- mods/content/beekeeping/trading.dm | 4 +-- 5 files changed, 52 insertions(+), 12 deletions(-) diff --git a/mods/content/beekeeping/closets.dm b/mods/content/beekeeping/closets.dm index 9caf9bb459c8..daf63a942d5c 100644 --- a/mods/content/beekeeping/closets.dm +++ b/mods/content/beekeeping/closets.dm @@ -5,7 +5,7 @@ /obj/structure/closet/crate/hydroponics/beekeeping/Initialize() . = ..() new /obj/item/stack/material/plank/mapped/wood/ten - new /obj/item/bee_smoker(src) + 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/hives/hive_extension.dm b/mods/content/beekeeping/hives/hive_extension.dm index d02839f4d40b..576bbfc369f0 100644 --- a/mods/content/beekeeping/hives/hive_extension.dm +++ b/mods/content/beekeeping/hives/hive_extension.dm @@ -11,8 +11,8 @@ var/raw_reserves = 0 /// Tracker for the last world.time that a frame was removed. var/frame_last_removed = 0 - /// Tracker for ticks remaning since we were last smoked. - var/smoked_out = 0 + /// Tracker for time that smoke will wear off. + var/smoked_until = 0 /datum/extension/insect_hive/New(datum/holder, _species_decl) ..() @@ -33,8 +33,7 @@ return ..() /datum/extension/insect_hive/Process() - if(smoked_out > 0) - smoked_out-- + if(world.time < smoked_until) return holding_species.process_hive(src) create_hive_products() @@ -69,7 +68,7 @@ /datum/extension/insect_hive/proc/frame_removed(obj/item/frame) frame_last_removed = world.time - if(!smoked_out) + if(world.time >= smoked_until) for(var/obj/effect/insect_swarm/swarm in swarms) swarm.swarm_agitation = min(100, swarm.swarm_agitation + 5) @@ -193,3 +192,10 @@ . = 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_swarm.dm b/mods/content/beekeeping/hives/hive_swarm.dm index 2e733a96b138..662e972cd41e 100644 --- a/mods/content/beekeeping/hives/hive_swarm.dm +++ b/mods/content/beekeeping/hives/hive_swarm.dm @@ -22,6 +22,8 @@ var/const/MAX_SWARM_STATE = 6 /// 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 delay = 1 SECOND @@ -76,7 +78,7 @@ swarm_transform.Turn(pick(90, 180, 270)) /obj/effect/insect_swarm/proc/is_agitated() - return QDELETED(owner) || swarm_agitation > 0 + return QDELETED(owner) || (swarm_agitation > 0 && world.time > smoked_until) /obj/effect/insect_swarm/proc/find_sting_target() for(var/mob/living/victim in view(7, src)) @@ -320,3 +322,8 @@ start_automove(closest_target) else start_automove(owner.holder) + +// TODO: update icon (twitching on ground?) +// TODO: lower agitation +/obj/effect/insect_swarm/proc/was_smoked(smoke_time = 10 SECONDS) + smoked_until = max(smoked_until, world.time + smoke_time) diff --git a/mods/content/beekeeping/items.dm b/mods/content/beekeeping/items.dm index a309ff921a28..1161ae82fd98 100644 --- a/mods/content/beekeeping/items.dm +++ b/mods/content/beekeeping/items.dm @@ -1,7 +1,34 @@ -/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 + +// 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 + + if(!smoked && isturf(A)) + for(var/obj/effect/insect_swarm/swarm in A) + swarm.was_smoked() + smoked = TRUE + + if(smoked) + var/turf/smoked_turf = get_turf(user) + 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 + + return ..() diff --git a/mods/content/beekeeping/trading.dm b/mods/content/beekeeping/trading.dm index f79cceb6cc50..c63d01f412c2 100644 --- a/mods/content/beekeeping/trading.dm +++ b/mods/content/beekeeping/trading.dm @@ -1,6 +1,6 @@ /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/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) ..() @@ -9,7 +9,7 @@ name = "Equipment - Beekeeping" contains = list( /obj/item/stack/material/plank/mapped/wood/ten, - /obj/item/bee_smoker, + /obj/item/smoker, /obj/item/hive_frame/crafted = 5, /obj/item/bee_pack ) From 53fca3d553c7e03f0d4c8cc97173529fbd4ede7a Mon Sep 17 00:00:00 2001 From: MistakeNot4892 Date: Mon, 22 Sep 2025 11:55:47 +1000 Subject: [PATCH 3/7] Things can be stored inside dead trees. --- code/game/objects/structures/flora/tree.dm | 5 +++++ 1 file changed, 5 insertions(+) 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)]" From 925e87955edf62bd1f8de6938477334cd0c88673 Mon Sep 17 00:00:00 2001 From: MistakeNot4892 Date: Mon, 22 Sep 2025 13:04:14 +1000 Subject: [PATCH 4/7] Implementing hive spawning/destruction logic. --- code/game/objects/structures/flora/plant.dm | 36 +++++++++++++++++-- code/game/objects/structures/flora/stump.dm | 1 + .../beekeeping/hives/hive_extension.dm | 11 ++++-- .../beekeeping/hives/hive_structure.dm | 11 +++--- mods/content/beekeeping/hives/hive_swarm.dm | 33 +++++++++++++---- .../hives/insect_species/_insects.dm | 5 +++ 6 files changed, 81 insertions(+), 16 deletions(-) diff --git a/code/game/objects/structures/flora/plant.dm b/code/game/objects/structures/flora/plant.dm index 9e44f55a2f3a..b17d620d6394 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 + 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 < 10) + pollen += plant.produces_pollen + +/* 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(SSprocessing, src) /obj/structure/flora/plant/Destroy() + if(is_processing) + STOP_PROCESSING(SSprocessing, src) plant = null . = ..() @@ -147,3 +157,23 @@ /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 + +/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/mods/content/beekeeping/hives/hive_extension.dm b/mods/content/beekeeping/hives/hive_extension.dm index 576bbfc369f0..7bd559e74cf4 100644 --- a/mods/content/beekeeping/hives/hive_extension.dm +++ b/mods/content/beekeeping/hives/hive_extension.dm @@ -1,3 +1,5 @@ +#define DEFAULT_FRAME_COST 20 + /datum/extension/insect_hive base_type = /datum/extension/insect_hive expected_type = /obj/structure @@ -42,7 +44,12 @@ return FALSE /datum/extension/insect_hive/proc/drop_nest(atom/drop_loc) - return + 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) @@ -161,7 +168,7 @@ adjust_health(rand(3,5)) return TRUE - if(!has_reserves(20)) + if(!has_reserves(DEFAULT_FRAME_COST)) return TRUE var/list/holder_contents = hive.get_contained_external_atoms() diff --git a/mods/content/beekeeping/hives/hive_structure.dm b/mods/content/beekeeping/hives/hive_structure.dm index f7586ad6eec4..33eb99e67c8d 100644 --- a/mods/content/beekeeping/hives/hive_structure.dm +++ b/mods/content/beekeeping/hives/hive_structure.dm @@ -25,11 +25,14 @@ 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) - if(isatom(loc)) - var/datum/extension/insect_hive/hive = get_extension(src, /datum/extension/insect_hive) - if(istype(hive)) - hive.drop_nest(loc) + 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 diff --git a/mods/content/beekeeping/hives/hive_swarm.dm b/mods/content/beekeeping/hives/hive_swarm.dm index 662e972cd41e..3284152915f1 100644 --- a/mods/content/beekeeping/hives/hive_swarm.dm +++ b/mods/content/beekeeping/hives/hive_swarm.dm @@ -211,7 +211,7 @@ 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?.raw_reserves >= 15) + if(owner?.raw_reserves >= DEFAULT_FRAME_COST) if(is_first_swarm_at_hive()) stop_automove() return @@ -307,15 +307,34 @@ 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/obj/machinery/portable_atmospherics/hydroponics/flower in view(src, 7)) - if(!flower.pollen) - continue - var/next_dist = get_dist(src, closest_target) - if(isnull(closest_dist) || next_dist < closest_dist) - closest_target = flower + 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) diff --git a/mods/content/beekeeping/hives/insect_species/_insects.dm b/mods/content/beekeeping/hives/insect_species/_insects.dm index 013e720ae593..0e3dcac55a51 100644 --- a/mods/content/beekeeping/hives/insect_species/_insects.dm +++ b/mods/content/beekeeping/hives/insect_species/_insects.dm @@ -126,6 +126,11 @@ 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) From be2b0e02f7e8b0b078dfc69a93acae7694620af2 Mon Sep 17 00:00:00 2001 From: MistakeNot4892 Date: Mon, 22 Sep 2025 14:47:08 +1000 Subject: [PATCH 5/7] Adjusting rate of development for hives/hive products. --- code/__defines/misc.dm | 5 ++++ code/game/objects/structures/flora/plant.dm | 6 ++--- .../modules/hydroponics/trays/tray_process.dm | 2 +- code/modules/mob/skills/skillset.dm | 3 ++- mods/content/beekeeping/_beekeeping.dm | 7 ++++++ .../beekeeping/hives/hive_extension.dm | 23 +++++++++++-------- mods/content/beekeeping/hives/hive_flora.dm | 4 ++-- mods/content/beekeeping/hives/hive_frame.dm | 15 ++++++++++-- mods/content/beekeeping/hives/hive_swarm.dm | 20 ++++++++++++---- .../hives/insect_species/_insects.dm | 16 ++++++------- mods/content/beekeeping/items.dm | 2 +- 11 files changed, 69 insertions(+), 34 deletions(-) diff --git a/code/__defines/misc.dm b/code/__defines/misc.dm index 4d5a639708f0..c88fc27812cd 100644 --- a/code/__defines/misc.dm +++ b/code/__defines/misc.dm @@ -402,3 +402,8 @@ #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)) diff --git a/code/game/objects/structures/flora/plant.dm b/code/game/objects/structures/flora/plant.dm index b17d620d6394..c56c18864799 100644 --- a/code/game/objects/structures/flora/plant.dm +++ b/code/game/objects/structures/flora/plant.dm @@ -18,7 +18,7 @@ if(plant?.produces_pollen <= 0) return PROCESS_KILL if(pollen < 10) - pollen += plant.produces_pollen + pollen += plant.produces_pollen * POLLEN_PRODUCTION_MULT /* Notes for future work moving logic off hydrotrays onto plants themselves: // check our immediate environment @@ -69,11 +69,11 @@ update_icon() . = ..() if(plant?.produces_pollen && !is_processing) - START_PROCESSING(SSprocessing, src) + START_PROCESSING(SSplants, src) /obj/structure/flora/plant/Destroy() if(is_processing) - STOP_PROCESSING(SSprocessing, src) + STOP_PROCESSING(SSplants, src) plant = null . = ..() diff --git a/code/modules/hydroponics/trays/tray_process.dm b/code/modules/hydroponics/trays/tray_process.dm index a62c81a8d1e2..d394227d2997 100644 --- a/code/modules/hydroponics/trays/tray_process.dm +++ b/code/modules/hydroponics/trays/tray_process.dm @@ -66,7 +66,7 @@ mutation_level = 0 if(pollen < 10) - pollen += seed?.produces_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/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/mods/content/beekeeping/_beekeeping.dm b/mods/content/beekeeping/_beekeeping.dm index 471217ac52d6..53ca84daf621 100644 --- a/mods/content/beekeeping/_beekeeping.dm +++ b/mods/content/beekeeping/_beekeeping.dm @@ -1,3 +1,10 @@ +#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 and Insects Content" diff --git a/mods/content/beekeeping/hives/hive_extension.dm b/mods/content/beekeeping/hives/hive_extension.dm index 7bd559e74cf4..0917903fae59 100644 --- a/mods/content/beekeeping/hives/hive_extension.dm +++ b/mods/content/beekeeping/hives/hive_extension.dm @@ -1,5 +1,3 @@ -#define DEFAULT_FRAME_COST 20 - /datum/extension/insect_hive base_type = /datum/extension/insect_hive expected_type = /obj/structure @@ -75,9 +73,12 @@ /datum/extension/insect_hive/proc/frame_removed(obj/item/frame) frame_last_removed = world.time - if(world.time >= smoked_until) - for(var/obj/effect/insect_swarm/swarm in swarms) - swarm.swarm_agitation = min(100, swarm.swarm_agitation + 5) + 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) return FALSE @@ -159,16 +160,16 @@ return TRUE // Naturally build up enough material for a new frame (or repairs). - if(!has_material(20)) + if(!has_material(FRAME_MATERIAL_COST)) add_material(1) // Damaged hives cannot produce combs or honey. if(current_health < 100) - if(consume_material(5)) + if(consume_material(HIVE_REPAIR_MATERIAL_COST)) adjust_health(rand(3,5)) return TRUE - if(!has_reserves(DEFAULT_FRAME_COST)) + if(!has_reserves(FRAME_RESERVE_COST)) return TRUE var/list/holder_contents = hive.get_contained_external_atoms() @@ -176,7 +177,9 @@ if(!frame.reagents || (frame.reagents.total_volume >= frame.reagents.maximum_volume)) continue var/fill_cost = REAGENTS_FREE_SPACE(frame.reagents) - if(consume_material(5) && consume_reserves(fill_cost)) + 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 @@ -189,7 +192,7 @@ 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(20)) + 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) diff --git a/mods/content/beekeeping/hives/hive_flora.dm b/mods/content/beekeeping/hives/hive_flora.dm index e6091c3610bd..d6bc3a50c34f 100644 --- a/mods/content/beekeeping/hives/hive_flora.dm +++ b/mods/content/beekeeping/hives/hive_flora.dm @@ -28,13 +28,13 @@ /obj/structure/flora/tree/get_supported_insects() var/static/list/_insects = list( /decl/insect_species/honeybees = 10, - /decl/insect_species/wasps = 1 + ///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 + ///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 index 50df27ad12b3..c3a4cdceb5de 100644 --- a/mods/content/beekeeping/hives/hive_frame.dm +++ b/mods/content/beekeeping/hives/hive_frame.dm @@ -41,10 +41,21 @@ 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/datum/extension/insect_hive/hive = get_extension(loc, /datum/extension/insect_hive) + var/atom/old_loc = loc . = ..() - if(. && istype(hive) && loc != hive.holder) + 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. diff --git a/mods/content/beekeeping/hives/hive_swarm.dm b/mods/content/beekeeping/hives/hive_swarm.dm index 3284152915f1..544b7f33406d 100644 --- a/mods/content/beekeeping/hives/hive_swarm.dm +++ b/mods/content/beekeeping/hives/hive_swarm.dm @@ -78,7 +78,7 @@ swarm_transform.Turn(pick(90, 180, 270)) /obj/effect/insect_swarm/proc/is_agitated() - return QDELETED(owner) || (swarm_agitation > 0 && world.time > smoked_until) + 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)) @@ -129,14 +129,21 @@ 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(!move_target) - move_target = find_sting_target() + 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) - return + 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)) @@ -211,7 +218,7 @@ 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?.raw_reserves >= DEFAULT_FRAME_COST) + if(owner?.has_reserves(FRAME_RESERVE_COST)) if(is_first_swarm_at_hive()) stop_automove() return @@ -346,3 +353,6 @@ // TODO: lower agitation /obj/effect/insect_swarm/proc/was_smoked(smoke_time = 10 SECONDS) smoked_until = max(smoked_until, world.time + smoke_time) + +/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 index 0e3dcac55a51..37e4481748cd 100644 --- a/mods/content/beekeeping/hives/insect_species/_insects.dm +++ b/mods/content/beekeeping/hives/insect_species/_insects.dm @@ -97,7 +97,8 @@ // 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/sting_mult = sting_amount * clamp(round(swarm.swarm_intensity/10), 1, 10) + 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 @@ -106,12 +107,9 @@ 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) - if(sting_mult <= sting_amount * 2) - to_chat(victim, SPAN_DANGER("You are stung on your [affecting.name] by \a [swarm]!")) - else - to_chat(victim, SPAN_DANGER("You are stung multiple times on your [affecting.name] by \a [swarm]!")) . = TRUE /decl/insect_species/proc/can_spawn_in_flora(var/obj/structure/flora) @@ -152,14 +150,14 @@ return // Try to grow an existing swarm until we're at our max. - if(hive_metadata.has_reserves(5) && length(hive_metadata.swarms)) + 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(5)) + 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(5)) + 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) @@ -175,5 +173,5 @@ 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(5)) + 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/items.dm b/mods/content/beekeeping/items.dm index 1161ae82fd98..f520e5085eab 100644 --- a/mods/content/beekeeping/items.dm +++ b/mods/content/beekeeping/items.dm @@ -24,7 +24,7 @@ smoked = TRUE if(smoked) - var/turf/smoked_turf = get_turf(user) + 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].")) From bb16e71d0c3064792fb2d2ad83caa76f3c812888 Mon Sep 17 00:00:00 2001 From: MistakeNot4892 Date: Mon, 22 Sep 2025 21:36:05 +1000 Subject: [PATCH 6/7] Commenting out wasps. --- mods/content/beekeeping/hives/hive_extension.dm | 9 ++++++++- mods/content/beekeeping/hives/hive_structure.dm | 2 +- .../hives/insect_species/insects_pollinators.dm | 2 ++ 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/mods/content/beekeeping/hives/hive_extension.dm b/mods/content/beekeeping/hives/hive_extension.dm index 0917903fae59..c315dd59e2ef 100644 --- a/mods/content/beekeeping/hives/hive_extension.dm +++ b/mods/content/beekeeping/hives/hive_extension.dm @@ -80,7 +80,14 @@ 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) +/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) diff --git a/mods/content/beekeeping/hives/hive_structure.dm b/mods/content/beekeeping/hives/hive_structure.dm index 33eb99e67c8d..855f9e42c7b8 100644 --- a/mods/content/beekeeping/hives/hive_structure.dm +++ b/mods/content/beekeeping/hives/hive_structure.dm @@ -8,7 +8,7 @@ /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)) + if(hive.try_hand_harvest(user, src)) return TRUE return ..() diff --git a/mods/content/beekeeping/hives/insect_species/insects_pollinators.dm b/mods/content/beekeeping/hives/insect_species/insects_pollinators.dm index 0f9b924e62fe..83aeb86f8462 100644 --- a/mods/content/beekeeping/hives/insect_species/insects_pollinators.dm +++ b/mods/content/beekeeping/hives/insect_species/insects_pollinators.dm @@ -14,6 +14,7 @@ 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" @@ -24,3 +25,4 @@ sting_amount = 5 swarm_color = COLOR_BRONZE swarm_type = /obj/effect/insect_swarm/pollinator // tarantula hunter... +*/ \ No newline at end of file From 5fe0242062be45f5e6cc3cc0871e416354b4d654 Mon Sep 17 00:00:00 2001 From: mistakenot4892 Date: Mon, 13 Oct 2025 10:32:08 +1100 Subject: [PATCH 7/7] Addressing some comments on beewrite PR. --- code/__defines/misc.dm | 1 + .../objects/items/devices/chameleonproj.dm | 5 +- code/game/objects/structures/flora/plant.dm | 5 +- .../modules/hydroponics/trays/tray_process.dm | 3 +- code/modules/mob/living/living.dm | 3 + code/modules/mob/mob_automove.dm | 1 - mods/content/beekeeping/hives/hive_swarm.dm | 73 ++++++++++-------- .../hives/insect_species/_insects.dm | 12 +++ mods/content/beekeeping/icons/swarm.dmi | Bin 1016 -> 1124 bytes 9 files changed, 64 insertions(+), 39 deletions(-) diff --git a/code/__defines/misc.dm b/code/__defines/misc.dm index c88fc27812cd..8198a65f2394 100644 --- a/code/__defines/misc.dm +++ b/code/__defines/misc.dm @@ -407,3 +407,4 @@ // 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 c56c18864799..278a60e8f019 100644 --- a/code/game/objects/structures/flora/plant.dm +++ b/code/game/objects/structures/flora/plant.dm @@ -7,7 +7,7 @@ var/dead = FALSE var/sampled = FALSE var/datum/seed/plant - var/harvestable = 0 + var/harvestable = 0 // Note that this is a counter, not a bool. var/pollen = 0 /obj/structure/flora/plant/large @@ -17,7 +17,7 @@ /obj/structure/flora/plant/Process() if(plant?.produces_pollen <= 0) return PROCESS_KILL - if(pollen < 10) + if(pollen < MAX_POLLEN_PER_FLOWER) pollen += plant.produces_pollen * POLLEN_PRODUCTION_MULT /* Notes for future work moving logic off hydrotrays onto plants themselves: @@ -164,6 +164,7 @@ 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)) diff --git a/code/modules/hydroponics/trays/tray_process.dm b/code/modules/hydroponics/trays/tray_process.dm index d394227d2997..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) + 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/mods/content/beekeeping/hives/hive_swarm.dm b/mods/content/beekeeping/hives/hive_swarm.dm index 544b7f33406d..084ef4461d84 100644 --- a/mods/content/beekeeping/hives/hive_swarm.dm +++ b/mods/content/beekeeping/hives/hive_swarm.dm @@ -6,7 +6,7 @@ default_pixel_z = 8 layer = ABOVE_HUMAN_LAYER pass_flags = PASS_FLAG_TABLE - movement_handlers = list(/datum/movement_handler/delay/insect_swarm) + 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 @@ -18,16 +18,11 @@ var/swarm_agitation = 0 /// Percentage value; if it drops to 0, the swarm will be destroyed. var/swarm_intensity = 1 - /// if more states are added to swarm.dmi, increase this - var/const/MAX_SWARM_STATE = 6 /// 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 - delay = 1 SECOND - /datum/movement_handler/delay/insect_swarm/DoMove(direction, mob/mover, is_external) ..() step(host, direction) @@ -46,8 +41,7 @@ if(!istype(owner)) PRINT_STACK_TRACE("Insect swarm created with invalid hive: '[owner]'") return INITIALIZE_HINT_QDEL - color = insect_type.swarm_color - icon = insect_type.swarm_icon + update_transform() update_swarm() LAZYDISTINCTADD(owner.swarms, src) START_PROCESSING(SSobj, src) @@ -61,9 +55,29 @@ 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() - icon_state = num2text(ceil((swarm_intensity / insect_type.max_swarm_intensity) * MAX_SWARM_STATE)) - if(icon_state == "1") + update_icon() + if(get_swarm_state() == 1) SetName(insect_type.name_singular) desc = insect_type.insect_desc gender = NEUTER @@ -72,21 +86,13 @@ desc = insect_type.swarm_desc gender = PLURAL - // Some icon variation via transform. - if(prob(75)) - var/matrix/swarm_transform = matrix() - swarm_transform.Turn(pick(90, 180, 270)) - /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.stat || victim.current_posture?.prone) - continue - if(victim.isSynthetic()) - continue - return victim + if(victim.simulated && !victim.is_playing_dead()) + return victim /obj/effect/insect_swarm/proc/merge(obj/effect/insect_swarm/other_swarm) @@ -189,23 +195,24 @@ 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() - SHOULD_CALL_PARENT(FALSE) move_target = null - //. = ..() // TODO work out why they're not automoving - walk(src, 0) + . = ..() + +/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) - SHOULD_CALL_PARENT(FALSE) move_target = target - //. = ..() // TODO work out why they're not automoving - if(move_target) - walk_to(src, move_target, 0, 7) - else - walk(src, 0) + . = ..() /obj/effect/insect_swarm/proc/handle_hive_behavior() @@ -272,6 +279,10 @@ 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 @@ -349,10 +360,10 @@ else start_automove(owner.holder) -// TODO: update icon (twitching on ground?) -// TODO: lower agitation /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 index 37e4481748cd..d380f6e2e80d 100644 --- a/mods/content/beekeeping/hives/insect_species/_insects.dm +++ b/mods/content/beekeeping/hives/insect_species/_insects.dm @@ -22,6 +22,7 @@ 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 @@ -72,6 +73,17 @@ 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)) diff --git a/mods/content/beekeeping/icons/swarm.dmi b/mods/content/beekeeping/icons/swarm.dmi index 1d8ea6f1b1ae8e237aaaefaf5352ad590422c65c..e55d936c1f15331e32068155b7bb6ea87076b676 100644 GIT binary patch delta 940 zcmV;d15^C?2jmEl7Yg760{{R3=mU4sks&Jqw2@7Wf04pFzBo5OJ2i!HuoJ7$h**Wj z#40o)R-q}e3e5;8R90~Ha{;Gc0N-xA%A#`K)&KwlLrFwIRCt{2o9lstAPj&FE|7DA zbOZOF?m(bCd=nJg=KEnqGzlR<(7Ub^1VIqK2W)MC(3+1A0cvf4AP9mW2*Ni4tqu4* zRBHo1e-LVIfN&O>8Lo!wVX9$-AP9mW2)_WeHh|BQb5m+kCZLO19%RDv^0TxN?5r z_O!g=&TDvC?Wx-@GXk9KTL%Z+o2ct91a)T%P5S`2W8?U;4Bb$-b+l7I*h6?rvG>3N z=k|lxnI1eHHOw%Z55%j`T1H$lSEV;qLwRLL6{T*0lQQh6;@IEu2wsufZmyzf+m3V? ze=$ty%yKclcoGie1;LBN)m6ac#e+d}dWd8J5Y-ds zrV_VIrkV0$|AT!#zk)uwvp@ap1_p!8f8=6;>MfQ!mr=$o0>0vDby7}>@mH3!>xlr?HxnL>UzJvCH-LYh>zk7Nb*Xdgw3IMn`K4e`h?P z>A@`ObPt&pgVk;>^oa`4TQB3~LcKJJz<6p-MZNr8$wSD<{*(=J6}&nI7aC2r9;3;< z%0KW0A=5lg{#+Mc+0f^phhU%Nsia*+u?zc54;e2r2ma!PzlBPmt{RcevuIe_eirU& z=e^(O>Rpkzff5Td&`Qm`w|slqf9v5)=XzvJnGJszmQ32O)POih_c;~BY9=+057-Ks zS-V}1t(7Ye`x0{{R3@2_&?ks&JqmXS@2f4P(uT>V_Y5d;9b!D;2Pce3gL z00Q+%L_t(|ob8+2xq~1KfDJB?;{7pj@s%?NE2!bF8!Z!l74R{}_wgE4MY8xP&MP`Pp;d+=#7$FFPAPB-QK(!6v z^W@r;e`*`B!}GVV=STOJ-@0D^gW8jz+6FACF{zwSM%rqWJP-sy5N_tlCHaL6`;XA& zM({Y$1>wu6xCuN^KH|nz2_0oB*!S1LKRRb0q(+XkWySGA^!eWu-OFizhyxuopNUI^ zAK*t;R`q#-$y&*F+(tPvuLnPzA9y^z-f-tNfAm&+?Dory04Mv_Ul*TL^IP2=$lXj#FaL z_&oA*R_{;U0_kiYqFF#IpvVJg_X&AQ(eE}U^#eo!LDRVUJBmBqBhEFA)f5%Vps)>H zuD4$UQ=hXKok}x8f4((1T3a~zfMx`fsM8~4dJI;(xzR={KyST_moxR!C<4Q&e>oP_ zPftZJA*1?JHprFt>fl{yG}(3xCifzL;PXPpd7P$mebFbW&q1eP8`Y_#T}7b_+e8l; zFEbN=^~T>yBT!R}z~)6XtZhFt_q5B=Z&UT|lDL5q3Nz4$mgQ(o{bjF*3!R@MW2|iW zyRc}|?qdVOAT5kqNKNAdvO^}4C9wqkC#|(-GADh!$QIO4k$)9N6-H98v2*|c002ov JPDHLkV1hfkhZq0=