Converts bees to basic mobs (#30662)

* Converts bees to basic mobs

* Undefs

* Linters
This commit is contained in:
PollardTheDragon
2025-10-28 16:53:18 -04:00
committed by GitHub
parent 5159e73c0d
commit ae4489cb2f
22 changed files with 561 additions and 434 deletions
@@ -80,7 +80,7 @@
for(var/i in 1 to 8)
// A large influx of bees, directly into the cargo workplace
spawn_list.Add(/mob/living/simple_animal/hostile/poison/bees)
spawn_list.Add(/mob/living/basic/bee)
for(var/i in 1 to 5)
var/decal = pick(/obj/effect/decal/cleanable/blood, /obj/effect/decal/cleanable/insectguts)
+21 -13
View File
@@ -11,7 +11,7 @@
return FALSE
/mob/living/simple_animal/hostile/poison/bees/bee_friendly()
/mob/living/basic/bee/bee_friendly()
return TRUE
/mob/living/simple_animal/bot/bee_friendly()
@@ -37,7 +37,7 @@
icon_state = "beebox"
anchored = TRUE
density = TRUE
var/mob/living/simple_animal/hostile/poison/bees/queen/queen_bee = null
var/mob/living/basic/bee/queen/queen_bee = null
var/list/bees = list() //bees owned by the box, not those inside it
var/list/honeycombs = list()
var/list/honey_frames = list()
@@ -51,7 +51,7 @@
/obj/structure/beebox/Destroy()
STOP_PROCESSING(SSobj, src)
for(var/mob/living/simple_animal/hostile/poison/bees/B in bees)
for(var/mob/living/basic/bee/B in bees)
B.beehome = null
bees.Cut()
bees = null
@@ -82,7 +82,7 @@
honey_frames += HF
for(var/i in 1 to get_max_bees())
var/mob/living/simple_animal/hostile/poison/bees/B = new(src)
var/mob/living/basic/bee/B = new(src)
bees += B
B.beehome = src
B.assign_reagent(R)
@@ -107,7 +107,7 @@
if((bee_resources >= BEE_RESOURCE_NEW_BEE_COST && prob(BEE_PROB_NEW_BEE)) || freebee)
if(!freebee)
bee_resources = max(bee_resources - BEE_RESOURCE_NEW_BEE_COST, 0)
var/mob/living/simple_animal/hostile/poison/bees/B = new(get_turf(src))
var/mob/living/basic/bee/B = new(get_turf(src))
B.beehome = src
B.assign_reagent(queen_bee.beegent)
bees += B
@@ -178,7 +178,7 @@
visible_message("<span class='notice'>[user] sets [qb] down inside the apiary, making it [qb.p_their()] new home.</span>")
var/relocated = 0
for(var/b in bees)
var/mob/living/simple_animal/hostile/poison/bees/B = b
var/mob/living/basic/bee/B = b
if(B.reagent_incompatible(queen_bee))
bees -= B
B.beehome = null
@@ -209,7 +209,7 @@
default_unfasten_wrench(user, I, time = 20)
/obj/structure/beebox/attack_animal(mob/living/simple_animal/M)
if(!istype(M, /mob/living/simple_animal/hostile/poison/bees))
if(!istype(M, /mob/living/basic/bee))
return ..()
M.forceMove(src)
@@ -222,15 +222,15 @@
/obj/structure/beebox/attack_hand(mob/user)
if(ishuman(user))
if(!user.bee_friendly())
//Time to get stung!
// Time to get stung!
var/bees = FALSE
for(var/b in bees) //everyone who's ever lived here now instantly hates you, suck it assistant!
var/mob/living/simple_animal/hostile/poison/bees/B = b
if(B.isqueen)
for(var/b in bees) // everyone who's ever lived here now instantly hates you, suck it assistant!
var/mob/living/basic/bee/B = b
if(B.is_queen)
continue
if(B.loc == src)
B.forceMove(drop_location())
B.target = user
B.ai_controller.set_blackboard_key(BB_BASIC_MOB_CURRENT_TARGET, user)
bees = TRUE
if(bees)
visible_message("<span class='danger'>[user] disturbs the bees!</span>")
@@ -281,7 +281,7 @@
if(disassembled)
mat_drop = 40
new /obj/item/stack/sheet/wood(loc, mat_drop)
for(var/mob/living/simple_animal/hostile/poison/bees/B in bees)
for(var/mob/living/basic/bee/B in bees)
if(B.loc == src)
B.forceMove(drop_location())
for(var/obj/item/honey_frame/HF in honey_frames)
@@ -292,6 +292,14 @@
/obj/structure/beebox/unwrenched
anchored = FALSE
/obj/structure/beebox/proc/habitable(mob/living/basic/target)
if(!istype(target, /mob/living/basic/bee))
return FALSE
var/mob/living/basic/bee/citizen = target
if(citizen.reagent_incompatible(queen_bee) || bees.len >= get_max_bees())
return FALSE
return TRUE
#undef BEEBOX_MAX_FRAMES
#undef BEES_RATIO
#undef BEE_PROB_NEW_BEE
@@ -1138,3 +1138,10 @@
myseed.mutate(roll / 2, get_mutation_focus())
if(top_range > 30 && prob(roll / 10))
mut_beamed = TRUE
/obj/machinery/hydroponics/proc/can_bee_pollinate()
if(isnull(myseed))
return FALSE
if(dead || recent_bee_visit)
return FALSE
return TRUE
@@ -0,0 +1,106 @@
/// if we have a hive, this will be our aggro distance
#define AGGRO_DISTANCE_FROM_HIVE 2
/datum/ai_behavior/hunt_target/pollinate
always_reset_target = TRUE
/datum/ai_behavior/hunt_target/pollinate/target_caught(mob/living/hunter, obj/machinery/hydroponics/hydro_target)
var/datum/callback/callback = CALLBACK(hunter, TYPE_PROC_REF(/mob/living/basic/bee, pollinate), hydro_target)
callback.Invoke()
/datum/ai_behavior/find_hunt_target/pollinate
action_cooldown = 10 SECONDS
/datum/ai_behavior/find_hunt_target/pollinate/valid_dinner(mob/living/source, obj/machinery/hydroponics/dinner, radius)
if(!dinner.can_bee_pollinate())
return FALSE
return can_see(source, dinner, radius)
/datum/ai_behavior/enter_exit_hive
behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_REQUIRE_REACH
action_cooldown = 10 SECONDS
/datum/ai_behavior/enter_exit_hive/setup(datum/ai_controller/controller, target_key, attack_key)
. = ..()
var/atom/target = controller.blackboard[target_key]
if(QDELETED(target))
return FALSE
set_movement_target(controller, target)
/datum/ai_behavior/enter_exit_hive/perform(seconds_per_tick, datum/ai_controller/controller, target_key, attack_key)
var/obj/structure/beebox/current_home = controller.blackboard[target_key]
var/atom/attack_target = controller.blackboard[attack_key]
if(attack_target) // forget about who we attacking when we go home
controller.clear_blackboard_key(attack_key)
controller.ai_interact(target = current_home, intent = INTENT_HELP)
return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED
/datum/ai_behavior/inhabit_hive
behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_REQUIRE_REACH
/datum/ai_behavior/inhabit_hive/setup(datum/ai_controller/controller, target_key)
. = ..()
var/atom/target = controller.blackboard[target_key]
if(QDELETED(target))
return FALSE
set_movement_target(controller, target)
/datum/ai_behavior/inhabit_hive/perform(seconds_per_tick, datum/ai_controller/controller, target_key)
var/obj/structure/beebox/potential_home = controller.blackboard[target_key]
var/mob/living/bee_pawn = controller.pawn
if(!potential_home.habitable(bee_pawn)) //the house become full before we get to it
return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED
controller.ai_interact(target = potential_home, intent = INTENT_HELP)
return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED
/datum/ai_behavior/inhabit_hive/finish_action(datum/ai_controller/controller, succeeded, target_key)
. = ..()
if(!succeeded)
controller.clear_blackboard_key(target_key) //failed to make it our home so find another
/datum/ai_behavior/find_and_set/bee_hive
action_cooldown = 10 SECONDS
/datum/ai_behavior/find_and_set/bee_hive/search_tactic(datum/ai_controller/controller, locate_path, search_range)
var/list/valid_hives = list()
var/mob/living/bee_pawn = controller.pawn
if(istype(bee_pawn.loc, /obj/structure/beebox))
return bee_pawn.loc //for premade homes
for(var/obj/structure/beebox/potential_home in oview(search_range, bee_pawn))
if(!potential_home.habitable(bee_pawn))
continue
valid_hives += potential_home
if(valid_hives.len)
return pick(valid_hives)
/datum/targeting_strategy/basic/bee
/datum/targeting_strategy/basic/bee/can_attack(mob/living/owner, atom/target, vision_range)
if(!isliving(target))
return FALSE
. = ..()
if(!.)
return FALSE
var/mob/living/mob_target = target
if(mob_target.mob_biotypes & MOB_PLANT)
return FALSE
var/datum/ai_controller/basic_controller/bee_ai = owner.ai_controller
if(isnull(bee_ai))
return FALSE
var/atom/bee_hive = bee_ai.blackboard[BB_CURRENT_HOME]
if(bee_hive && get_dist(target, bee_hive) > AGGRO_DISTANCE_FROM_HIVE && can_see(owner, bee_hive, 9))
return FALSE
return !(mob_target.bee_friendly())
#undef AGGRO_DISTANCE_FROM_HIVE
@@ -0,0 +1,108 @@
/datum/ai_controller/basic_controller/bee
blackboard = list(
BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic/bee,
)
ai_traits = PASSIVE_AI_FLAGS
ai_movement = /datum/ai_movement/basic_avoidance
idle_behavior = /datum/idle_behavior/idle_random_walk
planning_subtrees = list(
/datum/ai_planning_subtree/find_valid_home,
/datum/ai_planning_subtree/enter_exit_home,
/datum/ai_planning_subtree/find_and_hunt_target/pollinate,
/datum/ai_planning_subtree/simple_find_target/bee,
/datum/ai_planning_subtree/basic_melee_attack_subtree,
)
/datum/ai_controller/basic_controller/queen_bee
blackboard = list(
BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic/bee,
)
ai_traits = PASSIVE_AI_FLAGS
ai_movement = /datum/ai_movement/basic_avoidance
idle_behavior = /datum/idle_behavior/idle_random_walk
planning_subtrees = list(
/datum/ai_planning_subtree/find_valid_home,
/datum/ai_planning_subtree/enter_exit_home/queen,
/datum/ai_planning_subtree/simple_find_target,
/datum/ai_planning_subtree/basic_melee_attack_subtree,
)
/datum/ai_controller/basic_controller/syndibee
blackboard = list(
BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic/not_friends,
)
ai_movement = /datum/ai_movement/jps
idle_behavior = /datum/idle_behavior/idle_random_walk
planning_subtrees = list(
/datum/ai_planning_subtree/simple_find_target,
/datum/ai_planning_subtree/basic_melee_attack_subtree,
)
/datum/ai_planning_subtree/simple_find_target/bee/select_behaviors(datum/ai_controller/controller, seconds_per_tick)
var/atom/hydro_target = controller.blackboard[BB_TARGET_HYDRO]
if(hydro_target)
return SUBTREE_RETURN_FINISH_PLANNING
return ..()
/datum/ai_planning_subtree/find_valid_home
/datum/ai_planning_subtree/find_valid_home/select_behaviors(datum/ai_controller/controller, seconds_per_tick)
var/mob/living/work_bee = controller.pawn
var/obj/structure/beebox/current_home = controller.blackboard[BB_CURRENT_HOME]
if(QDELETED(current_home))
controller.queue_behavior(/datum/ai_behavior/find_and_set/bee_hive, BB_CURRENT_HOME, /obj/structure/beebox)
return
if(work_bee in current_home.bees)
return
controller.queue_behavior(/datum/ai_behavior/inhabit_hive, BB_CURRENT_HOME)
return SUBTREE_RETURN_FINISH_PLANNING
/datum/ai_planning_subtree/enter_exit_home
/// chance we go back home
var/flyback_chance = 15
/// chance we exit the home
var/exit_chance = 35
/datum/ai_planning_subtree/enter_exit_home/select_behaviors(datum/ai_controller/controller, seconds_per_tick)
var/obj/structure/beebox/current_home = controller.blackboard[BB_CURRENT_HOME]
if(QDELETED(current_home))
return
var/mob/living/bee_pawn = controller.pawn
var/action_prob = (bee_pawn.loc == current_home) ? exit_chance : flyback_chance
if(!SPT_PROB(action_prob, seconds_per_tick))
return
controller.queue_behavior(/datum/ai_behavior/enter_exit_hive, BB_CURRENT_HOME, BB_BASIC_MOB_CURRENT_TARGET)
return SUBTREE_RETURN_FINISH_PLANNING
// the queen spends more time in the hive
/datum/ai_planning_subtree/enter_exit_home/queen
flyback_chance = 85
exit_chance = 5
/datum/ai_planning_subtree/find_and_hunt_target/pollinate
target_key = BB_TARGET_HYDRO
hunting_behavior = /datum/ai_behavior/hunt_target/pollinate
finding_behavior = /datum/ai_behavior/find_hunt_target/pollinate
hunt_targets = list(/obj/machinery/hydroponics)
hunt_range = 10
hunt_chance = 85
/datum/ai_planning_subtree/find_and_hunt_target/pollinate/select_behaviors(datum/ai_controller/controller, seconds_per_tick)
var/atom/atom_pawn = controller.pawn
if(!isturf(atom_pawn.loc))
return
return ..()
@@ -0,0 +1,284 @@
#define BEE_TRAY_RECENT_VISIT 20 SECONDS /// How long in deciseconds until a tray can be visited by a bee again
#define BEE_DEFAULT_COLOUR "#e5e500" /// the colour we make the stripes of the bee if our reagent has no colour (or we have no reagent)
#define BEE_POLLINATE_YIELD_CHANCE 33 /// chance to increase yield of plant
#define BEE_POLLINATE_PEST_CHANCE 33 /// chance to decrease pest of plant
#define BEE_POLLINATE_POTENCY_CHANCE 50 /// chance to increase potancy of plant
#define BEE_FOODGROUPS RAW | MEAT | GORE | BUGS /// the bee food contents
/mob/living/basic/bee
name = "bee"
desc = "Buzzy buzzy bee, stingy sti- Ouch!"
icon_state = ""
icon = 'icons/mob/bees.dmi'
gender = FEMALE
speak_emote = list("buzzes")
melee_damage_lower = 1
melee_damage_upper = 1
attack_verb_continuous = "stings"
attack_verb_simple = "sting"
response_help_continuous = "shoos"
response_help_simple = "shoo"
response_disarm_continuous = "swats away"
response_disarm_simple = "swat away"
response_harm_continuous = "squashes"
response_harm_simple = "squash"
speed = 0.5
maxHealth = 10
health = 10
melee_damage_lower = 1
melee_damage_upper = 1
melee_attack_cooldown_min = 1.5 SECONDS
melee_attack_cooldown_max = 2.5 SECONDS
attack_sound = null // Stings are quiet
faction = list("hostile")
pass_flags = PASSTABLE | PASSGRILLE | PASSMOB
ventcrawler = VENTCRAWLER_ALWAYS
mob_size = MOB_SIZE_TINY
mob_biotypes = MOB_ORGANIC|MOB_BUG
density = FALSE
atmos_requirements = list("min_oxy" = 0, "max_oxy" = 0, "min_tox" = 0, "max_tox" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0)
minimum_survivable_temperature = 0
gold_core_spawnable = FRIENDLY_SPAWN
basic_mob_flags = DEL_ON_DEATH
initial_traits = list(TRAIT_FLYING)
ai_controller = /datum/ai_controller/basic_controller/bee
/// the reagent the bee has
var/datum/reagent/beegent = null
/// the house we live in
var/obj/structure/beebox/beehome = null
/// our icon base
var/icon_base = "bee"
/// Icon creation
var/static/list/bee_icons = list()
/// the bee is a queen?
var/is_queen = FALSE
/// Is this a syndibee?
var/bee_syndicate = FALSE
/mob/living/basic/bee/Initialize(mapload)
. = ..()
generate_bee_visuals()
AddComponent(/datum/component/swarming)
/mob/living/basic/bee/Process_Spacemove(movement_dir = 0, continuous_move = FALSE)
return TRUE
/mob/living/basic/bee/examine(mob/user)
. = ..()
if(!bee_syndicate && !beehome)
. += "<span class='warning'>This bee is homeless!</span>"
/mob/living/basic/bee/Destroy()
if(beehome)
beehome.bees -= src
beehome = null
beegent = null
return ..()
/mob/living/basic/bee/death(gibbed)
. = ..()
if(!.)
return
if(beehome)
if(beehome.bees)
beehome.bees.Remove(src)
beehome = null
// All bee sprites are made up of overlays. They do not have any special sprite overlays for items placed on them, such as collars, so this proc is unneeded.
/mob/living/basic/bee/regenerate_icons()
return
/mob/living/basic/bee/proc/generate_bee_visuals()
overlays.Cut()
var/col = BEE_DEFAULT_COLOUR
if(beegent && beegent.color)
col = beegent.color
var/image/base
if(!bee_icons["[icon_base]_base"])
bee_icons["[icon_base]_base"] = image(icon = 'icons/mob/bees.dmi', icon_state = "[icon_base]_base")
base = bee_icons["[icon_base]_base"]
overlays += base
var/image/greyscale
if(!bee_icons["[icon_base]_grey_[col]"])
bee_icons["[icon_base]_grey_[col]"] = image(icon = 'icons/mob/bees.dmi', icon_state = "[icon_base]_grey")
greyscale = bee_icons["[icon_base]_grey_[col]"]
greyscale.color = col
overlays += greyscale
var/image/wings
if(!bee_icons["[icon_base]_wings"])
bee_icons["[icon_base]_wings"] = image(icon = 'icons/mob/bees.dmi', icon_state = "[icon_base]_wings")
wings = bee_icons["[icon_base]_wings"]
overlays += wings
/mob/living/basic/bee/melee_attack(atom/target, list/modifiers, ignore_cooldown)
if(istype(target, /obj/machinery/hydroponics))
var/obj/machinery/hydroponics/hydro = target
pollinate(hydro)
return FALSE
if(istype(target, /obj/structure/beebox))
var/obj/structure/beebox/hive = target
handle_habitation(hive)
return FALSE
. = ..()
if(. && isliving(target) && (!client || a_intent == INTENT_HARM))
make_opaque()
var/mob/living/L = target
if(L.reagents)
if(beegent)
beegent.reaction_mob(L, REAGENT_INGEST)
L.reagents.add_reagent(beegent.id, rand(1, 5))
else
L.reagents.add_reagent("spidertoxin", 5)
/mob/living/basic/bee/proc/make_opaque()
// If a bee attacks someone, make it very easy to hit for a while
mouse_opacity = MOUSE_OPACITY_OPAQUE
/mob/living/basic/bee/proc/handle_habitation(obj/structure/beebox/hive)
if(hive == beehome) // if its our home, we enter or exit it
var/drop_location = (src in beehome.contents) ? get_turf(beehome) : beehome
forceMove(drop_location)
return
if(!isnull(hive.queen_bee) && is_queen) // if we are queen and house already have a queen, dont inhabit
return
if(!hive.habitable(src) || !isnull(beehome)) // if not habitable or we already have a home
return
beehome = hive
beehome.bees += src
if(is_queen)
beehome.queen_bee = src
/mob/living/basic/bee/proc/reagent_incompatible(mob/living/basic/bee/ruler)
if(!ruler)
return FALSE
if(ruler.beegent?.type != beegent?.type)
return TRUE
return FALSE
/mob/living/basic/bee/proc/pollinate(obj/machinery/hydroponics/Hydro)
if(!istype(Hydro) || !Hydro.myseed || Hydro.dead || Hydro.recent_bee_visit || Hydro.lid_closed)
return
Hydro.recent_bee_visit = TRUE
addtimer(VARSET_CALLBACK(Hydro, recent_bee_visit, FALSE), BEE_TRAY_RECENT_VISIT)
var/growth = health // Health also means how many bees are in the swarm, roughly.
// better healthier plants!
Hydro.adjustHealth(growth*0.5)
if(prob(BEE_POLLINATE_PEST_CHANCE))
Hydro.adjustPests(-10)
if(prob(BEE_POLLINATE_YIELD_CHANCE) && !Hydro.self_sustaining)
Hydro.yieldmod = 2
if(beehome)
beehome.bee_resources = min(beehome.bee_resources + growth, 100)
/mob/living/basic/bee/proc/assign_reagent(datum/reagent/R)
if(istype(R))
beegent = R
name = "[initial(name)] ([R.name])"
generate_bee_visuals()
/mob/living/basic/bee/queen
name = "queen bee"
desc = "She's the queen of bees, BZZ BZZ!"
icon_base = "queen"
is_queen = TRUE
mouse_opacity = MOUSE_OPACITY_OPAQUE
ai_controller = /datum/ai_controller/basic_controller/queen_bee
// leave pollination for the peasent bees
/mob/living/basic/bee/queen/melee_attack(atom/target, list/modifiers, ignore_cooldown)
. = ..()
if(. && beegent && isliving(target))
var/mob/living/L = target
beegent.reaction_mob(L, REAGENT_TOUCH)
L.reagents.add_reagent(beegent.id, rand(1, 5))
/mob/living/basic/bee/queen/pollinate()
return
/obj/item/queen_bee
name = "queen bee"
desc = "She's the queen of bees, BZZ BZZ!"
icon = 'icons/mob/bees.dmi'
icon_state = "queen_item"
gender = FEMALE
new_attack_chain = TRUE
/// Associated bee mob
var/mob/living/basic/bee/queen/queen
/obj/item/queen_bee/item_interaction(mob/living/user, obj/item/I, list/modifiers)
if(!istype(I, /obj/item/reagent_containers/syringe))
return ..()
var/obj/item/reagent_containers/syringe/S = I
if(S.reagents.has_reagent("royal_bee_jelly")) // We check it twice because if we use an if/else statement, it won't catch the check for blacklisted chemicals below
if(!S.reagents.has_reagent("royal_bee_jelly", 5))
to_chat(user, "<span class='warning'>You don't have enough royal bee jelly to split a bee in two!</span>")
return
S.reagents.remove_reagent("royal_bee_jelly", 5)
var/obj/item/queen_bee/qb = new(user.drop_location())
qb.queen = new(qb)
if(queen?.beegent)
qb.queen.assign_reagent(queen.beegent) //Bees use the global singleton instances of reagents, so we don't need to worry about one bee being deleted and her copies losing their reagents.
user.put_in_active_hand(qb)
user.visible_message("<span class='notice'>[user] injects [src] with royal bee jelly, causing it to split into two bees, MORE BEES!</span>", "<span class='warning'>You inject [src] with royal bee jelly, causing it to split into two bees, MORE BEES!</span>")
return
var/datum/reagent/R = GLOB.chemical_reagents_list[S.reagents.get_master_reagent_id()]
if(R && S.reagents.has_reagent(R.id, 5))
S.reagents.remove_reagent(R.id, 5) // Whether or not the chemical is blocked, we want it gone just because you tried to
if(R.id in GLOB.blocked_chems)
to_chat(user, "<span class='warning'>The [src]'s immune system rejects [R.name]!</span>")
return
queen.assign_reagent(R)
user.visible_message("<span class='warning'>[user] injects [src]'s genome with [R.name], mutating its DNA!</span>", "<span class='warning'>You inject [src]'s genome with [R.name], mutating its DNA!</span>")
name = queen.name
else
to_chat(user, "<span class='warning'>You don't have enough units of that chemical to modify the bee's DNA!</span>")
/obj/item/queen_bee/bought/Initialize(mapload)
. = ..()
queen = new(src)
/obj/item/queen_bee/Destroy()
QDEL_NULL(queen)
return ..()
// Syndicate Bees
/mob/living/basic/bee/syndi
name = "syndi-bee"
desc = "The result of a large influx of BEES!"
melee_damage_lower = 5
melee_damage_upper = 5
maxHealth = 25
health = 25
faction = list("hostile", "syndicate")
bee_syndicate = TRUE
mouse_opacity = MOUSE_OPACITY_OPAQUE
ai_controller = /datum/ai_controller/basic_controller/syndibee
/mob/living/basic/bee/syndi/Initialize(mapload)
. = ..()
beegent = GLOB.chemical_reagents_list["facid"] // Prepare to die
/mob/living/basic/bee/syndi/assign_reagent(datum/reagent/R)
return
/mob/living/basic/bee/syndi/pollinate() // No Pollination
return
#undef BEE_TRAY_RECENT_VISIT
#undef BEE_DEFAULT_COLOUR
#undef BEE_POLLINATE_YIELD_CHANCE
#undef BEE_POLLINATE_PEST_CHANCE
#undef BEE_POLLINATE_POTENCY_CHANCE
#undef BEE_FOODGROUPS
@@ -50,7 +50,7 @@
/mob/living/basic/chick,
/mob/living/basic/butterfly,
/mob/living/simple_animal/parrot,
/mob/living/simple_animal/hostile/poison/bees,
/mob/living/basic/bee,
)
suicide_messages = list(
@@ -56,7 +56,7 @@
/mob/living/basic/crab,
/mob/living/basic/butterfly,
/mob/living/simple_animal/parrot,
/mob/living/simple_animal/hostile/poison/bees,
/mob/living/basic/bee,
)
suicide_messages = list(
@@ -46,7 +46,7 @@
/mob/living/basic/crab,
/mob/living/basic/butterfly,
/mob/living/simple_animal/parrot,
/mob/living/simple_animal/hostile/poison/bees,
/mob/living/basic/bee,
)
suicide_messages = list(
@@ -1,394 +0,0 @@
#define BEE_IDLE_ROAMING 70 //The value of idle at which a bee in a beebox will try to wander
#define BEE_IDLE_GOHOME 0 //The value of idle at which a bee will try to go home
#define BEE_PROB_GOHOME 35 //Probability to go home when idle is below BEE_IDLE_GOHOME
#define BEE_PROB_GOROAM 5 //Probability to go roaming when idle is above BEE_IDLE_ROAMING
#define BEE_TRAY_RECENT_VISIT 200 //How long in deciseconds until a tray can be visited by a bee again
#define BEE_DEFAULT_COLOUR "#e5e500" //the colour we make the stripes of the bee if our reagent has no colour (or we have no reagent)
#define BEE_POLLINATE_YIELD_CHANCE 33
#define BEE_POLLINATE_PEST_CHANCE 33
/mob/living/simple_animal/hostile/poison/bees
name = "bee"
desc = "Buzzy buzzy bee, stingy sti- Ouch!"
icon_state = ""
icon = 'icons/mob/bees.dmi'
gender = FEMALE
speak_emote = list("buzzes")
emote_hear = list("buzzes")
turns_per_move = 0
melee_damage_lower = 1
melee_damage_upper = 1
attacktext = "stings"
response_help = "shoos"
response_disarm = "swats away"
response_harm = "squashes"
maxHealth = 10
health = 10
faction = list("hostile")
move_to_delay = 0
obj_damage = 0
environment_smash = 0
pass_flags = PASSTABLE | PASSGRILLE | PASSMOB
density = FALSE
mob_size = MOB_SIZE_TINY
mob_biotypes = MOB_ORGANIC | MOB_BUG
gold_core_spawnable = HOSTILE_SPAWN
search_objects = TRUE //have to find those plant trays!
//Spaceborn beings don't get hurt by space
atmos_requirements = list("min_oxy" = 0, "max_oxy" = 0, "min_tox" = 0, "max_tox" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0)
minbodytemp = 0
del_on_death = TRUE
initial_traits = list(TRAIT_FLYING)
var/datum/reagent/beegent = null //hehe, beegent
var/obj/structure/beebox/beehome = null
var/idle = 0
var/isqueen = FALSE
var/bee_syndicate = FALSE
var/icon_base = "bee"
var/last_attack = 0
var/opacity_time = 1 MINUTES
var/static/list/bee_icons = list()
var/static/beehometypecache = typecacheof(/obj/structure/beebox)
var/static/hydroponicstypecache = typecacheof(/obj/machinery/hydroponics)
/mob/living/simple_animal/hostile/poison/bees/Process_Spacemove(movement_dir = 0, continuous_move = FALSE)
return TRUE
/mob/living/simple_animal/hostile/poison/bees/Initialize(mapload)
. = ..()
AddComponent(/datum/component/swarming)
generate_bee_visuals()
/mob/living/simple_animal/hostile/poison/bees/Destroy()
beegent = null
if(beehome)
if(beehome.bees)
beehome.bees.Remove(src)
beehome = null
return ..()
/mob/living/simple_animal/hostile/poison/bees/death(gibbed)
. = ..()
if(!.)
return
if(beehome)
if(beehome.bees)
beehome.bees.Remove(src)
beehome = null
/mob/living/simple_animal/hostile/poison/bees/examine(mob/user)
. = ..()
if(!bee_syndicate && !beehome)
. += "<span class='warning'>This bee is homeless!</span>"
/mob/living/simple_animal/hostile/poison/bees/ListTargets() // Bee processing is expessive, so we override them finding targets here.
if(!search_objects) //In case we want to have purely hostile bees
return ..()
else
. = list() // The following code is only very slightly slower than just returning oview(vision_range, targets_from), but it saves us much more work down the line
var/list/searched_for = oview(vision_range, targets_from)
for(var/obj/A in searched_for)
. += A
for(var/mob/A in searched_for)
. += A
// All bee sprites are made up of overlays. They do not have any special sprite overlays for items placed on them, such as collars, so this proc is unneeded.
/mob/living/simple_animal/hostile/poison/bees/regenerate_icons()
return
/mob/living/simple_animal/hostile/poison/bees/proc/generate_bee_visuals()
overlays.Cut()
var/col = BEE_DEFAULT_COLOUR
if(beegent && beegent.color)
col = beegent.color
var/image/base
if(!bee_icons["[icon_base]_base"])
bee_icons["[icon_base]_base"] = image(icon = 'icons/mob/bees.dmi', icon_state = "[icon_base]_base")
base = bee_icons["[icon_base]_base"]
overlays += base
var/image/greyscale
if(!bee_icons["[icon_base]_grey_[col]"])
bee_icons["[icon_base]_grey_[col]"] = image(icon = 'icons/mob/bees.dmi', icon_state = "[icon_base]_grey")
greyscale = bee_icons["[icon_base]_grey_[col]"]
greyscale.color = col
overlays += greyscale
var/image/wings
if(!bee_icons["[icon_base]_wings"])
bee_icons["[icon_base]_wings"] = image(icon = 'icons/mob/bees.dmi', icon_state = "[icon_base]_wings")
wings = bee_icons["[icon_base]_wings"]
overlays += wings
//We don't attack beekeepers/people dressed as bees//Todo: bee costume
/mob/living/simple_animal/hostile/poison/bees/CanAttack(atom/the_target)
. = ..()
if(!.)
return FALSE
if(!bee_syndicate && isliving(the_target))
var/mob/living/H = the_target
return !H.bee_friendly()
/mob/living/simple_animal/hostile/poison/bees/Found(atom/A)
if(isliving(A))
var/mob/living/H = A
return !H.bee_friendly()
if(istype(A, /obj/machinery/hydroponics))
var/obj/machinery/hydroponics/Hydro = A
if(Hydro.myseed && !Hydro.dead && !Hydro.recent_bee_visit)
wanted_objects |= hydroponicstypecache //so we only hunt them while they're alive/seeded/not visisted
return TRUE
return FALSE
/mob/living/simple_animal/hostile/poison/bees/AttackingTarget()
//Pollinate
if(istype(target, /obj/machinery/hydroponics))
var/obj/machinery/hydroponics/Hydro = target
pollinate(Hydro)
else if(istype(target, /obj/structure/beebox))
if(target == beehome)
var/obj/structure/beebox/BB = target
forceMove(BB)
toggle_ai(AI_IDLE)
target = null
wanted_objects -= beehometypecache //so we don't attack beeboxes when not going home
return //no don't attack the goddamm box
else
. = ..()
if(. && isliving(target) && (!client || a_intent == INTENT_HARM))
make_opaque()
var/mob/living/L = target
if(L.reagents)
if(beegent)
beegent.reaction_mob(L, REAGENT_INGEST)
L.reagents.add_reagent(beegent.id, rand(1, 5))
else
L.reagents.add_reagent("spidertoxin", 5)
/mob/living/simple_animal/hostile/poison/bees/proc/make_opaque()
// If a bee attacks someone, make it very easy to hit for a while
last_attack = world.time
mouse_opacity = MOUSE_OPACITY_OPAQUE
/mob/living/simple_animal/hostile/poison/bees/Life(seconds, times_fired)
. = ..()
if(mind || (mouse_opacity == initial(mouse_opacity)))
return
var/diff = world.time - last_attack
if(diff >= opacity_time)
mouse_opacity = initial(mouse_opacity)
/mob/living/simple_animal/hostile/poison/bees/sentience_act()
. = ..()
make_opaque()
/mob/living/simple_animal/hostile/poison/bees/proc/assign_reagent(datum/reagent/R)
if(istype(R))
beegent = R
name = "[initial(name)] ([R.name])"
generate_bee_visuals()
/mob/living/simple_animal/hostile/poison/bees/proc/pollinate(obj/machinery/hydroponics/Hydro)
if(!istype(Hydro) || !Hydro.myseed || Hydro.dead || Hydro.recent_bee_visit || Hydro.lid_closed)
target = null
return
target = null //so we pick a new hydro tray next FindTarget(), instead of loving the same plant for eternity
wanted_objects -= hydroponicstypecache //so we only hunt them while they're alive/seeded/not visisted
Hydro.recent_bee_visit = TRUE
spawn(BEE_TRAY_RECENT_VISIT)
if(Hydro)
Hydro.recent_bee_visit = FALSE
var/growth = health //Health also means how many bees are in the swarm, roughly.
//better healthier plants!
Hydro.adjustHealth(growth*0.5)
if(prob(BEE_POLLINATE_PEST_CHANCE))
Hydro.adjustPests(-10)
if(prob(BEE_POLLINATE_YIELD_CHANCE) && !Hydro.self_sustaining)
Hydro.yieldmod = 2
if(beehome)
beehome.bee_resources = min(beehome.bee_resources + growth, 100)
/mob/living/simple_animal/hostile/poison/bees/handle_automated_action()
. = ..()
if(!.)
return
if(!bee_syndicate)
if(!isqueen)
if(loc == beehome)
idle = min(100, ++idle)
if(idle >= BEE_IDLE_ROAMING && prob(BEE_PROB_GOROAM))
forceMove(beehome.drop_location())
else
idle = max(0, --idle)
if(idle <= BEE_IDLE_GOHOME && prob(BEE_PROB_GOHOME))
if(!FindTarget())
wanted_objects |= beehometypecache //so we don't attack beeboxes when not going home
target = beehome
if(!beehome) //add ourselves to a beebox (of the same reagent) if we have no home
for(var/obj/structure/beebox/BB in view(vision_range, src))
if(reagent_incompatible(BB.queen_bee) || length(BB.bees) >= BB.get_max_bees())
continue
BB.bees |= src
beehome = BB
break // End loop after the first compatible find.
//Botany Queen Bee
/mob/living/simple_animal/hostile/poison/bees/queen
name = "queen bee"
desc = "She's the queen of bees, BZZ BZZ!"
icon_base = "queen"
isqueen = TRUE
mouse_opacity = MOUSE_OPACITY_OPAQUE
//the Queen doesn't leave the box on her own, and she CERTAINLY doesn't pollinate by herself
/mob/living/simple_animal/hostile/poison/bees/queen/Found(atom/A)
return FALSE
//leave pollination for the peasent bees
/mob/living/simple_animal/hostile/poison/bees/queen/AttackingTarget()
. = ..()
if(. && beegent && isliving(target))
var/mob/living/L = target
beegent.reaction_mob(L, REAGENT_TOUCH)
L.reagents.add_reagent(beegent.id, rand(1, 5))
//PEASENT BEES
/mob/living/simple_animal/hostile/poison/bees/queen/pollinate()
return
/mob/living/simple_animal/hostile/poison/bees/proc/reagent_incompatible(mob/living/simple_animal/hostile/poison/bees/B)
if(!B)
return FALSE
if(B.beegent && beegent && B.beegent.id != beegent.id || B.beegent && !beegent || !B.beegent && beegent)
return TRUE
return FALSE
/obj/item/queen_bee
name = "queen bee"
desc = "She's the queen of bees, BZZ BZZ!"
icon = 'icons/mob/bees.dmi'
icon_state = "queen_item"
gender = FEMALE
var/mob/living/simple_animal/hostile/poison/bees/queen/queen
/obj/item/queen_bee/attackby__legacy__attackchain(obj/item/I, mob/user, params)
if(!istype(I, /obj/item/reagent_containers/syringe))
return ..()
var/obj/item/reagent_containers/syringe/S = I
if(S.reagents.has_reagent("royal_bee_jelly")) // We check it twice because if we use an if/else statement, it won't catch the check for blacklisted chemicals below
if(!S.reagents.has_reagent("royal_bee_jelly", 5))
to_chat(user, "<span class='warning'>You don't have enough royal bee jelly to split a bee in two!</span>")
return
S.reagents.remove_reagent("royal_bee_jelly", 5)
var/obj/item/queen_bee/qb = new(user.drop_location())
qb.queen = new(qb)
if(queen?.beegent)
qb.queen.assign_reagent(queen.beegent) //Bees use the global singleton instances of reagents, so we don't need to worry about one bee being deleted and her copies losing their reagents.
user.put_in_active_hand(qb)
user.visible_message("<span class='notice'>[user] injects [src] with royal bee jelly, causing it to split into two bees, MORE BEES!</span>", "<span class='warning'>You inject [src] with royal bee jelly, causing it to split into two bees, MORE BEES!</span>")
return
var/datum/reagent/R = GLOB.chemical_reagents_list[S.reagents.get_master_reagent_id()]
if(R && S.reagents.has_reagent(R.id, 5))
S.reagents.remove_reagent(R.id, 5) // Whether or not the chemical is blocked, we want it gone just because you tried to
if(R.id in GLOB.blocked_chems)
to_chat(user, "<span class='warning'>The [src]'s immune system rejects [R.name]!</span>")
return
queen.assign_reagent(R)
user.visible_message("<span class='warning'>[user] injects [src]'s genome with [R.name], mutating its DNA!</span>", "<span class='warning'>You inject [src]'s genome with [R.name], mutating its DNA!</span>")
name = queen.name
else
to_chat(user, "<span class='warning'>You don't have enough units of that chemical to modify the bee's DNA!</span>")
/obj/item/queen_bee/bought/Initialize(mapload)
. = ..()
queen = new(src)
/obj/item/queen_bee/Destroy()
QDEL_NULL(queen)
return ..()
/mob/living/simple_animal/hostile/poison/bees/queen/consider_wakeup()
if(beehome && loc == beehome) // The queen is home and doesn't have to do anything.
return
return ..()
/mob/living/simple_animal/hostile/poison/bees/consider_wakeup()
if(beehome && loc == beehome) // If bees are chilling in their nest, they're not actively looking for targets
idle = min(100, ++idle)
if(idle >= BEE_IDLE_ROAMING && prob(BEE_PROB_GOROAM))
toggle_ai(AI_ON)
forceMove(beehome.drop_location())
else
..()
//Syndicate Bees
/mob/living/simple_animal/hostile/poison/bees/syndi
name = "syndi-bee"
desc = "The result of a large influx of BEES!"
melee_damage_lower = 5
melee_damage_upper = 5
maxHealth = 25
health = 25
faction = list("hostile", "syndicate")
search_objects = FALSE //these bees don't care about trivial things like plants, especially when there is havoc to sow
bee_syndicate = TRUE
var/list/master_and_friends = list()
mouse_opacity = MOUSE_OPACITY_OPAQUE
/mob/living/simple_animal/hostile/poison/bees/syndi/Initialize(mapload)
. = ..()
beegent = GLOB.chemical_reagents_list["facid"] //Prepare to die
/mob/living/simple_animal/hostile/poison/bees/syndi/Destroy()
master_and_friends.Cut()
return ..()
/mob/living/simple_animal/hostile/poison/bees/syndi/assign_reagent(datum/reagent/R)
return
/mob/living/simple_animal/hostile/poison/bees/syndi/pollinate() // No Pollination
return
/mob/living/simple_animal/hostile/poison/bees/syndi/Found(atom/A) //Typical usual hostile mob targeting list
return
/mob/living/simple_animal/hostile/poison/bees/syndi/CanAttack(atom/the_target)
. = ..()
if(!.)
return FALSE
if(isliving(the_target))
var/mob/living/L = the_target
if(ishuman(L))
var/mob/living/carbon/human/H = L
if(H in master_and_friends)
return FALSE
return TRUE
/mob/living/simple_animal/hostile/poison/bees/syndi/AttackingTarget()
. = ..()
if(. && target && isliving(target))
var/mob/living/L = target
if(L.stat)
LoseTarget()
#undef BEE_IDLE_ROAMING
#undef BEE_IDLE_GOHOME
#undef BEE_PROB_GOHOME
#undef BEE_PROB_GOROAM
#undef BEE_TRAY_RECENT_VISIT
#undef BEE_DEFAULT_COLOUR
#undef BEE_POLLINATE_YIELD_CHANCE
#undef BEE_POLLINATE_PEST_CHANCE
+2 -2
View File
@@ -474,9 +474,9 @@
user.visible_message("<span class='danger'>[user] devours \the [affecting]!</span>")
if(affecting.mind)
add_attack_logs(attacker, affecting, "Devoured")
if(istype(affecting, /mob/living/simple_animal/hostile/poison/bees)) //Eating a bee will end up damaging you
if(istype(affecting, /mob/living/basic/bee)) //Eating a bee will end up damaging you
var/obj/item/organ/external/mouth = user.get_organ(BODY_ZONE_PRECISE_MOUTH)
var/mob/living/simple_animal/hostile/poison/bees/B = affecting
var/mob/living/basic/bee/B = affecting
mouth.receive_damage(1)
if(B.beegent)
B.beegent.reaction_mob(assailant, REAGENT_INGEST)
@@ -49,7 +49,7 @@
beeagents += R
var/bee_amount = round(created_volume * 0.2)
for(var/i in 1 to bee_amount)
var/mob/living/simple_animal/hostile/poison/bees/new_bee = new(location)
var/mob/living/basic/bee/new_bee = new(location)
if(LAZYLEN(beeagents))
new_bee.assign_reagent(pick(beeagents))
+1 -1
View File
@@ -135,7 +135,7 @@
/mob/living/simple_animal/pet/dog/pug,
/mob/living/basic/bear/black,
/mob/living/basic/bear/brown,
/mob/living/simple_animal/hostile/poison/bees,
/mob/living/basic/bee,
/mob/living/basic/carp
)