New PVE Major Midround: Demonic Incursion (#29053)

* Initial commit. Event.

* Started converting mobs to basic mobs. Migo and Creature. Needs aggressiveness AI

* Makes CI happy

* Fixes some file names

* Ticks files

* Fixes file again

* Update code/modules/events/demon_incursion.dm

Co-authored-by: Luc <89928798+lewcc@users.noreply.github.com>
Signed-off-by: PollardTheDragon <144391971+PollardTheDragon@users.noreply.github.com>

* Announcement sound, spawn adjustments

* Reduced spread when portals multiply

* Elite spawns when portal count gets high

* Fixes missing comma

* Converted migo, blank, and most of hellhounds to basic mobs. Added new controllers and behaviors

* Fixes

* Linters!

* Faithless moved to basic mob

* LINTERS

* Makes skeletons basic mobs

* Fixes, Ranged Attacks, Ranged variants of nether mobs

* Handles portal expansion chance

* Linters

* Fixes mobs not breaking shit

* Fixes ranged attacks

* Fixes ranged attacks

* oops

* Another oops. No config changes are needed here

* Twenty percent chance that a nether mob is a grappler

* Dimensional tear fixes

* Adjusts awaken distance for hostile mobs, makes variable melee attack rate for basic mobs

* Variable initial spawns, variable spread rates.

* Whole lot of fixes from merge, hellhound completion

* Updated lavaland winter biodome

* Makes basic mobs able to hurt other mobs

* Makes spawners properly rally basic mobs to beat up the attacker

* Extra line

* Removed comment

* Makes hellhounds stop resting when attacked or when they find a new target

* Fixed initial portal spawn amounts

* Borgs now affected by basic mobs

* Nerfs portal spawn rate, nerfs portal integrity, nerfs portal max mobs

* Grapplers now teleport to missed turfs

* Removes duplicate notices

* Buff portals a small bit

* Makes nether portals no longer RR - the body is now recoverable from the blank it became

* Makes portals layer above mobs

* Removed excess ranged attack var

* Changes list for determining start count to rely on mobs with client instead of all clients

* Nerfs portal max mobs

* Portals can no longer spread to within 3 tiles of another portal except on initial event start

* Adjusts target portal count for big mobs

* Spawners now properly remove nest values of basic mobs

* Portals now glow an evil red. When portals are destroyed, 50% chance per mob to slay the mob

* add prowling and return to home behaviors

* cut this down

* be a tiny bit smarter

* Some code cleanup

* Removes hostile base type, removing excess code

* Gives /obj/ a basic mob attack handler. Fixes turrets

* Gives basic mobs a HUD

* Fixes skeleton death flag

* Adjusted initial spawns

* Incursion portals now slowly convert turfs, up to range 3 of them, to hellish flooring

* Increases reward per destroyed portal

* Makes the final portal of an incursion play a sound on destruction, couple portal fixes

* Fixes basic mob xenobiology interactions

* Non-shit portal sprites

* Adds hostile machine element, mobs now actively target turrets and emitters

* Properly gibs things when they should gib

* Linter fix

* Portals now layer under living mobs but over dead ones

* Adds blackbox checking for demon incursion portal counts

* Increases mob sight range slightly to account for widescreen, adds alt-color for grappler, delays incursion announcement a bit more

* Portals now are more likely to spread the less there are

* Incursion portals now repair themselves after not being damaged for some time

* Grilles now shock basic mobs

* Portals will now clean up basic mob corpses near them by gibbing them

* Portal spread chance is now exponential regression

* Portal mob spawns now linearly scale in time

* Fixes some skeleton oversights in ruin mapping

* Demon incursions no longer can spread to tiles in a space area, such as near brig plating

* Moves corpse cleanup to mobs via component

* Portals now drop bodies that are being eaten when they're destroyed.

* Addresses code review

* Docs some vars

---------

Signed-off-by: PollardTheDragon <144391971+PollardTheDragon@users.noreply.github.com>
Co-authored-by: Luc <89928798+lewcc@users.noreply.github.com>
Co-authored-by: warriorstar-orion <orion@snowfrost.garden>
This commit is contained in:
PollardTheDragon
2025-07-16 00:19:23 +00:00
committed by GitHub
co-authored by Luc warriorstar-orion
parent 3342288f79
commit 05791861b7
50 changed files with 1265 additions and 288 deletions
@@ -149,7 +149,7 @@
/turf/simulated/floor/wood,
/area/ruin/powered/snow_cabin)
"aH" = (
/mob/living/simple_animal/hostile/skeleton/arctic,
/mob/living/basic/skeleton/arctic,
/turf/simulated/floor/wood,
/area/ruin/powered/snow_cabin)
"aI" = (
@@ -22,7 +22,7 @@
/obj/structure/chair/comfy/shuttle{
dir = 4
},
/mob/living/simple_animal/hostile/retaliate/skeleton/warden{
/mob/living/basic/skeleton/warden{
dir = 4
},
/turf/simulated/floor/plasteel/airless{
@@ -5,7 +5,7 @@
/turf/simulated/floor/plasteel,
/area/ruin/space/unpowered)
"bO" = (
/mob/living/simple_animal/hostile/skeleton,
/mob/living/basic/skeleton,
/turf/simulated/floor/plasteel,
/area/ruin/space/unpowered)
"bQ" = (
+1
View File
@@ -13,6 +13,7 @@
// How far should we, by default, be looking for interesting things to de-idle?
#define AI_DEFAULT_INTERESTING_DIST 10
#define AI_SIMPLE_INTERESTING_DIST 15
/// Cooldown on planning if planning failed last time
+7 -1
View File
@@ -45,6 +45,8 @@
#define BB_TEMPORARILY_IGNORE_FACTION "BB_TEMPORARILY_IGNORE_FACTIONS"
///List of mobs who have damaged us
#define BB_BASIC_MOB_RETALIATE_LIST "BB_BASIC_MOB_SHITLIST"
///Blackboard key for a whitelist typecache of "things we can target while trying to move"
#define BB_OBSTACLE_TARGETING_WHITELIST "BB_targeting_whitelist"
//Hunting BB keys
@@ -145,7 +147,7 @@
// Misc
/// For /datum/ai_behavior/find_potential_targets, what if any field are we using currently
#define BB_FIND_TARGETS_FIELD(type) "bb_find_targets_field_[type]"
#define BB_FIND_TARGETS_FIELD(type) "BB_FIND_TARGETS_FIELD_[type]"
/// key that tells the wall we will mine
#define BB_TARGET_MINERAL_WALL "BB_TARGET_MINERAL_WALL"
@@ -153,3 +155,7 @@
#define BB_ORE_TARGET "BB_ORE_TARGET"
/// which ore types we will not eat
#define BB_ORE_IGNORE_TYPES "BB_ORE_IGNORE_TYPES"
#define BB_INCURSION_HOME_PORTAL "BB_INCURSION_HOME_PORTAL"
#define BB_INCURSION_HOME_PORTAL_TARGET "BB_INCURSION_HOME_PORTAL_TARGET"
#define BB_PROWL_TARGET "BB_PROWL_TARGET"
+1
View File
@@ -79,6 +79,7 @@
#define BELOW_MOB_LAYER 3.7
#define LYING_MOB_LAYER 3.8
#define ABOVE_LYING_MOB_LAYER 3.9
//#define MOB_LAYER 4 //For easy recordkeeping; this is a byond define
#define ABOVE_MOB_LAYER 4.1
#define HITSCAN_LAYER 4.2
+1
View File
@@ -640,6 +640,7 @@
#define BIOHAZARD_BLOB "Blob"
#define BIOHAZARD_XENO "Xenomorphs"
#define INCURSION_DEMONS "Demon Incursion"
#define MAX_ALLOWED_TELEPORTS_PER_PROCESS 20
+8
View File
@@ -843,6 +843,10 @@ SUBSYSTEM_DEF(ticker)
for(var/atom/blob_overmind in SSticker.mode.blob_overminds)
if(blob_overmind.admin_spawned)
return TRUE
if(INCURSION_DEMONS)
for(var/obj/portal in SSticker.mode.incursion_portals)
if(portal.admin_spawned)
return TRUE
/datum/controller/subsystem/ticker/proc/biohazard_count(biohazard)
switch(biohazard)
@@ -858,6 +862,8 @@ SUBSYSTEM_DEF(ticker)
return count_xenomorps()
if(BIOHAZARD_BLOB)
return length(SSticker.mode.blob_overminds)
if(INCURSION_DEMONS)
return length(SSticker.mode.incursion_portals)
CRASH("biohazard_count got unexpected [biohazard]")
@@ -875,5 +881,7 @@ SUBSYSTEM_DEF(ticker)
return count > 5
if(BIOHAZARD_BLOB)
return count > 0
if(INCURSION_DEMONS)
return count > 0
return FALSE
@@ -6,6 +6,7 @@ GLOBAL_LIST_EMPTY_TYPED(hostile_machines, /atom)
GLOBAL_LIST_INIT(target_interested_atoms, typecacheof(list(
/mob,
/obj/machinery/porta_turret,
/obj/machinery/power/emitter,
/obj/mecha,
)))
@@ -13,7 +14,7 @@ GLOBAL_LIST_INIT(target_interested_atoms, typecacheof(list(
action_cooldown = 2 SECONDS
behavior_flags = AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION
/// How far can we see stuff?
var/vision_range = 9
var/vision_range = 11
/// Blackboard key for aggro range, uses vision range if not specified
var/aggro_range_key = BB_AGGRO_RANGE
@@ -156,3 +157,6 @@ GLOBAL_LIST_INIT(target_interested_atoms, typecacheof(list(
/// Returns the desired final target from the filtered list of targets
/datum/ai_behavior/find_potential_targets/proc/pick_final_target(datum/ai_controller/controller, list/filtered_targets)
return pick(filtered_targets)
/datum/ai_behavior/find_potential_targets/bigger_range
vision_range = 16
@@ -0,0 +1,100 @@
/// If there's something between us and our target then we need to queue a behaviour to make it not be there
/datum/ai_planning_subtree/attack_obstacle_in_path
/// Blackboard key containing current target
var/target_key = BB_BASIC_MOB_CURRENT_TARGET
/// The action to execute, extend to add a different cooldown or something
var/attack_behaviour = /datum/ai_behavior/attack_obstructions
/datum/ai_planning_subtree/attack_obstacle_in_path/select_behaviors(datum/ai_controller/controller, seconds_per_tick)
. = ..()
var/atom/target = controller.blackboard[target_key]
if(QDELETED(target))
return
var/turf/next_step = get_step_towards(controller.pawn, target)
if(!next_step.is_blocked_turf(exclude_mobs = TRUE, source_atom = controller.pawn))
return
controller.queue_behavior(attack_behaviour, target_key)
// Don't cancel future planning, maybe we can move now
/// Something is in our way, get it outta here
/datum/ai_behavior/attack_obstructions
action_cooldown = 2 SECONDS
/// If we should attack walls, be prepared for complaints about breaches
var/can_attack_turfs = FALSE
/// For if you want your mob to be able to attack dense objects
var/can_attack_dense_objects = TRUE
/datum/ai_behavior/attack_obstructions/perform(seconds_per_tick, datum/ai_controller/controller, target_key)
var/mob/living/basic/basic_mob = controller.pawn
var/atom/target = controller.blackboard[target_key]
if(QDELETED(target))
return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED
var/turf/next_step = get_step_towards(basic_mob, target)
var/dir_to_next_step = get_dir(basic_mob, next_step)
// If moving diagonally we need to punch both ways, or more accurately the one we are blocked in
var/list/dirs_to_move = list()
if(dir_to_next_step && GLOB.diagonals)
for(var/direction in GLOB.cardinal)
if(direction & dir_to_next_step)
dirs_to_move += direction
else
dirs_to_move += dir_to_next_step
for(var/direction in dirs_to_move)
if(attack_in_direction(controller, basic_mob, direction))
return AI_BEHAVIOR_DELAY
return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED
/datum/ai_behavior/attack_obstructions/proc/attack_in_direction(datum/ai_controller/controller, mob/living/basic/basic_mob, direction)
var/turf/next_step = get_step(basic_mob, direction)
if(!next_step.is_blocked_turf(exclude_mobs = TRUE, source_atom = controller.pawn))
return FALSE
for(var/obj/object as anything in next_step.contents)
if(!can_smash_object(basic_mob, object))
continue
basic_mob.melee_attack(object)
return TRUE
if(can_attack_turfs)
basic_mob.melee_attack(next_step)
return TRUE
return FALSE
/datum/ai_behavior/attack_obstructions/proc/can_smash_object(mob/living/basic/basic_mob, obj/object)
if(!object.density && !can_attack_dense_objects)
return FALSE
if(object.IsObscured())
return FALSE
if(basic_mob.see_invisible < object.invisibility)
return FALSE
var/list/whitelist = basic_mob.ai_controller.blackboard[BB_OBSTACLE_TARGETING_WHITELIST]
if(whitelist && !is_type_in_typecache(object, whitelist))
return FALSE
if((!ismachinery(object) && !isstructure(object)))
return FALSE
return TRUE // It's in our way, let's get it out of our way
/datum/ai_behavior/attack_obstructions/avoid_breaches
/datum/ai_behavior/attack_obstructions/avoid_breaches/can_smash_object(mob/living/basic/basic_mob, obj/object)
. = ..()
if(istype(object, /obj/structure/window) || istype(object, /obj/machinery/door/airlock/external))
for(var/dir in GLOB.alldirs)
if(isspaceturf(get_step(object, dir)))
return FALSE
/datum/ai_planning_subtree/attack_obstacle_in_path/low_priority_target
target_key = BB_LOW_PRIORITY_HUNTING_TARGET
/datum/ai_planning_subtree/attack_obstacle_in_path/pet_target
target_key = BB_CURRENT_PET_TARGET
/datum/ai_planning_subtree/attack_obstacle_in_path/prowl
attack_behaviour = /datum/ai_behavior/attack_obstructions/avoid_breaches
target_key = BB_PROWL_TARGET
@@ -0,0 +1,28 @@
/datum/ai_behavior/find_hunt_target/prowl
search_turf_types = TRUE
/datum/ai_behavior/find_hunt_target/prowl/perform(seconds_per_tick, datum/ai_controller/controller, hunting_target_key, types_to_hunt, hunt_range)
var/mob/living/living_mob = controller.pawn
var/list/interesting_objects = search_turf_types ? RANGE_TURFS(hunt_range, living_mob) : oview(hunt_range, living_mob)
shuffle_inplace(interesting_objects)
for(var/atom/possible_dinner as anything in typecache_filter_list(interesting_objects, types_to_hunt))
if(!valid_dinner(living_mob, possible_dinner, hunt_range, controller, seconds_per_tick))
continue
controller.set_blackboard_key(hunting_target_key, possible_dinner)
return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED
return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED
/datum/ai_behavior/hunt_target/prowl
always_reset_target = TRUE
behavior_flags = parent_type::behavior_flags | AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION
/datum/ai_behavior/hunt_target/prowl/target_caught(mob/living/hunter, atom/hunted)
return // We're just going there
/datum/ai_planning_subtree/find_and_hunt_target/prowl
target_key = BB_PROWL_TARGET
finding_behavior = /datum/ai_behavior/find_hunt_target/prowl
hunting_behavior = /datum/ai_behavior/hunt_target/prowl
hunt_targets = list(/turf/simulated/floor/plasteel)
hunt_range = 8
hunt_chance = 50
@@ -0,0 +1,50 @@
/// Fire a ranged attack without interrupting movement.
/datum/ai_planning_subtree/ranged_skirmish
operational_datums = list(/datum/component/ranged_attacks)
/// Blackboard key holding target atom
var/target_key = BB_BASIC_MOB_CURRENT_TARGET
/// What AI behaviour do we actually run?
var/attack_behavior = /datum/ai_behavior/ranged_skirmish
/// If target is further away than this we don't fire
var/max_range = 9
/// If target is closer than this we don't fire
var/min_range = 2
/datum/ai_planning_subtree/ranged_skirmish/select_behaviors(datum/ai_controller/controller, seconds_per_tick)
. = ..()
if(!controller.blackboard_key_exists(target_key))
return
controller.queue_behavior(attack_behavior, target_key, BB_TARGETING_STRATEGY, BB_BASIC_MOB_CURRENT_TARGET_HIDING_LOCATION, max_range, min_range)
/// How often will we try to perform our ranged attack?
/datum/ai_behavior/ranged_skirmish
action_cooldown = 0.5 SECONDS
/datum/ai_behavior/ranged_skirmish/setup(datum/ai_controller/controller, target_key, targeting_strategy_key, hiding_location_key, max_range, min_range)
. = ..()
var/atom/target = controller.blackboard[hiding_location_key] || controller.blackboard[target_key]
return !QDELETED(target)
/datum/ai_behavior/ranged_skirmish/perform(seconds_per_tick, datum/ai_controller/controller, target_key, targeting_strategy_key, hiding_location_key, max_range, min_range)
var/atom/target = controller.blackboard[target_key]
if(QDELETED(target))
return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED
var/datum/targeting_strategy/targeting_strategy = GET_TARGETING_STRATEGY(controller.blackboard[targeting_strategy_key])
if(!targeting_strategy.can_attack(controller.pawn, target))
return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED
var/hiding_target = targeting_strategy.find_hidden_mobs(controller.pawn, target)
controller.set_blackboard_key(hiding_location_key, hiding_target)
target = hiding_target || target
var/distance = get_dist(controller.pawn, target)
if(distance > max_range || distance < min_range)
return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED
controller.ai_interact(target = target, intent = INTENT_HARM)
return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED
/datum/ai_planning_subtree/ranged_skirmish/no_minimum
min_range = 0
@@ -0,0 +1,35 @@
/// Find a target, walk at target, attack intervening obstacles
/datum/ai_controller/basic_controller/simple/simple_hostile_obstacles
planning_subtrees = list(
/datum/ai_planning_subtree/simple_find_target,
/datum/ai_planning_subtree/attack_obstacle_in_path,
/datum/ai_planning_subtree/basic_melee_attack_subtree,
)
/datum/ai_controller/basic_controller/simple/simple_hostile_obstacles/demonic_incursion
planning_subtrees = list(
/datum/ai_planning_subtree/simple_find_target,
/datum/ai_planning_subtree/attack_obstacle_in_path,
/datum/ai_planning_subtree/basic_melee_attack_subtree,
/datum/ai_planning_subtree/find_and_hunt_target/prowl,
/datum/ai_planning_subtree/attack_obstacle_in_path/prowl,
)
/// Find a target, walk towards it AND shoot it
/datum/ai_controller/basic_controller/simple/simple_skirmisher
planning_subtrees = list(
/datum/ai_planning_subtree/simple_find_target,
/datum/ai_planning_subtree/ranged_skirmish,
/datum/ai_planning_subtree/attack_obstacle_in_path,
/datum/ai_planning_subtree/basic_melee_attack_subtree,
)
/datum/ai_controller/basic_controller/simple/simple_skirmisher/demon_incursion
planning_subtrees = list(
/datum/ai_planning_subtree/simple_find_target,
/datum/ai_planning_subtree/ranged_skirmish,
/datum/ai_planning_subtree/attack_obstacle_in_path,
/datum/ai_planning_subtree/basic_melee_attack_subtree,
/datum/ai_planning_subtree/find_and_hunt_target/prowl,
/datum/ai_planning_subtree/attack_obstacle_in_path/prowl,
)
@@ -0,0 +1,8 @@
/datum/ai_controller/basic_controller/simple
blackboard = list(
BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic,
)
ai_movement = /datum/ai_movement/basic_avoidance
idle_behavior = /datum/idle_behavior/idle_random_walk
interesting_dist = AI_SIMPLE_INTERESTING_DIST
@@ -73,6 +73,12 @@
return FALSE
return TRUE
if(istype(the_target, /obj/machinery/power/emitter)) // Targetting emitters
var/obj/machinery/power/emitter/E = the_target
if(!E.active) // Don't attack if the emitter isn't active
return FALSE
return TRUE
return FALSE
/// Returns true if the mob and target share factions
@@ -0,0 +1,16 @@
/datum/component/incursion_mob_death
var/gib_delay = 2 MINUTES
/datum/component/incursion_mob_death/RegisterWithParent()
RegisterSignal(parent, COMSIG_MOB_DEATH, PROC_REF(extra_death_effect))
RegisterSignal(parent, COMSIG_PARENT_QDELETING, PROC_REF(extra_death_effect))
/datum/component/incursion_mob_death/proc/extra_death_effect(datum/source)
SIGNAL_HANDLER
if(prob(95)) // Save some corpses for xenobio
addtimer(CALLBACK(src, PROC_REF(delayed_gib)), gib_delay)
/datum/component/incursion_mob_death/proc/delayed_gib()
var/mob/living/demon = parent
new /obj/effect/temp_visual/demonic_grasp(demon.loc)
demon.gib()
+32 -4
View File
@@ -35,14 +35,17 @@
for(var/mob/living/simple_animal/L in spawned_mobs)
if(L.nest == src)
L.nest = null
for(var/mob/living/basic/L in spawned_mobs)
if(L.nest == src)
L.nest = null
spawned_mobs = null
/datum/component/spawner/proc/try_spawn_mob()
var/atom/P = parent
if(length(spawned_mobs) >= max_mobs)
return 0
return
if(spawn_delay > world.time)
return 0
return
spawn_delay = world.time + spawn_time
var/chosen_mob_type = pick(mob_types)
var/mob/living/simple_animal/L = new chosen_mob_type(P.loc)
@@ -52,6 +55,7 @@
L.faction = src.faction
P.visible_message("<span class='danger'>[L] [spawn_text] [P].</span>")
P.on_mob_spawn(L)
return L
/datum/component/spawner/proc/rally_spawned_mobs(parent, mob/living/target)
SIGNAL_HANDLER // COMSIG_SPAWNER_SET_TARGET
@@ -62,5 +66,29 @@
// start the cooldown first, because a rallied mob might fire on
// ourselves while this is happening, causing confusion
COOLDOWN_START(src, last_rally, 30 SECONDS)
for(var/mob/living/simple_animal/hostile/rallied as anything in spawned_mobs)
INVOKE_ASYNC(rallied, TYPE_PROC_REF(/mob/living/simple_animal/hostile, aggro_fast), target)
for(var/mob/living/rallied as anything in spawned_mobs)
if(istype(rallied, /mob/living/basic))
var/mob/living/basic/basic = rallied
basic.ai_controller.cancel_actions()
basic.ai_controller.set_blackboard_key(BB_BASIC_MOB_CURRENT_TARGET, target)
if(istype(rallied, /mob/living/simple_animal/hostile))
var/mob/living/simple_animal/hostile/simple = rallied
INVOKE_ASYNC(simple, TYPE_PROC_REF(/mob/living/simple_animal/hostile, aggro_fast), target)
/datum/component/spawner/demon_incursion_portal
/datum/component/spawner/demon_incursion_portal/try_spawn_mob()
var/atom/result = ..()
if(result && istype(result.ai_controller))
result.ai_controller.set_blackboard_key(BB_INCURSION_HOME_PORTAL, parent)
return result
/datum/component/spawner/demon_incursion_portal/rally_spawned_mobs(parent, mob/living/target)
if(!(COOLDOWN_FINISHED(src, last_rally)))
return
COOLDOWN_START(src, last_rally, 30 SECONDS)
for(var/mob/living/basic/rallied as anything in spawned_mobs)
rallied.ai_controller.cancel_actions()
rallied.ai_controller.queue_behavior(/datum/ai_behavior/return_home/incursion_portal, BB_INCURSION_HOME_PORTAL)
+1 -1
View File
@@ -82,7 +82,7 @@
return
if(!IS_HORIZONTAL(L) && !can_start_on_stander)
return
if(IS_HORIZONTAL(L) && !on_operable_surface(L) && !isanimal(L))
if(IS_HORIZONTAL(L) && !on_operable_surface(L) && !(isanimal(L) || isbasicmob(L)))
return
if(iscarbon(target))
var/mob/living/carbon/C = target
+19
View File
@@ -0,0 +1,19 @@
/// AIs will attack this as a potential target if they see it
/datum/element/hostile_machine
element_flags = ELEMENT_DETACH_ON_HOST_DESTROY
/datum/element/hostile_machine/Attach(datum/target)
. = ..()
if(!isatom(target))
return ELEMENT_INCOMPATIBLE
#ifdef UNIT_TESTS
if(!GLOB.target_interested_atoms[target.type])
stack_trace("Tried to make a hostile machine without updating ai targeting to include it, they must be synced")
#endif
GLOB.hostile_machines += target
/datum/element/hostile_machine/Detach(datum/source)
GLOB.hostile_machines -= source
return ..()
+6 -6
View File
@@ -112,14 +112,14 @@
action_icon_state = "glare"
gain_desc = "You have gained the ability to shapeshift into lesser hellhound form. This is a combat form with different abilities, tough but not invincible. It can regenerate itself over time by resting."
shapeshift_type = /mob/living/simple_animal/hostile/hellhound
current_shapes = list(/mob/living/simple_animal/hostile/hellhound)
shapeshift_type = /mob/living/basic/hellhound
current_shapes = list(/mob/living/basic/hellhound)
current_casters = list()
possible_shapes = list(/mob/living/simple_animal/hostile/hellhound)
possible_shapes = list(/mob/living/basic/hellhound)
/datum/spell/shapeshift/hellhound/greater
name = "Greater Hellhound Form"
shapeshift_type = /mob/living/simple_animal/hostile/hellhound/greater
current_shapes = list(/mob/living/simple_animal/hostile/hellhound/greater)
shapeshift_type = /mob/living/basic/hellhound/greater
current_shapes = list(/mob/living/basic/hellhound/greater)
current_casters = list()
possible_shapes = list(/mob/living/simple_animal/hostile/hellhound/greater)
possible_shapes = list(/mob/living/basic/hellhound/greater)
+1
View File
@@ -38,6 +38,7 @@
var/list/datum/mind/xenos = list()
var/list/datum/mind/eventmiscs = list()
var/list/blob_overminds = list()
var/list/incursion_portals = list()
var/list/datum/station_goal/station_goals = list() // A list of all station goals for this game mode
var/list/secondary_goal_grab_bags = null // Once initialized, contains an associative list of department_name -> list(secondary_goal_type). When a goal is requested, a type will be pulled out of the department's grab bag. When the bag is empty, it will be refilled from the list of all goals in that department, with the amount of each set to the type's weight, max 10.
+13
View File
@@ -91,6 +91,7 @@
spark_system.attach(src)
setup()
AddElement(/datum/element/hostile_machine)
/obj/machinery/porta_turret/Destroy()
QDEL_NULL(spark_system)
@@ -475,6 +476,18 @@ GLOBAL_LIST_EMPTY(turret_icons)
to_chat(M, "<span class='danger'>That object is useless to you.</span>")
return
/mob/living/handle_basic_attack(mob/living/basic/attacker, modifiers)
attacker.changeNext_move(CLICK_CD_MELEE)
attacker.do_attack_animation(src)
if(attacker.melee_damage_upper == 0 || (attacker.melee_damage_type != BRUTE && attacker.melee_damage_type != BURN))
return FALSE
if(!(stat & BROKEN))
visible_message("<span class='danger'>[attacker] [attacker.attack_verb_continuous] [src]!</span>")
..()
else
to_chat(attacker, "<span class='danger'>That object is useless to you.</span>")
return TRUE
/obj/machinery/porta_turret/attack_alien(mob/living/carbon/alien/humanoid/M)
M.changeNext_move(CLICK_CD_MELEE)
M.do_attack_animation(src)
+1
View File
@@ -160,6 +160,7 @@
set_light(lights_range_ambient, lights_power_ambient)
update_icon(UPDATE_OVERLAYS)
AddElement(/datum/element/hostile_machine)
/obj/mecha/update_overlays()
. = ..()
+1 -1
View File
@@ -187,7 +187,7 @@
//Swarm of creatures
T.visible_message("<span class='userdanger'>A swarm of creatures surround [user]!</span>")
for(var/direction in GLOB.alldirs)
new /mob/living/simple_animal/hostile/netherworld(get_step(get_turf(user), direction))
new /mob/living/basic/netherworld(get_step(get_turf(user), direction))
if(4)
//Destroy Equipment
T.visible_message("<span class='userdanger'>Everything [user] is holding and wearing disappears!</span>")
+17
View File
@@ -117,6 +117,23 @@
if(. && !play_soundeffect)
playsound(QDELETED(src) ? obj_turf : src, 'sound/effects/meteorimpact.ogg', 100, TRUE)
/obj/handle_basic_attack(mob/living/basic/attacker, modifiers)
if((attacker.a_intent == INTENT_HELP && attacker.ckey) || attacker.melee_damage_upper == 0)
attacker.custom_emote(EMOTE_VISIBLE, "[attacker.friendly_verb_continuous] [src].")
return FALSE
else
var/play_soundeffect = TRUE
if(attacker.environment_smash)
play_soundeffect = FALSE
var/obj_turf = get_turf(src) // play from the turf in case the object gets deleted mid attack
if(attacker.obj_damage)
. = attack_generic(attacker, attacker.obj_damage, attacker.melee_damage_type, MELEE, play_soundeffect, attacker.armour_penetration_flat, attacker.armour_penetration_percentage)
else
. = attack_generic(attacker, rand(attacker.melee_damage_lower, attacker.melee_damage_upper), attacker.melee_damage_type, MELEE, play_soundeffect, attacker.armour_penetration_flat, attacker.armour_penetration_percentage)
if(. && !play_soundeffect)
playsound(QDELETED(src) ? obj_turf : src, 'sound/effects/meteorimpact.ogg', 100, TRUE)
return TRUE
/obj/force_pushed(atom/movable/pusher, force = MOVE_FORCE_DEFAULT, direction)
return TRUE
+13
View File
@@ -66,6 +66,19 @@
take_damage(rand(5,10), BRUTE, MELEE, 1)
/obj/structure/grille/handle_basic_attack(mob/living/basic/attacker, modifiers)
. = ..()
if(!. || QDELETED(src) || shock(attacker, 70))
return
if(attacker.environment_smash >= ENVIRONMENT_SMASH_STRUCTURES)
playsound(src, 'sound/effects/grillehit.ogg', 80, TRUE)
obj_break()
attacker.visible_message("<span class='danger'>[attacker] smashes through [src]!</span>", "<span class='notice'>You smash through [src].</span>")
return
take_damage(rand(5, 10), BRUTE, MELEE, 1)
/obj/structure/grille/hulk_damage()
return 60
@@ -41,7 +41,7 @@
max_integrity = 150
max_mobs = 15
spawn_time = 150
mob_types = list(/mob/living/simple_animal/hostile/skeleton)
mob_types = list(/mob/living/basic/skeleton)
spawn_text = "climbs out of"
faction = list("skeleton")
+272
View File
@@ -0,0 +1,272 @@
/datum/event/demon_incursion
name = "demon incursion"
/// Corresponds to the number of process() runs the event has lasted for. Roughly 2 minutes here.
announceWhen = 60
/// Corresponds to the number of process() runs the event has lasted for. Roughly 2 minutes here.
endWhen = 60
/// The name of the notification for dchat
var/notify_title = "Demonic Incursion"
/// The icon of the notification
var/notify_image = "nether"
/// List of portals
var/list/portal_list = list()
/// The target number of portals
var/target_portals = 100
/datum/event/demon_incursion/setup()
impact_area = findEventArea()
/datum/event/demon_incursion/start()
if(isnull(impact_area))
log_debug("No valid event areas could be generated for demonic incursion.")
var/initial_portals = max(length(GLOB.crew_list) / 10, 1)
target_portals = max(initial_portals * 10, 30)
var/list/area_turfs = get_area_turfs(impact_area)
var/notice_sent = FALSE
while(length(area_turfs) && initial_portals > 0)
var/turf/T = pick_n_take(area_turfs)
if(T.is_blocked_turf(exclude_mobs = TRUE))
continue
// Give ghosts some time to jump there before it begins.
var/image/alert_overlay = image('icons/mob/nest.dmi', notify_image)
if(!notice_sent)
notify_ghosts("\A [src] is about to open in [get_area(T)].", title = notify_title, source = T, alert_overlay = alert_overlay, flashwindow = FALSE, action = NOTIFY_FOLLOW)
notice_sent = TRUE
addtimer(CALLBACK(src, PROC_REF(spawn_portal), T), 4 SECONDS)
// Energy overload; we mess with machines as an early warning and for extra spookiness.
for(var/obj/machinery/M in range(8, T))
INVOKE_ASYNC(M, TYPE_PROC_REF(/atom, get_spooked))
initial_portals--
if(initial_portals > 0)
log_debug("demonic incursion failed to find a valid turf in [impact_area]")
SSticker.record_biohazard_start(INCURSION_DEMONS)
SSevents.biohazards_this_round += INCURSION_DEMONS
/datum/event/demon_incursion/proc/spawn_portal(location)
var/obj/structure/spawner/nether/demon_incursion/new_portal = new /obj/structure/spawner/nether/demon_incursion(location)
new_portal.linked_incursion = src
portal_list += new_portal
// Too many portals - make a bad thing happen
if(length(portal_list) > target_portals)
target_portals *= 2
prepare_spawn_elite()
/datum/event/demon_incursion/proc/prepare_spawn_elite()
var/obj/structure/spawner/nether/demon_incursion/elite_portal = pick(portal_list)
elite_portal.visible_message("<span class='danger'>Something within [elite_portal] stirs...</span>")
var/list/potentialspawns = list(/mob/living/simple_animal/hostile/asteroid/elite/broodmother,
/mob/living/simple_animal/hostile/asteroid/elite/pandora,
/mob/living/simple_animal/hostile/asteroid/elite/legionnaire,
/mob/living/simple_animal/hostile/asteroid/elite/herald)
var/selected_elite = pick(potentialspawns)
addtimer(CALLBACK(src, PROC_REF(spawn_elite), null, elite_portal, selected_elite), 10 SECONDS)
/datum/event/demon_incursion/proc/spawn_elite(mob/dead/observer/elitemind, obj/structure/spawner/nether/demon_incursion/elite_portal, selected_elite)
var/mob/living/simple_animal/hostile/asteroid/elite/created_mob = new selected_elite(elite_portal.loc)
created_mob.faction = list("nether")
elite_portal.visible_message("<span class='userdanger'>[created_mob] emerges from [elite_portal]!</span>")
notify_ghosts("\A [created_mob] has emerged from an incursion portal in \the [get_area(src)]!", enter_link="<a href=byond://?src=[UID()];follow=1>(Click to help)</a>", source = created_mob, action = NOTIFY_FOLLOW)
qdel(elite_portal)
playsound(created_mob.loc,'sound/effects/phasein.ogg', 200, FALSE, 50, TRUE, TRUE)
/datum/event/demon_incursion/announce(false_alarm)
var/area/target_area = impact_area
if(!target_area)
if(false_alarm)
target_area = findEventArea()
if(isnull(target_area))
log_debug("Tried to announce a false-alarm tear without a valid area!")
kill()
return
else
log_debug("Tried to announce an incursion without a valid area!")
kill()
return
GLOB.major_announcement.Announce("Major bluespace energy spike detected at [target_area.name]. Extradimensional intruder alert. All personnel must prevent the incursion before the station is destroyed.", "Demonic Incursion Alert", 'sound/effects/siren-spooky.ogg', new_sound2 = 'sound/AI/outbreak_demon.ogg')
/obj/structure/spawner/nether/demon_incursion
name = "demonic portal"
density = FALSE
layer = ABOVE_LYING_MOB_LAYER // Portals are below living mobs, but layer over dead ones
spawn_time = 5 SECONDS // Short spawn time initially, it gets updated after it spawns initial mobs
max_mobs = 5 // We want a lot of mobs, but not too many
max_integrity = 200
mob_types = list(/mob/living/basic/netherworld/migo,
/mob/living/basic/netherworld,
/mob/living/basic/netherworld/blankbody,
/mob/living/basic/hellhound,
/mob/living/basic/skeleton,
/mob/living/basic/netherworld/faithless)
icon = 'icons/obj/structures/portal.dmi'
icon_state = "portal"
light_range = 4
light_power = 2
light_color = "#780606"
spawner_type = /datum/component/spawner/demon_incursion_portal
/// The event that spawned this portal
var/datum/event/demon_incursion/linked_incursion
/// Percentage chance that a portal will spread every time spread() is called
var/portal_spread_chance = 50
/// Lowest possible spread chance
var/min_spread_chance = 10
/// Lower bound for portal spreading
var/portal_spread_cooldown_min = 3 MINUTES
/// Upper bound for portal spreading
var/portal_spread_cooldown_max = 4 MINUTES
/// Time until next portal
var/expansion_delay
/// How fast does the portal spawn mobs after the initial spawns?
var/spawn_rate = 30 SECONDS
/// How many initial mobs does it spawn?
var/initial_spawns_min = 1
var/initial_spawns_max = 4
/// Are we spawning initial mobs?
var/spawning_initial_mobs = TRUE
/// Current tile spread distance
var/tile_spread = 1
/// Max tile Spread distance
var/tile_spread_max = 3
/// Turf type that is spread by the portals
var/turf_to_spread = /turf/simulated/floor/engine/cult/demon_incursion
/// How long of a cooldown on portal repair
var/portal_repair_cooldown = 15 SECONDS
COOLDOWN_DECLARE(portal_repair)
/obj/structure/spawner/nether/demon_incursion/Initialize(mapload)
. = ..()
expansion_delay = rand(portal_spread_cooldown_min, portal_spread_cooldown_max)
addtimer(CALLBACK(src, PROC_REF(spread)), expansion_delay)
var/initial_spawns = rand(initial_spawns_min, initial_spawns_max)
var/initial_spawn_time = spawn_time * initial_spawns - 1
addtimer(CALLBACK(src, PROC_REF(stop_initial_mobs)), initial_spawn_time)
if(turf_to_spread)
spread_turf()
SSticker.mode.incursion_portals += src
/obj/structure/spawner/nether/demon_incursion/examine(mob/user)
. = ..()
if(COOLDOWN_FINISHED(src, portal_repair) && obj_integrity < max_integrity)
. += "<span class='warning'>Dark tendrils are stabilizing the portal!</span>"
/obj/structure/spawner/nether/demon_incursion/process()
. = ..()
if(!spawning_initial_mobs)
update_spawn_time()
if(!COOLDOWN_FINISHED(src, portal_repair))
return
if(obj_integrity >= max_integrity)
return
obj_integrity += 5
new /obj/effect/temp_visual/heal(loc, COLOR_BLOOD_MACHINE)
/obj/structure/spawner/nether/demon_incursion/deconstruct(disassembled)
var/datum/component/spawner/spawn_comp = GetComponent(/datum/component/spawner)
for(var/mob/living/basic/summoned_mob in spawn_comp.spawned_mobs)
if(prob(50))
playsound(src, 'sound/magic/lightningbolt.ogg', 60, TRUE)
Beam(summoned_mob, icon_state = "purple_lightning", icon = 'icons/effects/effects.dmi', time = 1 SECONDS)
summoned_mob.addtimer(CALLBACK(summoned_mob, TYPE_PROC_REF(/mob/living/basic, gib)), 1 SECONDS)
var/reward_type = pick(/obj/item/stack/ore/bluespace_crystal, /obj/item/stack/ore/palladium, /obj/item/stack/ore/platinum, /obj/item/stack/ore/iridium, /obj/item/stack/ore/diamond)
var/obj/item/stack/ore/reward = new reward_type(loc)
reward.amount = 2
if(linked_incursion)
linked_incursion.portal_list -= src
if(!length(linked_incursion.portal_list))
playsound(src, 'sound/misc/demon_dies.ogg', 100, TRUE, ignore_walls = TRUE)
SSticker.mode.incursion_portals -= src
return ..()
/obj/structure/spawner/nether/demon_incursion/take_damage(damage_amount, damage_type, damage_flag, sound_effect, attack_dir, armour_penetration_flat, armour_penetration_percentage)
. = ..()
COOLDOWN_START(src, portal_repair, portal_repair_cooldown)
/obj/structure/spawner/nether/demon_incursion/on_mob_spawn(mob/created_mob)
. = ..()
created_mob.Move(get_step(created_mob.loc, pick(GLOB.alldirs)))
var/mob/living/basic/new_mob = created_mob
if(!istype(new_mob, /mob/living/basic))
return
if(new_mob.basic_mob_flags & DEL_ON_DEATH)
return
new_mob.AddComponent(/datum/component/incursion_mob_death)
/obj/structure/spawner/nether/demon_incursion/attacked_by(obj/item/attacker, mob/living/user)
. = ..()
SEND_SIGNAL(src, COMSIG_SPAWNER_SET_TARGET, user)
/obj/structure/spawner/nether/demon_incursion/bullet_act(obj/item/projectile/P)
. = ..()
if(P.firer)
SEND_SIGNAL(src, COMSIG_SPAWNER_SET_TARGET, P.firer)
/obj/structure/spawner/nether/demon_incursion/proc/update_spawn_time()
var/datum/component/spawner/spawn_comp = GetComponent(/datum/component/spawner)
var/spawn_increment = 0
for(var/mob/living/mob as anything in spawn_comp.spawned_mobs)
spawn_increment += 5 SECONDS
spawn_comp.spawn_time = spawn_time + spawn_increment
/obj/structure/spawner/nether/demon_incursion/proc/spread()
var/base_portal_count = max(length(GLOB.crew_list) / 10, 1)
if(length(linked_incursion.portal_list) <= base_portal_count)
portal_spread_chance = 100
else
// Spread chance runs on an exponential regression formula when above the base portal count.
var/log_value = (50 - min_spread_chance) / (100 - min_spread_chance)
var/midpoint = -(log(log_value) / base_portal_count)
var/euler_exponent = 2.71828 ** (-midpoint * (length(linked_incursion.portal_list) - base_portal_count))
portal_spread_chance = min_spread_chance + ((100 - min_spread_chance) * euler_exponent)
expansion_delay = rand(portal_spread_cooldown_min, portal_spread_cooldown_max)
if(!prob(portal_spread_chance))
addtimer(CALLBACK(src, PROC_REF(spread)), expansion_delay)
return
var/list/spawnable_turfs = list()
for(var/turf/simulated/floor/possible_loc in orange(8, src.loc))
if(!istype(possible_loc))
continue
if(istype(get_area(possible_loc), /area/space))
continue
if(possible_loc.is_blocked_turf(exclude_mobs = TRUE))
continue
var/density_check = TRUE
for(var/turf/possible_turf in view(3, possible_loc))
if(locate(/obj/structure/spawner/nether/demon_incursion) in possible_turf)
density_check = FALSE
continue
if(!density_check)
continue
spawnable_turfs += possible_loc
if(!spawnable_turfs)
return
var/turf/spawn_loc = pick_n_take(spawnable_turfs)
linked_incursion.spawn_portal(spawn_loc)
addtimer(CALLBACK(src, PROC_REF(spread)), expansion_delay)
return
/obj/structure/spawner/nether/demon_incursion/proc/stop_initial_mobs()
spawning_initial_mobs = FALSE
spawn_time = spawn_rate
update_spawn_time()
/obj/structure/spawner/nether/demon_incursion/proc/spread_turf()
for(var/turf/spread_turf in range(tile_spread, loc))
if(isfloorturf(spread_turf) && !istype(spread_turf, turf_to_spread))
spread_turf.ChangeTurf(turf_to_spread)
Beam(spread_turf, icon_state = "sendbeam", icon = 'icons/effects/effects.dmi', time = 0.5 SECONDS)
if(tile_spread < tile_spread_max)
tile_spread++
addtimer(CALLBACK(src, PROC_REF(spread_turf)), spawn_rate)
/turf/simulated/floor/engine/cult/demon_incursion
name = "hellish flooring"
/turf/simulated/floor/engine/cult/demon_incursion/Initialize(mapload)
. = ..()
icon_state = "culthell" // Cult floors auto adjust. This forces it to be the hell variant
+1
View File
@@ -207,6 +207,7 @@ GLOBAL_LIST_EMPTY(event_last_fired)
new /datum/event_meta(EVENT_LEVEL_MAJOR, "Terror Spiders", /datum/event/spider_terror, 15, list(ASSIGNMENT_SECURITY = 3), TRUE),
new /datum/event_meta(EVENT_LEVEL_MAJOR, "Slaughter Demon", /datum/event/spawn_slaughter, 20, is_one_shot = TRUE),
new /datum/event_meta(EVENT_LEVEL_MAJOR, "Shadow Demon", /datum/event/spawn_slaughter/shadow, 20, is_one_shot = TRUE),
new /datum/event_meta(EVENT_LEVEL_MAJOR, "Demonic Incursion", /datum/event/demon_incursion, 20, list(ASSIGNMENT_SECURITY = 3)),
new /datum/event_meta(EVENT_LEVEL_MAJOR, "Immovable Rod", /datum/event/immovable_rod, 0, list(ASSIGNMENT_ENGINEER = 10), TRUE)
//new /datum/event_meta(EVENT_LEVEL_MAJOR, "Floor Cluwne", /datum/event/spawn_floor_cluwne, 15, is_one_shot = TRUE)
//new /datum/event_meta(EVENT_LEVEL_MAJOR, "Pulse Demon Infiltration", /datum/event/spawn_pulsedemon, 20, is_one_shot = TRUE)
+7 -6
View File
@@ -73,15 +73,15 @@
pixel_x = -106
pixel_y = -96
/// What the leader of the dimensional tear will be
var/leader = /mob/living/simple_animal/hostile/hellhound/tear
var/leader = /mob/living/basic/hellhound/tear
var/spawn_max = 0
var/spawn_total = 0
var/list/possible_mobs = list(
/mob/living/simple_animal/hostile/hellhound,
/mob/living/simple_animal/hostile/skeleton,
/mob/living/simple_animal/hostile/netherworld,
/mob/living/simple_animal/hostile/netherworld/migo,
/mob/living/simple_animal/hostile/faithless)
/mob/living/basic/hellhound,
/mob/living/basic/skeleton,
/mob/living/basic/netherworld/,
/mob/living/basic/netherworld/migo,
/mob/living/basic/netherworld/faithless)
/obj/effect/tear/Initialize(mapload)
. = ..()
@@ -107,6 +107,7 @@
if(!leader)
return
var/mob/M = new leader(get_turf(src))
M.faction = list("rift")
playsound(M, 'sound/goonstation/voice/growl2.ogg', 100)
visible_message("<span class='danger'>With a terrifying growl, \a [M] steps out of the portal!</span>")
+54 -2
View File
@@ -20,6 +20,7 @@ RESTRICT_TYPE(/mob/living/basic)
desc = "If you can see this, make an issue report on GitHub."
healable = TRUE
icon = 'icons/mob/animal.dmi'
hud_type = /datum/hud/simple_animal
var/basic_mob_flags
@@ -30,6 +31,8 @@ RESTRICT_TYPE(/mob/living/basic)
var/icon_living
/// Icon when the animal is dead.
var/icon_dead
/// Icon when the animal is resting
var/icon_resting
/// We only try to show a gibbing animation if this exists.
var/icon_gib
/// The sound played on death
@@ -129,14 +132,27 @@ RESTRICT_TYPE(/mob/living/basic)
var/melee_damage_upper = 0
/// How much damage this simple animal does to objects, if any
var/obj_damage = 0
/// What can this mob break?
var/environment_smash = ENVIRONMENT_SMASH_NONE
/// Flat armour reduction, occurs after percentage armour penetration.
var/armour_penetration_flat = 0
/// Percentage armour reduction, happens before flat armour reduction.
var/armour_penetration_percentage = 0
/// Damage type of a simple mob's melee attack, should it do damage.
var/melee_damage_type = BRUTE
/// How often can you melee attack?
var/melee_attack_cooldown = 2 SECONDS
/// Lower bound for melee attack cooldown
var/melee_attack_cooldown_min = 2 SECONDS
/// Upper bound for melee attack cooldown
var/melee_attack_cooldown_max = 2 SECONDS
/// Loot this mob drops on death.
var/list/loot = list()
/// Compatibility with mob spawners
var/datum/component/spawner/nest
/// Footsteps
var/step_type
/mob/living/basic/Initialize(mapload)
. = ..()
@@ -146,6 +162,14 @@ RESTRICT_TYPE(/mob/living/basic)
apply_atmos_requirements()
apply_temperature_requirements()
if(step_type)
AddComponent(/datum/component/footstep, step_type)
/mob/living/basic/Destroy()
if(nest)
nest.spawned_mobs -= src
nest = null
return ..()
/mob/living/basic/movement_delay()
. = speed
@@ -198,6 +222,7 @@ RESTRICT_TYPE(/mob/living/basic)
/mob/living/basic/proc/early_melee_attack(atom/target, list/modifiers, ignore_cooldown = FALSE)
face_atom(target)
if(!ignore_cooldown)
var/melee_attack_cooldown = rand(melee_attack_cooldown_min, melee_attack_cooldown_max)
changeNext_move(melee_attack_cooldown)
if(SEND_SIGNAL(src, COMSIG_HOSTILE_PRE_ATTACKINGTARGET, target, Adjacent(target), modifiers) & COMPONENT_HOSTILE_NO_ATTACK)
return FALSE
@@ -233,6 +258,10 @@ RESTRICT_TYPE(/mob/living/basic)
. = ..()
if(!.)
return FALSE
if(nest)
nest.spawned_mobs -= src
nest = null
drop_loot()
if(!gibbed)
if(death_sound)
playsound(get_turf(src), death_sound, 200, 1)
@@ -302,6 +331,24 @@ RESTRICT_TYPE(/mob/living/basic)
apply_damage(damage, damagetype, null, getarmor(null, armorcheck))
return TRUE
/mob/living/basic/handle_basic_attack(mob/living/basic/attacker, modifiers)
. = ..()
if(.)
var/damage = rand(attacker.melee_damage_lower, attacker.melee_damage_upper)
return attack_threshold_check(damage, attacker.melee_damage_type)
/mob/living/basic/on_lying_down(new_lying_angle)
..()
if(icon_resting && stat != DEAD)
icon_state = icon_resting
ADD_TRAIT(src, TRAIT_IMMOBILIZED, LYING_DOWN_TRAIT) //simple mobs cannot crawl (unless they can)
/mob/living/basic/on_standing_up()
..()
if(icon_resting && stat != DEAD)
icon_state = icon_living
// Health/Damage adjustment, cribbed straight from simplemobs
/mob/living/basic/adjustHealth(amount, updating_health = TRUE)
@@ -333,3 +380,8 @@ RESTRICT_TYPE(/mob/living/basic)
/mob/living/basic/adjustStaminaLoss(amount, updating_health = TRUE)
if(damage_coeff[STAMINA])
return ..(amount * damage_coeff[STAMINA], updating_health)
/mob/living/basic/proc/drop_loot()
if(length(loot))
for(var/item in loot)
new item(get_turf(src))
@@ -145,3 +145,9 @@
controller.set_blackboard_key(BB_DEER_RESTING, world.time + 15 SECONDS)
controller.set_blackboard_key(BB_DEER_NEXT_REST_TIMER, world.time + rand(minimum_time, maximum_time))
return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED
/datum/ai_behavior/return_home/incursion_portal
behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT
/datum/ai_behavior/return_home/incursion_portal/perform(seconds_per_tick, datum/ai_controller/controller, target_key)
return AI_BEHAVIOR_SUCCEEDED
@@ -1,5 +1,4 @@
// Hellhound
/mob/living/simple_animal/hostile/hellhound
/mob/living/basic/hellhound
// Sprites by FoS: https://www.paradisestation.org/forum/profile/335-fos
name = "lesser hellhound"
desc = "A demonic-looking black canine monster with glowing red eyes and sharp teeth. A firey, lava-like substance drips from it."
@@ -8,55 +7,46 @@
icon_dead = "hellhound_dead"
icon_resting = "hellhound_rest"
mob_biotypes = MOB_ORGANIC | MOB_BEAST
atmos_requirements = list("min_oxy" = 0, "max_oxy" = 0, "min_tox" = 0, "max_tox" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0)
minbodytemp = 0
maxbodytemp = INFINITY
melee_damage_lower = 10 // slightly higher than araneus
melee_damage_upper = 30
melee_attack_cooldown_min = 1.5 SECONDS
melee_attack_cooldown_max = 2.5 SECONDS
environment_smash = ENVIRONMENT_SMASH_STRUCTURES
a_intent = INTENT_HARM
environment_smash = 1
speak_chance = 0
speed = 0
maxHealth = 250 // same as sgt araneus
health = 250
obj_damage = 50
robust_searching = TRUE
stat_attack = UNCONSCIOUS
attacktext = "savages"
attack_verb_continuous = "savages"
attack_verb_simple = "savage"
attack_sound = 'sound/effects/bite.ogg'
speak_emote = list("growls")
see_in_dark = 9
universal_understand = TRUE
wander = FALSE
var/life_regen_cycles = 0
var/life_regen_cycle_trigger = 10 // heal once for every X number of cycles spent resting
var/life_regen_amount = -10 // negative, because negative = healing
var/smoke_lastuse = 0
var/smoke_freq = 300 // 30 seconds
footstep_type = FOOTSTEP_MOB_CLAW
ai_controller = /datum/ai_controller/basic_controller/simple/hellhound
step_type = FOOTSTEP_MOB_CLAW
faction = list("nether")
atmos_requirements = list("min_oxy" = 0, "max_oxy" = 0, "min_tox" = 0, "max_tox" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0)
damage_coeff = list(BRUTE = 1, BURN = 1, TOX = 1, STAM = 0, OXY = 1)
gold_core_spawnable = HOSTILE_SPAWN
contains_xeno_organ = TRUE
ignore_generic_organs = TRUE
surgery_container = /datum/xenobiology_surgery_container/hound
/// How many cycles has the hellhound rested
var/life_regen_cycles = 0
/// Trigger number for health regen
var/life_regen_cycle_trigger = 10
/// How much is healed when regenerating
var/life_regen_amount = -10 // negative, because negative = healing
/// When did the hellhound last use smoke
var/smoke_lastuse = 0
/// How often can the hellhound use smoke
var/smoke_freq = 30 SECONDS
/mob/living/simple_animal/hostile/hellhound/Initialize(mapload)
/mob/living/basic/hellhound/Initialize(mapload)
. = ..()
var/datum/action/innate/demon_whisper/whisper_action = new
whisper_action.Grant(src)
ADD_TRAIT(src, TRAIT_NOBREATH, SPECIES_TRAIT)
AddElement(/datum/element/ai_retaliate)
/mob/living/simple_animal/hostile/hellhound/handle_automated_action()
if(!..())
return
if(IS_HORIZONTAL(src))
if(!wants_to_rest())
custom_emote(EMOTE_VISIBLE, "growls, and gets up.")
playsound(get_turf(src), 'sound/hallucinations/growl2.ogg', 50, 1)
stand_up()
else if(wants_to_rest())
custom_emote(EMOTE_VISIBLE, "lays down, and starts to lick their wounds.")
lay_down()
/mob/living/simple_animal/hostile/hellhound/examine(mob/user)
/mob/living/basic/hellhound/examine(mob/user)
. = ..()
if(stat != DEAD)
var/list/msgs = list()
@@ -77,34 +67,31 @@
msgs += "<span class='notice'>It is currently resting.</span>"
. += msgs.Join("<BR>")
/mob/living/simple_animal/hostile/hellhound/Life(seconds, times_fired)
/mob/living/basic/hellhound/Move(atom/newloc, direct, glide_size_override, update_dir)
. = ..()
if(stat != DEAD && IS_HORIZONTAL(src) && (getBruteLoss() || getFireLoss()))
if(life_regen_cycles >= life_regen_cycle_trigger)
life_regen_cycles = 0
to_chat(src, "<span class='notice'>You lick your wounds, helping them close.</span>")
adjustBruteLoss(life_regen_amount)
adjustFireLoss(life_regen_amount)
else
life_regen_cycles++
if(IS_HORIZONTAL(src))
stand_up()
/mob/living/simple_animal/hostile/hellhound/proc/wants_to_rest()
if(target)
return FALSE
if(getBruteLoss() || getFireLoss())
return TRUE
return FALSE
/mob/living/basic/hellhound/Life(seconds, times_fired)
. = ..()
if(stat != DEAD && (getBruteLoss() || getFireLoss()))
if(IS_HORIZONTAL(src))
if(life_regen_cycles >= life_regen_cycle_trigger)
life_regen_cycles = 0
to_chat(src, "<span class='notice'>You lick your wounds, helping them close.</span>")
adjustBruteLoss(life_regen_amount)
adjustFireLoss(life_regen_amount)
else
life_regen_cycles++
if(ai_controller.blackboard_key_exists(BB_BASIC_MOB_CURRENT_TARGET))
stand_up()
/mob/living/simple_animal/hostile/hellhound/attacked_by(obj/item/attacker, mob/living/user)
if(..())
return FINISH_ATTACK
/mob/living/basic/hellhound/apply_damage(damage, damagetype, def_zone, blocked, sharp, used_weapon, spread_damage)
. = ..()
if(stat != DEAD && IS_HORIZONTAL(src))
stand_up()
if(target && isliving(target))
var/mob/living/L = target
if(L.stat != CONSCIOUS)
target = user
/mob/living/simple_animal/hostile/hellhound/greater
/mob/living/basic/hellhound/greater
name = "greater hellhound"
desc = "A demonic-looking black canine monster with glowing red eyes and sharp teeth. Greater hounds are far stronger than their lesser kin, and should be engaged with extreme caution."
icon_state = "hellhoundgreater"
@@ -114,13 +101,12 @@
health = 400
force_threshold = 5 // no punching
universal_speak = TRUE
smoke_freq = 200
smoke_freq = 20 SECONDS
life_regen_cycle_trigger = 5
melee_damage_lower = 20
melee_damage_upper = 30
environment_smash = 2
/mob/living/simple_animal/hostile/hellhound/greater/Initialize(mapload)
/mob/living/basic/hellhound/greater/Initialize(mapload)
. = ..()
// Movement
AddSpell(new /datum/spell/ethereal_jaunt/shift)
@@ -140,16 +126,16 @@
var/datum/spell/aoe/conjure/creature/summonspell = new
summonspell.base_cooldown = 1
summonspell.invocation_type = "none"
summonspell.summon_type = list(/mob/living/simple_animal/hostile/hellhound)
summonspell.summon_type = list(/mob/living/basic/hellhound)
summonspell.summon_amt = 1
AddSpell(summonspell)
/mob/living/simple_animal/hostile/hellhound/greater/AttackingTarget()
/mob/living/basic/hellhound/greater/melee_attack(atom/target, list/modifiers, ignore_cooldown)
. = ..()
if(. && ishuman(target) && (!client || a_intent == INTENT_HARM))
special_aoe()
/mob/living/simple_animal/hostile/hellhound/greater/proc/special_aoe()
/mob/living/basic/hellhound/greater/proc/special_aoe()
if(world.time < (smoke_lastuse + smoke_freq))
return
smoke_lastuse = world.time
@@ -157,10 +143,9 @@
smoke.set_up(10, FALSE, loc)
smoke.start()
/mob/living/simple_animal/hostile/hellhound/tear
/mob/living/basic/hellhound/tear
name = "frenzied hellhound"
maxHealth = 300
health = 300
melee_damage_lower = 30
melee_damage_upper = 50
faction = list("rift")
@@ -0,0 +1,45 @@
/datum/ai_controller/basic_controller/simple/hellhound
blackboard = list(
BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic,
)
planning_subtrees = list(
/datum/ai_planning_subtree/simple_find_target,
/datum/ai_planning_subtree/attack_obstacle_in_path,
/datum/ai_planning_subtree/basic_melee_attack_subtree,
/datum/ai_planning_subtree/hellhound_rest,
/datum/ai_planning_subtree/find_and_hunt_target/prowl,
/datum/ai_planning_subtree/attack_obstacle_in_path/prowl,
)
/datum/ai_planning_subtree/hellhound_rest/select_behaviors(datum/ai_controller/controller, seconds_per_tick)
var/mob/living/pawn = controller.pawn
if(pawn.stat == DEAD)
return
if(pawn.getBruteLoss() || pawn.getFireLoss())
controller.queue_behavior(/datum/ai_behavior/hellhound_rest)
return SUBTREE_RETURN_FINISH_PLANNING
else
controller.queue_behavior(/datum/ai_behavior/hellhound_stand)
/datum/ai_behavior/hellhound_rest
action_cooldown = 2 SECONDS
/datum/ai_behavior/hellhound_rest/perform(seconds_per_tick, datum/ai_controller/controller, ...)
. = ..()
var/mob/living/basic/hellhound/hound = controller.pawn
if(!IS_HORIZONTAL(hound))
hound.lay_down()
return AI_BEHAVIOR_SUCCEEDED
/datum/ai_behavior/hellhound_stand
action_cooldown = 2 SECONDS
/datum/ai_behavior/hellhound_stand/perform(seconds_per_tick, datum/ai_controller/controller, ...)
. = ..()
var/mob/living/basic/hellhound/hound = controller.pawn
if(IS_HORIZONTAL(hound))
hound.stand_up()
return AI_BEHAVIOR_SUCCEEDED
@@ -0,0 +1,99 @@
/mob/living/basic/skeleton
name = "reanimated skeleton"
desc = "A real bonefied skeleton, doesn't seem like it wants to socialize."
icon = 'icons/mob/simple_human.dmi'
icon_state = "skeleton"
icon_living = "skeleton"
speak_emote = list("rattles")
mob_biotypes = MOB_UNDEAD | MOB_HUMANOID
maxHealth = 40
health = 40
a_intent = INTENT_HARM
melee_damage_lower = 15
melee_damage_upper = 15
harm_intent_damage = 5
obj_damage = 50
melee_attack_cooldown_min = 1.5 SECONDS
melee_attack_cooldown_max = 2.5 SECONDS
environment_smash = ENVIRONMENT_SMASH_STRUCTURES
minimum_survivable_temperature = 0
maximum_survivable_temperature = 1500
healable = FALSE
attack_verb_simple = "slash"
attack_verb_continuous = "slashes"
attack_sound = 'sound/hallucinations/growl1.ogg'
atmos_requirements = list("min_oxy" = 0, "max_oxy" = 0, "min_tox" = 0, "max_tox" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0)
damage_coeff = list(BRUTE = 1, BURN = 1, TOX = 1, STAM = 0, OXY = 1)
unsuitable_atmos_damage = 10
see_in_dark = 8
faction = list("skeleton")
lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE
basic_mob_flags = DEL_ON_DEATH
speed = 1
step_type = FOOTSTEP_MOB_SHOE
gold_core_spawnable = HOSTILE_SPAWN
loot = list(/obj/effect/decal/remains/human)
ai_controller = /datum/ai_controller/basic_controller/simple/simple_hostile_obstacles/demonic_incursion
/mob/living/basic/skeleton/Initialize(mapload)
. = ..()
AddElement(/datum/element/ai_retaliate)
/mob/living/basic/skeleton/arctic
name = "undead arctic explorer"
desc = "The reanimated remains of some poor traveler."
icon_state = "arctic_skeleton"
icon_living = "arctic_skeleton"
maxHealth = 55
health = 55
weather_immunities = list("snow")
gold_core_spawnable = NO_SPAWN
melee_damage_lower = 17
melee_damage_upper = 20
death_message = "collapses into a pile of bones, its gear falling to the floor!"
loot = list(/obj/effect/decal/remains/human,
/obj/item/spear,
/obj/item/clothing/shoes/winterboots,
/obj/item/clothing/suit/hooded/wintercoat)
/mob/living/basic/skeleton/warden
name = "skeleton warden"
desc = "The remains of a warden."
icon = 'icons/mob/simple_human.dmi'
icon_state = "skeleton_warden"
icon_living = "skeleton_warden"
loot = list(/obj/effect/decal/cleanable/shreds, /mob/living/basic/skeleton/angered_warden)
maxHealth = 300
health = 300
melee_damage_lower = 15
melee_damage_upper = 15
death_message = null
gold_core_spawnable = NO_SPAWN
ai_controller = /datum/ai_controller/basic_controller/simple/simple_hostile_obstacles/skeleton_warden
/mob/living/basic/skeleton/warden/Process_Spacemove(movement_dir = 0, continuous_move = FALSE)
return TRUE
/mob/living/basic/skeleton/angered_warden
name = "angered skeleton warden" // round 2
desc = "An angry skeleton."
icon = 'icons/mob/simple_human.dmi'
icon_state = "skeleton_warden_alt"
icon_living = "skeleton_warden_alt"
attack_verb_simple = "claw"
attack_verb_continuous = "claws"
attack_sound = 'sound/hallucinations/growl1.ogg'
maxHealth = 200
health = 200
speed = -1
melee_damage_lower = 30
melee_damage_upper = 30
loot = list(/obj/effect/decal/remains/human, /obj/item/clothing/head/warden, /obj/item/card/sec_shuttle_ruin)
gold_core_spawnable = NO_SPAWN
ai_controller = /datum/ai_controller/basic_controller/simple/simple_hostile_obstacles/skeleton_warden
/mob/living/basic/skeleton/angered_warden/Process_Spacemove(movement_dir = 0, continuous_move = FALSE)
return TRUE
/datum/ai_controller/basic_controller/simple/simple_hostile_obstacles/skeleton_warden
idle_behavior = null // Don't idly wander
@@ -0,0 +1,26 @@
/mob/living/basic/netherworld/blankbody
name = "blank body"
desc = "This looks human enough, but its flesh has an ashy texture, and it's face is featureless save an eerie smile."
icon_state = "blank-body"
icon_living = "blank-body"
icon_dead = "blank-dead"
health = 100
maxHealth = 100
obj_damage = 50
melee_damage_lower = 5
melee_damage_upper = 10
attack_verb_continuous = "punches"
attack_verb_simple = "punch"
attack_sound = 'sound/weapons/bladeslice.ogg'
melee_attack_cooldown_min = 0.5 SECONDS
melee_attack_cooldown_max = 1.5 SECONDS
speak_emote = list("screams")
death_message = "falls apart into a fine dust."
/// The body/brain of the player turned into a blank, if the blank was turned
var/mob/living/held_body
/mob/living/basic/netherworld/blankbody/death(gibbed)
. = ..()
if(held_body)
held_body.forceMove(loc)
qdel(src)
@@ -0,0 +1,23 @@
/mob/living/basic/netherworld/faithless
name = "Faithless"
desc = "The Wish Granter's faith in humanity, incarnate."
speak_emote = list("wails", "growls")
icon_state = "faithless"
icon_living = "faithless"
icon_dead = "faithless_dead"
mob_biotypes = MOB_ORGANIC | MOB_HUMANOID
maxHealth = 80
health = 80
melee_damage_lower = 15
melee_damage_upper = 15
harm_intent_damage = 10
obj_damage = 50
attack_verb_simple = "grip"
attack_verb_continuous = "grips"
attack_sound = 'sound/hallucinations/growl1.ogg'
speed = 0
gold_core_spawnable = HOSTILE_SPAWN
faction = "faithless"
/mob/living/basic/netherworld/faithless/Process_Spacemove(movement_dir = 0, continuous_move = FALSE)
return TRUE
@@ -0,0 +1,197 @@
/mob/living/basic/netherworld/migo
name = "mi-go"
desc = "A pinkish, fungoid crustacean-like creature with numerous pairs of clawed appendages and a head covered with waving antennae."
speak_emote = list("screams", "clicks", "chitters", "barks", "moans", "growls", "meows", "reverberates", "roars", "squeaks", "rattles", "exclaims", "yells", "remarks", "mumbles", "jabbers", "stutters", "seethes")
icon_state = "mi-go"
icon_living = "mi-go"
icon_dead = "mi-go-dead"
attack_verb_simple = "lacerate"
attack_verb_continuous = "lacerates"
speed = 0
death_message = "wails as its form turns into a pulpy mush."
death_sound = 'sound/voice/hiss6.ogg'
gold_core_spawnable = HOSTILE_SPAWN
/// List of sounds the migo can make
var/static/list/migo_sounds
/// Will the migo dodge?
var/dodge_prob = 10
surgery_container = /datum/xenobiology_surgery_container/migo
/mob/living/basic/netherworld/migo/Initialize(mapload)
. = ..()
migo_sounds = list(
'sound/items/bubblewrap.ogg',
'sound/items/change_jaws.ogg',
'sound/items/crowbar.ogg',
'sound/items/drink.ogg',
'sound/items/deconstruct.ogg',
'sound/items/change_drill.ogg',
'sound/items/dodgeball.ogg',
'sound/items/eatfood.ogg',
'sound/items/screwdriver.ogg',
'sound/items/weeoo1.ogg',
'sound/items/wirecutter.ogg',
'sound/items/welder.ogg',
'sound/items/zip.ogg',
'sound/items/rped.ogg',
'sound/items/ratchet.ogg',
'sound/items/polaroid1.ogg',
'sound/items/pshoom.ogg',
'sound/items/airhorn.ogg',
'sound/voice/bcreep.ogg',
'sound/voice/biamthelaw.ogg',
'sound/voice/ed209_20sec.ogg',
'sound/voice/hiss3.ogg',
'sound/voice/hiss6.ogg',
'sound/voice/mpatchedup.ogg',
'sound/voice/mfeelbetter.ogg',
'sound/weapons/sear.ogg',
'sound/ambience/antag/tatoralert.ogg',
'sound/mecha/nominal.ogg',
'sound/mecha/weapdestr.ogg',
'sound/mecha/critdestr.ogg',
'sound/mecha/imag_enh.ogg',
'sound/effects/adminhelp.ogg',
'sound/effects/alert.ogg',
'sound/effects/attackblob.ogg',
'sound/effects/bamf.ogg',
'sound/effects/blobattack.ogg',
'sound/effects/break_stone.ogg',
'sound/effects/bubbles.ogg',
'sound/effects/bubbles2.ogg',
'sound/effects/clang.ogg',
'sound/effects/clownstep2.ogg',
'sound/effects/dimensional_rend.ogg',
'sound/effects/doorcreaky.ogg',
'sound/effects/empulse.ogg',
'sound/effects/explosionfar.ogg',
'sound/effects/explosion1.ogg',
'sound/effects/grillehit.ogg',
'sound/effects/genetics.ogg',
'sound/effects/heartbeat.ogg',
'sound/effects/hyperspace_begin.ogg',
'sound/effects/hyperspace_end.ogg',
'sound/goonstation/effects/screech.ogg',
'sound/effects/phasein.ogg',
'sound/effects/picaxe1.ogg',
'sound/effects/sparks1.ogg',
'sound/effects/smoke.ogg',
'sound/effects/splat.ogg',
'sound/effects/snap.ogg',
'sound/effects/tendril_destroyed.ogg',
'sound/effects/supermatter.ogg',
'sound/misc/desceration-01.ogg',
'sound/misc/desceration-02.ogg',
'sound/misc/desceration-03.ogg',
'sound/misc/bloblarm.ogg',
'sound/goonstation/misc/airraid_loop.ogg',
'sound/misc/interference.ogg',
'sound/misc/notice1.ogg',
'sound/misc/notice2.ogg',
'sound/misc/sadtrombone.ogg',
'sound/misc/slip.ogg',
'sound/weapons/armbomb.ogg',
'sound/weapons/chainsaw.ogg',
'sound/weapons/emitter.ogg',
'sound/weapons/emitter2.ogg',
'sound/weapons/blade1.ogg',
'sound/weapons/bladeslice.ogg',
'sound/weapons/blastcannon.ogg',
'sound/weapons/blaster.ogg',
'sound/weapons/bulletflyby3.ogg',
'sound/weapons/circsawhit.ogg',
'sound/weapons/cqchit2.ogg',
'sound/weapons/drill.ogg',
'sound/weapons/genhit1.ogg',
'sound/weapons/gunshots/gunshot_silenced.ogg',
'sound/weapons/gunshots/gunshot.ogg',
'sound/weapons/handcuffs.ogg',
'sound/weapons/homerun.ogg',
'sound/weapons/kenetic_accel.ogg',
'sound/machines/fryer/deep_fryer_emerge.ogg',
'sound/machines/airlock_alien_prying.ogg',
'sound/machines/airlock_close.ogg',
'sound/machines/airlockforced.ogg',
'sound/machines/airlock_open.ogg',
'sound/machines/alarm.ogg',
'sound/machines/blender.ogg',
'sound/machines/boltsdown.ogg',
'sound/machines/boltsup.ogg',
'sound/machines/buzz-sigh.ogg',
'sound/machines/buzz-two.ogg',
'sound/machines/chime.ogg',
'sound/machines/defib_charge.ogg',
'sound/machines/defib_failed.ogg',
'sound/machines/defib_ready.ogg',
'sound/machines/defib_zap.ogg',
'sound/machines/deniedbeep.ogg',
'sound/machines/ding.ogg',
'sound/machines/disposalflush.ogg',
'sound/machines/door_close.ogg',
'sound/machines/door_open.ogg',
'sound/machines/engine_alert1.ogg',
'sound/machines/engine_alert2.ogg',
'sound/machines/hiss.ogg',
'sound/machines/honkbot_evil_laugh.ogg',
'sound/machines/juicer.ogg',
'sound/machines/ping.ogg',
'sound/ambience/signal.ogg',
'sound/machines/synth_no.ogg',
'sound/machines/synth_yes.ogg',
'sound/machines/terminal_alert.ogg',
'sound/machines/twobeep.ogg',
'sound/machines/ventcrawl.ogg',
'sound/machines/warning-buzzer.ogg',
'sound/ai/outbreak5.ogg',
'sound/ai/outbreak7.ogg',
'sound/ai/alert.ogg',
'sound/ai/radiation.ogg',
'sound/ai/eshuttle_call.ogg',
'sound/ai/eshuttle_dock.ogg',
'sound/ai/eshuttle_recall.ogg',
'sound/ai/aimalf.ogg',
'sound/ambience/ambigen1.ogg',
'sound/ambience/ambigen3.ogg',
'sound/ambience/ambigen4.ogg',
'sound/ambience/ambigen5.ogg',
'sound/ambience/ambigen6.ogg',
'sound/ambience/ambigen10.ogg',
'sound/hallucinations/over_here1.ogg',
'sound/hallucinations/over_here2.ogg',
'sound/hallucinations/over_here3.ogg'
)
/// Makes the migo more likely to dodge around the more damaged it is
/mob/living/basic/netherworld/migo/proc/update_dodge_chance(health_ratio)
dodge_prob = LERP(50, 10, health_ratio)
/mob/living/basic/netherworld/migo/proc/make_migo_sound()
playsound(src, pick(migo_sounds), 50, TRUE)
/mob/living/basic/netherworld/migo/say(message, bubble_type, list/spans = list(), sanitize = TRUE, datum/language/language = null, ignore_spam = FALSE, forced = null)
..()
if(stat)
return
make_migo_sound()
/mob/living/basic/netherworld/migo/Life()
..()
if(stat)
return
if(prob(10))
make_migo_sound()
/mob/living/basic/netherworld/migo/Move(atom/newloc, dir, step_x, step_y)
if(!ckey && prob(dodge_prob) && moving_diagonally == 0 && isturf(loc) && isturf(newloc))
return dodge(newloc, dir)
else
return ..()
/mob/living/basic/netherworld/migo/proc/dodge(moving_to, move_direction)
// Assuming we move towards the target we want to swerve toward them to get closer
var/cdir = turn(move_direction, 45)
var/ccdir = turn(move_direction, -45)
. = Move(get_step(loc, pick(cdir, ccdir)))
if(!.)// Can't dodge there so we just carry on
. = Move(moving_to, move_direction)
@@ -0,0 +1,38 @@
/mob/living/basic/netherworld
name = "creature"
desc = "A sanity-destroying otherthing from the netherworld."
icon_state = "otherthing-pink"
icon_living = "otherthing-pink"
icon_dead = "otherthing-pink-dead"
health = 80
maxHealth = 80
obj_damage = 100
melee_damage_lower = 25
melee_damage_upper = 50
melee_attack_cooldown_min = 1.5 SECONDS
melee_attack_cooldown_max = 2.5 SECONDS
environment_smash = ENVIRONMENT_SMASH_STRUCTURES
ai_controller = /datum/ai_controller/basic_controller/simple/simple_hostile_obstacles/demonic_incursion
attack_verb_simple = "chomp"
attack_verb_continuous = "chomps"
attack_sound = 'sound/weapons/bladeslice.ogg'
speak_emote = list("screams")
gold_core_spawnable = HOSTILE_SPAWN
atmos_requirements = list("min_oxy" = 0, "max_oxy" = 0, "min_tox" = 0, "max_tox" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0)
damage_coeff = list(BRUTE = 1, BURN = 1, TOX = 1, STAM = 0, OXY = 1)
faction = list("nether")
contains_xeno_organ = TRUE
surgery_container = /datum/xenobiology_surgery_container/sweating
step_type = FOOTSTEP_MOB_SHOE
/// The chance of it being a grappler variant
var/grappler_chance = 20
/mob/living/basic/netherworld/Initialize(mapload)
. = ..()
if(prob(grappler_chance))
AddComponent(/datum/component/ranged_attacks, projectile_type = /obj/item/projectile/energy/demonic_grappler, projectile_sound = 'sound/weapons/wave.ogg')
name = "grappling " + name
ai_controller = new /datum/ai_controller/basic_controller/simple/simple_skirmisher/demon_incursion(src)
update_appearance(UPDATE_NAME)
color = "#5494DA"
AddElement(/datum/element/ai_retaliate)
@@ -69,3 +69,23 @@
visible_message("<span class='notice'>[M] punches [src], but doesn't leave a dent.</span>", \
"<span class='notice'>[M] punches [src], but doesn't leave a dent.</span>")
return FALSE
/mob/living/silicon/handle_basic_attack(mob/living/basic/attacker, modifiers)
. = ..()
if(.)
var/damage = rand(attacker.melee_damage_lower, attacker.melee_damage_upper)
damage = run_armor(damage, attacker.melee_damage_type, MELEE, 0, attacker.armour_penetration_flat, attacker.armour_penetration_percentage)
switch(attacker.melee_damage_type)
if(BRUTE)
adjustBruteLoss(damage)
if(BURN)
adjustFireLoss(damage)
if(TOX)
adjustToxLoss(damage)
if(OXY)
adjustOxyLoss(damage)
if(CLONE)
adjustCloneLoss(damage)
if(STAMINA)
adjustStaminaLoss(damage)
updatehealth()
@@ -123,3 +123,9 @@
else
visual_effect_icon = ATTACK_EFFECT_SMASH
..()
/mob/living/simple_animal/handle_basic_attack(mob/living/basic/attacker, modifiers)
. = ..()
if(.)
var/damage = rand(attacker.melee_damage_lower, attacker.melee_damage_upper)
return attack_threshold_check(damage, attacker.melee_damage_type)
@@ -1,34 +0,0 @@
/mob/living/simple_animal/hostile/faithless
name = "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
speak_chance = 0
turns_per_move = 5
response_help = "passes through the"
response_disarm = "shoves"
response_harm = "hits the"
speed = 0
maxHealth = 80
health = 80
obj_damage = 50
harm_intent_damage = 10
melee_damage_lower = 15
melee_damage_upper = 15
attacktext = "grips"
attack_sound = 'sound/hallucinations/growl1.ogg'
speak_emote = list("growls")
emote_taunt = list("wails")
taunt_chance = 25
atmos_requirements = list("min_oxy" = 0, "max_oxy" = 0, "min_tox" = 0, "max_tox" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0)
minbodytemp = 0
faction = list("faithless")
gold_core_spawnable = HOSTILE_SPAWN
footstep_type = FOOTSTEP_MOB_SHOE
/mob/living/simple_animal/hostile/faithless/Process_Spacemove(movement_dir = 0, continuous_move = FALSE)
return 1
@@ -1,69 +1,3 @@
/mob/living/simple_animal/hostile/netherworld
name = "creature"
desc = "A sanity-destroying otherthing from the netherworld."
icon_state = "otherthing-pink"
icon_living = "otherthing-pink"
icon_dead = "otherthing-pink-dead"
health = 80
maxHealth = 80
obj_damage = 100
melee_damage_lower = 25
melee_damage_upper = 50
attacktext = "chomps"
attack_sound = 'sound/weapons/bladeslice.ogg'
speak_emote = list("screams")
gold_core_spawnable = HOSTILE_SPAWN
atmos_requirements = list("min_oxy" = 0, "max_oxy" = 0, "min_tox" = 0, "max_tox" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0)
minbodytemp = 0
faction = list("nether")
contains_xeno_organ = TRUE
surgery_container = /datum/xenobiology_surgery_container/sweating
/mob/living/simple_animal/hostile/netherworld/migo
name = "mi-go"
desc = "A pinkish, fungoid crustacean-like creature with numerous pairs of clawed appendages and a head covered with waving antennae."
speak_emote = list("screams", "clicks", "chitters", "barks", "moans", "growls", "meows", "reverberates", "roars", "squeaks", "rattles", "exclaims", "yells", "remarks", "mumbles", "jabbers", "stutters", "seethes")
icon_state = "mi-go"
icon_living = "mi-go"
icon_dead = "mi-go-dead"
attacktext = "lacerates"
speed = -0.5
deathmessage = "wails as its form turns into a pulpy mush."
death_sound = 'sound/voice/hiss6.ogg'
surgery_container = /datum/xenobiology_surgery_container/migo
/mob/living/simple_animal/hostile/netherworld/migo/Initialize(mapload)
. = ..()
/mob/living/simple_animal/hostile/netherworld/migo/say(message, bubble_type, list/spans = list(), sanitize = TRUE, datum/language/language = null, ignore_spam = FALSE, forced = null)
..()
if(stat)
return
var/chosen_sound = pick(GLOB.migo_sounds)
playsound(src, chosen_sound, 50, TRUE)
/mob/living/simple_animal/hostile/netherworld/migo/Life()
..()
if(stat)
return
if(prob(10))
var/chosen_sound = pick(GLOB.migo_sounds)
playsound(src, chosen_sound, 50, TRUE)
/mob/living/simple_animal/hostile/netherworld/blankbody
name = "blank body"
desc = "This looks human enough, but its flesh has an ashy texture, and it's face is featureless save an eerie smile."
icon_state = "blank-body"
icon_living = "blank-body"
icon_dead = "blank-dead"
gold_core_spawnable = NO_SPAWN
health = 100
maxHealth = 100
melee_damage_lower = 5
melee_damage_upper = 10
attacktext = "punches"
deathmessage = "falls apart into a fine dust."
/obj/structure/spawner/nether
name = "netherworld link"
desc = null //see examine()
@@ -73,7 +7,7 @@
max_mobs = 15
icon = 'icons/mob/nest.dmi'
spawn_text = "crawls through"
mob_types = list(/mob/living/simple_animal/hostile/netherworld/migo, /mob/living/simple_animal/hostile/netherworld, /mob/living/simple_animal/hostile/netherworld/blankbody)
mob_types = list(/mob/living/basic/netherworld/migo, /mob/living/basic/netherworld, /mob/living/basic/netherworld/blankbody)
faction = list("nether")
/obj/structure/spawner/nether/Initialize(mapload)
@@ -103,9 +37,17 @@
M.adjustBruteLoss(60)
new /obj/effect/gibspawner/generic(get_turf(M), M.dna)
if(M.stat == DEAD)
var/mob/living/simple_animal/hostile/netherworld/blankbody/blank
var/mob/living/basic/netherworld/blankbody/blank
blank = new(loc)
blank.name = "[M]"
blank.desc = "It's [M], but [M.p_their()] flesh has an ashy texture, and [M.p_their()] face is featureless save an eerie smile."
visible_message("<span class='warning'>[M] reemerges from the link!</span>")
qdel(M)
blank.held_body = M
M.forceMove(blank)
/obj/structure/spawner/nether/deconstruct(disassembled)
for(var/mob/living/M in contents)
if(M)
M.forceMove(loc)
return ..()
@@ -77,45 +77,6 @@
del_on_death = TRUE
footstep_type = FOOTSTEP_MOB_SHOE
/mob/living/simple_animal/hostile/retaliate/skeleton/warden
name = "skeleton warden"
desc = "The remains of a warden."
icon = 'icons/mob/simple_human.dmi'
icon_state = "skeleton_warden"
icon_living = "skeleton_warden"
wander = FALSE
loot = list(/obj/effect/decal/cleanable/shreds, /mob/living/simple_animal/hostile/skeleton/angered_warden)
maxHealth = 300
health = 300
melee_damage_lower = 15
melee_damage_upper = 15
deathmessage = null
gold_core_spawnable = NO_SPAWN
/mob/living/simple_animal/hostile/retaliate/skeleton/warden/Process_Spacemove(movement_dir = 0, continuous_move = FALSE)
return TRUE
/mob/living/simple_animal/hostile/skeleton/angered_warden
name = "angered skeleton warden" //round 2
desc = "An angry skeleton."
icon = 'icons/mob/simple_human.dmi'
icon_state = "skeleton_warden_alt"
icon_living = "skeleton_warden_alt"
attacktext = "claws"
attack_sound = 'sound/hallucinations/growl1.ogg'
maxHealth = 200
health = 200
speed = -1
melee_damage_lower = 30
melee_damage_upper = 30
loot = list(/obj/effect/decal/remains/human, /obj/item/clothing/head/warden, /obj/item/card/sec_shuttle_ruin)
gold_core_spawnable = NO_SPAWN
/mob/living/simple_animal/hostile/skeleton/angered_warden/Process_Spacemove(movement_dir = 0, continuous_move = FALSE)
return TRUE
/mob/living/simple_animal/hostile/retaliate/zombie
name = "zombie"
icon = 'icons/mob/human.dmi'
@@ -1,50 +0,0 @@
/mob/living/simple_animal/hostile/skeleton
name = "reanimated skeleton"
desc = "A real bonefied skeleton, doesn't seem like it wants to socialize."
icon = 'icons/mob/simple_human.dmi'
icon_state = "skeleton"
icon_living = "skeleton"
mob_biotypes = MOB_UNDEAD | MOB_HUMANOID
turns_per_move = 5
speak_emote = list("rattles")
emote_see = list("rattles")
a_intent = INTENT_HARM
maxHealth = 40
health = 40
speed = 1
harm_intent_damage = 5
melee_damage_lower = 15
melee_damage_upper = 15
minbodytemp = 0
maxbodytemp = 1500
healable = FALSE //they're skeletons how would bruise packs help them??
attacktext = "slashes"
attack_sound = 'sound/hallucinations/growl1.ogg'
atmos_requirements = list("min_oxy" = 0, "max_oxy" = 0, "min_tox" = 0, "max_tox" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0)
unsuitable_atmos_damage = 10
robust_searching = TRUE
stat_attack = UNCONSCIOUS
gold_core_spawnable = HOSTILE_SPAWN
faction = list("skeleton")
see_in_dark = 8
lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE
deathmessage = "collapses into a pile of bones!"
del_on_death = TRUE
loot = list(/obj/effect/decal/remains/human)
/mob/living/simple_animal/hostile/skeleton/arctic
name = "undead arctic explorer"
desc = "The reanimated remains of some poor traveler."
icon_state = "arctic_skeleton"
icon_living = "arctic_skeleton"
maxHealth = 55
health = 55
weather_immunities = list("snow")
gold_core_spawnable = NO_SPAWN
melee_damage_lower = 17
melee_damage_upper = 20
deathmessage = "collapses into a pile of bones, its gear falling to the floor!"
loot = list(/obj/effect/decal/remains/human,
/obj/item/spear,
/obj/item/clothing/shoes/winterboots,
/obj/item/clothing/suit/hooded/wintercoat)
@@ -50,6 +50,7 @@
sparks = new
sparks.attach(src)
sparks.set_up(5, 1, src)
AddElement(/datum/element/hostile_machine)
/obj/machinery/power/emitter/cherenkov
icon_state = "emitter_+a"
@@ -305,6 +305,24 @@
add_attack_logs(firer, target, "Shot with a [type] [teleport_target ? "(Destination: [teleport_target])" : ""]")
return ..()
/obj/item/projectile/energy/demonic_grappler
name = "demonic grappler"
icon_state = "bluespace"
damage = 0
damage_type = BURN
nodamage = 1
flag = "energy"
pass_flags = PASSTABLE | PASSGLASS | PASSGRILLE
/obj/item/projectile/energy/demonic_grappler/on_hit(atom/target, blocked = 0)
if(isliving(target))
var/turf/source_turf = get_turf(firer)
do_teleport(target, source_turf)
else
var/turf/miss_turf = get_step(target, get_dir(target, firer))
do_teleport(firer, miss_turf)
return ..()
/obj/item/projectile/snowball
name = "snowball"
icon_state = "snowball"
Binary file not shown.

After

Width:  |  Height:  |  Size: 731 B

+15 -3
View File
@@ -453,6 +453,7 @@
#include "code\datums\ai\ai_controller.dm"
#include "code\datums\ai\ai_planning_subtree.dm"
#include "code\datums\ai\basic_mobs\basic_controller.dm"
#include "code\datums\ai\basic_mobs\simple_controller.dm"
#include "code\datums\ai\basic_mobs\basic_ai_behaviors\basic_attacking.dm"
#include "code\datums\ai\basic_mobs\basic_ai_behaviors\emote_with_target.dm"
#include "code\datums\ai\basic_mobs\basic_ai_behaviors\befriend_target.dm"
@@ -463,10 +464,13 @@
#include "code\datums\ai\basic_mobs\basic_ai_behaviors\targeting.dm"
#include "code\datums\ai\basic_mobs\basic_ai_behaviors\tipped_reaction.dm"
#include "code\datums\ai\basic_mobs\basic_ai_behaviors\travel_towards.dm"
#include "code\datums\ai\basic_mobs\basic_subtrees\attack_obstacle_in_path.dm"
#include "code\datums\ai\basic_mobs\basic_subtrees\find_food.dm"
#include "code\datums\ai\basic_mobs\basic_subtrees\flee_target.dm"
#include "code\datums\ai\basic_mobs\basic_subtrees\mine_walls.dm"
#include "code\datums\ai\basic_mobs\basic_subtrees\prowl.dm"
#include "code\datums\ai\basic_mobs\basic_subtrees\random_speech.dm"
#include "code\datums\ai\basic_mobs\basic_subtrees\ranged_skirmish.dm"
#include "code\datums\ai\basic_mobs\basic_subtrees\simple_attack_target.dm"
#include "code\datums\ai\basic_mobs\basic_subtrees\simple_find_nearest_target_to_flee.dm"
#include "code\datums\ai\basic_mobs\basic_subtrees\stare_at_thing.dm"
@@ -478,6 +482,7 @@
#include "code\datums\ai\basic_mobs\hunting_behavior\find_and_hunt_target.dm"
#include "code\datums\ai\basic_mobs\hunting_behavior\find_hunt_target.dm"
#include "code\datums\ai\basic_mobs\hunting_behavior\hunt_target.dm"
#include "code\datums\ai\basic_mobs\generic_controllers\hostile_controllers.dm"
#include "code\datums\ai\generic\find_and_set.dm"
#include "code\datums\ai\generic\generic_behaviors.dm"
#include "code\datums\ai\idle_behaviors\idle_behavior.dm"
@@ -520,6 +525,7 @@
#include "code\datums\components\forces_doors_open.dm"
#include "code\datums\components\fullauto.dm"
#include "code\datums\components\ghost_direct_control.dm"
#include "code\datums\components\incursion_mob_death.dm"
#include "code\datums\components\inherent_radioactivity.dm"
#include "code\datums\components\jetpack_component.dm"
#include "code\datums\components\label.dm"
@@ -627,6 +633,7 @@
#include "code\datums\elements\decal_element.dm"
#include "code\datums\elements\earhealing.dm"
#include "code\datums\elements\high_value_item.dm"
#include "code\datums\elements\hostile_machine.dm"
#include "code\datums\elements\rad_insulation.dm"
#include "code\datums\elements\relay_attackers.dm"
#include "code\datums\elements\ridable.dm"
@@ -2042,6 +2049,7 @@
#include "code\modules\events\camera_failure.dm"
#include "code\modules\events\carp_migration.dm"
#include "code\modules\events\communications_blackout.dm"
#include "code\modules\events\demon_incursion.dm"
#include "code\modules\events\disease_outbreak.dm"
#include "code\modules\events\door_runtime.dm"
#include "code\modules\events\dust.dm"
@@ -2393,6 +2401,13 @@
#include "code\modules\mob\living\stat_states.dm"
#include "code\modules\mob\living\taste.dm"
#include "code\modules\mob\living\basic\basic_mob.dm"
#include "code\modules\mob\living\basic\hostile\hellhound.dm"
#include "code\modules\mob\living\basic\hostile\hellhound_ai.dm"
#include "code\modules\mob\living\basic\hostile\skeleton_mob.dm"
#include "code\modules\mob\living\basic\nether_mobs\blankbody.dm"
#include "code\modules\mob\living\basic\nether_mobs\faithless.dm"
#include "code\modules\mob\living\basic\nether_mobs\migo.dm"
#include "code\modules\mob\living\basic\nether_mobs\nether_mobs.dm"
#include "code\modules\mob\living\basic\farm_animals\cow.dm"
#include "code\modules\mob\living\basic\farm_animals\deer.dm"
#include "code\modules\mob\living\basic\farm_animals\deer_ai.dm"
@@ -2604,12 +2619,10 @@
#include "code\modules\mob\living\simple_animal\hostile\creature.dm"
#include "code\modules\mob\living\simple_animal\hostile\deathsquid.dm"
#include "code\modules\mob\living\simple_animal\hostile\drakehound.dm"
#include "code\modules\mob\living\simple_animal\hostile\faithless.dm"
#include "code\modules\mob\living\simple_animal\hostile\feral_cat.dm"
#include "code\modules\mob\living\simple_animal\hostile\floorcluwne.dm"
#include "code\modules\mob\living\simple_animal\hostile\giant_spider.dm"
#include "code\modules\mob\living\simple_animal\hostile\headslug.dm"
#include "code\modules\mob\living\simple_animal\hostile\hellhound.dm"
#include "code\modules\mob\living\simple_animal\hostile\hivebot.dm"
#include "code\modules\mob\living\simple_animal\hostile\hostile.dm"
#include "code\modules\mob\living\simple_animal\hostile\illusion.dm"
@@ -2619,7 +2632,6 @@
#include "code\modules\mob\living\simple_animal\hostile\mushroom.dm"
#include "code\modules\mob\living\simple_animal\hostile\netherworld.dm"
#include "code\modules\mob\living\simple_animal\hostile\pirate.dm"
#include "code\modules\mob\living\simple_animal\hostile\skeleton_mob.dm"
#include "code\modules\mob\living\simple_animal\hostile\soviet.dm"
#include "code\modules\mob\living\simple_animal\hostile\syndicate_mobs.dm"
#include "code\modules\mob\living\simple_animal\hostile\tree.dm"
Binary file not shown.