diff --git a/baystation12.dme b/baystation12.dme index 51f7d910084..7293b81662d 100644 --- a/baystation12.dme +++ b/baystation12.dme @@ -1856,7 +1856,10 @@ #include "code\modules\events\gravity.dm" #include "code\modules\events\grid_check.dm" #include "code\modules\events\infestation.dm" +#include "code\modules\events\infestation_hive_space.dm" #include "code\modules\events\ion_storm.dm" +#include "code\modules\events\leviathan_attack.dm" +#include "code\modules\events\leviathan_spawn.dm" #include "code\modules\events\mail.dm" #include "code\modules\events\maint_drones.dm" #include "code\modules\events\meteors.dm" @@ -2426,11 +2429,13 @@ #include "code\modules\mob\living\simple_animal\hostile\giant_spider\nurse.dm" #include "code\modules\mob\living\simple_animal\hostile\giant_spider\spitter.dm" #include "code\modules\mob\living\simple_animal\hostile\infestation\_infestation.dm" +#include "code\modules\mob\living\simple_animal\hostile\infestation\aggregate.dm" #include "code\modules\mob\living\simple_animal\hostile\infestation\assembler.dm" #include "code\modules\mob\living\simple_animal\hostile\infestation\broodling.dm" #include "code\modules\mob\living\simple_animal\hostile\infestation\eviscerator.dm" #include "code\modules\mob\living\simple_animal\hostile\infestation\floatfly.dm" #include "code\modules\mob\living\simple_animal\hostile\infestation\larva.dm" +#include "code\modules\mob\living\simple_animal\hostile\infestation\meatchip.dm" #include "code\modules\mob\living\simple_animal\hostile\infestation\rhino.dm" #include "code\modules\mob\living\simple_animal\hostile\infestation\spitter.dm" #include "code\modules\mob\living\simple_animal\hostile\infestation\structures\hive_heart.dm" diff --git a/code/__defines/misc.dm b/code/__defines/misc.dm index 9d82fe1a4ee..0e922787e96 100644 --- a/code/__defines/misc.dm +++ b/code/__defines/misc.dm @@ -65,10 +65,11 @@ #define MAX_TEXTFILE_LENGTH 128000 // 512GQ file // Event defines. -#define EVENT_LEVEL_MUNDANE 1 -#define EVENT_LEVEL_MODERATE 2 -#define EVENT_LEVEL_MAJOR 3 -#define EVENT_LEVEL_EXO 4 +#define EVENT_LEVEL_MUNDANE 1 +#define EVENT_LEVEL_MODERATE 2 +#define EVENT_LEVEL_MAJOR 3 +#define EVENT_LEVEL_CATASTROPHE 4 +#define EVENT_LEVEL_EXO 5 //General-purpose life speed define for plants. #define HYDRO_SPEED_MULTIPLIER 1 diff --git a/code/_onclick/item_attack.dm b/code/_onclick/item_attack.dm index bfbd1572759..05dfd653b01 100644 --- a/code/_onclick/item_attack.dm +++ b/code/_onclick/item_attack.dm @@ -98,7 +98,7 @@ avoid code duplication. This includes items that may sometimes act as a standard return 1 //Called when a weapon is used to make a successful melee attack on a mob. Returns whether damage was dealt. -/obj/item/proc/apply_hit_effect(mob/living/target, mob/living/user, var/hit_zone) +/obj/item/proc/apply_hit_effect(mob/living/target, mob/living/user, hit_zone) if(hitsound) playsound(loc, hitsound, 50, 1, -1) diff --git a/code/controllers/configuration.dm b/code/controllers/configuration.dm index c985d35420d..ace327889ef 100644 --- a/code/controllers/configuration.dm +++ b/code/controllers/configuration.dm @@ -169,11 +169,29 @@ var/list/gamemode_cache = list() // Event settings var/expected_round_length = 3 HOURS /// If the first delay has a custom start time - var/static/list/event_first_run = list(EVENT_LEVEL_MUNDANE = null, EVENT_LEVEL_MODERATE = null, EVENT_LEVEL_MAJOR = list("lower" = 80 MINUTES, "upper" = 100 MINUTES), EVENT_LEVEL_EXO = list("lower" = 50 MINUTES, "upper" = 80 MINUTES)) + var/static/list/event_first_run = list( + EVENT_LEVEL_MUNDANE = null, + EVENT_LEVEL_MODERATE = null, + EVENT_LEVEL_MAJOR = list("lower" = 80 MINUTES, "upper" = 100 MINUTES), + EVENT_LEVEL_CATASTROPHE = list("lower" = 150 MINUTES, "upper" = 200 MINUTES), + EVENT_LEVEL_EXO = list("lower" = 50 MINUTES, "upper" = 80 MINUTES), + ) /// The lowest delay until next event - var/static/list/event_delay_lower = list(EVENT_LEVEL_MUNDANE = 10 MINUTES, EVENT_LEVEL_MODERATE = 30 MINUTES, EVENT_LEVEL_MAJOR = 50 MINUTES, EVENT_LEVEL_EXO = 40 MINUTES) + var/static/list/event_delay_lower = list( + EVENT_LEVEL_MUNDANE = 10 MINUTES, + EVENT_LEVEL_MODERATE = 30 MINUTES, + EVENT_LEVEL_MAJOR = 50 MINUTES, + EVENT_LEVEL_CATASTROPHE = 100 MINUTES, + EVENT_LEVEL_EXO = 40 MINUTES, + ) /// The upper delay until next event - var/static/list/event_delay_upper = list(EVENT_LEVEL_MUNDANE = 15 MINUTES, EVENT_LEVEL_MODERATE = 45 MINUTES, EVENT_LEVEL_MAJOR = 70 MINUTES, EVENT_LEVEL_EXO = 60 MINUTES) + var/static/list/event_delay_upper = list( + EVENT_LEVEL_MUNDANE = 15 MINUTES, + EVENT_LEVEL_MODERATE = 45 MINUTES, + EVENT_LEVEL_MAJOR = 70 MINUTES, + EVENT_LEVEL_CATASTROPHE = 120 MINUTES, + EVENT_LEVEL_EXO = 60 MINUTES, + ) var/abandon_allowed = 1 var/ooc_allowed = 1 @@ -665,17 +683,29 @@ var/list/gamemode_cache = list() var/values = text2numlist(value, ";") config.event_first_run[EVENT_LEVEL_MAJOR] = list("lower" = MinutesToTicks(values[1]), "upper" = MinutesToTicks(values[2])) + if("event_custom_start_catastrophe") + var/values = text2numlist(value, ";") + config.event_first_run[EVENT_LEVEL_CATASTROPHE] = list("lower" = MinutesToTicks(values[1]), "upper" = MinutesToTicks(values[2])) + + if("event_custom_start_exo") + var/values = text2numlist(value, ";") + config.event_first_run[EVENT_LEVEL_EXO] = list("lower" = MinutesToTicks(values[1]), "upper" = MinutesToTicks(values[2])) + if("event_delay_lower") var/values = text2numlist(value, ";") config.event_delay_lower[EVENT_LEVEL_MUNDANE] = MinutesToTicks(values[1]) config.event_delay_lower[EVENT_LEVEL_MODERATE] = MinutesToTicks(values[2]) config.event_delay_lower[EVENT_LEVEL_MAJOR] = MinutesToTicks(values[3]) + config.event_delay_lower[EVENT_LEVEL_CATASTROPHE] = MinutesToTicks(values[4]) + config.event_delay_lower[EVENT_LEVEL_EXO] = MinutesToTicks(values[5]) if("event_delay_upper") var/values = text2numlist(value, ";") config.event_delay_upper[EVENT_LEVEL_MUNDANE] = MinutesToTicks(values[1]) config.event_delay_upper[EVENT_LEVEL_MODERATE] = MinutesToTicks(values[2]) config.event_delay_upper[EVENT_LEVEL_MAJOR] = MinutesToTicks(values[3]) + config.event_delay_lower[EVENT_LEVEL_CATASTROPHE] = MinutesToTicks(values[4]) + config.event_delay_lower[EVENT_LEVEL_EXO] = MinutesToTicks(values[5]) if("starlight") value = text2num(value) diff --git a/code/controllers/subsystems/event.dm b/code/controllers/subsystems/event.dm index 7c4f5b8ff08..f4e6fee5f7e 100644 --- a/code/controllers/subsystems/event.dm +++ b/code/controllers/subsystems/event.dm @@ -33,7 +33,8 @@ SUBSYSTEM_DEF(event) EVENT_LEVEL_MUNDANE = new/datum/event_container/mundane, EVENT_LEVEL_MODERATE = new/datum/event_container/moderate, EVENT_LEVEL_MAJOR = new/datum/event_container/major, - EVENT_LEVEL_EXO = new/datum/event_container/exo + EVENT_LEVEL_CATASTROPHE = new/datum/event_container/catastrophe, + EVENT_LEVEL_EXO = new/datum/event_container/exo, ) if(GLOB.using_map.use_overmap) diff --git a/code/game/objects/effects/temporary.dm b/code/game/objects/effects/temporary.dm index 38a8169d4b9..020c568d7a8 100644 --- a/code/game/objects/effects/temporary.dm +++ b/code/game/objects/effects/temporary.dm @@ -137,3 +137,21 @@ animate(src, transform = M1, alpha = 192, time = 0.5 SECONDS) animate(time = 2 SECONDS) animate(transform = M2, alpha = 0, time = 0.5 SECONDS) + +/obj/effect/temp_visual/bite + name = "bite" + icon_state = "bite" + icon = 'icons/effects/effects.dmi' + opacity = FALSE + anchored = TRUE + mouse_opacity = FALSE + layer = ABOVE_HUMAN_LAYER + + duration = 1 SECONDS + +/obj/effect/temp_visual/bite/Initialize() + . = ..() + addtimer(CALLBACK(src, .proc/FadeOut), (duration * 0.8)) + +/obj/effect/temp_visual/bite/proc/FadeOut() + animate(src, alpha = 0, (duration * 0.2)) diff --git a/code/modules/events/event_container.dm b/code/modules/events/event_container.dm index b0be3bcdaf0..5898f0bdfc9 100644 --- a/code/modules/events/event_container.dm +++ b/code/modules/events/event_container.dm @@ -9,7 +9,7 @@ #define ASSIGNMENT_EXPLORATION "Exploration" #define ASSIGNMENT_SECURITY "Security" -var/global/list/severity_to_string = list(EVENT_LEVEL_MUNDANE = "Mundane", EVENT_LEVEL_MODERATE = "Moderate", EVENT_LEVEL_MAJOR = "Major", EVENT_LEVEL_EXO = "Exoplanet") +var/global/list/severity_to_string = list(EVENT_LEVEL_MUNDANE = "Mundane", EVENT_LEVEL_MODERATE = "Moderate", EVENT_LEVEL_MAJOR = "Major", EVENT_LEVEL_CATASTROPHE = "Catastrophe", EVENT_LEVEL_EXO = "Exoplanet") /datum/event_container var/severity = -1 @@ -196,10 +196,17 @@ var/global/list/severity_to_string = list(EVENT_LEVEL_MUNDANE = "Mundane", EVENT new /datum/event_meta(EVENT_LEVEL_MAJOR, "Bluespace Drive Instability", /datum/event/bsd_instability, 0,list(ASSIGNMENT_ENGINEER = 10, ASSIGNMENT_ANY = 5), 1), ) +/datum/event_container/catastrophe + severity = EVENT_LEVEL_CATASTROPHE + available_events = list( + new /datum/event_meta(EVENT_LEVEL_CATASTROPHE, "Nothing", /datum/event/nothing, 100), + new /datum/event_meta(EVENT_LEVEL_CATASTROPHE, "Leviathan", /datum/event/leviathan_spawn, 0, list(ASSIGNMENT_COMMAND = 5, ASSIGNMENT_EXPLORATION = 5, ASSIGNMENT_SECURITY = 5, ASSIGNMENT_MEDICAL = 5, ASSIGNMENT_ENGINEER = 5)) + ) + /datum/event_container/exo severity = EVENT_LEVEL_EXO available_events = list( - new /datum/event_meta(EVENT_LEVEL_EXO, "Nothing", /datum/event/nothing, 100), + new /datum/event_meta(EVENT_LEVEL_EXO, "Nothing", /datum/event/nothing, 100), new /datum/event_meta(EVENT_LEVEL_EXO, "Exoplanet Awakening", /datum/event/exo_awakening, 0, list(ASSIGNMENT_ANY = 2, ASSIGNMENT_EXPLORATION = 5)) ) diff --git a/code/modules/events/infestation_hive_space.dm b/code/modules/events/infestation_hive_space.dm new file mode 100644 index 00000000000..230a125ed78 --- /dev/null +++ b/code/modules/events/infestation_hive_space.dm @@ -0,0 +1,40 @@ +// Nothing more than a far more lazy copy of carp migration +// Used exclusively by infestation hive overmap obstacle +/datum/event/infestation_hive_space + var/mobs_per_tick = 1 + /// Types of mobs that we can spawn in and throw at the ship, mostly flying types + var/list/available_mob_types = list(/mob/living/simple_animal/hostile/infestation/floatfly) + +/datum/event/infestation_hive_space/announce() + var/announcement = "" + if(severity > EVENT_LEVEL_MODERATE) + announcement = "A massive migration of unknown biological entities has been detected in the vicinity of the [location_name()]. Exercise external operations with caution." + else + announcement = "A large migration of unknown biological entities has been detected in the vicinity of the [location_name()]. Caution is advised." + + command_announcement.Announce(announcement, "[location_name()] Sensor Array", zlevels = affecting_z) + +/datum/event/infestation_hive_space/tick() + SpawnMobs() + +/datum/event/infestation_hive_space/proc/SpawnMobs(dir, speed) + if(!living_observers_present(affecting_z)) + return + + var/Z = pick(affecting_z) + if(!dir) + dir = pick(GLOB.cardinal) + + if(!speed) + speed = rand(1, 6) + + for(var/i = 1 to mobs_per_tick) + var/turf/T = get_random_edge_turf(dir,TRANSITIONEDGE + 2, Z) + if(istype(T, /turf/space)) + var/mob/living/simple_animal/hostile/M = pick(available_mob_types) + M = new M(T) + M.throw_at(get_random_edge_turf(GLOB.reverse_dir[dir], TRANSITIONEDGE + 2, Z), 250, speed, callback = CALLBACK(src, /datum/event/infestation_hive_space/proc/check_gib,M)) + +/datum/event/infestation_hive_space/proc/check_gib(mob/living/L) + if(L.health <= 15) + L.gib() diff --git a/code/modules/events/leviathan_attack.dm b/code/modules/events/leviathan_attack.dm new file mode 100644 index 00000000000..19bcc86ff10 --- /dev/null +++ b/code/modules/events/leviathan_attack.dm @@ -0,0 +1,99 @@ +// Sends you to fucking hell for daring to enter this sector, or for being too slow +// Bombards the ship with beams that may damage the ship and cause breaches +// Additionally, spawns hearts of the hive directly on the ship +/datum/event/leviathan_attack + has_skybox_image = TRUE + var/beams_per_tick = 1 + // Checks against world.time, not event ticks + var/infestation_spawn_cooldown + var/infestation_spawn_cooldown_time = 60 SECONDS + /// How many random mobs are spawned around heart of the hive on activation + var/infestation_spawn_count = 5 + /// Types of mobs = chance the infestation spawn will create; It utilizes pickweight + var/list/infestation_spawn_types = list( + /mob/living/simple_animal/hostile/infestation/larva = 30, + /mob/living/simple_animal/hostile/infestation/broodling = 15, + /mob/living/simple_animal/hostile/infestation/floatfly = 10, + /mob/living/simple_animal/hostile/infestation/eviscerator = 6, + /mob/living/simple_animal/hostile/infestation/spitter = 5, + /mob/living/simple_animal/hostile/infestation/assembler = 3, + /mob/living/simple_animal/hostile/infestation/rhino = 1, + /mob/living/simple_animal/hostile/infestation/larva/implant/implanter = 1, + ) + +/datum/event/leviathan_attack/get_skybox_image() + var/image/res = overlay_image('icons/skybox/ionbox.dmi', "ions", COLOR_MAROON, RESET_COLOR) + res.blend_mode = BLEND_ADD + return res + +/datum/event/leviathan_attack/announce() + command_announcement.Announce( + "DANGER! [location_name()] is currently in close vicinity to Leviathan-class abomination!\n\ + It is advised to immediately leave the area before damage to the ship reaches critical level!", + "[GLOB.using_map.company_name] Infestation Alert", + zlevels = affecting_z, + ) + +/datum/event/leviathan_attack/tick() + BeamAttack() + if(world.time >= infestation_spawn_cooldown) + InfestationSpawn() + +/datum/event/leviathan_attack/proc/BeamAttack() + if(!living_observers_present(affecting_z)) + return + + var/Z = pick(affecting_z) + var/dir = pick(GLOB.cardinal) + + for(var/i = 1 to beams_per_tick) + var/turf/T = get_random_edge_turf(dir, TRANSITIONEDGE + 2, Z) + if(istype(T, /turf/space)) + var/obj/item/projectile/beam/gigabeam/leviathan/P = new(T) + P.dispersion += pick(0, 0.1, 0.2) + P.launch(get_random_edge_turf(GLOB.reverse_dir[dir], TRANSITIONEDGE + 2, Z), pick(BP_ALL_LIMBS), T) + +/datum/event/leviathan_attack/proc/InfestationSpawn(turf/T) + infestation_spawn_cooldown = world.time + infestation_spawn_cooldown_time + if(!living_observers_present(affecting_z)) + return + + if(!istype(T)) + T = pick_area_turf_in_single_z_level(list(/proc/is_not_space_area), list(/proc/not_turf_contains_dense_objects, /proc/is_not_open_space, /proc/is_not_space_turf), pick(affecting_z)) + + new /obj/effect/hive_heart(T) + new /datum/bubble_effect/infestation(T.x, T.y, T.z, 1, 1) + var/list/valid_spawns = list() + for(var/turf/TT in oview(T, 3)) + if(is_space_turf(TT)) + continue + if(is_open_space(TT)) + continue + valid_spawns += TT + for(var/i = 1 to infestation_spawn_count) + var/mob/living/L = pickweight(infestation_spawn_types) + var/turf/TT = pick(valid_spawns) + new L(TT) + +/datum/bubble_effect/infestation/New() + ..() + START_PROCESSING(SSfastprocess, src) + +/datum/bubble_effect/infestation/Destroy() + STOP_PROCESSING(SSfastprocess, src) + return ..() + +/datum/bubble_effect/infestation/Process() + if(radius > 7) + qdel(src) + return PROCESS_KILL + Tick() + +/datum/bubble_effect/infestation/TurfEffect(turf/T) + if(TICK_CHECK) + return TRUE + if(T.density || is_open_space(T) || is_space_turf(T)) + return + if(prob(33)) + return // Continue + T.ChangeTurf(/turf/simulated/floor/exoplanet/flesh) diff --git a/code/modules/events/leviathan_spawn.dm b/code/modules/events/leviathan_spawn.dm new file mode 100644 index 00000000000..0b3422a7dbc --- /dev/null +++ b/code/modules/events/leviathan_spawn.dm @@ -0,0 +1,27 @@ +// This event spawns a "Leaviathan" - giant abomination overmap "obstacle"; +// As long as it is alive - the sector will be constantly flooded with infestation hive overmap obstacles, +// which operate similar to carp schools. +// When ship approaches Leviathan itself - it will be bombarded with abominations and flesh meteors. +// It takes 8 OFD charges to kill it. +/datum/event/leviathan_spawn + announceWhen = 5 + endWhen = 6 + var/spawned_loc = "UNKNOWN" + +/datum/event/leviathan_spawn/announce() + command_announcement.Announce( + "Leviathan class abomination has been spotted within the sector. All civilian personnel is advised to evacuate \ + to the closest safe area immediatelly. \nMilitary responders must contain the threat until it is too late.\n\n\ + Last known coordinates of the creature: [spawned_loc]", + "[GLOB.using_map.company_name] Infestation Alert", + 'sound/effects/alarm_catastrophe.ogg', + zlevels = affecting_z, + ) + +/datum/event/leviathan_spawn/start() + var/list/candidate_turfs = block(locate(OVERMAP_EDGE, OVERMAP_EDGE, GLOB.using_map.overmap_z), locate(GLOB.using_map.overmap_size - OVERMAP_EDGE, GLOB.using_map.overmap_size - OVERMAP_EDGE, GLOB.using_map.overmap_z)) + candidate_turfs = where(candidate_turfs, /proc/can_not_locate, /obj/effect/overmap/visitable) + var/turf/T = pick(candidate_turfs) + new /obj/effect/temp_visual/ftl(T) + new /obj/effect/overmap/event/leviathan(T) + spawned_loc = "[T.x]:[T.y]" diff --git a/code/modules/mob/living/simple_animal/hostile/infestation/_infestation.dm b/code/modules/mob/living/simple_animal/hostile/infestation/_infestation.dm index 51f7f92355f..4cc481746ab 100644 --- a/code/modules/mob/living/simple_animal/hostile/infestation/_infestation.dm +++ b/code/modules/mob/living/simple_animal/hostile/infestation/_infestation.dm @@ -61,6 +61,11 @@ QDEL_IN(src, (5 SECONDS)) return ..() +// While they are "resistant" to high temperatures, they are specifically weak to fire +/mob/living/simple_animal/hostile/infestation/fire_burn_temperature() + . = ..() + . *= 3 + /mob/living/simple_animal/hostile/infestation/proc/BecomeEgg() name = "egg" desc = "A weird egg..?" diff --git a/code/modules/mob/living/simple_animal/hostile/infestation/aggregate.dm b/code/modules/mob/living/simple_animal/hostile/infestation/aggregate.dm new file mode 100644 index 00000000000..a6b0281f815 --- /dev/null +++ b/code/modules/mob/living/simple_animal/hostile/infestation/aggregate.dm @@ -0,0 +1,91 @@ +/mob/living/simple_animal/hostile/infestation/aggregate + name = "aggregate" + desc = "A repulsive mass of flesh that is constantly regenerating itself." + icon = 'icons/mob/simple_animal/abominable_infestation/48x48.dmi' + icon_state = "aggregate" + icon_living = "aggregate" + icon_dead = "aggregate_dead" + mob_size = MOB_LARGE + movement_cooldown = 7 + + // A giant fuck-off bite attack; Don't come close to this thing + natural_weapon = /obj/item/natural_weapon/bite/aggregate + + health = 1200 + maxHealth = 1200 + + meat_type = /obj/item/reagent_containers/food/snacks/abominationmeat + meat_amount = 15 + skin_material = MATERIAL_SKIN_CHITIN + skin_amount = 10 + bone_material = MATERIAL_BONE_CARTILAGE + bone_amount = 2 + + death_sounds = list('sound/simple_mob/abominable_infestation/aggregate/death.ogg') + + /// Percent of max HP restored every Life() tick + var/regeneration_speed = 0.005 + /// How much health should we have before throwing a new meatchip + var/spawn_health = 0 + /// How much percents of max HP is reduced from damage_to_spawn on each new meatchip spawn + var/spawn_health_reduction = 0.03 + +/obj/item/natural_weapon/bite/aggregate + name = "malformed teeth" + attack_verb = list("bitten with many of its deformed teeth") + hitsound = 'sound/simple_mob/abominable_infestation/aggregate/attack.ogg' + force = 50 // Yes. Fifty. Do NOT fight it in melee. + armor_penetration = 40 + +// Show the foolish mortals just how deadly this attack was! +/obj/item/natural_weapon/bite/aggregate/apply_hit_effect(mob/living/target, mob/living/user, hit_zone) + . = ..() + for(var/i = 1 to 3) + addtimer(CALLBACK(src, .proc/SpawnBiteEffect, target), i-1) + +/obj/item/natural_weapon/bite/aggregate/proc/SpawnBiteEffect(mob/living/target) + if(QDELETED(target)) + return + var/obj/effect/temp_visual/bite/B = new (get_turf(target)) + B.pixel_x = rand(-16, 16) + B.pixel_y = rand(-16, 16) + +/mob/living/simple_animal/hostile/infestation/aggregate/Initialize() + . = ..() + spawn_health = round(maxHealth * (1 - spawn_health_reduction)) + +/mob/living/simple_animal/hostile/infestation/aggregate/Life() + . = ..() + if(!.) + return + if(health <= maxHealth) + adjustBruteLoss(-round(maxHealth * regeneration_speed)) + +/mob/living/simple_animal/hostile/infestation/aggregate/updatehealth() + . = ..() + if(stat == DEAD || health <= 0) + return + if(health > spawn_health) + return + addtimer(CALLBACK(src, .proc/SpawnMeatChip), rand(1, 4)) + +/mob/living/simple_animal/hostile/infestation/aggregate/proc/SpawnMeatChip() + if(stat == DEAD || health <= 0) + return + spawn_health -= round(maxHealth * spawn_health_reduction) + var/list/potential_targets = list() + for(var/mob/living/L in view(7, src)) + if(L.stat) + continue + if(L.faction == faction) + continue + potential_targets += L + var/atom/throw_target = null + if(LAZYLEN(potential_targets)) + throw_target = pick(potential_targets) + visible_message(SPAN_WARNING("A tiny creature flies off the [src]!")) + playsound(src, 'sound/simple_mob/abominable_infestation/aggregate/spawn_meatchip.ogg', 50, TRUE) + var/mob/living/simple_animal/hostile/infestation/meatchip/M = new(get_turf(src)) + if(!throw_target) + throw_target = pick(getcircle(get_turf(src), 3)) + M.throw_at(get_turf(throw_target), 3, 6) diff --git a/code/modules/mob/living/simple_animal/hostile/infestation/larva.dm b/code/modules/mob/living/simple_animal/hostile/infestation/larva.dm index 6c007be43fe..2afacc58909 100644 --- a/code/modules/mob/living/simple_animal/hostile/infestation/larva.dm +++ b/code/modules/mob/living/simple_animal/hostile/infestation/larva.dm @@ -15,8 +15,8 @@ pass_flags = PASS_FLAG_TABLE | PASS_FLAG_GRILLE movement_cooldown = 1.5 - health = 20 - maxHealth = 20 + health = 10 + maxHealth = 10 meat_type = /obj/item/reagent_containers/food/snacks/abominationmeat meat_amount = 1 diff --git a/code/modules/mob/living/simple_animal/hostile/infestation/meatchip.dm b/code/modules/mob/living/simple_animal/hostile/infestation/meatchip.dm new file mode 100644 index 00000000000..6a97ae2e786 --- /dev/null +++ b/code/modules/mob/living/simple_animal/hostile/infestation/meatchip.dm @@ -0,0 +1,30 @@ +// The tiny and fast annoying enemies spawned by aggregate +/mob/living/simple_animal/hostile/infestation/meatchip + name = "meatchip" + desc = "A tiny, digusting creature." + icon_state = "meatchip" + icon_living = "meatchip" + icon_dead = "meatchip_dead" + mob_size = MOB_TINY + movement_cooldown = 1.2 + + natural_weapon = /obj/item/natural_weapon/claws/meatchip + melee_attack_delay = 0 + + health = 30 + maxHealth = 30 + + meat_type = /obj/item/reagent_containers/food/snacks/abominationmeat + meat_amount = 1 + skin_material = MATERIAL_SKIN_CHITIN + skin_amount = 1 + bone_material = MATERIAL_BONE_CARTILAGE + bone_amount = 1 + + death_sounds = list('sound/simple_mob/abominable_infestation/meatchip/death.ogg') + +/obj/item/natural_weapon/claws/meatchip + force = 5 + armor_penetration = 15 + hitsound = 'sound/weapons/slashmiss.ogg' + attack_cooldown = 1 diff --git a/code/modules/mob/living/simple_animal/hostile/infestation/structures/hive_heart.dm b/code/modules/mob/living/simple_animal/hostile/infestation/structures/hive_heart.dm index 18ad025f990..8312d6b1544 100644 --- a/code/modules/mob/living/simple_animal/hostile/infestation/structures/hive_heart.dm +++ b/code/modules/mob/living/simple_animal/hostile/infestation/structures/hive_heart.dm @@ -38,7 +38,7 @@ /* Mob healing effect */ var/healing_mobs_range = 7 // Just on the screen /// How much health is restored every HealMobs() proc - var/healing_mobs_strength = 20 + var/healing_mobs_strength = 50 var/healing_mobs_cooldown = 5 SECONDS var/healing_mobs_faction = "abominable_infestation" diff --git a/code/modules/overmap/disperser/disperser_fire.dm b/code/modules/overmap/disperser/disperser_fire.dm index dc159a50a2c..2f78f4a83ad 100644 --- a/code/modules/overmap/disperser/disperser_fire.dm +++ b/code/modules/overmap/disperser/disperser_fire.dm @@ -121,10 +121,7 @@ return TRUE /obj/machinery/computer/ship/disperser/proc/fire_at_event(obj/effect/overmap/event/finaltarget, chargetype) - if(chargetype & finaltarget.weaknesses) - var/turf/T = finaltarget.loc - qdel(finaltarget) - overmap_event_handler.update_hazards(T) + finaltarget.FiredAt(src, chargetype) /obj/machinery/computer/ship/disperser/proc/fire_at_sector(obj/effect/overmap/visitable/finaltarget, obj/structure/ship_munition/disperser_charge/charge, chargetype) var/list/targetareas = finaltarget.get_areas() diff --git a/code/modules/overmap/events/event.dm b/code/modules/overmap/events/event.dm index b7d458bb481..d7324ce3327 100644 --- a/code/modules/overmap/events/event.dm +++ b/code/modules/overmap/events/event.dm @@ -198,6 +198,13 @@ . = ..() overmap_event_handler.update_hazards(T) +/// Called from disperser_fire.dm whenever a charge is fired at this event +/obj/effect/overmap/event/proc/FiredAt(obj/machinery/computer/ship/disperser/source, chargetype) + if(chargetype & weaknesses) + var/turf/T = get_turf(src) + qdel(src) + overmap_event_handler.update_hazards(T) + /obj/effect/overmap/event/meteor name = "asteroid field" events = list(/datum/event/meteor_wave/overmap) @@ -263,6 +270,171 @@ opacity = 0 color = "#aaaaaa" +// Spawned by catastrophe level event. +// Takes 8 hits to kill with OFD, constantly spawns infestation hives in the sector as long as it is alive +// TODO: Make it fly around the space, similar to ships +/obj/effect/overmap/event/leviathan + name = "leviathan" + dir = EAST + weaknesses = OVERMAP_WEAKNESS_EXPLOSIVE | OVERMAP_WEAKNESS_FIRE + events = list(/datum/event/leviathan_attack) + event_icon_states = list("leviathan1") + opacity = 0 + color = COLOR_MAROON + /// When reaches 0 - finally dies + var/health = 8 + /// How often it spawns infestation hives + var/hive_cooldown_time = 100 SECONDS + var/hive_cooldown + /// How many hives are spawned per activation + var/hive_spawn_count = 2 + /// Wail sounds a sound to all nearby visitable places; Affected mobs get some negative effects as a result + var/wail_cooldown_time_lower = 40 SECONDS + var/wail_cooldown_time_upper = 100 SECONDS + var/wail_cooldown + var/list/wail_sounds = list( + 'sound/simple_mob/abominable_infestation/leviathan/wail1.ogg', + 'sound/simple_mob/abominable_infestation/leviathan/wail1-long.ogg', + 'sound/simple_mob/abominable_infestation/leviathan/wail2.ogg', + ) + var/death_sound = 'sound/simple_mob/abominable_infestation/leviathan/death.ogg' + +/obj/effect/overmap/event/leviathan/Initialize() + . = ..() + hive_cooldown = world.time + hive_cooldown_time + wail_cooldown = world.time + wail_cooldown_time_lower + START_PROCESSING(SSobj, src) + // You have a total of 25 minutes to kill it, before it completely overruns the sector + addtimer(CALLBACK(src, .proc/WarnApocalypse), 20 MINUTES) + +/obj/effect/overmap/event/leviathan/Process() + if(world.time >= hive_cooldown) + INVOKE_ASYNC(src, .proc/SpawnHives) + if(world.time > wail_cooldown) + INVOKE_ASYNC(src, .proc/Wail) + +/obj/effect/overmap/event/leviathan/Destroy() + STOP_PROCESSING(SSobj, src) + var/list/affected_z = list() + for(var/obj/effect/overmap/visitable/V in range(3, src)) + affected_z |= V.map_z + for(var/mob/M in GLOB.player_list) + if(isnewplayer(M)) + continue + if(!(M.z in affected_z)) + continue + M.playsound_local(get_turf(M), death_sound, 100, FALSE) + to_chat(M, SPAN_DANGER("A terrible scream echoes through the space. The Leviathan has been finally defeated...")) + return ..() + +/obj/effect/overmap/event/leviathan/FiredAt(obj/machinery/computer/ship/disperser/source, chargetype) + if(chargetype & weaknesses) + health -= 1 + if(health <= 0) + return ..() + +/obj/effect/overmap/event/leviathan/proc/SpawnHives(amount = hive_spawn_count) + hive_cooldown = world.time + hive_cooldown_time + var/list/candidate_turfs = block(locate(OVERMAP_EDGE, OVERMAP_EDGE, GLOB.using_map.overmap_z), locate(GLOB.using_map.overmap_size - OVERMAP_EDGE, GLOB.using_map.overmap_size - OVERMAP_EDGE, GLOB.using_map.overmap_z)) + candidate_turfs = where(candidate_turfs, /proc/can_not_locate, /obj/effect/overmap) + for(var/i = 1 to amount) + if(!LAZYLEN(candidate_turfs)) + break + var/turf/T = pick(candidate_turfs) + new /obj/effect/overmap/event/infestation_hive(T) + candidate_turfs -= T + +/obj/effect/overmap/event/leviathan/proc/Wail() + wail_cooldown = world.time + rand(wail_cooldown_time_lower, wail_cooldown_time_upper) + var/list/affected_z = list() + for(var/obj/effect/overmap/visitable/V in range(3, src)) + affected_z |= V.map_z + if(!LAZYLEN(affected_z)) + return + var/wail_sound = pick(wail_sounds) + var/sound_vol = rand(75, 150) + for(var/mob/M in GLOB.player_list) + if(isnewplayer(M)) + continue + if(!(M.z in affected_z)) + continue + M.playsound_local(get_turf(M), wail_sound, sound_vol, TRUE) + to_chat(M, SPAN_WARNING("A terrifying creatures is wailing somewhere far from you, yet it sends chills down your spine...")) + if(isliving(M)) + var/mob/living/L = M + flash_color(L, flash_color = COLOR_MAROON, flash_time = 100) + // Evil effects >:) + if(ishuman(L) && prob(50)) + var/mob/living/carbon/human/H = L + H.confused = max(H.confused + 5, H.confused) + var/obj/item/organ/external/my_head = H.get_organ(BP_HEAD) + H.custom_pain(SPAN_DANGER("This terrible wail makes your head hurt a lot!"), 25, affecting = my_head) + +/obj/effect/overmap/event/leviathan/proc/WarnApocalypse() + if(QDELETED(src)) + return + + var/list/affected_z = list() + for(var/i in map_sectors) + affected_z |= text2num(i) + command_announcement.Announce( + "The structure of space is being manipulated by the Leviathan entity. \n\ + All nearby vessels have approximately 5 minutes before it is too late.", + "[GLOB.using_map.company_name] Infestation Alert", + 'sound/effects/alarm_catastrophe.ogg', + zlevels = affected_z, + ) + + addtimer(CALLBACK(src, .proc/StartApocalypse), 5 MINUTES) + +/obj/effect/overmap/event/leviathan/proc/StartApocalypse() + if(QDELETED(src)) + return + + var/list/affected_z = list() + for(var/i in map_sectors) + affected_z |= text2num(i) + command_announcement.Announce( + "The hyperspace fluctuations have entered their final phase. All vessels are recommended to evacuate via bluespace teleportation. \n\ + It is now too late.", + "[GLOB.using_map.company_name] Infestation Alert", + 'sound/effects/alarm_catastrophe.ogg', + zlevels = affected_z, + ) + + // Tell players that they are, in fact, dead + for(var/mob/M in GLOB.player_list) + if(isnewplayer(M)) + continue + if(!(M.z in affected_z)) + continue + M.playsound_local(get_turf(M), 'sound/simple_mob/abominable_infestation/leviathan/apocalypse.ogg', 100, FALSE) + to_chat(M, SPAN_DANGER("A terrible noise disturbs the space, something bad has truly happened. It is all over.")) + flash_color(M, flash_color = COLOR_MAROON, flash_time = 200) + if(ishuman(M)) + var/mob/living/carbon/human/H = M + H.confused = max(M.confused + 30, M.confused) + var/obj/item/organ/external/my_head = H.get_organ(BP_HEAD) + H.custom_pain(SPAN_DANGER("MY HEAD HURTS! WHAT IS GOING ON!?"), 150, affecting = my_head) + + // UNRAVEL THE FUCKING REALITY!!! + SpawnHives(120) + var/list/overmap_turfs = block(locate(OVERMAP_EDGE, OVERMAP_EDGE, GLOB.using_map.overmap_z), locate(GLOB.using_map.overmap_size - OVERMAP_EDGE, GLOB.using_map.overmap_size - OVERMAP_EDGE, GLOB.using_map.overmap_z)) + for(var/turf/unsimulated/map/T in shuffle(overmap_turfs)) + if(QDELETED(T)) + continue + T.icon_state = "hell01" + sleep(1) + +/obj/effect/overmap/event/infestation_hive + name = "infestation hive" + events = list(/datum/event/infestation_hive_space) + opacity = 0 + difficulty = EVENT_LEVEL_MODERATE + event_icon_states = list("hive1", "hive2") + weaknesses = OVERMAP_WEAKNESS_EXPLOSIVE | OVERMAP_WEAKNESS_FIRE + color = COLOR_MAROON + //These now are basically only used to spawn hazards. Will be useful when we need to spawn group of moving hazards /datum/overmap_event var/name = "map event" diff --git a/code/modules/projectiles/projectile/beams.dm b/code/modules/projectiles/projectile/beams.dm index 4e84ac8e1c2..3b5a58a82a3 100644 --- a/code/modules/projectiles/projectile/beams.dm +++ b/code/modules/projectiles/projectile/beams.dm @@ -463,10 +463,28 @@ tracer_type = /obj/effect/projectile/tracer/emitter impact_type = /obj/effect/projectile/impact/emitter + var/explosion_high = 1 + var/explosion_medium = 3 + var/explosion_low = 5 + /obj/item/projectile/beam/gigabeam/on_impact(atom/target) if(QDELETED(src)) return var/turf/T = get_turf(target) if(T) - explosion(T, 1, 3, 5) + explosion(T, explosion_high, explosion_medium, explosion_low, adminlog = ismob(firer)) return ..() + +// Used by leviathan attack event +/obj/item/projectile/beam/gigabeam/leviathan + name = "concentrated energy beam" + icon_state = "hcult" + damage = 25 + agony = 25 + life_span = 100 + + muzzle_type = /obj/effect/projectile/muzzle/cult/heavy + tracer_type = /obj/effect/projectile/tracer/cult/heavy + impact_type = /obj/effect/projectile/impact/cult/heavy + + explosion_low = 7 diff --git a/config/example/config.txt b/config/example/config.txt index 44a5d40aeab..c0a60230d74 100644 --- a/config/example/config.txt +++ b/config/example/config.txt @@ -288,17 +288,19 @@ EXPECTED_ROUND_LENGTH 180 ## The lower delay between events in minutes. ## Affect mundane, moderate, and major events respectively -EVENT_DELAY_LOWER 10;30;50 +EVENT_DELAY_LOWER 10;30;50;100;30 ## The upper delay between events in minutes. ## Affect mundane, moderate, and major events respectively -EVENT_DELAY_UPPER 15;45;70 +EVENT_DELAY_UPPER 15;45;70;120;40 ## The delay until the first time an event of the given severity runs in minutes. ## Unset setting use the EVENT_DELAY_LOWER and EVENT_DELAY_UPPER values instead. # EVENT_CUSTOM_START_MINOR 10;15 # EVENT_CUSTOM_START_MODERATE 30;45 EVENT_CUSTOM_START_MAJOR 80;100 +#EVENT_CUSTOM_START_CATASTROPHE 120;160 +#EVENT_CUSTOM_START_EXO 20;30 ## Uncomment to make proccall require R_ADMIN instead of R_DEBUG ## designed for environments where you have testers but don't want them diff --git a/icons/effects/effects.dmi b/icons/effects/effects.dmi index 80e81fd38d0..7fadf8836b7 100644 Binary files a/icons/effects/effects.dmi and b/icons/effects/effects.dmi differ diff --git a/icons/mob/simple_animal/abominable_infestation/32x32.dmi b/icons/mob/simple_animal/abominable_infestation/32x32.dmi index 6170a78f247..55a492853bd 100644 Binary files a/icons/mob/simple_animal/abominable_infestation/32x32.dmi and b/icons/mob/simple_animal/abominable_infestation/32x32.dmi differ diff --git a/icons/mob/simple_animal/abominable_infestation/48x48.dmi b/icons/mob/simple_animal/abominable_infestation/48x48.dmi index d73c654e343..c5b61ead976 100644 Binary files a/icons/mob/simple_animal/abominable_infestation/48x48.dmi and b/icons/mob/simple_animal/abominable_infestation/48x48.dmi differ diff --git a/icons/obj/overmap.dmi b/icons/obj/overmap.dmi index 985d0a71133..012f4c8e456 100644 Binary files a/icons/obj/overmap.dmi and b/icons/obj/overmap.dmi differ diff --git a/sound/effects/alarm_catastrophe.ogg b/sound/effects/alarm_catastrophe.ogg new file mode 100644 index 00000000000..99a2ce5261b Binary files /dev/null and b/sound/effects/alarm_catastrophe.ogg differ diff --git a/sound/simple_mob/abominable_infestation/aggregate/attack.ogg b/sound/simple_mob/abominable_infestation/aggregate/attack.ogg new file mode 100644 index 00000000000..655fe9cc924 Binary files /dev/null and b/sound/simple_mob/abominable_infestation/aggregate/attack.ogg differ diff --git a/sound/simple_mob/abominable_infestation/aggregate/death.ogg b/sound/simple_mob/abominable_infestation/aggregate/death.ogg new file mode 100644 index 00000000000..3b0ff3242b8 Binary files /dev/null and b/sound/simple_mob/abominable_infestation/aggregate/death.ogg differ diff --git a/sound/simple_mob/abominable_infestation/aggregate/spawn_meatchip.ogg b/sound/simple_mob/abominable_infestation/aggregate/spawn_meatchip.ogg new file mode 100644 index 00000000000..3f9477cecc3 Binary files /dev/null and b/sound/simple_mob/abominable_infestation/aggregate/spawn_meatchip.ogg differ diff --git a/sound/simple_mob/abominable_infestation/leviathan/apocalypse.ogg b/sound/simple_mob/abominable_infestation/leviathan/apocalypse.ogg new file mode 100644 index 00000000000..14569d3a9b4 Binary files /dev/null and b/sound/simple_mob/abominable_infestation/leviathan/apocalypse.ogg differ diff --git a/sound/simple_mob/abominable_infestation/leviathan/death.ogg b/sound/simple_mob/abominable_infestation/leviathan/death.ogg new file mode 100644 index 00000000000..8b18577402e Binary files /dev/null and b/sound/simple_mob/abominable_infestation/leviathan/death.ogg differ diff --git a/sound/simple_mob/abominable_infestation/leviathan/wail1-long.ogg b/sound/simple_mob/abominable_infestation/leviathan/wail1-long.ogg new file mode 100644 index 00000000000..73fdaae4d94 Binary files /dev/null and b/sound/simple_mob/abominable_infestation/leviathan/wail1-long.ogg differ diff --git a/sound/simple_mob/abominable_infestation/leviathan/wail1.ogg b/sound/simple_mob/abominable_infestation/leviathan/wail1.ogg new file mode 100644 index 00000000000..e0ee5f65e57 Binary files /dev/null and b/sound/simple_mob/abominable_infestation/leviathan/wail1.ogg differ diff --git a/sound/simple_mob/abominable_infestation/leviathan/wail2.ogg b/sound/simple_mob/abominable_infestation/leviathan/wail2.ogg new file mode 100644 index 00000000000..ede81fa928b Binary files /dev/null and b/sound/simple_mob/abominable_infestation/leviathan/wail2.ogg differ diff --git a/sound/simple_mob/abominable_infestation/meatchip/death.ogg b/sound/simple_mob/abominable_infestation/meatchip/death.ogg new file mode 100644 index 00000000000..3cf2b3aa12c Binary files /dev/null and b/sound/simple_mob/abominable_infestation/meatchip/death.ogg differ