diff --git a/_maps/map_files/RandomRuins/LavaRuins/lavaland_biodome_winter.dmm b/_maps/map_files/RandomRuins/LavaRuins/lavaland_biodome_winter.dmm index 5720ef32047..703539c83e5 100644 --- a/_maps/map_files/RandomRuins/LavaRuins/lavaland_biodome_winter.dmm +++ b/_maps/map_files/RandomRuins/LavaRuins/lavaland_biodome_winter.dmm @@ -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" = ( diff --git a/_maps/map_files/RandomRuins/SpaceRuins/abandoned_sec_shuttle.dmm b/_maps/map_files/RandomRuins/SpaceRuins/abandoned_sec_shuttle.dmm index d972e10bdc6..fb9db8eabd7 100644 --- a/_maps/map_files/RandomRuins/SpaceRuins/abandoned_sec_shuttle.dmm +++ b/_maps/map_files/RandomRuins/SpaceRuins/abandoned_sec_shuttle.dmm @@ -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{ diff --git a/_maps/map_files/RandomRuins/SpaceRuins/derelict5.dmm b/_maps/map_files/RandomRuins/SpaceRuins/derelict5.dmm index 6589aca2c26..5bbd8d64a0c 100644 --- a/_maps/map_files/RandomRuins/SpaceRuins/derelict5.dmm +++ b/_maps/map_files/RandomRuins/SpaceRuins/derelict5.dmm @@ -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" = ( diff --git a/code/__DEFINES/ai/ai_defines.dm b/code/__DEFINES/ai/ai_defines.dm index 6edf0e98531..1cf3ea125a1 100644 --- a/code/__DEFINES/ai/ai_defines.dm +++ b/code/__DEFINES/ai/ai_defines.dm @@ -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 diff --git a/code/__DEFINES/ai/blackboard_defines.dm b/code/__DEFINES/ai/blackboard_defines.dm index cdcf1c21362..4291ac1cd55 100644 --- a/code/__DEFINES/ai/blackboard_defines.dm +++ b/code/__DEFINES/ai/blackboard_defines.dm @@ -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" diff --git a/code/__DEFINES/layers.dm b/code/__DEFINES/layers.dm index f7f5145a212..c834d74cd0d 100644 --- a/code/__DEFINES/layers.dm +++ b/code/__DEFINES/layers.dm @@ -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 diff --git a/code/__DEFINES/misc_defines.dm b/code/__DEFINES/misc_defines.dm index 3c78bf684d2..e5fb01f0a20 100644 --- a/code/__DEFINES/misc_defines.dm +++ b/code/__DEFINES/misc_defines.dm @@ -640,6 +640,7 @@ #define BIOHAZARD_BLOB "Blob" #define BIOHAZARD_XENO "Xenomorphs" +#define INCURSION_DEMONS "Demon Incursion" #define MAX_ALLOWED_TELEPORTS_PER_PROCESS 20 diff --git a/code/controllers/subsystem/SSticker.dm b/code/controllers/subsystem/SSticker.dm index c69207c53e5..6c336b355bb 100644 --- a/code/controllers/subsystem/SSticker.dm +++ b/code/controllers/subsystem/SSticker.dm @@ -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 diff --git a/code/datums/ai/basic_mobs/basic_ai_behaviors/targeting.dm b/code/datums/ai/basic_mobs/basic_ai_behaviors/targeting.dm index 637f15c1334..d1dcedcc19a 100644 --- a/code/datums/ai/basic_mobs/basic_ai_behaviors/targeting.dm +++ b/code/datums/ai/basic_mobs/basic_ai_behaviors/targeting.dm @@ -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 diff --git a/code/datums/ai/basic_mobs/basic_subtrees/attack_obstacle_in_path.dm b/code/datums/ai/basic_mobs/basic_subtrees/attack_obstacle_in_path.dm new file mode 100644 index 00000000000..09fc8ab4105 --- /dev/null +++ b/code/datums/ai/basic_mobs/basic_subtrees/attack_obstacle_in_path.dm @@ -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 diff --git a/code/datums/ai/basic_mobs/basic_subtrees/prowl.dm b/code/datums/ai/basic_mobs/basic_subtrees/prowl.dm new file mode 100644 index 00000000000..24706ba16eb --- /dev/null +++ b/code/datums/ai/basic_mobs/basic_subtrees/prowl.dm @@ -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 diff --git a/code/datums/ai/basic_mobs/basic_subtrees/ranged_skirmish.dm b/code/datums/ai/basic_mobs/basic_subtrees/ranged_skirmish.dm new file mode 100644 index 00000000000..220336592a4 --- /dev/null +++ b/code/datums/ai/basic_mobs/basic_subtrees/ranged_skirmish.dm @@ -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 diff --git a/code/datums/ai/basic_mobs/generic_controllers/hostile_controllers.dm b/code/datums/ai/basic_mobs/generic_controllers/hostile_controllers.dm new file mode 100644 index 00000000000..490a1c12e21 --- /dev/null +++ b/code/datums/ai/basic_mobs/generic_controllers/hostile_controllers.dm @@ -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, + ) diff --git a/code/datums/ai/basic_mobs/simple_controller.dm b/code/datums/ai/basic_mobs/simple_controller.dm new file mode 100644 index 00000000000..3f714baf7a2 --- /dev/null +++ b/code/datums/ai/basic_mobs/simple_controller.dm @@ -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 diff --git a/code/datums/ai/targeting_strategy/basic_targeting_strategy.dm b/code/datums/ai/targeting_strategy/basic_targeting_strategy.dm index 9ff1c493ef2..b867dd8b68a 100644 --- a/code/datums/ai/targeting_strategy/basic_targeting_strategy.dm +++ b/code/datums/ai/targeting_strategy/basic_targeting_strategy.dm @@ -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 diff --git a/code/datums/components/incursion_mob_death.dm b/code/datums/components/incursion_mob_death.dm new file mode 100644 index 00000000000..f63d69bd2f3 --- /dev/null +++ b/code/datums/components/incursion_mob_death.dm @@ -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() diff --git a/code/datums/components/spawner.dm b/code/datums/components/spawner.dm index eb845a9aba6..1b791a95d33 100644 --- a/code/datums/components/spawner.dm +++ b/code/datums/components/spawner.dm @@ -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("[L] [spawn_text] [P].") 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) diff --git a/code/datums/components/surgery_initiator.dm b/code/datums/components/surgery_initiator.dm index 51f0ad1a9e1..3d2c53a04b8 100644 --- a/code/datums/components/surgery_initiator.dm +++ b/code/datums/components/surgery_initiator.dm @@ -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 diff --git a/code/datums/elements/hostile_machine.dm b/code/datums/elements/hostile_machine.dm new file mode 100644 index 00000000000..7d8d1274784 --- /dev/null +++ b/code/datums/elements/hostile_machine.dm @@ -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 ..() diff --git a/code/datums/spells/shapeshift.dm b/code/datums/spells/shapeshift.dm index 4b601beabe7..3d2f38cbea9 100644 --- a/code/datums/spells/shapeshift.dm +++ b/code/datums/spells/shapeshift.dm @@ -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) diff --git a/code/game/gamemodes/game_mode.dm b/code/game/gamemodes/game_mode.dm index 5b4a22dbc5d..8a4000edfd7 100644 --- a/code/game/gamemodes/game_mode.dm +++ b/code/game/gamemodes/game_mode.dm @@ -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. diff --git a/code/game/machinery/portable_turret.dm b/code/game/machinery/portable_turret.dm index 6c95d15e7af..1a7bdd34f25 100644 --- a/code/game/machinery/portable_turret.dm +++ b/code/game/machinery/portable_turret.dm @@ -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, "That object is useless to you.") 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("[attacker] [attacker.attack_verb_continuous] [src]!") + ..() + else + to_chat(attacker, "That object is useless to you.") + 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) diff --git a/code/game/mecha/mecha.dm b/code/game/mecha/mecha.dm index e84aa61d6aa..5415994362c 100644 --- a/code/game/mecha/mecha.dm +++ b/code/game/mecha/mecha.dm @@ -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() . = ..() diff --git a/code/game/objects/items/weapons/dice.dm b/code/game/objects/items/weapons/dice.dm index 02a5817119f..abe0740e846 100644 --- a/code/game/objects/items/weapons/dice.dm +++ b/code/game/objects/items/weapons/dice.dm @@ -187,7 +187,7 @@ //Swarm of creatures T.visible_message("A swarm of creatures surround [user]!") 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("Everything [user] is holding and wearing disappears!") diff --git a/code/game/objects/obj_defense.dm b/code/game/objects/obj_defense.dm index 5e9099bade5..0b16dd94f30 100644 --- a/code/game/objects/obj_defense.dm +++ b/code/game/objects/obj_defense.dm @@ -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 diff --git a/code/game/objects/structures/grille.dm b/code/game/objects/structures/grille.dm index e41215c7684..eb6af458257 100644 --- a/code/game/objects/structures/grille.dm +++ b/code/game/objects/structures/grille.dm @@ -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("[attacker] smashes through [src]!", "You smash through [src].") + return + + take_damage(rand(5, 10), BRUTE, MELEE, 1) + /obj/structure/grille/hulk_damage() return 60 diff --git a/code/game/objects/structures/monster_spawner.dm b/code/game/objects/structures/monster_spawner.dm index d5d69be47e9..498ebfdc9df 100644 --- a/code/game/objects/structures/monster_spawner.dm +++ b/code/game/objects/structures/monster_spawner.dm @@ -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") diff --git a/code/modules/events/demon_incursion.dm b/code/modules/events/demon_incursion.dm new file mode 100644 index 00000000000..738313e4e35 --- /dev/null +++ b/code/modules/events/demon_incursion.dm @@ -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("Something within [elite_portal] stirs...") + 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("[created_mob] emerges from [elite_portal]!") + notify_ghosts("\A [created_mob] has emerged from an incursion portal in \the [get_area(src)]!", enter_link="(Click to help)", 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) + . += "Dark tendrils are stabilizing the portal!" + +/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 diff --git a/code/modules/events/event_container.dm b/code/modules/events/event_container.dm index 271f68c9f52..7a799f0480c 100644 --- a/code/modules/events/event_container.dm +++ b/code/modules/events/event_container.dm @@ -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) diff --git a/code/modules/events/tear.dm b/code/modules/events/tear.dm index 66f7fabe4de..a61ecda9b11 100644 --- a/code/modules/events/tear.dm +++ b/code/modules/events/tear.dm @@ -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("With a terrifying growl, \a [M] steps out of the portal!") diff --git a/code/modules/mob/living/basic/basic_mob.dm b/code/modules/mob/living/basic/basic_mob.dm index a9c2a7ea8f3..429f276be59 100644 --- a/code/modules/mob/living/basic/basic_mob.dm +++ b/code/modules/mob/living/basic/basic_mob.dm @@ -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)) diff --git a/code/modules/mob/living/basic/farm_animals/deer_ai.dm b/code/modules/mob/living/basic/farm_animals/deer_ai.dm index 78010e9b069..f486a7e7cf3 100644 --- a/code/modules/mob/living/basic/farm_animals/deer_ai.dm +++ b/code/modules/mob/living/basic/farm_animals/deer_ai.dm @@ -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 diff --git a/code/modules/mob/living/simple_animal/hostile/hellhound.dm b/code/modules/mob/living/basic/hostile/hellhound.dm similarity index 60% rename from code/modules/mob/living/simple_animal/hostile/hellhound.dm rename to code/modules/mob/living/basic/hostile/hellhound.dm index a9c237f7498..9d0927d7232 100644 --- a/code/modules/mob/living/simple_animal/hostile/hellhound.dm +++ b/code/modules/mob/living/basic/hostile/hellhound.dm @@ -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 += "It is currently resting." . += msgs.Join("
") -/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, "You lick your wounds, helping them close.") - 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, "You lick your wounds, helping them close.") + 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") diff --git a/code/modules/mob/living/basic/hostile/hellhound_ai.dm b/code/modules/mob/living/basic/hostile/hellhound_ai.dm new file mode 100644 index 00000000000..51b5a0894f2 --- /dev/null +++ b/code/modules/mob/living/basic/hostile/hellhound_ai.dm @@ -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 diff --git a/code/modules/mob/living/basic/hostile/skeleton_mob.dm b/code/modules/mob/living/basic/hostile/skeleton_mob.dm new file mode 100644 index 00000000000..54d3c75ac24 --- /dev/null +++ b/code/modules/mob/living/basic/hostile/skeleton_mob.dm @@ -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 diff --git a/code/modules/mob/living/basic/nether_mobs/blankbody.dm b/code/modules/mob/living/basic/nether_mobs/blankbody.dm new file mode 100644 index 00000000000..2e06b93219d --- /dev/null +++ b/code/modules/mob/living/basic/nether_mobs/blankbody.dm @@ -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) diff --git a/code/modules/mob/living/basic/nether_mobs/faithless.dm b/code/modules/mob/living/basic/nether_mobs/faithless.dm new file mode 100644 index 00000000000..8943d267e48 --- /dev/null +++ b/code/modules/mob/living/basic/nether_mobs/faithless.dm @@ -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 diff --git a/code/modules/mob/living/basic/nether_mobs/migo.dm b/code/modules/mob/living/basic/nether_mobs/migo.dm new file mode 100644 index 00000000000..fc1334f2edc --- /dev/null +++ b/code/modules/mob/living/basic/nether_mobs/migo.dm @@ -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) + diff --git a/code/modules/mob/living/basic/nether_mobs/nether_mobs.dm b/code/modules/mob/living/basic/nether_mobs/nether_mobs.dm new file mode 100644 index 00000000000..3c5b169bcdc --- /dev/null +++ b/code/modules/mob/living/basic/nether_mobs/nether_mobs.dm @@ -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) diff --git a/code/modules/mob/living/silicon/silicon_defense.dm b/code/modules/mob/living/silicon/silicon_defense.dm index 6b564a9a80b..3d3947cda3e 100644 --- a/code/modules/mob/living/silicon/silicon_defense.dm +++ b/code/modules/mob/living/silicon/silicon_defense.dm @@ -69,3 +69,23 @@ visible_message("[M] punches [src], but doesn't leave a dent.", \ "[M] punches [src], but doesn't leave a dent.") 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() diff --git a/code/modules/mob/living/simple_animal/animal_defense.dm b/code/modules/mob/living/simple_animal/animal_defense.dm index 5b919326889..27f880dbb5f 100644 --- a/code/modules/mob/living/simple_animal/animal_defense.dm +++ b/code/modules/mob/living/simple_animal/animal_defense.dm @@ -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) diff --git a/code/modules/mob/living/simple_animal/hostile/faithless.dm b/code/modules/mob/living/simple_animal/hostile/faithless.dm deleted file mode 100644 index 22f4b014e19..00000000000 --- a/code/modules/mob/living/simple_animal/hostile/faithless.dm +++ /dev/null @@ -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 diff --git a/code/modules/mob/living/simple_animal/hostile/netherworld.dm b/code/modules/mob/living/simple_animal/hostile/netherworld.dm index dcc173d775a..2c56e03cd5e 100644 --- a/code/modules/mob/living/simple_animal/hostile/netherworld.dm +++ b/code/modules/mob/living/simple_animal/hostile/netherworld.dm @@ -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("[M] reemerges from the link!") - 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 ..() + diff --git a/code/modules/mob/living/simple_animal/hostile/retaliate/undead.dm b/code/modules/mob/living/simple_animal/hostile/retaliate/undead.dm index f67d27cdd21..d1be7e7ae34 100644 --- a/code/modules/mob/living/simple_animal/hostile/retaliate/undead.dm +++ b/code/modules/mob/living/simple_animal/hostile/retaliate/undead.dm @@ -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' diff --git a/code/modules/mob/living/simple_animal/hostile/skeleton_mob.dm b/code/modules/mob/living/simple_animal/hostile/skeleton_mob.dm deleted file mode 100644 index e2baff75166..00000000000 --- a/code/modules/mob/living/simple_animal/hostile/skeleton_mob.dm +++ /dev/null @@ -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) diff --git a/code/modules/power/engines/singularity/emitter.dm b/code/modules/power/engines/singularity/emitter.dm index fb2e752b5d5..5f74677ae06 100644 --- a/code/modules/power/engines/singularity/emitter.dm +++ b/code/modules/power/engines/singularity/emitter.dm @@ -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" diff --git a/code/modules/projectiles/projectile/special_projectiles.dm b/code/modules/projectiles/projectile/special_projectiles.dm index 7aa798a0fde..e546319f663 100644 --- a/code/modules/projectiles/projectile/special_projectiles.dm +++ b/code/modules/projectiles/projectile/special_projectiles.dm @@ -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" diff --git a/icons/obj/structures/portal.dmi b/icons/obj/structures/portal.dmi new file mode 100644 index 00000000000..00356ea6618 Binary files /dev/null and b/icons/obj/structures/portal.dmi differ diff --git a/paradise.dme b/paradise.dme index 9db5919be35..86505e69a1f 100644 --- a/paradise.dme +++ b/paradise.dme @@ -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" diff --git a/sound/AI/outbreak_demon.ogg b/sound/AI/outbreak_demon.ogg new file mode 100644 index 00000000000..733cfcd9e13 Binary files /dev/null and b/sound/AI/outbreak_demon.ogg differ