seedling basic mobs (#78224)

## About The Pull Request
this is a mob that was removed from the game since years ago. this pr
refactors them and readds them to the game. seedlings now can have
different colored petals!

![petalcolors](https://github.com/tgstation/tgstation/assets/138636438/7c657c49-9186-4563-a93f-eba35e335ce9)


to obtain this monster, botany can now grow them out of 'seedling' seeds
that they can find in the exotic seeds crate. this mob loves to help all
plants and when its grown out of the seed, it will help the botanists
look after their plants. it can water plants (if it finds a bucket),
remove weeds and remove dead plants. they will also use their solarbeam
ability to provide small healing to damaged plants. it will try to
refill its bucket with water if there is a water source near by. it will
also follow the orders of whoever harvested it.


![plantbotany](https://github.com/tgstation/tgstation/assets/138636438/4cde0613-0869-4fea-8851-2e6f4680d9d7)

traitors can buy a seed from the uplink that grows an evil version of
the seedlings, they can command them to attack people and use abilities
and to cause chaos on the station.


## Why It's Good For The Game
refacotrs the seedlings into basic mobs

## Changelog
🆑
refactor: seedlings have been refactored into basic mobs please report
any bugs
add: seedlings now can have different colored petals and can look after
botanys plants
add: seedlings are re-added to the game! they grow out of seedling seeds
obtainable from exotic seed crates or traitor uplink
/🆑
This commit is contained in:
Ben10Omintrix
2023-09-22 17:28:48 +03:00
committed by GitHub
parent a2ff18730d
commit 0976420f2f
16 changed files with 654 additions and 256 deletions
+16
View File
@@ -130,3 +130,19 @@
#define BB_AUTOMATED_MINING "automated_mining"
/// key that stores the nearest dead human
#define BB_NEARBY_DEAD_MINER "nearby_dead_miner"
//seedling keys
/// the water can we will pick up
#define BB_WATERCAN_TARGET "watercan_target"
/// the hydrotray we will heal
#define BB_HYDROPLANT_TARGET "hydroplant_target"
/// minimum weed levels for us to cure
#define BB_WEEDLEVEL_THRESHOLD "weedlevel_threshold"
/// minimum water levels for us to refill
#define BB_WATERLEVEL_THRESHOLD "waterlevel_threshold"
/// key holds our solarbeam ability
#define BB_SOLARBEAM_ABILITY "solarbeam_ability"
/// key holds our rapid seeds ability
#define BB_RAPIDSEEDS_ABILITY "rapidseeds_ability"
/// key holds the tray we will beam
#define BB_BEAMABLE_HYDROPLANT_TARGET "beamable_hydroplant_target"
@@ -16,6 +16,8 @@
var/hunt_range = 2
/// What are the chances we hunt something at any given moment
var/hunt_chance = 100
///do we finish planning subtree
var/finish_planning = TRUE
/datum/ai_planning_subtree/find_and_hunt_target/New()
. = ..()
@@ -35,13 +37,15 @@
// We're not hunting anything, look around for something
if(isnull(hunted))
controller.queue_behavior(finding_behavior, target_key, hunt_targets, hunt_range)
return
// We ARE hunting something, execute the hunt.
// Note that if our AI controller has multiple hunting subtrees set,
// we may accidentally be executing another tree's hunt - not ideal,
// try to set a unique target key if you have multiple
else
controller.queue_behavior(hunting_behavior, target_key, BB_HUNTING_COOLDOWN)
controller.queue_behavior(hunting_behavior, target_key, BB_HUNTING_COOLDOWN)
if(finish_planning)
return SUBTREE_RETURN_FINISH_PLANNING //If we're hunting we're too busy for anything else
/// Finds a specific atom type to hunt.
@@ -124,3 +128,19 @@
/datum/ai_behavior/hunt_target/unarmed_attack_target/target_caught(mob/living/hunter, obj/structure/cable/hunted)
hunter.UnarmedAttack(hunted, TRUE)
/datum/ai_behavior/hunt_target/use_ability_on_target
always_reset_target = TRUE
///the ability we will use
var/ability_key
/datum/ai_behavior/hunt_target/use_ability_on_target/perform(seconds_per_tick, datum/ai_controller/controller, hunting_target_key, hunting_cooldown_key)
var/datum/action/cooldown/ability = controller.blackboard[ability_key]
if(QDELETED(ability) || !ability.IsAvailable())
finish_action(controller, FALSE, hunting_target_key)
return ..()
/datum/ai_behavior/hunt_target/use_ability_on_target/target_caught(mob/living/hunter, atom/hunted)
var/datum/action/cooldown/ability = hunter.ai_controller.blackboard[ability_key]
ability.InterceptClickOn(hunter, null, hunted)
@@ -56,6 +56,7 @@
/obj/item/seeds/liberty = 5,
/obj/item/seeds/replicapod = 5,
/obj/item/seeds/reishi = 5,
/obj/item/seeds/seedling = 5,
/obj/item/seeds/nettle/death = 1,
/obj/item/seeds/plump/walkingmushroom = 1,
/obj/item/seeds/cannabis/rainbow = 1,
+14 -12
View File
@@ -62,18 +62,20 @@
including one replica-pod seed and two mystery seeds!"
cost = CARGO_CRATE_VALUE * 3
access_view = ACCESS_HYDROPONICS
contains = list(/obj/item/seeds/nettle,
/obj/item/seeds/replicapod,
/obj/item/seeds/plump,
/obj/item/seeds/liberty,
/obj/item/seeds/amanita,
/obj/item/seeds/reishi,
/obj/item/seeds/bamboo,
/obj/item/seeds/eggplant/eggy,
/obj/item/seeds/rainbow_bunch,
/obj/item/seeds/shrub,
/obj/item/seeds/random = 2,
)
contains = list(
/obj/item/seeds/amanita,
/obj/item/seeds/bamboo,
/obj/item/seeds/eggplant/eggy,
/obj/item/seeds/liberty,
/obj/item/seeds/nettle,
/obj/item/seeds/plump,
/obj/item/seeds/replicapod,
/obj/item/seeds/reishi,
/obj/item/seeds/rainbow_bunch,
/obj/item/seeds/seedling,
/obj/item/seeds/shrub,
/obj/item/seeds/random = 2,
)
crate_name = "exotic seeds crate"
crate_type = /obj/structure/closet/crate/hydroponics
@@ -0,0 +1,28 @@
/obj/item/seeds/seedling
name = "pack of seedling seeds"
desc = "These seeds grow into a floral assistant which can help look after other plants!"
icon_state = "seed-seedling"
growing_icon = 'icons/obj/service/hydroponics/growing_fruits.dmi'
species = "seedling"
plantname = "Seedling Plant"
product = /mob/living/basic/seedling
lifespan = 40
endurance = 7
maturation = 10
production = 1
growthstages = 2
yield = 1
instability = 15
potency = 30
/obj/item/seeds/seedling/harvest(mob/harvester)
var/obj/machinery/hydroponics/parent = loc
var/list/grow_locations = get_adjacent_open_turfs(parent)
var/turf/final_location = length(grow_locations) ? pick(grow_locations) : get_turf(parent)
var/mob/living/basic/seedling/seed_pet = new product(final_location)
seed_pet.befriend(harvester)
parent.update_tray(user = harvester, product_count = 1)
/obj/item/seeds/seedling/evil
product = /mob/living/basic/seedling/meanie
icon_state = "seed-seedling-evil"
@@ -93,6 +93,10 @@
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
@@ -0,0 +1,348 @@
#define SEEDLING_STATE_NEUTRAL 0
#define SEEDLING_STATE_WARMUP 1
#define SEEDLING_STATE_ACTIVE 2
/**
* A mobile plant with a rapid ranged attack.
* It can pick up watering cans and look after plants.
*/
/mob/living/basic/seedling
name = "seedling"
desc = "This oversized, predatory flower conceals what can only be described as an organic energy cannon."
icon = 'icons/mob/simple/jungle/seedling.dmi'
icon_state = "seedling"
icon_living = "seedling"
icon_dead = "seedling_dead"
habitable_atmos = list("min_oxy" = 2, "max_oxy" = 0, "min_plas" = 0, "max_plas" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0)
minimum_survivable_temperature = 0
maximum_survivable_temperature = 450
mob_biotypes = MOB_ORGANIC | MOB_PLANT
maxHealth = 100
health = 100
pixel_y = -14
base_pixel_y = -14
pixel_x = -14
base_pixel_x = -14
response_harm_continuous = "strikes"
response_harm_simple = "strike"
melee_damage_lower = 30
melee_damage_upper = 30
lighting_cutoff_green = 20
lighting_cutoff_blue = 25
mob_size = MOB_SIZE_LARGE
attack_sound = 'sound/weapons/bladeslice.ogg'
attack_vis_effect = ATTACK_EFFECT_SLASH
ai_controller = /datum/ai_controller/basic_controller/seedling
///the state of combat we are in
var/combatant_state = SEEDLING_STATE_NEUTRAL
///the colors our petals can have
var/static/list/possible_colors = list(COLOR_RED, COLOR_YELLOW, COLOR_OLIVE, COLOR_CYAN)
///appearance when we are in our normal state
var/mutable_appearance/petal_neutral
///appearance when we are in our warmup state
var/mutable_appearance/petal_warmup
///appearance when we are in the firing state
var/mutable_appearance/petal_active
///appearance when we are dead
var/mutable_appearance/petal_dead
///the bucket we carry
var/obj/item/reagent_containers/cup/held_can
///commands we follow
var/list/seedling_commands = list(
/datum/pet_command/idle,
/datum/pet_command/free,
/datum/pet_command/follow,
)
/mob/living/basic/seedling/Initialize(mapload)
. = ..()
var/datum/action/cooldown/mob_cooldown/projectile_attack/rapid_fire/seedling/seed_attack = new(src)
seed_attack.Grant(src)
ai_controller.set_blackboard_key(BB_RAPIDSEEDS_ABILITY, seed_attack)
var/datum/action/cooldown/mob_cooldown/solarbeam/beam_attack = new(src)
beam_attack.Grant(src)
ai_controller.set_blackboard_key(BB_SOLARBEAM_ABILITY, beam_attack)
var/petal_color = pick(possible_colors)
petal_neutral = mutable_appearance(icon, "[icon_state]_overlay")
petal_neutral.color = petal_color
petal_warmup = mutable_appearance(icon, "[icon_state]_charging_overlay")
petal_warmup.color = petal_color
petal_active = mutable_appearance(icon, "[icon_state]_fire_overlay")
petal_active.color = petal_color
petal_dead = mutable_appearance(icon, "[icon_state]_dead_overlay")
petal_dead.color = petal_color
AddElement(/datum/element/wall_smasher)
AddComponent(/datum/component/obeys_commands, seedling_commands)
RegisterSignal(src, COMSIG_HOSTILE_PRE_ATTACKINGTARGET, PROC_REF(pre_attack))
RegisterSignal(src, COMSIG_KB_MOB_DROPITEM_DOWN, PROC_REF(drop_can))
update_appearance()
/mob/living/basic/seedling/proc/pre_attack(mob/living/puncher, atom/target)
SIGNAL_HANDLER
if(istype(target, /obj/machinery/hydroponics))
treat_hydro_tray(target)
return COMPONENT_HOSTILE_NO_ATTACK
if(isnull(held_can))
return
if(istype(target, /obj/structure/sink) || istype(target, /obj/structure/reagent_dispensers))
INVOKE_ASYNC(held_can, TYPE_PROC_REF(/obj/item, melee_attack_chain), src, target)
return COMPONENT_HOSTILE_NO_ATTACK
///seedlings can water trays, remove weeds, or remove dead plants
/mob/living/basic/seedling/proc/treat_hydro_tray(obj/machinery/hydroponics/hydro)
if(hydro.plant_status == HYDROTRAY_PLANT_DEAD)
balloon_alert(src, "dead plant removed")
hydro.set_seed(null)
return
if(hydro.weedlevel > 0)
balloon_alert(src, "weeds uprooted")
hydro.set_weedlevel(0)
return
var/list/can_reagents = held_can?.reagents.reagent_list
if(!length(can_reagents))
return
if((locate(/datum/reagent/water) in can_reagents) && (hydro.waterlevel < hydro.maxwater))
INVOKE_ASYNC(held_can, TYPE_PROC_REF(/obj/item, melee_attack_chain), src, hydro)
return
/mob/living/basic/seedling/UnarmedAttack(atom/attack_target, proximity_flag, list/modifiers)
. = ..()
if(!. || !proximity_flag || held_can)
return
if(!istype(attack_target, /obj/item/reagent_containers/cup/watering_can))
return
var/obj/item/can_target = attack_target
can_target.forceMove(src)
/mob/living/basic/seedling/proc/change_combatant_state(state)
combatant_state = state
update_appearance()
/mob/living/basic/seedling/attackby(obj/item/can, mob/living/carbon/human/user, list/modifiers)
if(istype(can, /obj/item/reagent_containers/cup/watering_can) && isnull(held_can))
can.forceMove(src)
return
return ..()
/mob/living/basic/seedling/Entered(atom/movable/arrived, atom/old_loc, list/atom/old_locs)
if(istype(arrived, /obj/item/reagent_containers/cup/watering_can))
held_can = arrived
update_appearance()
return ..()
/mob/living/basic/seedling/update_overlays()
. = ..()
if(stat == DEAD)
. += petal_dead
return
switch(combatant_state)
if(SEEDLING_STATE_NEUTRAL)
. += petal_neutral
if(held_can)
. += mutable_appearance(icon, "seedling_can_overlay")
if(SEEDLING_STATE_WARMUP)
. += petal_warmup
if(SEEDLING_STATE_ACTIVE)
. += petal_active
/mob/living/basic/seedling/update_icon_state()
. = ..()
if(stat == DEAD)
return
switch(combatant_state)
if(SEEDLING_STATE_NEUTRAL)
icon_state = "seedling"
if(SEEDLING_STATE_WARMUP)
icon_state = "seedling_charging"
if(SEEDLING_STATE_ACTIVE)
icon_state = "seedling_fire"
/mob/living/basic/seedling/proc/drop_can(mob/living/user)
SIGNAL_HANDLER
if(isnull(held_can))
return
dropItemToGround(held_can)
return COMSIG_KB_ACTIVATED
/mob/living/basic/seedling/Exited(atom/movable/gone, direction)
. = ..()
if(gone != held_can)
return
held_can = null
update_appearance()
/mob/living/basic/seedling/death(gibbed)
. = ..()
if(isnull(held_can))
return
held_can.forceMove(drop_location())
/mob/living/basic/seedling/Destroy()
QDEL_NULL(held_can)
return ..()
/mob/living/basic/seedling/meanie
maxHealth = 400
health = 400
faction = list(FACTION_JUNGLE)
ai_controller = /datum/ai_controller/basic_controller/seedling/meanie
seedling_commands = list(
/datum/pet_command/idle,
/datum/pet_command/free,
/datum/pet_command/follow,
/datum/pet_command/point_targetting/attack,
/datum/pet_command/point_targetting/use_ability/solarbeam,
/datum/pet_command/point_targetting/use_ability/rapidseeds,
)
//abilities
/datum/action/cooldown/mob_cooldown/projectile_attack/rapid_fire/seedling
name = "Solar Energy"
button_icon = 'icons/obj/weapons/guns/projectiles.dmi'
button_icon_state = "seedling"
desc = "Fire small beams of solar energy."
cooldown_time = 10 SECONDS
projectile_type = /obj/projectile/seedling
default_projectile_spread = 10
shot_count = 10
shot_delay = 0.2 SECONDS
melee_cooldown_time = 0 SECONDS
shared_cooldown = NONE
///how long we must charge up before firing off
var/charge_up_timer = 3 SECONDS
///is the owner of this ability a seedling?
var/is_seedling = FALSE
/datum/action/cooldown/mob_cooldown/projectile_attack/rapid_fire/seedling/Grant(mob/grant_to)
. = ..()
if(isnull(owner))
return
is_seedling = istype(owner, /mob/living/basic/seedling)
/datum/action/cooldown/mob_cooldown/projectile_attack/rapid_fire/seedling/IsAvailable(feedback)
. = ..()
if(!.)
return FALSE
if(!is_seedling)
return TRUE
var/mob/living/basic/seedling/seed_owner = owner
if(seed_owner.combatant_state != SEEDLING_STATE_NEUTRAL)
if(feedback)
seed_owner.balloon_alert(seed_owner, "charging!")
return FALSE
return TRUE
/datum/action/cooldown/mob_cooldown/projectile_attack/rapid_fire/seedling/Activate(atom/target)
if(is_seedling)
var/mob/living/basic/seedling/seed_owner = owner
seed_owner.change_combatant_state(state = SEEDLING_STATE_WARMUP)
addtimer(CALLBACK(src, PROC_REF(attack_sequence), owner, target), charge_up_timer)
StartCooldown()
return TRUE
/datum/action/cooldown/mob_cooldown/projectile_attack/rapid_fire/seedling/attack_sequence(mob/living/firer, atom/target)
if(is_seedling)
var/mob/living/basic/seedling/seed_owner = owner
seed_owner.change_combatant_state(state = SEEDLING_STATE_ACTIVE)
addtimer(CALLBACK(seed_owner, TYPE_PROC_REF(/mob/living/basic/seedling, change_combatant_state), SEEDLING_STATE_NEUTRAL), 4 SECONDS)
return ..()
/datum/action/cooldown/mob_cooldown/solarbeam
name = "Solar Beam"
button_icon = 'icons/effects/beam.dmi'
button_icon_state = "solar_beam"
desc = "Concentrate the power of the sun onto your target!"
cooldown_time = 30 SECONDS
shared_cooldown = NONE
///how long will it take for us to charge up the beam
var/beam_charge_up = 3 SECONDS
///is the owner of this ability a seedling?
var/is_seedling = FALSE
/datum/action/cooldown/mob_cooldown/solarbeam/Grant(mob/grant_to)
. = ..()
if(isnull(owner))
return
is_seedling = istype(owner, /mob/living/basic/seedling)
/datum/action/cooldown/mob_cooldown/solarbeam/IsAvailable(feedback)
. = ..()
if(!.)
return FALSE
if(!is_seedling)
return TRUE
var/mob/living/basic/seedling/seed_owner = owner
if(seed_owner.combatant_state != SEEDLING_STATE_NEUTRAL)
if(feedback)
seed_owner.balloon_alert(seed_owner, "charging!")
return FALSE
return TRUE
/datum/action/cooldown/mob_cooldown/solarbeam/Activate(atom/target)
if(is_seedling)
var/mob/living/basic/seedling/seed_owner = owner
seed_owner.change_combatant_state(state = SEEDLING_STATE_WARMUP)
var/turf/target_turf = get_turf(target)
playsound(owner, 'sound/effects/seedling_chargeup.ogg', 100, FALSE)
var/obj/effect/temp_visual/solarbeam_killsat/owner_beam = new(get_turf(owner))
animate(owner_beam, transform = matrix().Scale(1, 32), alpha = 255, time = beam_charge_up)
var/obj/effect/temp_visual/solarbeam_killsat/target_beam = new(target_turf)
animate(target_beam, transform = matrix().Scale(2, 1), alpha = 255, time = beam_charge_up)
addtimer(CALLBACK(src, PROC_REF(launch_beam), owner, target_turf), beam_charge_up)
StartCooldown()
return TRUE
///the solarbeam will damage people, otherwise it will heal plants
/datum/action/cooldown/mob_cooldown/solarbeam/proc/launch_beam(mob/living/firer, turf/target_turf)
for(var/atom/target_atom as anything in target_turf)
if(istype(target_atom, /obj/machinery/hydroponics))
var/obj/machinery/hydroponics/hydro = target_atom
hydro.adjust_plant_health(10)
new /obj/effect/temp_visual/heal(target_turf, COLOR_HEALING_CYAN)
if(!isliving(target_atom))
continue
var/mob/living/living_target = target_atom
living_target.adjust_fire_stacks(0.2)
living_target.ignite_mob()
living_target.adjustFireLoss(30)
playsound(target_turf, 'sound/magic/lightningbolt.ogg', 50, TRUE)
if(!is_seedling)
return
var/mob/living/basic/seedling/seed_firer = firer
seed_firer.change_combatant_state(state = SEEDLING_STATE_NEUTRAL)
#undef SEEDLING_STATE_NEUTRAL
#undef SEEDLING_STATE_WARMUP
#undef SEEDLING_STATE_ACTIVE
@@ -0,0 +1,178 @@
/datum/ai_controller/basic_controller/seedling
blackboard = list(
BB_TARGETTING_DATUM = new /datum/targetting_datum/basic,
BB_PET_TARGETTING_DATUM = new /datum/targetting_datum/not_friends,
BB_WEEDLEVEL_THRESHOLD = 3,
BB_WATERLEVEL_THRESHOLD = 90,
)
ai_movement = /datum/ai_movement/basic_avoidance
idle_behavior = /datum/idle_behavior/idle_random_walk
planning_subtrees = list(
/datum/ai_planning_subtree/pet_planning,
/datum/ai_planning_subtree/find_and_hunt_target/watering_can,
/datum/ai_planning_subtree/find_and_hunt_target/fill_watercan,
/datum/ai_planning_subtree/find_and_hunt_target/treat_hydroplants,
/datum/ai_planning_subtree/find_and_hunt_target/beamable_hydroplants,
)
/datum/ai_planning_subtree/find_and_hunt_target/watering_can
target_key = BB_WATERCAN_TARGET
finding_behavior = /datum/ai_behavior/find_hunt_target
hunting_behavior = /datum/ai_behavior/hunt_target/unarmed_attack_target
hunt_targets = list(/obj/item/reagent_containers/cup/watering_can)
hunt_range = 7
/datum/ai_planning_subtree/find_and_hunt_target/watering_can/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick)
var/mob/living/living_pawn = controller.pawn
if(locate(/obj/item/reagent_containers/cup/watering_can) in living_pawn) //we already have what we came for!
return
return ..()
/datum/ai_planning_subtree/find_and_hunt_target/treat_hydroplants
target_key = BB_HYDROPLANT_TARGET
finding_behavior = /datum/ai_behavior/find_and_set/treatable_hydro
hunting_behavior = /datum/ai_behavior/hunt_target/unarmed_attack_target/treat_hydroplant
hunt_targets = list(/obj/machinery/hydroponics)
hunt_range = 7
/datum/ai_behavior/find_and_set/treatable_hydro
/datum/ai_behavior/find_and_set/treatable_hydro/search_tactic(datum/ai_controller/controller, locate_path, search_range)
var/list/possible_trays = list()
var/mob/living/living_pawn = controller.pawn
var/waterlevel_threshold = controller.blackboard[BB_WATERLEVEL_THRESHOLD]
var/weedlevel_threshold = controller.blackboard[BB_WEEDLEVEL_THRESHOLD]
var/watering_can = locate(/obj/item/reagent_containers/cup/watering_can) in living_pawn
for(var/obj/machinery/hydroponics/hydro in oview(search_range, controller.pawn))
if(isnull(hydro.myseed))
continue
if(hydro.waterlevel < waterlevel_threshold && watering_can)
possible_trays += hydro
continue
if(hydro.weedlevel > weedlevel_threshold || hydro.plant_status == HYDROTRAY_PLANT_DEAD)
possible_trays += hydro
continue
if(possible_trays.len)
return pick(possible_trays)
/datum/ai_behavior/hunt_target/unarmed_attack_target/treat_hydroplant
hunt_cooldown = 2 SECONDS
always_reset_target = TRUE
/datum/ai_behavior/hunt_target/unarmed_attack_target/treat_hydroplant/target_caught(mob/living/living_pawn, obj/machinery/hydroponics/hydro_target)
if(QDELETED(hydro_target) || QDELETED(hydro_target.myseed))
return
if(hydro_target.plant_status == HYDROTRAY_PLANT_DEAD)
living_pawn.manual_emote("weeps...") //weep over the dead plants
return ..()
/datum/ai_planning_subtree/find_and_hunt_target/beamable_hydroplants
target_key = BB_BEAMABLE_HYDROPLANT_TARGET
finding_behavior = /datum/ai_behavior/find_and_set/beamable_hydroplants
hunting_behavior = /datum/ai_behavior/hunt_target/use_ability_on_target/solarbeam
hunt_targets = list(/obj/machinery/hydroponics)
hunt_range = 7
/datum/ai_planning_subtree/find_and_hunt_target/beamable_hydroplants/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick)
var/datum/action/cooldown/solar_ability = controller.blackboard[BB_SOLARBEAM_ABILITY]
if(QDELETED(solar_ability) || !solar_ability.IsAvailable())
return
return ..()
/datum/ai_behavior/hunt_target/use_ability_on_target/solarbeam
behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION
required_distance = 2
action_cooldown = 1 MINUTES
ability_key = BB_SOLARBEAM_ABILITY
/datum/ai_behavior/hunt_target/use_ability_on_target/solarbeam/setup(datum/ai_controller/controller, target_key, ability_key)
. = ..()
var/obj/target = controller.blackboard[target_key]
if(QDELETED(target))
return FALSE
set_movement_target(controller, target)
/datum/ai_behavior/find_and_set/beamable_hydroplants/search_tactic(datum/ai_controller/controller, locate_path, search_range)
var/list/possible_trays = list()
for(var/obj/machinery/hydroponics/hydro in oview(search_range, controller.pawn))
if(isnull(hydro.myseed))
continue
if(hydro.plant_health < hydro.myseed.endurance)
possible_trays += hydro
if(possible_trays.len)
return pick(possible_trays)
/datum/ai_planning_subtree/find_and_hunt_target/fill_watercan
target_key = BB_LOW_PRIORITY_HUNTING_TARGET
finding_behavior = /datum/ai_behavior/find_hunt_target/suitable_dispenser
hunting_behavior = /datum/ai_behavior/hunt_target/unarmed_attack_target/water_source
hunt_targets = list(/obj/structure/sink, /obj/structure/reagent_dispensers)
hunt_range = 7
/datum/ai_planning_subtree/find_and_hunt_target/fill_watercan/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick)
var/mob/living/living_pawn = controller.pawn
var/obj/item/reagent_containers/can = locate(/obj/item/reagent_containers/cup/watering_can) in living_pawn
if(isnull(can))
return
if(locate(/datum/reagent/water) in can.reagents.reagent_list)
return
return ..()
/datum/ai_behavior/find_hunt_target/suitable_dispenser
/datum/ai_behavior/find_hunt_target/suitable_dispenser/valid_dinner(mob/living/source, obj/structure/water_source, radius)
if(!(locate(/datum/reagent/water) in water_source.reagents.reagent_list))
return FALSE
return can_see(source, water_source, radius)
/datum/ai_behavior/hunt_target/unarmed_attack_target/water_source
behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_REQUIRE_REACH | AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION
hunt_cooldown = 5 SECONDS
/datum/ai_controller/basic_controller/seedling/meanie
blackboard = list(
BB_TARGETTING_DATUM = new /datum/targetting_datum/basic,
BB_PET_TARGETTING_DATUM = new /datum/targetting_datum/not_friends,
)
planning_subtrees = list(
/datum/ai_planning_subtree/pet_planning,
/datum/ai_planning_subtree/simple_find_target,
/datum/ai_planning_subtree/targeted_mob_ability/seedling_rapid,
/datum/ai_planning_subtree/targeted_mob_ability/solarbeam,
/datum/ai_planning_subtree/basic_melee_attack_subtree,
)
/datum/ai_planning_subtree/targeted_mob_ability/seedling_rapid
ability_key = BB_RAPIDSEEDS_ABILITY
finish_planning = FALSE
/datum/ai_planning_subtree/targeted_mob_ability/solarbeam
ability_key = BB_SOLARBEAM_ABILITY
finish_planning = FALSE
///pet commands
/datum/pet_command/point_targetting/use_ability/solarbeam
command_name = "Launch solarbeam"
command_desc = "Command your pet to launch a solarbeam at your target!"
radial_icon = 'icons/effects/beam.dmi'
radial_icon_state = "solar_beam"
speech_commands = list("beam", "solar")
pet_ability_key = BB_SOLARBEAM_ABILITY
/datum/pet_command/point_targetting/use_ability/rapidseeds
command_name = "Rapid seeds"
command_desc = "Command your pet to launch a volley of seeds at your target!"
radial_icon = 'icons/obj/weapons/guns/projectiles.dmi'
radial_icon_state = "seedling"
speech_commands = list("rapid", "seeds", "volley")
pet_ability_key = BB_RAPIDSEEDS_ABILITY
@@ -0,0 +1,32 @@
/obj/projectile/seedling
name = "solar energy"
icon_state = "seedling"
damage = 10
damage_type = BURN
light_range = 2
armor_flag = ENERGY
light_color = LIGHT_COLOR_DIM_YELLOW
speed = 1.6
hitsound = 'sound/weapons/sear.ogg'
hitsound_wall = 'sound/weapons/effects/searwall.ogg'
nondirectional_sprite = TRUE
/obj/projectile/seedling/on_hit(atom/target)
if(!isliving(target))
return ..()
var/mob/living/living_target = target
if(FACTION_JUNGLE in living_target.faction)
return
return ..()
/obj/effect/temp_visual/solarbeam_killsat
name = "beam of solar energy"
icon_state = "solar_beam"
icon = 'icons/effects/beam.dmi'
plane = LIGHTING_PLANE
layer = LIGHTING_PRIMARY_LAYER
duration = 3 SECONDS
alpha = 200
randomdir = FALSE
@@ -1,240 +0,0 @@
#define SEEDLING_STATE_NEUTRAL 0
#define SEEDLING_STATE_WARMUP 1
#define SEEDLING_STATE_ACTIVE 2
#define SEEDLING_STATE_RECOVERY 3
//A plant rooted in the ground that forfeits its melee attack in favor of ranged barrages.
//It will fire flurries of solar energy, and occasionally charge up a powerful blast that makes it vulnerable to attack.
/mob/living/simple_animal/hostile/jungle/seedling
name = "seedling"
desc = "This oversized, predatory flower conceals what can only be described as an organic energy cannon, and it will not die until its hidden vital organs are sliced out. \
The concentrated streams of energy it sometimes produces require its full attention, attacking it during this time will prevent it from finishing its attack."
icon = 'icons/mob/simple/jungle/seedling.dmi'
icon_state = "seedling"
icon_living = "seedling"
icon_dead = "seedling_dead"
mob_biotypes = MOB_ORGANIC | MOB_PLANT
maxHealth = 100
health = 100
melee_damage_lower = 30
melee_damage_upper = 30
SET_BASE_PIXEL(-16, -14)
minimum_distance = 3
move_to_delay = 20
vision_range = 9
aggro_vision_range = 15
ranged = TRUE
ranged_cooldown_time = 10
projectiletype = /obj/projectile/seedling
projectilesound = 'sound/weapons/pierce.ogg'
robust_searching = TRUE
stat_attack = HARD_CRIT
move_resist = MOVE_FORCE_EXTREMELY_STRONG
var/combatant_state = SEEDLING_STATE_NEUTRAL
var/mob/living/beam_debuff_target
var/solar_beam_identifier = 0
/obj/projectile/seedling
name = "solar energy"
icon_state = "seedling"
damage = 10
damage_type = BURN
light_range = 2
armor_flag = ENERGY
light_color = LIGHT_COLOR_DIM_YELLOW
hitsound = 'sound/weapons/sear.ogg'
hitsound_wall = 'sound/weapons/effects/searwall.ogg'
nondirectional_sprite = TRUE
/obj/projectile/seedling/Bump(atom/A)//Stops seedlings from destroying other jungle mobs through FF
if(isliving(A))
var/mob/living/L = A
if(FACTION_JUNGLE in L.faction)
return FALSE
return ..()
/obj/effect/temp_visual/solarbeam_killsat
name = "beam of solar energy"
icon_state = "solar_beam"
icon = 'icons/effects/beam.dmi'
plane = LIGHTING_PLANE
layer = LIGHTING_PRIMARY_LAYER
duration = 5
randomdir = FALSE
/datum/status_effect/seedling_beam_indicator
id = "seedling beam indicator"
duration = 3 SECONDS
status_type = STATUS_EFFECT_MULTIPLE
alert_type = null
tick_interval = 0.2 SECONDS
var/atom/movable/screen/seedling/seedling_screen_object
var/atom/target
/datum/status_effect/seedling_beam_indicator/on_creation(mob/living/new_owner, target_plant)
. = ..()
if(.)
target = target_plant
tick()
/datum/status_effect/seedling_beam_indicator/on_apply()
if(owner.client)
seedling_screen_object = new /atom/movable/screen/seedling()
owner.client.screen += seedling_screen_object
tick()
return ..()
/datum/status_effect/seedling_beam_indicator/Destroy()
if(owner)
if(owner.client)
owner.client.screen -= seedling_screen_object
return ..()
/datum/status_effect/seedling_beam_indicator/tick(seconds_between_ticks)
var/target_angle = get_angle(owner, target)
var/matrix/final = matrix()
final.Turn(target_angle)
seedling_screen_object.transform = final
/atom/movable/screen/seedling
icon = 'icons/mob/simple/jungle/arachnid.dmi'
icon_state = "seedling_beam_indicator"
screen_loc = "CENTER:-16,CENTER:-16"
/mob/living/simple_animal/hostile/jungle/seedling/Goto()
if(combatant_state != SEEDLING_STATE_NEUTRAL)
return
return ..()
/mob/living/simple_animal/hostile/jungle/seedling/AttackingTarget()
if(isliving(target))
if(ranged_cooldown <= world.time && combatant_state == SEEDLING_STATE_NEUTRAL)
OpenFire(target)
return
return ..()
/mob/living/simple_animal/hostile/jungle/seedling/OpenFire()
WarmupAttack()
/mob/living/simple_animal/hostile/jungle/seedling/proc/WarmupAttack()
if(combatant_state == SEEDLING_STATE_NEUTRAL)
combatant_state = SEEDLING_STATE_WARMUP
SSmove_manager.stop_looping(src)
update_icons()
var/target_dist = get_dist(src,target)
var/living_target_check = isliving(target)
if(living_target_check)
if(target_dist > 7)//Offscreen check
SolarBeamStartup(target)
return
if(get_dist(src,target) >= 4 && prob(40))
SolarBeamStartup(target)
return
addtimer(CALLBACK(src, PROC_REF(Volley)), 5)
/mob/living/simple_animal/hostile/jungle/seedling/proc/SolarBeamStartup(mob/living/living_target)//It's more like requiem than final spark
if(combatant_state == SEEDLING_STATE_WARMUP && target)
combatant_state = SEEDLING_STATE_ACTIVE
living_target.apply_status_effect(/datum/status_effect/seedling_beam_indicator, src)
beam_debuff_target = living_target
playsound(src,'sound/effects/seedling_chargeup.ogg', 100, FALSE)
if(get_dist(src,living_target) > 7)
playsound(living_target,'sound/effects/seedling_chargeup.ogg', 100, FALSE)
solar_beam_identifier = world.time
addtimer(CALLBACK(src, PROC_REF(Beamu), living_target, solar_beam_identifier), 35)
/mob/living/simple_animal/hostile/jungle/seedling/proc/Beamu(mob/living/living_target, beam_id = 0)
if(combatant_state == SEEDLING_STATE_ACTIVE && living_target && beam_id == solar_beam_identifier)
if(living_target.z == z)
update_icons()
var/obj/effect/temp_visual/solarbeam_killsat/S = new (get_turf(src))
var/matrix/starting = matrix()
starting.Scale(1,32)
starting.Translate(0,520)
S.transform = starting
var/obj/effect/temp_visual/solarbeam_killsat/K = new (get_turf(living_target))
var/matrix/final = matrix()
final.Scale(1,32)
final.Translate(0,512)
K.transform = final
living_target.adjustFireLoss(30)
living_target.adjust_fire_stacks(0.2)//Just here for the showmanship
living_target.ignite_mob()
playsound(living_target,'sound/weapons/sear.ogg', 50, TRUE)
addtimer(CALLBACK(src, PROC_REF(AttackRecovery)), 5)
return
AttackRecovery()
/mob/living/simple_animal/hostile/jungle/seedling/proc/Volley()
if(combatant_state == SEEDLING_STATE_WARMUP && target)
combatant_state = SEEDLING_STATE_ACTIVE
update_icons()
var/datum/callback/cb = CALLBACK(src, PROC_REF(InaccurateShot))
for(var/i in 1 to 13)
addtimer(cb, i)
addtimer(CALLBACK(src, PROC_REF(AttackRecovery)), 14)
/mob/living/simple_animal/hostile/jungle/seedling/proc/InaccurateShot()
if(!QDELETED(target) && combatant_state == SEEDLING_STATE_ACTIVE && !stat)
if(get_dist(src,target) <= 3)//If they're close enough just aim straight at them so we don't miss at point blank ranges
Shoot(target)
return
var/turf/our_turf = get_turf(src)
var/obj/projectile/seedling/readied_shot = new /obj/projectile/seedling(our_turf)
readied_shot.preparePixelProjectile(target, src, null, rand(-10, 10))
readied_shot.fire()
playsound(src, projectilesound, 100, TRUE)
/mob/living/simple_animal/hostile/jungle/seedling/proc/AttackRecovery()
if(combatant_state == SEEDLING_STATE_ACTIVE)
combatant_state = SEEDLING_STATE_RECOVERY
update_icons()
ranged_cooldown = world.time + ranged_cooldown_time
if(target)
face_atom(target)
addtimer(CALLBACK(src, PROC_REF(ResetNeutral)), 10)
/mob/living/simple_animal/hostile/jungle/seedling/proc/ResetNeutral()
combatant_state = SEEDLING_STATE_NEUTRAL
if(target && !stat)
update_icons()
Goto(target, move_to_delay, minimum_distance)
/mob/living/simple_animal/hostile/jungle/seedling/adjustHealth(amount, updating_health = TRUE, forced = FALSE)
. = ..()
if(combatant_state == SEEDLING_STATE_ACTIVE && beam_debuff_target)
beam_debuff_target.remove_status_effect(/datum/status_effect/seedling_beam_indicator)
beam_debuff_target = null
solar_beam_identifier = 0
AttackRecovery()
/mob/living/simple_animal/hostile/jungle/seedling/update_icons()
. = ..()
if(!stat)
switch(combatant_state)
if(SEEDLING_STATE_NEUTRAL)
icon_state = "seedling"
if(SEEDLING_STATE_WARMUP)
icon_state = "seedling_charging"
if(SEEDLING_STATE_ACTIVE)
icon_state = "seedling_fire"
if(SEEDLING_STATE_RECOVERY)
icon_state = "seedling"
/mob/living/simple_animal/hostile/jungle/seedling/GiveTarget()
if(target)
if(combatant_state == SEEDLING_STATE_WARMUP || combatant_state == SEEDLING_STATE_ACTIVE)//So it doesn't 180 and blast you in the face while it's firing at someone else
return
return ..()
/mob/living/simple_animal/hostile/jungle/seedling/LoseTarget()
if(combatant_state == SEEDLING_STATE_WARMUP || combatant_state == SEEDLING_STATE_ACTIVE)
return
return ..()
#undef SEEDLING_STATE_NEUTRAL
#undef SEEDLING_STATE_WARMUP
#undef SEEDLING_STATE_ACTIVE
#undef SEEDLING_STATE_RECOVERY
@@ -136,7 +136,6 @@
/mob/living/simple_animal/hostile/jungle,
/mob/living/simple_animal/hostile/jungle/leaper,
/mob/living/simple_animal/hostile/jungle/mook,
/mob/living/simple_animal/hostile/jungle/seedling,
/mob/living/simple_animal/hostile/megafauna,
/mob/living/simple_animal/hostile/megafauna/blood_drunk_miner,
/mob/living/simple_animal/hostile/megafauna/blood_drunk_miner/doom,
+7
View File
@@ -325,3 +325,10 @@
cost = 14 //High cost because of the potential for extreme damage in the hands of a skilled scientist.
restricted_roles = list(JOB_RESEARCH_DIRECTOR, JOB_SCIENTIST)
surplus = 5
/datum/uplink_item/role_restricted/evil_seedling
name = "Evil Seedling"
desc = "A rare seed we have recovered that grows into a dangerous species that will aid you with your tasks!"
item = /obj/item/seeds/seedling/evil
cost = 8
restricted_roles = list(JOB_BOTANIST)
Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 31 KiB

