mirror of
https://github.com/ParadiseSS13/Paradise.git
synced 2026-08-24 12:37:26 +01:00
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:
co-authored by
Luc
warriorstar-orion
parent
3342288f79
commit
05791861b7
@@ -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" = (
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -640,6 +640,7 @@
|
||||
|
||||
#define BIOHAZARD_BLOB "Blob"
|
||||
#define BIOHAZARD_XENO "Xenomorphs"
|
||||
#define INCURSION_DEMONS "Demon Incursion"
|
||||
|
||||
#define MAX_ALLOWED_TELEPORTS_PER_PROCESS 20
|
||||
|
||||
|
||||
@@ -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()
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 ..()
|
||||
@@ -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)
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
. = ..()
|
||||
|
||||
@@ -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>")
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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")
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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)
|
||||
|
||||
@@ -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>")
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
+52
-67
@@ -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
@@ -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.
Reference in New Issue
Block a user