Basic Mob Brimdemon (#78424)

## About The Pull Request

Fixes #71330

The brimdemon was basically already perfect (well, it has a novel means
of attacking) so I didn't get too fancy with this one, it's _largely_
just a straightforward conversion.
Following this change it's a little slower to back off, but better at
lining up with people in order to blast them. Additionally, its beam is
now a mob ability so you can give it to other mobs if you so desire.

Because I can't help doing a _little_ tinkering, Brimdemons now explode
2.5 seconds after they die, after a brief warning animation.

## Why It's Good For The Game

Simple mobs must die

## Changelog

🆑
add: Brimdemon corpses release an explosion shortly after death, just to
keep you on your toes.
refactor: Brimdemons now use the basic mob framework which (should)
improve their pathfinding somewhat. Please bug report any unusual
behaviour.
admin: The brimdemon's beam ability can be given to any mob, for your
Binding of Isaac event
/🆑
This commit is contained in:
Jacquerel
2023-09-22 19:12:43 -06:00
committed by GitHub
parent 48c0039ff7
commit b2ecd81be4
45 changed files with 548 additions and 393 deletions
+3 -1
View File
@@ -29,7 +29,9 @@
/// Don't move if being pulled
#define STOP_MOVING_WHEN_PULLED (1<<0)
/// Continue processing even if dead
#define CAN_ACT_WHILE_DEAD (1<<1)
#define CAN_ACT_WHILE_DEAD (1<<1)
/// Stop processing while in a progress bar
#define PAUSE_DURING_DO_AFTER (1<<2)
//Base Subtree defines
+5
View File
@@ -349,6 +349,11 @@ multiple modular subtrees with behaviors
minimum_distance = iter_behavior.required_distance
return minimum_distance
/// Returns true if we have a blackboard key with the provided key and it is not qdeleting
/datum/ai_controller/proc/blackboard_key_exists(key)
var/datum/key_value = blackboard[key]
return !QDELETED(key_value)
/**
* Used to manage references to datum by AI controllers
*
+4 -9
View File
@@ -20,13 +20,8 @@
if(is_type_in_list(controller.pawn, baby_types))
return
var/atom/target = controller.blackboard[BB_BABIES_TARGET]
// Find target
if(QDELETED(target))
controller.queue_behavior(/datum/ai_behavior/find_partner, BB_BABIES_TARGET, BB_BABIES_PARTNER_TYPES, BB_BABIES_CHILD_TYPES)
return
// Do target
controller.queue_behavior(/datum/ai_behavior/make_babies, BB_BABIES_TARGET, BB_BABIES_CHILD_TYPES)
return SUBTREE_RETURN_FINISH_PLANNING
if(controller.blackboard_key_exists(BB_BABIES_TARGET))
controller.queue_behavior(/datum/ai_behavior/make_babies, BB_BABIES_TARGET, BB_BABIES_CHILD_TYPES)
return SUBTREE_RETURN_FINISH_PLANNING
controller.queue_behavior(/datum/ai_behavior/find_partner, BB_BABIES_TARGET, BB_BABIES_PARTNER_TYPES, BB_BABIES_CHILD_TYPES)
@@ -15,10 +15,13 @@
/datum/ai_controller/basic_controller/able_to_run()
. = ..()
if(isliving(pawn))
var/mob/living/living_pawn = pawn
if(IS_DEAD_OR_INCAP(living_pawn))
return FALSE
if(!isliving(pawn))
return
var/mob/living/living_pawn = pawn
if(!(ai_traits & CAN_ACT_WHILE_DEAD) && IS_DEAD_OR_INCAP(living_pawn))
return FALSE
if(ai_traits & PAUSE_DURING_DO_AFTER && LAZYLEN(living_pawn.do_afters))
return FALSE
/datum/ai_controller/basic_controller/proc/update_speed(mob/living/basic/basic_mob)
SIGNAL_HANDLER
@@ -4,8 +4,7 @@
/datum/ai_behavior/basic_melee_attack/setup(datum/ai_controller/controller, target_key, targetting_datum_key, hiding_location_key)
. = ..()
var/datum/targetting_datum/targetting_datum = controller.blackboard[targetting_datum_key]
if(isnull(targetting_datum))
if(!controller.blackboard_key_exists(targetting_datum_key))
CRASH("No target datum was supplied in the blackboard for [controller.pawn]")
//Hiding location is priority
@@ -19,12 +19,6 @@
var/atom/target = controller.blackboard[target_key]
if (QDELETED(target))
controller.clear_blackboard_key(target_key)
return
if (!isliving(target))
return
var/mob/living/living_target = target
if(living_target.stat >= UNCONSCIOUS)
controller.clear_blackboard_key(target_key)
/datum/ai_behavior/targeted_mob_ability/proc/get_ability_to_use(datum/ai_controller/controller, ability_key)
return controller.blackboard[ability_key]
@@ -0,0 +1,33 @@
/// Attack something which is already adjacent to us, without ending planning
/datum/ai_planning_subtree/basic_melee_attack_subtree/opportunistic
melee_attack_behavior = /datum/ai_behavior/basic_melee_attack/opportunistic
end_planning = FALSE
/datum/ai_planning_subtree/basic_melee_attack_subtree/opportunistic/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick)
. = ..()
var/atom/target = controller.blackboard[BB_BASIC_MOB_CURRENT_TARGET]
if(QDELETED(target) || !controller.pawn.Adjacent(target))
return
if (isliving(controller.pawn))
var/mob/living/pawn = controller.pawn
if (LAZYLEN(pawn.do_afters))
return
controller.queue_behavior(melee_attack_behavior, BB_BASIC_MOB_CURRENT_TARGET, BB_TARGETTING_DATUM, BB_BASIC_MOB_CURRENT_TARGET_HIDING_LOCATION)
/// Attack something which is already adjacent to us without moving
/datum/ai_behavior/basic_melee_attack/opportunistic
action_cooldown = 0.2 SECONDS // We gotta check unfortunately often because we're in a race condition with nextmove
behavior_flags = AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION
/datum/ai_behavior/basic_melee_attack/opportunistic/setup(datum/ai_controller/controller, target_key, targetting_datum_key, hiding_location_key)
if (!controller.blackboard_key_exists(targetting_datum_key))
CRASH("No target datum was supplied in the blackboard for [controller.pawn]")
return controller.blackboard_key_exists(target_key)
/datum/ai_behavior/basic_melee_attack/opportunistic/perform(seconds_per_tick, datum/ai_controller/controller, target_key, targetting_datum_key, hiding_location_key)
var/atom/movable/atom_pawn = controller.pawn
if(!atom_pawn.CanReach(controller.blackboard[target_key]))
finish_action(controller, TRUE, target_key) // Don't clear target
return FALSE
. = ..()
finish_action(controller, TRUE, target_key) // Try doing something else
@@ -7,11 +7,8 @@
if(!SPT_PROB(climb_chance, seconds_per_tick))
return
var/obj/structure/flora/tree/target = controller.blackboard[BB_CLIMBED_TREE]
if(controller.blackboard_key_exists(BB_CLIMBED_TREE))
controller.queue_behavior(/datum/ai_behavior/climb_tree, BB_CLIMBED_TREE)
return SUBTREE_RETURN_FINISH_PLANNING
if(QDELETED(target))
controller.queue_behavior(/datum/ai_behavior/find_and_set/valid_tree, BB_CLIMBED_TREE, /obj/structure/flora/tree)
return
controller.queue_behavior(/datum/ai_behavior/climb_tree, BB_CLIMBED_TREE)
return SUBTREE_RETURN_FINISH_PLANNING
controller.queue_behavior(/datum/ai_behavior/find_and_set/valid_tree, BB_CLIMBED_TREE, /obj/structure/flora/tree)
@@ -3,8 +3,7 @@
/datum/ai_planning_subtree/find_food/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick)
. = ..()
var/atom/target = controller.blackboard[BB_BASIC_MOB_CURRENT_TARGET]
if(!QDELETED(target))
if(controller.blackboard_key_exists(BB_BASIC_MOB_CURRENT_TARGET))
// Busy with something
return
@@ -1,10 +1,9 @@
/datum/ai_planning_subtree/find_paper_and_write
/datum/ai_planning_subtree/find_paper_and_write/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick)
var/obj/item/inhand_paper = controller.blackboard[BB_SIMPLE_CARRY_ITEM]
var/mob/living/basic/wizard = controller.pawn
if(!QDELETED(inhand_paper))
if(controller.blackboard_key_exists(BB_SIMPLE_CARRY_ITEM))
controller.queue_behavior(/datum/ai_behavior/write_on_paper, BB_SIMPLE_CARRY_ITEM, BB_WRITING_LIST)
return SUBTREE_RETURN_FINISH_PLANNING
@@ -9,10 +9,7 @@
/datum/ai_planning_subtree/flee_target/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick)
. = ..()
if (!controller.blackboard[BB_BASIC_MOB_FLEEING])
return
var/atom/target = controller.blackboard[target_key]
if(QDELETED(target))
if (!controller.blackboard[BB_BASIC_MOB_FLEEING] || !controller.blackboard_key_exists(target_key))
return
controller.queue_behavior(flee_behaviour, target_key, hiding_place_key)
return SUBTREE_RETURN_FINISH_PLANNING //we gotta get out of here.
@@ -11,9 +11,6 @@
/datum/ai_planning_subtree/maintain_distance/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick)
. = ..()
var/mob/living/living_pawn = controller.pawn
if(LAZYLEN(living_pawn.do_afters))
return
var/atom/target = controller.blackboard[target_key]
if (!isliving(target) || !can_see(controller.pawn, target, view_distance))
return // Don't run away from cucumbers, they're not snakes
@@ -0,0 +1,71 @@
/// Try to line up with a cardinal direction of your target
/datum/ai_planning_subtree/move_to_cardinal
/// Behaviour to execute to line ourselves up
var/move_behaviour = /datum/ai_behavior/move_to_cardinal
/// Blackboard key in which to store selected target
var/target_key = BB_BASIC_MOB_CURRENT_TARGET
/datum/ai_planning_subtree/move_to_cardinal/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick)
. = ..()
if(!controller.blackboard_key_exists(target_key))
return
controller.queue_behavior(move_behaviour, target_key)
/// Try to line up with a cardinal direction of your target
/datum/ai_behavior/move_to_cardinal
required_distance = 0
behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_MOVE_AND_PERFORM | AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION
/// How close to our target is too close?
var/minimum_distance = 1
/// How far away is too far?
var/maximum_distance = 9
/datum/ai_behavior/move_to_cardinal/setup(datum/ai_controller/controller, target_key)
var/atom/target = controller.blackboard[target_key]
if(QDELETED(target))
return FALSE
target_nearest_cardinal(controller, target)
return TRUE
/// Set our movement target to the closest cardinal space to our target
/datum/ai_behavior/move_to_cardinal/proc/target_nearest_cardinal(datum/ai_controller/controller, atom/target)
var/atom/move_target
var/closest = INFINITY
for (var/dir in GLOB.cardinals)
var/turf/cardinal_turf = get_ranged_target_turf(target, dir, minimum_distance)
if (cardinal_turf.is_blocked_turf())
continue
var/distance_to = get_dist(controller.pawn, cardinal_turf)
if (distance_to >= closest)
continue
closest = distance_to
move_target = cardinal_turf
if (isnull(move_target))
move_target = target
if (controller.current_movement_target == move_target)
return
set_movement_target(controller, move_target)
/datum/ai_behavior/move_to_cardinal/perform(seconds_per_tick, datum/ai_controller/controller, target_key)
var/atom/target = controller.blackboard[target_key]
if (QDELETED(target))
finish_action(controller = controller, succeeded = FALSE, target_key = target_key)
return
if (!(get_dir(controller.pawn, target) in GLOB.cardinals))
target_nearest_cardinal(controller, target)
return
var/distance_to_target = get_dist(controller.pawn, target)
if (distance_to_target < minimum_distance)
target_nearest_cardinal(controller, target)
return
if (distance_to_target > maximum_distance)
return
finish_action(controller = controller, succeeded = TRUE, target_key = target_key)
return
/datum/ai_behavior/move_to_cardinal/finish_action(datum/ai_controller/controller, succeeded, target_key)
if (!succeeded)
controller.clear_blackboard_key(target_key)
return ..()
@@ -8,8 +8,7 @@
/datum/ai_planning_subtree/ranged_skirmish/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick)
. = ..()
var/atom/target = controller.blackboard[target_key]
if(QDELETED(target))
if(!controller.blackboard_key_exists(target_key))
return
controller.queue_behavior(attack_behavior, target_key, BB_TARGETTING_DATUM, BB_BASIC_MOB_CURRENT_TARGET_HIDING_LOCATION)
@@ -1,13 +1,16 @@
/datum/ai_planning_subtree/basic_melee_attack_subtree
/// What do we do in order to attack someone?
var/datum/ai_behavior/basic_melee_attack/melee_attack_behavior = /datum/ai_behavior/basic_melee_attack
/// Is this the last thing we do? (if we set a movement target, this will usually be yes)
var/end_planning = TRUE
/datum/ai_planning_subtree/basic_melee_attack_subtree/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick)
. = ..()
var/atom/target = controller.blackboard[BB_BASIC_MOB_CURRENT_TARGET]
if(QDELETED(target))
if(!controller.blackboard_key_exists(BB_BASIC_MOB_CURRENT_TARGET))
return
controller.queue_behavior(melee_attack_behavior, BB_BASIC_MOB_CURRENT_TARGET, BB_TARGETTING_DATUM, BB_BASIC_MOB_CURRENT_TARGET_HIDING_LOCATION)
return SUBTREE_RETURN_FINISH_PLANNING //we are going into battle...no distractions.
if (end_planning)
return SUBTREE_RETURN_FINISH_PLANNING //we are going into battle...no distractions.
/datum/ai_planning_subtree/basic_ranged_attack_subtree
operational_datums = list(/datum/component/ranged_attacks)
@@ -15,8 +18,7 @@
/datum/ai_planning_subtree/basic_ranged_attack_subtree/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick)
. = ..()
var/atom/target = controller.blackboard[BB_BASIC_MOB_CURRENT_TARGET]
if(QDELETED(target))
if(!controller.blackboard_key_exists(BB_BASIC_MOB_CURRENT_TARGET))
return
controller.queue_behavior(ranged_attack_behavior, BB_BASIC_MOB_CURRENT_TARGET, BB_TARGETTING_DATUM, BB_BASIC_MOB_CURRENT_TARGET_HIDING_LOCATION)
return SUBTREE_RETURN_FINISH_PLANNING //we are going into battle...no distractions.
@@ -16,7 +16,7 @@
/datum/ai_behavior/sleep_after_targetless_time/perform(seconds_per_tick, datum/ai_controller/controller, target_key)
var/atom/target = controller.blackboard[target_key]
finish_action(controller, succeeded = !target, seconds_per_tick = seconds_per_tick)
finish_action(controller, succeeded = QDELETED(target), seconds_per_tick = seconds_per_tick)
/datum/ai_behavior/sleep_after_targetless_time/finish_action(datum/ai_controller/controller, succeeded, seconds_per_tick)
. = ..()
@@ -13,9 +13,11 @@
if (!ability_key)
CRASH("You forgot to tell this mob where to find its ability")
var/mob/living/target = controller.blackboard[target_key]
if (!controller.blackboard_key_exists(target_key))
return
var/datum/action/cooldown/using_action = controller.blackboard[ability_key]
if (QDELETED(target) || QDELETED(using_action) || !using_action.IsAvailable())
if (!using_action?.IsAvailable())
return
controller.queue_behavior(use_ability_behaviour, ability_key, target_key)
@@ -15,7 +15,7 @@
CRASH("You forgot to tell this mob where to find its ability")
var/datum/action/cooldown/using_action = controller.blackboard[ability_key]
if (QDELETED(using_action) || !using_action.IsAvailable())
if (!using_action?.IsAvailable())
return
controller.queue_behavior(use_ability_behaviour, ability_key)
+1 -2
View File
@@ -8,8 +8,7 @@
/datum/ai_behavior/find_and_set/perform(seconds_per_tick, datum/ai_controller/controller, set_key, locate_path, search_range)
. = ..()
var/atom/current_target = controller.blackboard[set_key]
if (!QDELETED(current_target))
if (controller.blackboard_key_exists(set_key))
return
var/find_this_thing = search_tactic(controller, locate_path, search_range)
if(find_this_thing)
@@ -14,3 +14,13 @@
/datum/idle_behavior/idle_random_walk/less_walking
walk_chance = 10
/// Only walk if we don't have a target
/datum/idle_behavior/idle_random_walk/no_target
/// Where do we look for a target?
var/target_key = BB_BASIC_MOB_CURRENT_TARGET
/datum/idle_behavior/idle_random_walk/no_target/perform_idle_behavior(seconds_per_tick, datum/ai_controller/controller)
if (!controller.blackboard_key_exists(target_key))
return
return ..()
@@ -8,8 +8,8 @@
/obj/effect/spawner/random/lavaland_mob/legion = 30,
/obj/effect/spawner/random/lavaland_mob/watcher = 40,
/mob/living/basic/mining/bileworm = 20,
/mob/living/basic/mining/brimdemon = 20,
/mob/living/basic/mining/lobstrosity/lava = 20,
/mob/living/simple_animal/hostile/asteroid/brimdemon = 20,
/mob/living/basic/mining/goldgrub = 10,
/obj/structure/spawner/lavaland = 2,
/obj/structure/spawner/lavaland/goliath = 3,
@@ -7,11 +7,11 @@
icon_state = "large_egg"
loot = list(
/mob/living/basic/mining/bileworm = 1,
/mob/living/basic/mining/brimdemon = 1,
/mob/living/basic/mining/goldgrub = 1,
/mob/living/basic/mining/goliath = 1,
/mob/living/basic/mining/lobstrosity/lava = 1,
/mob/living/basic/mining/watcher = 1,
/mob/living/simple_animal/hostile/asteroid/brimdemon = 1,
/mob/living/simple_animal/hostile/asteroid/hivelord/legion = 1,
)
@@ -43,12 +43,12 @@
unset_click_ability(source, refund_cooldown = TRUE)
/datum/action/cooldown/spell/pointed/projectile/furious_steel/InterceptClickOn(mob/living/caller, params, atom/click_target)
/datum/action/cooldown/spell/pointed/projectile/furious_steel/InterceptClickOn(mob/living/caller, params, atom/target)
// Let the caster prioritize using items like guns over blade casts
if(caller.get_active_held_item())
return FALSE
// Let the caster prioritize melee attacks like punches and shoves over blade casts
if(get_dist(caller, click_target) <= 1)
if(get_dist(caller, target) <= 1)
return FALSE
return ..()
@@ -69,14 +69,10 @@
/datum/ai_planning_subtree/sculpt_statues
/datum/ai_planning_subtree/sculpt_statues/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick)
var/obj/target = controller.blackboard[BB_TARGET_ROCK]
if(QDELETED(target))
controller.queue_behavior(/datum/ai_behavior/find_and_set, BB_TARGET_ROCK, /obj/structure/flora/rock/icy)
return
controller.queue_behavior(/datum/ai_behavior/sculpt_statue, BB_TARGET_ROCK)
return SUBTREE_RETURN_FINISH_PLANNING
if(controller.blackboard_key_exists(BB_TARGET_ROCK))
controller.queue_behavior(/datum/ai_behavior/sculpt_statue, BB_TARGET_ROCK)
return SUBTREE_RETURN_FINISH_PLANNING
controller.queue_behavior(/datum/ai_behavior/find_and_set, BB_TARGET_ROCK, /obj/structure/flora/rock/icy)
/datum/ai_behavior/sculpt_statue
behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_REQUIRE_REACH | AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION
@@ -135,16 +131,13 @@
/datum/ai_planning_subtree/burn_trees/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick)
var/datum/action/cooldown/using_action = controller.blackboard[BB_WHELP_STRAIGHTLINE_FIRE]
if (!using_action.IsAvailable())
if (!using_action?.IsAvailable())
return
var/obj/structure/target = controller.blackboard[BB_TARGET_TREE]
if(QDELETED(target))
controller.queue_behavior(/datum/ai_behavior/set_target_tree, BB_TARGET_TREE)
return
controller.queue_behavior(/datum/ai_behavior/targeted_mob_ability/and_clear_target/burn_trees, BB_WHELP_STRAIGHTLINE_FIRE, BB_TARGET_TREE)
return SUBTREE_RETURN_FINISH_PLANNING
if(controller.blackboard_key_exists(BB_TARGET_TREE))
controller.queue_behavior(/datum/ai_behavior/targeted_mob_ability/and_clear_target/burn_trees, BB_WHELP_STRAIGHTLINE_FIRE, BB_TARGET_TREE)
return SUBTREE_RETURN_FINISH_PLANNING
controller.queue_behavior(/datum/ai_behavior/set_target_tree, BB_TARGET_TREE)
/datum/ai_behavior/set_target_tree
@@ -15,9 +15,7 @@
/datum/ai_planning_subtree/bileworm_attack
/datum/ai_planning_subtree/bileworm_attack/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick)
var/mob/living/target = controller.blackboard[BB_BASIC_MOB_CURRENT_TARGET]
if(QDELETED(target))
if (!controller.blackboard_key_exists(BB_BASIC_MOB_CURRENT_TARGET))
return
var/datum/action/cooldown/mob_cooldown/resurface = controller.blackboard[BB_BILEWORM_RESURFACE]
@@ -0,0 +1,126 @@
/// Fires a bloody beam. Brimdemon Blast!
/datum/action/cooldown/mob_cooldown/brimbeam
name = "brimstone blast"
desc = "Unleash a barrage of infernal energies in the targeted direction."
button_icon = 'icons/mob/simple/lavaland/lavaland_monsters.dmi'
button_icon_state = "brimdemon_firing"
background_icon_state = "bg_demon"
overlay_icon_state = "bg_demon_border"
click_to_activate = TRUE
cooldown_time = 5 SECONDS
melee_cooldown_time = 0
check_flags = AB_CHECK_CONSCIOUS | AB_CHECK_INCAPACITATED
/// How far does our beam go?
var/beam_range = 10
/// How long does our beam last?
var/beam_duration = 2 SECONDS
/// How long do we wind up before firing?
var/charge_duration = 1 SECONDS
/// Overlay we show when we're about to fire
var/static/image/direction_overlay = image('icons/mob/simple/lavaland/lavaland_monsters.dmi', "brimdemon_telegraph_dir")
/// A list of all the beam parts.
var/list/beam_parts = list()
/datum/action/cooldown/mob_cooldown/brimbeam/Destroy()
extinguish_laser()
return ..()
/datum/action/cooldown/mob_cooldown/brimbeam/Activate(atom/target)
StartCooldown(360 SECONDS)
owner.face_atom(target)
owner.move_resist = MOVE_FORCE_VERY_STRONG
owner.add_overlay(direction_overlay)
owner.balloon_alert_to_viewers("charging...")
var/fully_charged = do_after(owner, delay = charge_duration, target = owner)
owner.cut_overlay(direction_overlay)
if (!fully_charged)
StartCooldown()
return TRUE
if (!fire_laser())
var/static/list/fail_emotes = list("coughs.", "wheezes.", "belches out a puff of black smoke.")
owner.manual_emote(pick(fail_emotes))
StartCooldown()
return TRUE
do_after(owner, delay = beam_duration, target = owner)
extinguish_laser()
StartCooldown()
return TRUE
/// Create a laser in the direction we are facing
/datum/action/cooldown/mob_cooldown/brimbeam/proc/fire_laser()
owner.visible_message(span_danger("[owner] fires a brimbeam!"))
playsound(owner, 'sound/creatures/brimdemon.ogg', 150, FALSE, 0, 3)
var/turf/target_turf = get_ranged_target_turf(owner, owner.dir, beam_range)
var/turf/origin_turf = get_turf(owner)
var/list/affected_turfs = get_line(origin_turf, target_turf) - origin_turf
for(var/turf/affected_turf in affected_turfs)
if(affected_turf.opacity)
break
var/blocked = FALSE
for(var/obj/potential_block in affected_turf.contents)
if(potential_block.opacity)
blocked = TRUE
break
if(blocked)
break
var/atom/new_brimbeam = new /obj/effect/brimbeam(affected_turf)
new_brimbeam.dir = owner.dir
beam_parts += new_brimbeam
for(var/mob/living/hit_mob in affected_turf.contents)
hit_mob.apply_damage(damage = 25, damagetype = BURN)
to_chat(hit_mob, span_userdanger("You're blasted by [owner]'s brimbeam!"))
RegisterSignal(new_brimbeam, COMSIG_QDELETING, PROC_REF(extinguish_laser)) // In case idk a singularity eats it or something
if(!length(beam_parts))
return FALSE
var/atom/last_brimbeam = beam_parts[length(beam_parts)]
last_brimbeam.icon_state = "brimbeam_end"
var/atom/first_brimbeam = beam_parts[1]
first_brimbeam.icon_state = "brimbeam_start"
return TRUE
/// Get rid of our laser when we are done with it
/datum/action/cooldown/mob_cooldown/brimbeam/proc/extinguish_laser()
if(!length(beam_parts))
return FALSE
owner.move_resist = initial(owner.move_resist)
for(var/obj/effect/brimbeam/beam in beam_parts)
beam.disperse()
beam_parts = list()
/// Segments of the actual beam, these hurt if you stand in them
/obj/effect/brimbeam
name = "brimbeam"
icon = 'icons/mob/simple/lavaland/lavaland_monsters.dmi'
icon_state = "brimbeam_mid"
layer = ABOVE_MOB_LAYER
plane = ABOVE_GAME_PLANE
mouse_opacity = MOUSE_OPACITY_TRANSPARENT
light_color = LIGHT_COLOR_BLOOD_MAGIC
light_power = 3
light_range = 2
/obj/effect/brimbeam/Initialize(mapload)
. = ..()
START_PROCESSING(SSfastprocess, src)
/obj/effect/brimbeam/Destroy()
STOP_PROCESSING(SSfastprocess, src)
return ..()
/obj/effect/brimbeam/process()
for(var/mob/living/hit_mob in get_turf(src))
damage(hit_mob)
/// Hurt the passed mob
/obj/effect/brimbeam/proc/damage(mob/living/hit_mob)
hit_mob.apply_damage(damage = 5, damagetype = BURN)
to_chat(hit_mob, span_danger("You're damaged by [src]!"))
/// Disappear
/obj/effect/brimbeam/proc/disperse()
animate(src, time = 0.5 SECONDS, alpha = 0)
QDEL_IN(src, 0.5 SECONDS)
@@ -0,0 +1,87 @@
/// Lavaland mob which tries to line up with its target and fire a laser
/mob/living/basic/mining/brimdemon
name = "brimdemon"
desc = "A volatile creature resembling an enormous horned skull. Its response to almost any stimulus is to unleash a beam of infernal energy."
icon = 'icons/mob/simple/lavaland/lavaland_monsters.dmi'
icon_state = "brimdemon"
icon_living = "brimdemon"
icon_dead = "brimdemon_dead"
speed = 3
maxHealth = 250
health = 250
friendly_verb_continuous = "scratches at"
friendly_verb_simple = "scratch at"
speak_emote = list("cackles")
melee_damage_lower = 7.5
melee_damage_upper = 7.5
attack_sound = 'sound/weapons/bite.ogg'
melee_attack_cooldown = 0.6 SECONDS
attack_vis_effect = ATTACK_EFFECT_BITE
attack_verb_continuous = "bites"
attack_verb_simple = "bite"
death_message = "wails as infernal energy escapes from its wounds, leaving it an empty husk."
death_sound = 'sound/magic/demon_dies.ogg'
light_color = LIGHT_COLOR_BLOOD_MAGIC
light_power = 5
light_range = 1.4
ai_controller = /datum/ai_controller/basic_controller/brimdemon
crusher_loot = /obj/item/crusher_trophy/brimdemon_fang
butcher_results = list(
/obj/item/food/meat/slab = 2,
/obj/effect/decal/cleanable/brimdust = 1,
/obj/item/organ/internal/monster_core/brimdust_sac = 1,
)
/// How we get blasting
var/datum/action/cooldown/mob_cooldown/brimbeam/beam
/mob/living/basic/mining/brimdemon/Initialize(mapload)
. = ..()
AddElement(/datum/element/footstep, FOOTSTEP_MOB_CLAW)
beam = new(src)
beam.Grant(src)
ai_controller.set_blackboard_key(BB_TARGETTED_ACTION, beam)
/mob/living/basic/mining/brimdemon/Destroy()
QDEL_NULL(beam)
return ..()
/mob/living/basic/mining/brimdemon/RangedAttack(atom/target, modifiers)
beam.Trigger(target = target)
/mob/living/basic/mining/brimdemon/death(gibbed)
. = ..()
if (gibbed)
return
var/obj/effect/temp_visual/brim_burst/bang = new(loc)
forceMove(bang)
/// Show a funny animation before doing an explosion
/obj/effect/temp_visual/brim_burst
name = "bursting brimdemon"
icon = 'icons/mob/simple/lavaland/lavaland_monsters.dmi'
icon_state = "brimdemon_dead"
duration = 1.9 SECONDS
/obj/effect/temp_visual/brim_burst/Initialize(mapload)
. = ..()
addtimer(CALLBACK(src, PROC_REF(bang)), duration - (1 DECISECONDS), TIMER_DELETE_ME)
animate(src, color = "#ff8888", transform = matrix().Scale(1.1), time = 0.7 SECONDS)
animate(color = "#ffffff", transform = matrix(), time = 0.2 SECONDS)
animate(color = "#ff4444", transform = matrix().Scale(1.3), time = 0.5 SECONDS)
animate(color = "#ffffff", transform = matrix(), time = 0.2 SECONDS)
animate(color = "#ff0000", transform = matrix().Scale(1.5), time = 0.3 SECONDS)
/// Make an explosion
/obj/effect/temp_visual/brim_burst/proc/bang()
var/turf/origin_turf = get_turf(src)
playsound(origin_turf, 'sound/effects/pop_expl.ogg', 50)
new /obj/effect/temp_visual/explosion/fast(origin_turf)
var/list/possible_targets = range(1, origin_turf)
for(var/mob/living/target in possible_targets)
var/armor = target.run_armor_check(attack_flag = BOMB)
target.apply_damage(20, damagetype = BURN, blocked = armor, spread_damage = TRUE)
for (var/atom/movable/thing as anything in contents)
thing.forceMove(loc)
@@ -0,0 +1,50 @@
/**
* Slap someone who is nearby, line up with target, blast with a beam
*/
/datum/ai_controller/basic_controller/brimdemon
blackboard = list(
BB_TARGETTING_DATUM = new /datum/targetting_datum/basic/brimdemon,
)
ai_traits = PAUSE_DURING_DO_AFTER
ai_movement = /datum/ai_movement/basic_avoidance
idle_behavior = /datum/idle_behavior/idle_random_walk/no_target
planning_subtrees = list(
/datum/ai_planning_subtree/simple_find_target,
/datum/ai_planning_subtree/basic_melee_attack_subtree/opportunistic,
/datum/ai_planning_subtree/move_to_cardinal/brimdemon,
/datum/ai_planning_subtree/targeted_mob_ability/brimbeam,
)
/datum/targetting_datum/basic/brimdemon
stat_attack = HARD_CRIT
/datum/ai_planning_subtree/move_to_cardinal/brimdemon
move_behaviour = /datum/ai_behavior/move_to_cardinal/brimdemon
/datum/ai_behavior/move_to_cardinal/brimdemon
minimum_distance = 2
/datum/ai_behavior/move_to_cardinal/brimdemon/finish_action(datum/ai_controller/controller, succeeded, target_key)
. = ..()
if (!succeeded)
return
var/mob/living/target = controller.blackboard[target_key]
var/datum/action/cooldown/ability = controller.blackboard[BB_TARGETTED_ACTION]
if(!ability?.IsAvailable())
return
ability.InterceptClickOn(caller = controller.pawn, target = target)
/datum/ai_planning_subtree/targeted_mob_ability/brimbeam
use_ability_behaviour = /datum/ai_behavior/targeted_mob_ability/brimbeam
/datum/ai_behavior/targeted_mob_ability/brimbeam
/// Don't shoot if too far away
var/max_target_distance = 9
/datum/ai_behavior/targeted_mob_ability/brimbeam/perform(seconds_per_tick, datum/ai_controller/controller, ability_key, target_key)
var/mob/living/target = controller.blackboard[target_key]
if (QDELETED(target) || !(get_dir(controller.pawn, target) in GLOB.cardinals) || get_dist(controller.pawn, target) > max_target_distance)
finish_action(controller, succeeded = FALSE, ability_key = ability_key, target_key = target_key)
return
return ..()
@@ -0,0 +1,56 @@
/// Brimdemon crusher trophy, it... makes a funny sound?
/obj/item/crusher_trophy/brimdemon_fang
name = "brimdemon's fang"
icon_state = "brimdemon_fang"
desc = "A fang from a brimdemon's corpse."
denied_type = /obj/item/crusher_trophy/brimdemon_fang
/// Cartoon punching vfx
var/static/list/comic_phrases = list("BOOM", "BANG", "KABLOW", "KAPOW", "OUCH", "BAM", "KAPOW", "WHAM", "POW", "KABOOM")
/obj/item/crusher_trophy/brimdemon_fang/effect_desc()
return "mark detonation creates visual and audiosensory effects on the target"
/obj/item/crusher_trophy/brimdemon_fang/on_mark_detonation(mob/living/target, mob/living/user)
target.balloon_alert_to_viewers("[pick(comic_phrases)]!")
playsound(target, 'sound/lavaland/brimdemon_crush.ogg', 100)
/// Reagent pool left by dying brimdemon
/obj/effect/decal/cleanable/brimdust
name = "brimdust"
desc = "Dust from a brimdemon. It is considered valuable for its' botanical abilities."
icon_state = "brimdust"
icon = 'icons/obj/mining.dmi'
layer = FLOOR_CLEAN_LAYER
mergeable_decal = FALSE
/obj/effect/decal/cleanable/brimdust/Initialize(mapload)
. = ..()
reagents.add_reagent(/datum/reagent/brimdust, 15)
/// Ashwalker ore sensor crafted from brimdemon ash
/obj/item/ore_sensor
name = "ore sensor"
desc = "Using demonic frequencies, this ear-mounted tool detects ores in the nearby terrain."
icon_state = "oresensor"
icon = 'icons/obj/mining.dmi'
slot_flags = ITEM_SLOT_EARS
var/range = 5
var/cooldown = 4 SECONDS //between the standard and the advanced ore scanner in strength
COOLDOWN_DECLARE(ore_sensing_cooldown)
/obj/item/ore_sensor/equipped(mob/user, slot, initial)
. = ..()
if(slot & ITEM_SLOT_EARS)
START_PROCESSING(SSobj, src)
else
STOP_PROCESSING(SSobj, src)
/obj/item/ore_sensor/dropped(mob/user, silent)
. = ..()
STOP_PROCESSING(SSobj, src)
/obj/item/ore_sensor/process(seconds_per_tick)
if(!COOLDOWN_FINISHED(src, ore_sensing_cooldown))
return
COOLDOWN_START(src, ore_sensing_cooldown, cooldown)
mineral_scan_pulse(get_turf(src), range)
@@ -108,10 +108,15 @@
if(!dig_ability.IsAvailable())
return
var/mob/living/target = controller.blackboard[BB_BASIC_MOB_CURRENT_TARGET]
var/has_target = controller.blackboard_key_exists(BB_BASIC_MOB_CURRENT_TARGET)
//a storm is coming or someone is nearby, its time to escape
if(currently_underground || !currently_underground && storm_approaching || !QDELETED(target))
if(currently_underground)
if(has_target)
return
controller.queue_behavior(/datum/ai_behavior/use_mob_ability/burrow, BB_BURROW_ABILITY)
return SUBTREE_RETURN_FINISH_PLANNING
if(storm_approaching || has_target)
controller.queue_behavior(/datum/ai_behavior/use_mob_ability/burrow, BB_BURROW_ABILITY)
return SUBTREE_RETURN_FINISH_PLANNING
@@ -122,14 +127,10 @@
/datum/ai_planning_subtree/grub_mine
/datum/ai_planning_subtree/grub_mine/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick)
var/turf/target_wall = controller.blackboard[BB_TARGET_MINERAL_WALL]
if(QDELETED(target_wall))
controller.queue_behavior(/datum/ai_behavior/find_mineral_wall, BB_TARGET_MINERAL_WALL)
return
controller.queue_behavior(/datum/ai_behavior/mine_wall, BB_TARGET_MINERAL_WALL)
return SUBTREE_RETURN_FINISH_PLANNING
if(controller.blackboard_key_exists(BB_TARGET_MINERAL_WALL))
controller.queue_behavior(/datum/ai_behavior/mine_wall, BB_TARGET_MINERAL_WALL)
return SUBTREE_RETURN_FINISH_PLANNING
controller.queue_behavior(/datum/ai_behavior/find_mineral_wall, BB_TARGET_MINERAL_WALL)
/datum/ai_behavior/mine_wall
behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_REQUIRE_REACH | AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION
@@ -168,7 +169,7 @@
/datum/pet_command/grub_spit/execute_action(datum/ai_controller/controller)
var/datum/action/cooldown/spit_ability = controller.blackboard[BB_SPIT_ABILITY]
if(QDELETED(spit_ability) || !spit_ability.IsAvailable())
if(!spit_ability?.IsAvailable())
return
controller.queue_behavior(/datum/ai_behavior/use_mob_ability, BB_SPIT_ABILITY)
controller.clear_blackboard_key(BB_ACTIVE_PET_COMMAND)
@@ -96,8 +96,7 @@
var/target_key = BB_GOLIATH_HOLE_TARGET
/datum/ai_planning_subtree/goliath_dig/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick)
var/turf/target_turf = controller.blackboard[target_key]
if (QDELETED(target_turf))
if (!controller.blackboard_key_exists(target_key))
return
controller.queue_behavior(/datum/ai_behavior/goliath_dig, target_key)
return SUBTREE_RETURN_FINISH_PLANNING
@@ -3,6 +3,7 @@
BB_TARGETTING_DATUM = new /datum/targetting_datum/basic,
)
ai_traits = PAUSE_DURING_DO_AFTER
ai_movement = /datum/ai_movement/basic_avoidance
idle_behavior = /datum/idle_behavior/idle_random_walk
planning_subtrees = list(
@@ -19,14 +19,10 @@
///find dead humans and report their location on the radio
/datum/ai_planning_subtree/locate_dead_humans/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick)
var/mob/living/dead_target = controller.blackboard[BB_NEARBY_DEAD_MINER]
if(QDELETED(dead_target))
controller.queue_behavior(/datum/ai_behavior/find_and_set/unconscious_human, BB_NEARBY_DEAD_MINER, /mob/living/carbon/human)
return
controller.queue_behavior(/datum/ai_behavior/send_sos_message, BB_NEARBY_DEAD_MINER)
return SUBTREE_RETURN_FINISH_PLANNING
if(controller.blackboard_key_exists(BB_NEARBY_DEAD_MINER))
controller.queue_behavior(/datum/ai_behavior/send_sos_message, BB_NEARBY_DEAD_MINER)
return SUBTREE_RETURN_FINISH_PLANNING
controller.queue_behavior(/datum/ai_behavior/find_and_set/unconscious_human, BB_NEARBY_DEAD_MINER, /mob/living/carbon/human)
/datum/ai_behavior/find_and_set/unconscious_human/search_tactic(datum/ai_controller/controller, locate_path, search_range)
for(var/mob/living/carbon/human/target in oview(search_range, controller.pawn))
@@ -73,18 +69,12 @@
///mine walls if we are on automated mining mode
/datum/ai_planning_subtree/minebot_mining/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick)
var/turf/mineral_target = controller.blackboard[BB_TARGET_MINERAL_TURF]
var/automated_mining = controller.blackboard[BB_AUTOMATED_MINING]
if(!automated_mining)
if(!controller.blackboard[BB_AUTOMATED_MINING])
return
if(QDELETED(mineral_target))
controller.queue_behavior(/datum/ai_behavior/find_mineral_wall/minebot, BB_TARGET_MINERAL_TURF)
return
controller.queue_behavior(/datum/ai_behavior/minebot_mine_turf, BB_TARGET_MINERAL_TURF)
return SUBTREE_RETURN_FINISH_PLANNING
if(controller.blackboard_key_exists(BB_TARGET_MINERAL_TURF))
controller.queue_behavior(/datum/ai_behavior/minebot_mine_turf, BB_TARGET_MINERAL_TURF)
return SUBTREE_RETURN_FINISH_PLANNING
controller.queue_behavior(/datum/ai_behavior/find_mineral_wall/minebot, BB_TARGET_MINERAL_TURF)
/datum/ai_behavior/find_mineral_wall/minebot
@@ -190,7 +180,7 @@
/datum/pet_command/minebot_ability/execute_action(datum/ai_controller/controller)
var/datum/action/cooldown/ability = controller.blackboard[ability_key]
if(QDELETED(ability) || !ability.IsAvailable())
if(!ability?.IsAvailable())
return
controller.queue_behavior(/datum/ai_behavior/use_mob_ability, ability_key)
controller.clear_blackboard_key(BB_ACTIVE_PET_COMMAND)
@@ -34,9 +34,7 @@
/datum/ai_planning_subtree/find_nearest_magicarp_spell_target/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick)
var/datum/action/cooldown/using_action = controller.blackboard[BB_MAGICARP_SPELL]
if (QDELETED(using_action))
return
if (!using_action.IsAvailable())
if (!using_action?.IsAvailable())
return
var/spell_targetting = controller.blackboard[BB_MAGICARP_SPELL_SPECIAL_TARGETTING]
@@ -12,9 +12,10 @@
if (!rift_behaviour)
CRASH("Forgot to specify rift behaviour for [src]")
var/mob/living/target = controller.blackboard[BB_BASIC_MOB_CURRENT_TARGET]
if (!controller.blackboard_key_exists(BB_BASIC_MOB_CURRENT_TARGET))
return
var/datum/action/cooldown/using_action = controller.blackboard[BB_CARP_RIFT]
if (QDELETED(target) || QDELETED(using_action) || !using_action.IsAvailable())
if (!using_action?.IsAvailable())
return
controller.queue_behavior(rift_behaviour, BB_CARP_RIFT, BB_BASIC_MOB_CURRENT_TARGET)
@@ -17,14 +17,10 @@
/datum/ai_planning_subtree/heal_the_blind
/datum/ai_planning_subtree/heal_the_blind/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick)
var/mob/living/carbon/target = controller.blackboard[BB_BLIND_TARGET]
if(QDELETED(target))
controller.queue_behavior(/datum/ai_behavior/find_the_blind, BB_BLIND_TARGET, BB_EYE_DAMAGE_THRESHOLD)
return
controller.queue_behavior(/datum/ai_behavior/heal_eye_damage, BB_BLIND_TARGET)
return SUBTREE_RETURN_FINISH_PLANNING
if(controller.blackboard_key_exists(BB_BLIND_TARGET))
controller.queue_behavior(/datum/ai_behavior/heal_eye_damage, BB_BLIND_TARGET)
return SUBTREE_RETURN_FINISH_PLANNING
controller.queue_behavior(/datum/ai_behavior/find_the_blind, BB_BLIND_TARGET, BB_EYE_DAMAGE_THRESHOLD)
/datum/targetting_datum/basic/eyeball/can_attack(mob/living/owner, atom/target)
. = ..()
@@ -54,14 +54,10 @@
if(!SPT_PROB(relay_chance, seconds_per_tick))
return
var/mob/hive_target = controller.blackboard[BB_HIVE_PARTNER]
if(QDELETED(hive_target))
controller.queue_behavior(/datum/ai_behavior/find_and_set/hive_partner, BB_HIVE_PARTNER, /mob/living/basic/hivebot)
return
controller.queue_behavior(/datum/ai_behavior/relay_message, BB_HIVE_PARTNER)
return SUBTREE_RETURN_FINISH_PLANNING
if (controller.blackboard_key_exists(BB_HIVE_PARTNER))
controller.queue_behavior(/datum/ai_behavior/relay_message, BB_HIVE_PARTNER)
return SUBTREE_RETURN_FINISH_PLANNING
controller.queue_behavior(/datum/ai_behavior/find_and_set/hive_partner, BB_HIVE_PARTNER, /mob/living/basic/hivebot)
/datum/ai_planning_subtree/find_and_hunt_target/repair_machines
target_key = BB_MACHINE_TARGET
@@ -4,7 +4,7 @@
BB_TARGETTING_DATUM = new /datum/targetting_datum/basic(),
)
ai_traits = STOP_MOVING_WHEN_PULLED
ai_traits = STOP_MOVING_WHEN_PULLED | PAUSE_DURING_DO_AFTER
ai_movement = /datum/ai_movement/basic_avoidance
idle_behavior = /datum/idle_behavior/idle_random_walk/less_walking
@@ -62,12 +62,9 @@
var/target_key = BB_SPIDER_WEB_TARGET
/datum/ai_planning_subtree/spin_web/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick)
var/datum/action/cooldown/using_action = controller.blackboard[action_key]
var/turf/target_turf = controller.blackboard[target_key]
if (QDELETED(using_action) || QDELETED(target_turf))
return
controller.queue_behavior(/datum/ai_behavior/spin_web, action_key, target_key)
return SUBTREE_RETURN_FINISH_PLANNING
if (controller.blackboard_key_exists(action_key) && controller.blackboard_key_exists(target_key))
controller.queue_behavior(/datum/ai_behavior/spin_web, action_key, target_key)
return SUBTREE_RETURN_FINISH_PLANNING
/// Move to an unwebbed nearby turf and web it up
/datum/ai_behavior/spin_web
@@ -1,242 +0,0 @@
#define BRIMBEAM_RANGE 10
/mob/living/simple_animal/hostile/asteroid/brimdemon
name = "brimdemon"
desc = "A misshapen demon with big, red eyes and a hinged mouth. Not much is known about the creatures \
due to their response to any unexpected stimulus being \"brimbeam\", a deadly blood-laser barrage."
icon = 'icons/mob/simple/lavaland/lavaland_monsters.dmi'
icon_state = "brimdemon"
icon_living = "brimdemon"
icon_dead = "brimdemon_dead"
mob_biotypes = MOB_ORGANIC|MOB_BEAST
speak_emote = list("cackles")
emote_taunt = list("screeches")
emote_hear = list("cackles","screeches")
combat_mode = TRUE
stat_attack = HARD_CRIT
ranged_cooldown_time = 5 SECONDS
vision_range = 9
retreat_distance = 2
speed = 3
move_to_delay = 5
maxHealth = 250
health = 250
obj_damage = 15
melee_damage_lower = 7.5
melee_damage_upper = 7.5
rapid_melee = 2 // every second attack
attack_verb_continuous = "bites"
attack_verb_simple = "bite"
attack_sound = 'sound/weapons/bite.ogg'
attack_vis_effect = ATTACK_EFFECT_BITE
butcher_results = list(
/obj/item/food/meat/slab = 2,
/obj/effect/decal/cleanable/brimdust = 1,
/obj/item/organ/internal/monster_core/brimdust_sac = 1,
)
loot = list()
robust_searching = TRUE
footstep_type = FOOTSTEP_MOB_CLAW
death_message = "wails as infernal energy escapes from its wounds, leaving it an empty husk."
death_sound = 'sound/magic/demon_dies.ogg'
light_color = LIGHT_COLOR_BLOOD_MAGIC
light_power = 5
light_range = 1.4
crusher_loot = /obj/item/crusher_trophy/brimdemon_fang
/// Are we charging/firing? If yes stops our movement.
var/firing = FALSE
/// A list of all the beam parts.
var/list/beamparts = list()
/mob/living/simple_animal/hostile/asteroid/brimdemon/Destroy()
QDEL_LIST(beamparts)
return ..()
/mob/living/simple_animal/hostile/asteroid/brimdemon/Login()
ranged = TRUE
return ..()
/mob/living/simple_animal/hostile/asteroid/brimdemon/Logout()
ranged = FALSE
return ..()
/mob/living/simple_animal/hostile/asteroid/brimdemon/death()
firing = FALSE
cut_overlay("brimdemon_telegraph_dir")
move_resist = initial(move_resist)
return ..()
/mob/living/simple_animal/hostile/asteroid/brimdemon/Goto(target, delay, minimum_distance)
if(firing)
return FALSE
return ..()
/mob/living/simple_animal/hostile/asteroid/brimdemon/MoveToTarget(list/possible_targets)
if(firing)
return FALSE
return ..()
/mob/living/simple_animal/hostile/asteroid/brimdemon/AttackingTarget(atom/attacked_target)
if(firing)
return FALSE
return ..()
/mob/living/simple_animal/hostile/asteroid/brimdemon/Move(atom/newloc, dir, step_x , step_y)
if(firing)
return FALSE
return ..()
/mob/living/simple_animal/hostile/asteroid/brimdemon/OpenFire()
if(firing)
balloon_alert(src, "already firing!")
return
if(!COOLDOWN_FINISHED(src, ranged_cooldown))
balloon_alert(src, "on cooldown!")
return
firing = TRUE
set_dir_on_move = FALSE
icon_state = "brimdemon_firing"
move_resist = MOVE_FORCE_VERY_STRONG
add_overlay("brimdemon_telegraph_dir")
visible_message(span_danger("[src] starts charging!"))
balloon_alert(src, "charging...")
addtimer(CALLBACK(src, PROC_REF(fire_laser)), 1 SECONDS)
COOLDOWN_START(src, ranged_cooldown, ranged_cooldown_time)
/mob/living/simple_animal/hostile/asteroid/brimdemon/Moved(atom/old_loc, movement_dir, forced, list/old_locs, momentum_change = TRUE)
. = ..()
check_fire()
/mob/living/simple_animal/hostile/asteroid/brimdemon/proc/check_fire()
if(key || QDELETED(target) || get_dist(src, target) > BRIMBEAM_RANGE || !(get_dir(src, target) in GLOB.cardinals))
return
face_atom(target)
OpenFire()
/// Fires a brimbeam, getting a line of turfs between it and the direction to the target and creating a brimbeam effect on every one of them.
/mob/living/simple_animal/hostile/asteroid/brimdemon/proc/fire_laser()
if(stat == DEAD)
return
visible_message(span_danger("[src] fires a brimbeam!"))
balloon_alert(src, "brimbeam fired")
playsound(src, 'sound/creatures/brimdemon.ogg', 150, FALSE, 0, 3)
cut_overlay("brimdemon_telegraph_dir")
var/turf/target_turf = get_ranged_target_turf(src, dir, BRIMBEAM_RANGE)
var/turf/origin_turf = get_turf(src)
var/list/affected_turfs = get_line(origin_turf, target_turf) - origin_turf
for(var/turf/affected_turf in affected_turfs)
var/blocked = FALSE
if(affected_turf.opacity)
blocked = TRUE
for(var/obj/potential_block in affected_turf.contents)
if(potential_block.opacity)
blocked = TRUE
break
if(blocked)
break
var/atom/new_brimbeam = new /obj/effect/brimbeam(affected_turf)
new_brimbeam.dir = dir
beamparts += new_brimbeam
for(var/mob/living/hit_mob in affected_turf.contents)
hit_mob.adjustFireLoss(25)
to_chat(hit_mob, span_userdanger("You're hit by [src]'s brimbeam!"))
if(length(beamparts))
var/atom/last_brimbeam = beamparts[length(beamparts)]
last_brimbeam.icon_state = "brimbeam_end"
var/atom/first_brimbeam = beamparts[1]
first_brimbeam.icon_state = "brimbeam_start"
addtimer(CALLBACK(src, PROC_REF(end_laser)), 2 SECONDS)
/// Deletes all the brimbeam parts and sets variables back to their initial ones.
/mob/living/simple_animal/hostile/asteroid/brimdemon/proc/end_laser()
if(stat != DEAD)
icon_state = initial(icon_state)
move_resist = initial(move_resist)
set_dir_on_move = initial(set_dir_on_move)
firing = FALSE
for(var/obj/effect/brimbeam/beam in beamparts)
animate(beam, time = 0.5 SECONDS, alpha = 0)
QDEL_IN(beam, 0.5 SECONDS)
beamparts -= beam
/obj/effect/brimbeam
name = "brimbeam"
icon = 'icons/mob/simple/lavaland/lavaland_monsters.dmi'
icon_state = "brimbeam_mid"
layer = ABOVE_MOB_LAYER
plane = ABOVE_GAME_PLANE
mouse_opacity = MOUSE_OPACITY_TRANSPARENT
light_color = LIGHT_COLOR_BLOOD_MAGIC
light_power = 3
light_range = 2
/obj/effect/brimbeam/Initialize(mapload)
. = ..()
START_PROCESSING(SSfastprocess, src)
/obj/effect/brimbeam/Destroy()
STOP_PROCESSING(SSfastprocess, src)
return ..()
/obj/effect/brimbeam/process()
for(var/mob/living/hit_mob in get_turf(src))
damage(hit_mob)
/obj/effect/brimbeam/proc/damage(mob/living/hit_mob)
hit_mob.adjustFireLoss(5)
to_chat(hit_mob, span_danger("You're damaged by [src]!"))
/obj/item/crusher_trophy/brimdemon_fang
name = "brimdemon's fang"
icon_state = "brimdemon_fang"
desc = "A fang from a brimdemon's corpse."
denied_type = /obj/item/crusher_trophy/brimdemon_fang
var/static/list/comic_phrases = list("BOOM", "BANG", "KABLOW", "KAPOW", "OUCH", "BAM", "KAPOW", "WHAM", "POW", "KABOOM")
/obj/item/crusher_trophy/brimdemon_fang/effect_desc()
return "mark detonation creates visual and audiosensory effects on the target"
/obj/item/crusher_trophy/brimdemon_fang/on_mark_detonation(mob/living/target, mob/living/user)
target.balloon_alert_to_viewers("[pick(comic_phrases)]!")
playsound(target, 'sound/lavaland/brimdemon_crush.ogg', 100)
/obj/effect/decal/cleanable/brimdust
name = "brimdust"
desc = "Dust from a brimdemon. It is considered valuable for its' botanical abilities."
icon_state = "brimdust"
icon = 'icons/obj/mining.dmi'
layer = FLOOR_CLEAN_LAYER
mergeable_decal = FALSE
/obj/effect/decal/cleanable/brimdust/Initialize(mapload)
. = ..()
reagents.add_reagent(/datum/reagent/brimdust, 15)
/obj/item/ore_sensor
name = "ore sensor"
desc = "Using demonic frequencies, this ear-mounted tool detects ores in the nearby terrain."
icon_state = "oresensor"
icon = 'icons/obj/mining.dmi'
slot_flags = ITEM_SLOT_EARS
var/range = 5
var/cooldown = 4 SECONDS //between the standard and the advanced ore scanner in strength
COOLDOWN_DECLARE(ore_sensing_cooldown)
/obj/item/ore_sensor/equipped(mob/user, slot, initial)
. = ..()
if(slot & ITEM_SLOT_EARS)
START_PROCESSING(SSobj, src)
else
STOP_PROCESSING(SSobj, src)
/obj/item/ore_sensor/dropped(mob/user, silent)
. = ..()
STOP_PROCESSING(SSobj, src)
/obj/item/ore_sensor/process(seconds_per_tick)
if(!COOLDOWN_FINISHED(src, ore_sensing_cooldown))
return
COOLDOWN_START(src, ore_sensing_cooldown, cooldown)
mineral_scan_pulse(get_turf(src), range)
#undef BRIMBEAM_RANGE
@@ -64,17 +64,17 @@
build_all_button_icons()
return TRUE
/datum/action/cooldown/spell/pointed/InterceptClickOn(mob/living/caller, params, atom/click_target)
/datum/action/cooldown/spell/pointed/InterceptClickOn(mob/living/caller, params, atom/target)
var/atom/aim_assist_target
if(aim_assist && isturf(click_target))
if(aim_assist && isturf(target))
// Find any human in the list. We aren't picky, it's aim assist after all
aim_assist_target = locate(/mob/living/carbon/human) in click_target
aim_assist_target = locate(/mob/living/carbon/human) in target
if(!aim_assist_target)
// If we didn't find a human, we settle for any living at all
aim_assist_target = locate(/mob/living) in click_target
aim_assist_target = locate(/mob/living) in target
return ..(caller, params, aim_assist_target || click_target)
return ..(caller, params, aim_assist_target || target)
/datum/action/cooldown/spell/pointed/is_valid_target(atom/cast_on)
if(cast_on == owner)
@@ -35,27 +35,27 @@
return FALSE
return TRUE
/datum/action/cooldown/spell/pointed/swap/InterceptClickOn(mob/living/caller, params, atom/click_target)
/datum/action/cooldown/spell/pointed/swap/InterceptClickOn(mob/living/caller, params, atom/target)
if(LAZYACCESS(params2list(params), RIGHT_CLICK))
if(!IsAvailable(feedback = TRUE))
return FALSE
if(!target)
return FALSE
if(!isliving(click_target) || isturf(click_target))
if(!isliving(target) || isturf(target))
// Find any living being in the list. We aren't picky, it's aim assist after all
click_target = locate(/mob/living) in click_target
if(!click_target)
target = locate(/mob/living) in target
if(!target)
to_chat(owner, span_warning("You can only select living beings as secondary target!"))
return FALSE
if(click_target == owner)
if(target == owner)
if(!isnull(second_target))
to_chat(owner, span_notice("You cancel your secondary swap target!"))
second_target = null
else
to_chat(owner, span_warning("You have no secondary swap target!"))
return FALSE
second_target = click_target
to_chat(owner, span_notice("You select [click_target.name] as a secondary swap target!"))
second_target = target
to_chat(owner, span_notice("You select [target.name] as a secondary swap target!"))
return FALSE
return ..()
@@ -51,7 +51,6 @@
/mob/living/simple_animal/hostile/alien/queen/large,
/mob/living/simple_animal/hostile/alien/sentinel,
/mob/living/simple_animal/hostile/asteroid,
/mob/living/simple_animal/hostile/asteroid/brimdemon,
/mob/living/simple_animal/hostile/asteroid/curseblob,
/mob/living/simple_animal/hostile/asteroid/elite,
/mob/living/simple_animal/hostile/asteroid/elite/broodmother,
+6 -1
View File
@@ -811,6 +811,7 @@
#include "code\datums\ai\basic_mobs\basic_ai_behaviors\travel_towards.dm"
#include "code\datums\ai\basic_mobs\basic_ai_behaviors\ventcrawling.dm"
#include "code\datums\ai\basic_mobs\basic_ai_behaviors\write_on_paper.dm"
#include "code\datums\ai\basic_mobs\basic_subtrees\attack_adjacent_target.dm"
#include "code\datums\ai\basic_mobs\basic_subtrees\attack_obstacle_in_path.dm"
#include "code\datums\ai\basic_mobs\basic_subtrees\climb_tree.dm"
#include "code\datums\ai\basic_mobs\basic_subtrees\find_food.dm"
@@ -818,6 +819,7 @@
#include "code\datums\ai\basic_mobs\basic_subtrees\find_parent.dm"
#include "code\datums\ai\basic_mobs\basic_subtrees\flee_target.dm"
#include "code\datums\ai\basic_mobs\basic_subtrees\maintain_distance.dm"
#include "code\datums\ai\basic_mobs\basic_subtrees\move_to_cardinal.dm"
#include "code\datums\ai\basic_mobs\basic_subtrees\opportunistic_ventcrawler.dm"
#include "code\datums\ai\basic_mobs\basic_subtrees\ranged_skirmish.dm"
#include "code\datums\ai\basic_mobs\basic_subtrees\simple_attack_target.dm"
@@ -4296,6 +4298,10 @@
#include "code\modules\mob\living\basic\lavaland\bileworm\bileworm_instrument.dm"
#include "code\modules\mob\living\basic\lavaland\bileworm\bileworm_loot.dm"
#include "code\modules\mob\living\basic\lavaland\bileworm\bileworm_vileworm.dm"
#include "code\modules\mob\living\basic\lavaland\brimdemon\brimbeam.dm"
#include "code\modules\mob\living\basic\lavaland\brimdemon\brimdemon.dm"
#include "code\modules\mob\living\basic\lavaland\brimdemon\brimdemon_ai.dm"
#include "code\modules\mob\living\basic\lavaland\brimdemon\brimdemon_loot.dm"
#include "code\modules\mob\living\basic\lavaland\goldgrub\goldgrub.dm"
#include "code\modules\mob\living\basic\lavaland\goldgrub\goldgrub_abilities.dm"
#include "code\modules\mob\living\basic\lavaland\goldgrub\goldgrub_ai.dm"
@@ -4630,7 +4636,6 @@
#include "code\modules\mob\living\simple_animal\hostile\megafauna\hierophant.dm"
#include "code\modules\mob\living\simple_animal\hostile\megafauna\legion.dm"
#include "code\modules\mob\living\simple_animal\hostile\megafauna\wendigo.dm"
#include "code\modules\mob\living\simple_animal\hostile\mining_mobs\brimdemon.dm"
#include "code\modules\mob\living\simple_animal\hostile\mining_mobs\curse_blob.dm"
#include "code\modules\mob\living\simple_animal\hostile\mining_mobs\gutlunch.dm"
#include "code\modules\mob\living\simple_animal\hostile\mining_mobs\hivelord.dm"
@@ -0,0 +1 @@
/mob/living/simple_animal/hostile/asteroid/brimdemon : /mob/living/basic/mining/brimdemon{@OLD}