diff --git a/code/_globalvars/phobias.dm b/code/_globalvars/phobias.dm index 2928bce9eaf..35b738867f4 100644 --- a/code/_globalvars/phobias.dm +++ b/code/_globalvars/phobias.dm @@ -68,7 +68,7 @@ GLOBAL_LIST_INIT(phobia_mobs, list( /mob/living/simple_animal/bot/mulebot/paranormal, /mob/living/simple_animal/hostile/construct, /mob/living/simple_animal/hostile/dark_wizard, - /mob/living/simple_animal/hostile/faithless, + /mob/living/basic/faithless, /mob/living/simple_animal/hostile/heretic_summon, /mob/living/simple_animal/hostile/imp, /mob/living/simple_animal/hostile/retaliate/bat, diff --git a/code/datums/ai/basic_mobs/basic_subtrees/attack_obstacle_in_path.dm b/code/datums/ai/basic_mobs/basic_subtrees/attack_obstacle_in_path.dm index 8a4e566d791..7943e463074 100644 --- a/code/datums/ai/basic_mobs/basic_subtrees/attack_obstacle_in_path.dm +++ b/code/datums/ai/basic_mobs/basic_subtrees/attack_obstacle_in_path.dm @@ -25,6 +25,8 @@ action_cooldown = 1 SECONDS /// If we should attack walls, be prepared for complaints about breaches var/can_attack_turfs = FALSE + /// Tries to bump open airlocks with an attack + var/bump_open_airlock = FALSE /datum/ai_behavior/attack_obstructions/perform(delta_time, datum/ai_controller/controller, target_key) . = ..() @@ -76,3 +78,6 @@ if (basic_mob.see_invisible < object.invisibility) return FALSE return TRUE // It's in our way, let's get it out of our way + +/datum/ai_planning_subtree/attack_obstacle_in_path/low_priority_target + target_key = BB_LOW_PRIORITY_HUNTING_TARGET diff --git a/code/datums/ai/basic_mobs/basic_subtrees/speech_subtree.dm b/code/datums/ai/basic_mobs/basic_subtrees/speech_subtree.dm index b7c56ec01a9..221b2259600 100644 --- a/code/datums/ai/basic_mobs/basic_subtrees/speech_subtree.dm +++ b/code/datums/ai/basic_mobs/basic_subtrees/speech_subtree.dm @@ -106,3 +106,7 @@ dog_pawn.update_dog_speech(src) return ..() + +/datum/ai_planning_subtree/random_speech/faithless + speech_chance = 1 + emote_see = list("wails.") diff --git a/code/datums/ai/hunting_behavior/hunting_behaviors.dm b/code/datums/ai/hunting_behavior/hunting_behaviors.dm index 31975d7ea53..9d84e8a37ac 100644 --- a/code/datums/ai/hunting_behavior/hunting_behaviors.dm +++ b/code/datums/ai/hunting_behavior/hunting_behaviors.dm @@ -75,6 +75,8 @@ behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT /// How long do we have to wait after a successful hunt? var/hunt_cooldown = 5 SECONDS + /// Do we reset the target after attacking something, so we can check for status changes. + var/always_reset_target = FALSE /datum/ai_behavior/hunt_target/setup(datum/ai_controller/controller, hunting_target_key, hunting_cooldown_key) . = ..() @@ -118,6 +120,8 @@ controller.blackboard[hunting_cooldown_key] = world.time + hunt_cooldown else if(hunting_target_key) controller.blackboard[hunting_target_key] = null + if(always_reset_target && hunting_target_key) + controller.blackboard[hunting_target_key] = null /datum/ai_behavior/hunt_target/unarmed_attack_target diff --git a/code/datums/ai/hunting_behavior/hunting_lights.dm b/code/datums/ai/hunting_behavior/hunting_lights.dm new file mode 100644 index 00000000000..f26743b7dfa --- /dev/null +++ b/code/datums/ai/hunting_behavior/hunting_lights.dm @@ -0,0 +1,23 @@ +/datum/ai_planning_subtree/find_and_hunt_target/look_for_light_fixtures + target_key = BB_LOW_PRIORITY_HUNTING_TARGET + finding_behavior = /datum/ai_behavior/find_hunt_target/light_fixtures + hunting_behavior = /datum/ai_behavior/hunt_target/light_fixtures + hunt_targets = list(/obj/machinery/light) + hunt_range = 7 + +/datum/ai_behavior/hunt_target/light_fixtures + hunt_cooldown = 10 SECONDS + always_reset_target = TRUE + +/datum/ai_behavior/hunt_target/light_fixtures/target_caught(mob/living/hunter, obj/machinery/light/hunted) + hunter.UnarmedAttack(hunted, TRUE) + +/datum/ai_behavior/find_hunt_target/light_fixtures + +/datum/ai_behavior/find_hunt_target/light_fixtures/valid_dinner(mob/living/source, obj/machinery/light/dinner, radius) + if(istype(dinner, /obj/machinery/light)) + var/obj/machinery/light/light_target = dinner + if(light_target.status == LIGHT_BROKEN) //light is already broken + return FALSE + + return can_see(source, dinner, radius) diff --git a/code/datums/components/pry_open_door.dm b/code/datums/components/pry_open_door.dm new file mode 100644 index 00000000000..11abf36d190 --- /dev/null +++ b/code/datums/components/pry_open_door.dm @@ -0,0 +1,52 @@ +/** + * Attached to a basic mob. + * Causes attacks on doors to attempt to open them. + */ +/datum/component/pry_open_door + /// Odds the attack opens the door + var/open_chance + /// Time it takes to open a door with force + var/force_wait + +/datum/component/pry_open_door/Initialize(open_chance = 100, force_wait = 10 SECONDS) + . = ..() + + if(!isbasicmob(parent)) + return COMPONENT_INCOMPATIBLE + src.open_chance = open_chance + src.force_wait = force_wait + +/datum/component/pry_open_door/RegisterWithParent() + RegisterSignal(parent, COMSIG_HOSTILE_POST_ATTACKINGTARGET, PROC_REF(hostile_attackingtarget)) + +/datum/component/pry_open_door/UnregisterFromParent() + UnregisterSignal(parent, COMSIG_HOSTILE_POST_ATTACKINGTARGET) + +/datum/component/pry_open_door/proc/hostile_attackingtarget(mob/living/basic/attacker, atom/target, success) + SIGNAL_HANDLER + + if(!success) + return + if(istype(target, /obj/machinery/door/airlock) && prob(open_chance)) + var/obj/machinery/door/airlock/airlock_target = target + INVOKE_ASYNC(src, PROC_REF(open_door), attacker, airlock_target) + +/datum/component/pry_open_door/proc/open_door(mob/living/basic/attacker, obj/machinery/door/airlock/airlock_target) + if(airlock_target.locked) + to_chat(attacker, span_warning("The airlock's bolts prevent it from being forced!")) + return + else if(!airlock_target.allowed(attacker) && airlock_target.hasPower()) + attacker.visible_message(span_warning("We start forcing the [airlock_target] open."), \ + span_hear("You hear a metal screeching sound.")) + playsound(airlock_target, 'sound/machines/airlock_alien_prying.ogg', 100, TRUE) + if(!do_after(attacker, force_wait, airlock_target)) + return + if(airlock_target.locked) + return + attacker.visible_message(span_warning("We force the [airlock_target] to open.")) + airlock_target.open(2) + else if(!airlock_target.hasPower()) + attacker.visible_message(span_warning("We force the [airlock_target] to open.")) + airlock_target.open(1) + else + airlock_target.open(0) diff --git a/code/modules/mob/living/basic/space_fauna/faithless/faithless.dm b/code/modules/mob/living/basic/space_fauna/faithless/faithless.dm new file mode 100644 index 00000000000..f8fe28a5572 --- /dev/null +++ b/code/modules/mob/living/basic/space_fauna/faithless/faithless.dm @@ -0,0 +1,84 @@ +/mob/living/basic/faithless + name = "The Faithless" + desc = "The Wish Granter's faith in humanity, incarnate." + icon_state = "faithless" + icon_living = "faithless" + icon_dead = "faithless_dead" + mob_biotypes = MOB_ORGANIC|MOB_HUMANOID + gender = MALE + response_help_continuous = "passes through" + response_help_simple = "pass through" + speed = 0.5 + maxHealth = 80 + health = 80 + + obj_damage = 50 + melee_damage_lower = 15 + melee_damage_upper = 15 + attack_verb_continuous = "grips" + attack_verb_simple = "grip" + attack_sound = 'sound/hallucinations/growl1.ogg' + speak_emote = list("growls") + + unsuitable_atmos_damage = 0 + unsuitable_cold_damage = 0 + unsuitable_heat_damage = 0 + + faction = list("faithless") + gold_core_spawnable = HOSTILE_SPAWN + + ai_controller = /datum/ai_controller/basic_controller/faithless + +/mob/living/basic/faithless/Initialize(mapload) + . = ..() + ADD_TRAIT(src, TRAIT_SPACEWALK, INNATE_TRAIT) + AddElement(/datum/element/footstep, FOOTSTEP_MOB_SHOE) + AddComponent(/datum/component/pry_open_door) + +/datum/ai_controller/basic_controller/faithless + blackboard = list( + BB_TARGETTING_DATUM = new /datum/targetting_datum/basic/faithless(), + BB_LOW_PRIORITY_HUNTING_TARGET = null, // lights + ) + + ai_movement = /datum/ai_movement/basic_avoidance + idle_behavior = /datum/idle_behavior/idle_random_walk + planning_subtrees = list( + /datum/ai_planning_subtree/simple_find_target, + /datum/ai_planning_subtree/attack_obstacle_in_path, + /datum/ai_planning_subtree/attack_obstacle_in_path/low_priority_target, + /datum/ai_planning_subtree/basic_melee_attack_subtree/faithless, + /datum/ai_planning_subtree/find_and_hunt_target/look_for_light_fixtures, + /datum/ai_planning_subtree/random_speech/faithless, + ) + +/datum/targetting_datum/basic/faithless + stat_attack = UNCONSCIOUS + +/datum/ai_planning_subtree/basic_melee_attack_subtree/faithless + melee_attack_behavior = /datum/ai_behavior/basic_melee_attack/faithless + +/datum/ai_behavior/basic_melee_attack/faithless + action_cooldown = 1 SECONDS + /// What are the odds we paralyze a target + var/paralyze_chance = 12 + /// How long do we paralyze a target for if we attack them + var/paralyze_duration = 2 SECONDS + +/datum/ai_behavior/basic_melee_attack/faithless/perform(delta_time, datum/ai_controller/controller, target_key, targetting_datum_key, hiding_location_key) + . = ..() + var/datum/weakref/weak_target = controller.blackboard[target_key] + var/atom/target = weak_target?.resolve() + var/mob/living/living_pawn = controller.pawn + + if(!isliving(target)) + return + var/mob/living/living_target = target + if(living_target.pulledby != living_pawn && !HAS_AI_CONTROLLER_TYPE(living_target.pulledby, /datum/ai_controller/basic_controller/faithless)) //Dont steal from my fellow faithless. + if(living_pawn.Adjacent(living_target) && isturf(living_target.loc) && living_target.stat == SOFT_CRIT) + living_target.grabbedby(living_pawn) //Drag their bodies around as a menace. + if(prob(paralyze_chance) && iscarbon(target)) + var/mob/living/carbon/carbon_target = target + carbon_target.Paralyze(paralyze_duration) + carbon_target.visible_message(span_danger("\The [living_pawn] knocks down \the [carbon_target]!"), \ + span_userdanger("\The [living_pawn] knocks you down!")) diff --git a/code/modules/mob/living/simple_animal/hostile/faithless.dm b/code/modules/mob/living/simple_animal/hostile/faithless.dm deleted file mode 100644 index bbfdaa4d5d6..00000000000 --- a/code/modules/mob/living/simple_animal/hostile/faithless.dm +++ /dev/null @@ -1,48 +0,0 @@ -/mob/living/simple_animal/hostile/faithless - name = "The Faithless" - desc = "The Wish Granter's faith in humanity, incarnate." - icon_state = "faithless" - icon_living = "faithless" - icon_dead = "faithless_dead" - mob_biotypes = MOB_ORGANIC|MOB_HUMANOID - gender = MALE - speak_chance = 0 - turns_per_move = 5 - response_help_continuous = "passes through" - response_help_simple = "pass through" - emote_taunt = list("wails") - taunt_chance = 25 - speed = 0 - maxHealth = 80 - health = 80 - stat_attack = HARD_CRIT - robust_searching = 1 - - harm_intent_damage = 10 - obj_damage = 50 - melee_damage_lower = 15 - melee_damage_upper = 15 - attack_verb_continuous = "grips" - attack_verb_simple = "grip" - attack_sound = 'sound/hallucinations/growl1.ogg' - speak_emote = list("growls") - - atmos_requirements = list("min_oxy" = 0, "max_oxy" = 0, "min_plas" = 0, "max_plas" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0) - minbodytemp = 0 - - faction = list("faithless") - gold_core_spawnable = HOSTILE_SPAWN - - footstep_type = FOOTSTEP_MOB_SHOE - -/mob/living/simple_animal/hostile/faithless/Initialize(mapload) - . = ..() - ADD_TRAIT(src, TRAIT_SPACEWALK, INNATE_TRAIT) - -/mob/living/simple_animal/hostile/faithless/AttackingTarget() - . = ..() - if(. && prob(12) && iscarbon(target)) - var/mob/living/carbon/C = target - C.Paralyze(60) - C.visible_message(span_danger("\The [src] knocks down \the [C]!"), \ - span_userdanger("\The [src] knocks you down!")) diff --git a/code/modules/unit_tests/simple_animal_freeze.dm b/code/modules/unit_tests/simple_animal_freeze.dm index 4a7fc2c110e..2521607ea45 100644 --- a/code/modules/unit_tests/simple_animal_freeze.dm +++ b/code/modules/unit_tests/simple_animal_freeze.dm @@ -149,7 +149,6 @@ /mob/living/simple_animal/hostile/construct/wraith/noncult, /mob/living/simple_animal/hostile/dark_wizard, /mob/living/simple_animal/hostile/eyeball, - /mob/living/simple_animal/hostile/faithless, /mob/living/simple_animal/hostile/giant_spider, /mob/living/simple_animal/hostile/giant_spider/hunter, /mob/living/simple_animal/hostile/giant_spider/hunter/flesh, diff --git a/tgstation.dme b/tgstation.dme index b49f6643d47..5ad9e201cc6 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -765,6 +765,7 @@ #include "code\datums\ai\hauntium\hauntium_subtrees.dm" #include "code\datums\ai\hunting_behavior\hunting_behaviors.dm" #include "code\datums\ai\hunting_behavior\hunting_cockroach.dm" +#include "code\datums\ai\hunting_behavior\hunting_lights.dm" #include "code\datums\ai\hunting_behavior\hunting_mouse.dm" #include "code\datums\ai\idle_behaviors\_idle_behavior.dm" #include "code\datums\ai\idle_behaviors\idle_dog.dm" @@ -911,6 +912,7 @@ #include "code\datums\components\pellet_cloud.dm" #include "code\datums\components\phylactery.dm" #include "code\datums\components\pricetag.dm" +#include "code\datums\components\pry_open_door.dm" #include "code\datums\components\punchcooldown.dm" #include "code\datums\components\puzzgrid.dm" #include "code\datums\components\radiation_countdown.dm" @@ -3688,6 +3690,7 @@ #include "code\modules\mob\living\basic\space_fauna\carp\carp_controllers.dm" #include "code\modules\mob\living\basic\space_fauna\carp\magicarp.dm" #include "code\modules\mob\living\basic\space_fauna\carp\megacarp.dm" +#include "code\modules\mob\living\basic\space_fauna\faithless\faithless.dm" #include "code\modules\mob\living\basic\syndicate\syndicate.dm" #include "code\modules\mob\living\basic\syndicate\syndicate_ai.dm" #include "code\modules\mob\living\basic\vermin\axolotl.dm" @@ -3900,7 +3903,6 @@ #include "code\modules\mob\living\simple_animal\hostile\cat_butcher.dm" #include "code\modules\mob\living\simple_animal\hostile\dark_wizard.dm" #include "code\modules\mob\living\simple_animal\hostile\eyeballs.dm" -#include "code\modules\mob\living\simple_animal\hostile\faithless.dm" #include "code\modules\mob\living\simple_animal\hostile\giant_spider.dm" #include "code\modules\mob\living\simple_animal\hostile\goose.dm" #include "code\modules\mob\living\simple_animal\hostile\headcrab.dm" diff --git a/tools/UpdatePaths/Scripts/72479_replace_faithless b/tools/UpdatePaths/Scripts/72479_replace_faithless new file mode 100644 index 00000000000..94d690326d0 --- /dev/null +++ b/tools/UpdatePaths/Scripts/72479_replace_faithless @@ -0,0 +1 @@ +/mob/living/simple_animal/hostile/faithless : /mob/living/basic/faithless{@OLD}