+4 -1
View File
@@ -3863,6 +3863,7 @@
#include "code\modules\hydroponics\grown\random.dm"
#include "code\modules\hydroponics\grown\replicapod.dm"
#include "code\modules\hydroponics\grown\root.dm"
#include "code\modules\hydroponics\grown\seedling.dm"
#include "code\modules\hydroponics\grown\sugarcane.dm"
#include "code\modules\hydroponics\grown\tea_coffee.dm"
#include "code\modules\hydroponics\grown\tobacco.dm"
@@ -4283,6 +4284,9 @@
#include "code\modules\mob\living\basic\jungle\mega_arachnid\mega_arachnid.dm"
#include "code\modules\mob\living\basic\jungle\mega_arachnid\mega_arachnid_abilities.dm"
#include "code\modules\mob\living\basic\jungle\mega_arachnid\mega_arachnid_ai.dm"
#include "code\modules\mob\living\basic\jungle\seedling\seedling.dm"
#include "code\modules\mob\living\basic\jungle\seedling\seedling_ai.dm"
#include "code\modules\mob\living\basic\jungle\seedling\seedling_projectiles.dm"
#include "code\modules\mob\living\basic\lavaland\mining.dm"
#include "code\modules\mob\living\basic\lavaland\basilisk\basilisk.dm"
#include "code\modules\mob\living\basic\lavaland\basilisk\basilisk_overheat.dm"
@@ -4616,7 +4620,6 @@
#include "code\modules\mob\living\simple_animal\hostile\jungle\_jungle_mobs.dm"
#include "code\modules\mob\living\simple_animal\hostile\jungle\leaper.dm"
#include "code\modules\mob\living\simple_animal\hostile\jungle\mook.dm"
#include "code\modules\mob\living\simple_animal\hostile\jungle\seedling.dm"
#include "code\modules\mob\living\simple_animal\hostile\megafauna\_megafauna.dm"
#include "code\modules\mob\living\simple_animal\hostile\megafauna\blood_drunk_miner.dm"
#include "code\modules\mob\living\simple_animal\hostile\megafauna\bubblegum.dm"