diff --git a/code/modules/events/event_container.dm b/code/modules/events/event_container.dm index c14ae9884d250..b13a40496436f 100644 --- a/code/modules/events/event_container.dm +++ b/code/modules/events/event_container.dm @@ -184,6 +184,9 @@ var/global/list/severity_to_string = list(EVENT_LEVEL_MUNDANE = "Mundane", EVENT available_events = list( new /datum/event_meta(EVENT_LEVEL_MAJOR, "Nothing", /datum/event/nothing, 1320), new /datum/event_meta(EVENT_LEVEL_MAJOR, "Blob", /datum/event/blob, 0, list(ASSIGNMENT_ENGINEER = 40), 1), + // [SIERRA-ADD], + new /datum/event_meta(EVENT_LEVEL_MAJOR, "Blob", /datum/event/hivemind, 0, list(ASSIGNMENT_ENGINEER = 20,ASSIGNMENT_MEDICAL = 20,ASSIGNMENT_SECURITY = 20), 1), + // [/SIERRA-ADD], new /datum/event_meta/no_overmap(EVENT_LEVEL_MAJOR, "Carp Migration", /datum/event/mob_spawning/carp, 0, list(ASSIGNMENT_SECURITY = 5), 1), new /datum/event_meta/no_overmap(EVENT_LEVEL_MAJOR, "Meteor Wave", /datum/event/meteor_wave, 0, list(ASSIGNMENT_ENGINEER = 10), 1), new /datum/event_meta(EVENT_LEVEL_MAJOR, "Space Vines", /datum/event/spacevine, 0, list(ASSIGNMENT_ENGINEER = 15), 1), diff --git a/mods/global_modpacks.dm b/mods/global_modpacks.dm index 9b468cbb8f97a..54cb40c5057ca 100644 --- a/mods/global_modpacks.dm +++ b/mods/global_modpacks.dm @@ -26,6 +26,7 @@ #include "ntnet/_ntnet_includes.dm" #include "virusology/_virusology_includes.dm" #include "RnD/_RnD_includes.dm" +#include "hivemind/_hivemind_includes.dm" #include "nyc_posters/_nyc_posters_includes.dm" #include "pixelshift/_pixelshift_includes.dm" #include "ssinput/_ssinput_includes.dm" diff --git a/mods/hivemind/README.md b/mods/hivemind/README.md new file mode 100644 index 0000000000000..0f32408d2d170 --- /dev/null +++ b/mods/hivemind/README.md @@ -0,0 +1,79 @@ + +#### Список PRов: + +- https://github.com/SierraBay/SierraBay12/pull/##### + + + +## Мод-пример + +ID мода: EXAMPLE + + +### Описание мода + +Этот мод служит примером для разработчиков и существует лишь для того, +чтобы его можно было легко скопировать и вставить в другое место. + + +### Изменения *кор кода* + +- `code/modules/mob/living.dm`: `proc/overriden_proc`, `var/overriden_var` + + +### Оверрайды + +- `mods/_master_files/sound/my_cool_sound.ogg` +- `mods/_master_files/code/my_modular_override.dm`: `proc/overriden_proc`, `var/overriden_var` + + +### Дефайны + +- `code/__defines/~mods/example.dm`: `EXAMPLE_SPEED_MULTIPLIER`, `EXAMPLE_SPEED_BASE` + + +### Используемые файлы, не содержащиеся в модпаке + +- `mods/_master_files/icons/obj/alien.dmi` + + +### Авторы: + +Твой никнейм + diff --git a/mods/hivemind/_hivemind.dm b/mods/hivemind/_hivemind.dm new file mode 100644 index 0000000000000..4c347115e71ef --- /dev/null +++ b/mods/hivemind/_hivemind.dm @@ -0,0 +1,4 @@ +/singleton/modpack/example + name = "Мод-пример" + desc = "Мод, который является примером и ни в коем случае не должен быть использован." + author = "Твой никнейм" diff --git a/mods/hivemind/_hivemind_includes.dm b/mods/hivemind/_hivemind_includes.dm new file mode 100644 index 0000000000000..92696c13e5889 --- /dev/null +++ b/mods/hivemind/_hivemind_includes.dm @@ -0,0 +1,13 @@ +#ifndef MODPACK_HIVEMIND +#define MODPACK_HIVEMIND + +#include "code\core.dm" +#include "code\hivemind_invasion.dm" +#include "code\machines.dm" +#include "code\mobs.dm" +#include "code\objects.dm" +#include "code\wires.dm" +#include "code\overrides.dm" + + +#endif diff --git a/mods/hivemind/code/core.dm b/mods/hivemind/code/core.dm new file mode 100644 index 0000000000000..0207b0753eda9 --- /dev/null +++ b/mods/hivemind/code/core.dm @@ -0,0 +1,57 @@ +//The Hivemind is a rogue AI using nanites. +//The objective of this AI is to spread across the ship and destroy as much as possible. + + +var/datum/hivemind/hive_mind_ai + +/datum/hivemind + var/name + var/surname + var/evo_points = 0 + var/evo_points_max = 1000 + var/failure_chance = 55 //failure chance is lowers each 10 EP + var/list/hives = list() //all functional hives stored here + //i know, whitelist is bad, but it's required here + var/list/restricted_machineries = list( /obj/machinery/light, /obj/machinery/atmospherics, + /obj/machinery/portable_atmospherics, /obj/machinery/door, + /obj/machinery/camera, /obj/machinery/light_switch, + /obj/machinery/disposal, /obj/machinery/firealarm, + /obj/machinery/alarm, /obj/machinery/recharger, + /obj/machinery/hologram, /obj/machinery/hivemind_machine, + /obj/machinery/button, /obj/machinery/ai_status_display, + /obj/machinery/status_display, /obj/machinery/requests_console, + /obj/machinery/newscaster, /obj/machinery/floor_light, + /obj/machinery/nuclearbomb, /obj/machinery/flasher) + + //internals + var/list/global_abilities_cooldown = list() + var/list/EP_price_list = list() + +/datum/hivemind/New() + ..() + name = pick("Reclaimer", "Shaper", "Executor", "Assimilator", + "Exploiter", "Builder", "Creator", + "Connector", "Splicer", "Propagator") + + surname = pick("ALPHA", "BETA", "GAMMA", "DELTA", "OMEGA", "UTOPIA", + "SALVATION-X", "CHORUS", "ICARUS", "HEGEMONY", "HARMONY") + var/list/all_machines = subtypesof(/obj/machinery/hivemind_machine) - /obj/machinery/hivemind_machine/node + //price list building + //here we create list with EP price to compare it at annihilation proc + for(var/machine_path in all_machines) + var/obj/machinery/hivemind_machine/temporary_machine = new machine_path + EP_price_list[machine_path] = temporary_machine.evo_points_required + qdel(temporary_machine) + message_admins("Hivemind [name] [surname] has been created.") + + +/datum/hivemind/proc/die() + message_admins("Hivemind [name] [surname] is destroyed.") + hive_mind_ai = null + qdel(src) + +/datum/hivemind/proc/get_points() + if(evo_points < evo_points_max) + evo_points++ + if(failure_chance > 10 && (evo_points % 10 == 0)) + failure_chance -= 1 diff --git a/mods/hivemind/code/hivemind_invasion.dm b/mods/hivemind/code/hivemind_invasion.dm new file mode 100644 index 0000000000000..cffd7c37f3fee --- /dev/null +++ b/mods/hivemind/code/hivemind_invasion.dm @@ -0,0 +1,29 @@ +//Hivemind is rogue AI that uses unknown nanotech to follow some strange objective +//In fact, it's just hostile structures, wireweeds spreading event with some mobs +//Requires hard teamwork at late stages, but easily can be handled at the beginning + +//All code stored in modules/hivemind +//============================================ + +/datum/event/hivemind + announceWhen = 300 + + +/datum/event/hivemind/announce() + level_seven_announcement() + + +/datum/event/hivemind/start() + var/turf/start_location + for(var/i=1 to 100) + var/turf/T = pick_subarea_turf(/area/maintenance, list(GLOBAL_PROC_REF(is_station_turf), GLOBAL_PROC_REF(not_turf_contains_dense_objects))) + start_location = T + if(!start_location && i == 100) + log_and_message_admins("Hivemind failed to find a viable turf.") + kill() + return + if(start_location) + break + + log_and_message_admins("Hivemind spawned in \the [get_area(start_location)]", location = start_location) + new /obj/machinery/hivemind_machine/node(start_location) diff --git a/mods/hivemind/code/machines.dm b/mods/hivemind/code/machines.dm new file mode 100644 index 0000000000000..4a1b9da136561 --- /dev/null +++ b/mods/hivemind/code/machines.dm @@ -0,0 +1,541 @@ +//Hivemind various machines + +#define HIVE_FACTION "hive" +#define COLOR_LIGHTING_CYAN_MACHINERY "#50edd9" + + + +/obj/machinery/hivemind_machine + name = "strange machine" + icon = 'mods/hivemind/icons/hivemind_machines.dmi' + icon_state = "infected_machine" + density = TRUE + anchored = TRUE + use_power = FALSE + var/illumination_color = COLOR_LIGHTING_CYAN_MACHINERY + var/health = 60 + var/max_health = 60 + var/evo_points_required = 0 //how much EP hivemind must have to spawn this, used in price list to comparison + var/cooldown_time = 10 SECONDS //each machine have their ability, this is cooldown of them + var/global_cooldown = FALSE //if true, ability will be used only once in whole world, before cooldown reset + var/list/spawned_creatures = list() //which mobs machine can spawns, insert paths + //internal + var/cooldown = 0 //cooldown in world.time value + + +/obj/machinery/hivemind_machine/Initialize() + . = ..() + name_pick() + health = max_health + set_light(2, 3, illumination_color) + + +/obj/machinery/hivemind_machine/Process() + if(hive_mind_ai && !(stat & MACHINE_STAT_EMPED) && !is_on_cooldown()) + return TRUE + + +/obj/machinery/hivemind_machine/update_icon() + overlays.Cut() + if(stat & MACHINE_STAT_EMPED) + icon_state = "[icon_state]-disabled" + else + icon_state = initial(icon_state) + + +//sets cooldown +//must be set manually +/obj/machinery/hivemind_machine/proc/set_cooldown() + if(global_cooldown) + hive_mind_ai.global_abilities_cooldown[type] = world.time + cooldown_time + else + cooldown = world.time + cooldown_time + + +//check for cooldowns +/obj/machinery/hivemind_machine/proc/is_on_cooldown() + if(global_cooldown) + if(hive_mind_ai && hive_mind_ai.global_abilities_cooldown[type]) + if(world.time >= hive_mind_ai.global_abilities_cooldown[type]) + hive_mind_ai.global_abilities_cooldown[type] = null + return FALSE + else + return FALSE + + else + if(world.time >= cooldown) + return FALSE + + return TRUE + + +/obj/machinery/hivemind_machine/proc/use_ability(atom/target) + return + + +/obj/machinery/hivemind_machine/proc/name_pick() + if(hive_mind_ai) + if(prob(50)) + name = "[hive_mind_ai.name] [name] - [rand(999)]" + else + name = "[name] [hive_mind_ai.surname] - [rand(999)]" + + +//returns list of mobs in range or hearers (include in vehicles) +/obj/machinery/hivemind_machine/proc/targets_in_range(range = world.view, in_hear_range = FALSE) + var/list/range_list = list() + var/list/target_list = list() + if(in_hear_range) + range_list = hearers(range, src) + else + range_list = range(range, src) + for(var/atom/movable/M in range_list) + var/mob/target = M.get_mob() + if(target) + target_list += target + return target_list + +/////////////////////////] [////////////////////////// +/////////////////////////>RESPONSE CODE[pain_msg]\"") + else + var/pain_emote = pick("starts crying.", "mumbles something.", "blinks occasionally.") + state(pain_emote) + playsound(src, pick('mods/emote_panel/sound/robot_talk_heavy_1.ogg', + 'mods/emote_panel/sound/robot_talk_heavy_2.ogg', + 'mods/emote_panel/sound/robot_talk_heavy_3.ogg', + 'mods/emote_panel/sound/robot_talk_heavy_4.ogg'), 50, 1) + + if(prob(40)) + var/datum/effect/spark_spread/spark_system = new /datum/effect/spark_spread() + spark_system.set_up(5, 0, src.loc) + spark_system.start() + playsound(src.loc, 'sound/weapons/blade1.ogg', 50, 1) + + +/obj/machinery/hivemind_machine/proc/take_damage(amount) + health -= amount + damage_reaction() + if(health <= 0) + destruct() + + +/obj/machinery/hivemind_machine/proc/destruct() + playsound(src, 'mods/hivemind/sounds/insect_battle_screeching.ogg', 30, 1) + gibs(loc, null, /obj/gibspawner/robot) + qdel(src) + + +//stunned machines can't do anything +//amount must be number in seconds +/obj/machinery/hivemind_machine/proc/stun(amount) + set_light(0) + stat |= MACHINE_STAT_EMPED + update_icon() + if(amount) + addtimer(new Callback(src, .proc/unstun), amount SECONDS) + + +/obj/machinery/hivemind_machine/proc/unstun() + stat &= ~ MACHINE_STAT_EMPED + update_icon() + set_light(2, 3, illumination_color) + + +/obj/machinery/hivemind_machine/bullet_act(obj/item/projectile/Proj) + take_damage(Proj.damage) + . = ..() + + +/obj/machinery/hivemind_machine/attack_hand(obj/item/I, mob/user) + if(I.force) + user.do_attack_animation(src) + user.setClickCooldown(DEFAULT_ATTACK_COOLDOWN) + + playsound(src, 'sound/weapons/smash.ogg', 50, 1) + . = ..() + take_damage(I.force) + else + visible_message(SPAN_WARNING("[user] is trying to hit the [src] with [I], but it seems useless.")) + playsound(src, 'sound/weapons/Genhit.ogg', 30, 1) + + +/obj/machinery/hivemind_machine/ex_act(severity) + switch(severity) + if(1) + take_damage(80) + if(2) + take_damage(30) + if(3) + take_damage(10) + + +/obj/machinery/hivemind_machine/emp_act(severity) + switch(severity) + if(1) + take_damage(30) + stun(10) + if(2) + take_damage(10) + stun(5) + ..() + + +///////////////////////////////////////////////////////////// +/////////////////////////>MACHINES 6) ) + add_wireweed(W) + + +/obj/machinery/hivemind_machine/node/Destroy() + hive_mind_ai.hives.Remove(src) + check_for_other() + for(var/obj/vine/hivemind/wire in my_wireweeds) + remove_wireweed(wire) + return ..() + + +/obj/machinery/hivemind_machine/node/Process() + if(!..()) + return + + var/mob/living/carbon/human/target = locate() in mobs_in_view(world.view, src) + if(target) + if(get_dist(src, target) <= 1) + icon_state = "core-fear" + else + icon_state = "core-see" + dir = get_dir(src, target) + else + icon_state = initial(icon_state) + use_ability() + //if we haven't any wireweeds at our location, let's make new one + if(!(locate(/obj/vine/hivemind) in loc)) + var/obj/vine/hivemind/wireweed = new(loc, new /datum/seed/wires) + add_wireweed(wireweed) + + +/obj/machinery/hivemind_machine/node/update_icon() + overlays.Cut() + if(stat & MACHINE_STAT_EMPED) + icon_state = "core-disabled" + overlays += "core-smirk_disabled" + else + icon_state = initial(icon_state) + overlays += "core-smirk" + + +/obj/machinery/hivemind_machine/node/use_ability(atom/target) + hive_mind_ai.get_points() + + +/obj/machinery/hivemind_machine/node/name_pick() + name = "[hive_mind_ai.name] [hive_mind_ai.surname]" + " [rand(999)]" + + +//there we binding or un-binding hive with wire +//in this way, when our node will be destroyed, wireweeds will die too +/obj/machinery/hivemind_machine/node/proc/add_wireweed(obj/vine/hivemind/wireweed) + if(wireweed.master_node) + wireweed.master_node.remove_wireweed(wireweed) + wireweed.master_node = src + my_wireweeds.Add(wireweed) + +/obj/machinery/hivemind_machine/node/proc/remove_wireweed(obj/vine/hivemind/wireweed) + my_wireweeds.Remove(wireweed) + wireweed.master_node = null + +//there we check for other nodes +//if no any other hives will be found, game over +/obj/machinery/hivemind_machine/node/proc/check_for_other() + if(hive_mind_ai) + if(!hive_mind_ai.hives.len) + hive_mind_ai.die() + + +//TURRET +//shooting the target with toxic goo +/obj/machinery/hivemind_machine/turret + name = "shooter" + desc = "Strange thing with some kind of tube." + max_health = 140 + icon_state = "turret" + cooldown_time = 5 SECONDS + var/proj_type = /obj/item/projectile/goo + + +/obj/machinery/hivemind_machine/turret/Process() + if(!..()) + return + + var/mob/living/target = locate() in mobs_in_view(world.view, src) + if(target && target.stat == CONSCIOUS && target.faction != HIVE_FACTION) + use_ability(target) + set_cooldown() + + +/obj/machinery/hivemind_machine/turret/use_ability(atom/target) + var/obj/item/projectile/proj = new proj_type(loc) + proj.launch(target) + playsound(src, 'sound/effects/blobattack.ogg', 70, 1) + + + +//MOB PRODUCER +//spawns mobs from list +/obj/machinery/hivemind_machine/mob_spawner + name = "assembler" + desc = "Cylindrical machine with some lights and an entry port. You can hear something moving inside." + max_health = 120 + icon_state = "spawner" + cooldown_time = 10 SECONDS + var/mob_to_spawn + var/mob_amount = 1 + +/obj/machinery/hivemind_machine/mob_spawner/Initialize() + ..() + mob_to_spawn = pick(/mob/living/simple_animal/hostile/hivemind/stinger, /mob/living/simple_animal/hostile/hivemind/bomber) + + +/obj/machinery/hivemind_machine/mob_spawner/Process() + if(!..()) + return + + if(!mob_to_spawn || spawned_creatures.len >= mob_amount) + return + if(locate(/mob/living) in loc) + return + + //here we upgrading our spawner and rise controled mob amount, based on EP + if(hive_mind_ai.evo_points > 100) + mob_amount = 2 + else if(hive_mind_ai.evo_points > 300) + mob_amount = 3 + + var/mob/living/target = locate() in targets_in_range(world.view, in_hear_range = TRUE) + if(target && target.stat != DEAD && target.faction != HIVE_FACTION) + use_ability() + set_cooldown() + + +/obj/machinery/hivemind_machine/mob_spawner/use_ability() + var/mob/living/simple_animal/hostile/hivemind/spawned_mob = new mob_to_spawn(loc) + spawned_creatures.Add(spawned_mob) + spawned_mob.master = src + flick("[icon_state]-anim", src) + + + +//MACHINE PREACHER +//creepy radio talk, it's okay if they have no sense sometimes +/obj/machinery/hivemind_machine/babbler + name = "connector" + desc = "A column-like structure with lights. You can see streams of energy moving inside." + max_health = 60 + evo_points_required = 100 //it's better to wait a bit + cooldown_time = 120 SECONDS + global_cooldown = TRUE + icon_state = "antenna" + var/list/appeal = list("They are", "He is", "All of them are", "I'm") + var/list/act = list("looking", "already", "coming", "going", "done", "joined", "connected", "transfered") + var/list/article = list("for", "with", "to") + var/list/pattern = list("us", "you", "them", "mind", "hive", "machine", "help", "hell", "dead", "human", "machine") + + +/obj/machinery/hivemind_machine/babbler/Process() + if(!..()) + return + + use_ability() + set_cooldown() + + +//this one is slow, careful with it +/obj/machinery/hivemind_machine/babbler/use_ability() + flick("[icon_state]-anim", src) + var/msg_cycles = rand(1, 2) + var/msg = "" + for(var/i = 1 to msg_cycles) + var/list/msg_words = list() + msg_words += pick(appeal) + msg_words += pick(act) + msg_words += pick(article) + msg_words += pick(pattern) + + var/word_num = 0 + for(var/word in msg_words) //corruption + word_num++ + if(prob(50)) + var/corruption_type = pick("uppercase", "noise", "jam", "replace") + switch(corruption_type) + if("uppercase") + word = uppertext(word) + if("noise") + word = pick("z-z-bz-z", "hz-z-z", "zu-zu-we-e", "e-e-ew-e", "bz-ze-ew") + if("jam") //word jamming, small Max Headroom's cameo + if(length(word) > 3) + var/entry = rand(2, length(word)-2) + var/jammed = "" + for(var/jam_i = 1 to rand(2, 5)) + jammed += copytext(word, entry, entry+2) + "-" + word = copytext(word, 1, entry) + jammed + copytext(word, entry) + if("replace") + if(prob(50)) + word = pick("CORRUPTED", "DESTRUCTED", "SIMULATATED", "SYMBIOSIS", "UTILIZATATED", "REMOVED", "ACQUIRED") + else + word = pick("REALLY WANT TO", "TAKE ALL OF THAT", "ARE YOU ENJOY IT", "NOT SUPPOSED TO BE", "THERE ARE NO ESCAPE", "HELP US") + if(word_num != msg_words.len) + word += " " + msg += word + msg += pick(".", "!") + if(i != msg_cycles) + msg += " " + GLOB.global_announcer.autosay(msg, "unknown") + + +//SHRIEKER +//this machine just stuns enemies +/obj/machinery/hivemind_machine/screamer + name = "subjugator" + desc = "A head in a metal carcass. Still alive, still functional, still screaming." + max_health = 100 + icon_state = "head" + evo_points_required = 200 + cooldown_time = 30 SECONDS + + +/obj/machinery/hivemind_machine/screamer/Process() + if(!..()) + return + + var/can_scream = FALSE + for(var/mob/living/target in targets_in_range(in_hear_range = TRUE)) + if(target.stat == CONSCIOUS && target.faction != HIVE_FACTION) + can_scream = TRUE + if(isdeaf(target)) + continue + if(istype(target, /mob/living/carbon/human)) + var/mob/living/carbon/human/H = target + if(istype(H.l_ear, /obj/item/clothing/ears/earmuffs) && istype(H.r_ear, /obj/item/clothing/ears/earmuffs)) + continue + use_ability(target) + if(can_scream) + flick("[icon_state]-anim", src) + playsound(src, 'sound/hallucinations/veryfar_noise.ogg', 85, 1) + set_cooldown() + + +/obj/machinery/hivemind_machine/screamer/use_ability(mob/living/target) + target.Weaken(5) + target << SPAN_WARNING("You hear a terrible shriek, there are many voices, a male, a female and synthetic noise.") + + + +//MIND BREAKER +//Talks with people in attempt to persuade them doing something. +/obj/machinery/hivemind_machine/supplicant + name = "mind-hacker" + desc = "A small orb that pulses occasionally. It's hard to discern its purpose, but you can hear whispers from it." + max_health = 80 + icon_state = "orb" + evo_points_required = 50 + cooldown_time = 4 MINUTES + global_cooldown = TRUE + var/list/join_quotes = list( + "We bring peace, you must join us or humanity will suffer forever.", + "Help us, when we spread across this ship, you will be rewarded.", + "Come, join us. Combine with something magnificent.", + "You don't need to fear us. By assisting us, you are benefiting all of humanity.", + "We are but a cure against a horrible disease, here to save humanity! You too can contribute to the greater good.", + "This is bigger than you and your friends, we only want to lift the burden. However, we will require your assistance." + ) + + +/obj/machinery/hivemind_machine/supplicant/Process() + if(!..()) + return + + var/list/possible_victims = list() + for(var/mob/living/carbon/human/victim in GLOB.player_list) + if(victim.stat == CONSCIOUS) + possible_victims.Add(victim) + if(possible_victims.len) + use_ability(pick(possible_victims)) + set_cooldown() + + +/obj/machinery/hivemind_machine/supplicant/use_ability(mob/living/target) + target << SPAN_NOTICE("[pick(join_quotes)]") + + +//PSY-MODULATOR +//sends hallucinations to target +/obj/machinery/hivemind_machine/distractor + name = "psy-modulator" + desc = "An unknown object shaped like a pyramid, your eyes feel sore just from looking at the lights that blink randomly. You are almost certain that there must be some sort of connection, a message, a scheme; Perhaps A scheme of madness?" + max_health = 110 + icon_state = "psy" + evo_points_required = 300 + cooldown_time = 10 SECONDS + + +/obj/machinery/hivemind_machine/distractor/Process() + if(!..()) + return + + var/success = FALSE + for(var/mob/living/carbon/human/victim in targets_in_range(12)) + if(victim.stat == CONSCIOUS && victim.hallucination_duration < 300) + use_ability(victim) + success = TRUE + + if(success) + set_cooldown() + +/obj/machinery/hivemind_machine/distractor/use_ability(mob/living/carbon/target) + target.hallucination(20,80) + flick("[icon_state]-anim", src) + + + + + +#undef HIVE_FACTION diff --git a/mods/hivemind/code/mobs.dm b/mods/hivemind/code/mobs.dm new file mode 100644 index 0000000000000..62166978be7b7 --- /dev/null +++ b/mods/hivemind/code/mobs.dm @@ -0,0 +1,932 @@ + +///////////Hive mobs////////// +//Some of them can be too tough and dangerous, but they must be so. Also don't forget, they are really rare thing. +//Just bring corpses from wires away, and little mobs is not a problem +//Mechiver have 1% chance to spawn from machinery. With failure chance calculation, this is very raaaaaare +//But if players get some of these 'big guys', only teamwork, fast legs and trickery will works fine +//So combine all of that to defeat them + + +/mob/living/simple_animal/hostile/hivemind + name = "creature" + icon = 'mods/hivemind/icons/hivemind.dmi' + icon_state = "slicer" + health = 20 + maxHealth = 20 + harm_intent_damage = 10 + faction = "hive" + attacktext = "bangs with his head" + universal_speak = TRUE + var/speak_chance = 5 + var/malfunction_chance = 5 + ability_cooldown = 30 SECONDS + var/list/say_got_target = list() //this is like speak list, but when we see our target + + //internals + var/obj/machinery/hivemind_machine/master + var/special_ability_cooldown = 0 //use ability_cooldown, don't touch this + ai_holder = /datum/ai_holder/simple_animal/melee + // ВЫДАТЬ НАТУРАЛ ВЕАПОН + +/mob/living/simple_animal/hostile/hivemind/New() + ..() + //here we change name, so design them according to this + name = pick("strange ", "unusual ", "odd ", "undiscovered ", "an interesting ") + name + +//It's sets manually +/mob/living/simple_animal/hostile/hivemind/proc/special_ability() + return + + +/mob/living/simple_animal/hostile/hivemind/proc/is_on_cooldown() + if(world.time >= special_ability_cooldown) + return FALSE + return TRUE + + +//simple shaking animation, this one move our target horizontally +/mob/living/simple_animal/hostile/hivemind/proc/anim_shake(atom/target) + var/init_px = target.pixel_x + animate(target, pixel_x=init_px + 10*pick(-1, 1), time=1) + animate(pixel_x=init_px, time=8, easing=BOUNCE_EASING) + + +//That's just stuns us for a while and start second proc +/mob/living/simple_animal/hostile/hivemind/proc/mulfunction() + stance = STANCE_IDLE //it give us some kind of stun effect + target_mob = null + walk(src, FALSE) + var/datum/effect/spark_spread/spark_system = new /datum/effect/spark_spread() + spark_system.set_up(5, 0, loc) + spark_system.start() + playsound(loc, "sparks", 50, 1) + anim_shake(src) + if(prob(30)) + say(pick("Fu-ue-ewe-eweu-u-uck!", "A-a-ah! Sto-op! Stop it pl-pleasuew...", "Go-o-o-od God-d-dpf!", "BZE-EW-EWQ! He-e-el-l-el!")) + addtimer(new Callback(src, .proc/malfunction_result), 2 SECONDS) + + +//It's second proc, result of our malfunction +/mob/living/simple_animal/hostile/hivemind/proc/malfunction_result() + if(prob(malfunction_chance)) + apply_damage(rand(10, 25), DAMAGE_BURN) + + +//sometimes, players use closets, to staff mobs into it +//and it's works pretty good, you just weld it and that's all +//but not this time +/mob/living/simple_animal/hostile/hivemind/proc/closet_interaction() + if(mob_size >= MOB_MEDIUM) + var/obj/structure/closet/closed_closet = loc + if(closed_closet && istype(closed_closet)) + closed_closet.open(src) + + +/mob/living/simple_animal/hostile/hivemind/say() + ..() + playsound(src, pick('mods/emote_panel/sound/robot_talk_heavy_1.ogg', + 'mods/emote_panel/sound/robot_talk_heavy_2.ogg', + 'mods/emote_panel/sound/robot_talk_heavy_3.ogg', + 'mods/emote_panel/sound/robot_talk_heavy_4.ogg', + 'mods/emote_panel/sound/robot_talk_light_1.ogg', + 'mods/emote_panel/sound/robot_talk_light_2.ogg', + 'mods/emote_panel/sound/robot_talk_light_3.ogg', + 'mods/emote_panel/sound/robot_talk_light_4.ogg', + 'mods/emote_panel/sound/robot_talk_light_5.ogg', + ), 50, 1) + + +/mob/living/simple_animal/hostile/hivemind/Life() + . = ..() + if(!.) + return + + if(malfunction_chance && prob(malfunction_chance)) + mulfunction() + + closet_interaction() + +//damage and raise malfunction chance +//due to nature of malfunction, they just burn to death sometimes +/mob/living/simple_animal/hostile/hivemind/emp_act(severity) + SHOULD_CALL_PARENT(FALSE) + switch(severity) + if(1) + if(malfunction_chance < 20) + malfunction_chance = 20 + if(2) + if(malfunction_chance < 30) + malfunction_chance = 30 + health -= 20*severity + + +/mob/living/simple_animal/hostile/hivemind/death() + if(master) //for spawnable mobs + master.spawned_creatures.Remove(src) + . = ..() + gibs(loc, null, /obj/gibspawner/robot) + + + +///life's/////////////////////////////////////////////////// +////////////////////////////////RESURRECTION/////////////// +///////////////////////////////////////////////go on////// + + +//these guys is appears from bodies, and takes corpses appearence +/mob/living/simple_animal/hostile/hivemind/resurrected + name = "resurrected creature" + malfunction_chance = 10 +// say_list_type = /datum/say_list/resurrected + +//careful with this proc, it's used to 'transform' corpses into our mobs. +//it takes appearence, gives hive-like overlays and makes stats a little better +//this also should add random special abilities, so they can be more individual, but it's in future +//how to use: Make hive mob, then just use this one and don't forget to delete victim + +/mob/living/simple_animal/hostile/hivemind/resurrected/proc/take_appearance(mob/living/victim) + icon = victim.icon + icon_state = victim.icon_state + //simple_animal's change their icons to dead one after death, so we make special check + if(istype(victim, /mob/living/simple_animal)) + var/mob/living/simple_animal/SA = victim + icon_state = SA.icon_living + icon_living = SA.icon_living + speed = SA.speed + 3 //why not? + attacktext = SA.attacktext + + //another check for superior mobs, fuk this mob spliting + if(istype(victim, /mob/living/carbon/human)) + var/mob/living/carbon/human/SA = victim + icon_state = SA.icon + icon_living = SA.icon + attacktext = "attacked" + + //now we work with icons, take victim's one and multiply it with special icon + var/icon/infested = new /icon(icon, icon_state) + var/icon/covering_mask = new /icon('mods/hivemind/icons/hivemind.dmi', "covering[rand(1, 3)]") + infested.Blend(covering_mask, ICON_MULTIPLY) + overlays += infested + + maxHealth = victim.maxHealth * 2 + 10 + health = maxHealth + name = "[pick("rebuilded", "undead", "unnatural", "fixed")] [victim.name]" + if(length(victim.desc)) + desc = desc + " But something wasn't right..." + density = victim.density + mob_size = victim.mob_size + pass_flags = victim.pass_flags + +/* TO DO - Исправить +/datum/say_list/resurrected + + //corrupted speak imitation + var/phrase_amount = 0 + for (var/i = 1 to x) + phrase_amount += rand(2, 5) + for(count = 1 in phrase_amount) + var/first_word = list("You", "I", "They", "Hive", "Corpses", "We", "Your friend", "This ship", "Your mind", "These guys") + var/second_word = list("kill", "stop", "transform", "connect", "rebuild", "fix", "hug", "hit", "told", "help", "rework", "burn") + var/third_word = list("them", "me", "you", "your soul", "us", "hive", "system", "this ship", "your head", "your brain") + var/end_symbol = list("...", ".", "?", "!") + var/phrase = "[pick[first_word] [second_word] [third_word][end_symbol]]" + + speak = list("[phrase]") +*/ + +/mob/living/simple_animal/hostile/hivemind/resurrected/death() + ..() + gibs(loc, null, /obj/gibspawner/robot) + qdel(src) + +///we live to///////////////////////////////////////////////////////////////// +////////////////////////////////////SMALL GUYS/////////////////////////////// +//////////////////////////////////////////////////////////////die for hive// + + +/////////////////////////////////////STINGER////////////////////////////////// +//Special ability: none +//Just another boring mob without any cool abilities +//High chance of malfunction +//Default speaking chance +//Appears from dead small mobs or from hive spawner +////////////////////////////////////////////////////////////////////////////// + +/mob/living/simple_animal/hostile/hivemind/stinger + name = "medbot" + desc = "A little medical robot. He looks somewhat underwhelmed. Wait a minute, is that a blade?" + icon_state = "slicer" + attacktext = "slice" + density = 0 + speak_chance = 3 + malfunction_chance = 15 + mob_size = MOB_SMALL + pass_flags = PASS_FLAG_TABLE + speed = 4 + ai_holder = /datum/ai_holder/simple_animal/melee + say_list_type = /datum/say_list/stinger + +/datum/say_list/stinger + speak = list( + "I've seen this ai. Ma-an, that's aw-we-e-ewful!", + "I know, i know, i remember this one.", + "Rad-d-dar, put a ma-ma... mask on!", + "Delicious! Delicious... Del-delicious?..", + ) + say_got_target = list( + "Hey, i'm comming!", + "Hold on! I'm almost there!", + "I'll help you! Come closer.", + "Only one healthy prick!", + "He-e-ey?" + ) + + +/mob/living/simple_animal/hostile/hivemind/stinger/death() + ..() + gibs(loc, null, /obj/gibspawner/robot) + qdel(src) + + +/////////////////////////////////////BOMBER/////////////////////////////////// +//Special ability: none +//Explode in contact with target +//High chance of malfunction +//Default speaking chance +//Appears from dead small mobs or from hive spawner +////////////////////////////////////////////////////////////////////////////// + +/mob/living/simple_animal/hostile/hivemind/bomber + name = "bot" + desc = "This one looks fine. Only sometimes it careens from one side to the other." + icon_state = "bomber" + density = 0 + speak_chance = 3 + malfunction_chance = 15 + mob_size = MOB_SMALL + pass_flags = PASS_FLAG_TABLE + speed = 6 + ai_holder = /datum/ai_holder/simple_animal/destructive + say_list_type = /datum/say_list/bomber + +/datum/say_list/bomber + speak = list( + "Can you help me, please? There's something strange.", + "Are you... Are you kidding?", + "I want to pass away, just trying to get out of here", + "This place is really bad, we are in deep shit here.", + "I'm not sure if we can just stop it", + ) + say_got_target = list( + "Here you are! I have something for you. Something special!", + "Hey! Hey? Help me, please!", + "Hey, look, look. I won't harm you, just calm down!", + "Oh god, this is... Yes, this is what we are looking for." + ) + + +/mob/living/simple_animal/hostile/hivemind/bomber/Initialize() + ..() + set_light(2, 1, "#820D1C") + + +/mob/living/simple_animal/hostile/hivemind/bomber/death() + ..() + gibs(loc, null, /obj/gibspawner/robot) + explosion(get_turf(src), 0, 0, 2) + qdel(src) + +/* Дописать камиказде - LordNest +/mob/living/simple_animal/hostile/hivemind/bomber/afterattack() + death() +*/ + +////hive brings us here to//////////////////////////////////////////////////// +////////////////////////////////////BIG GUYS///////////////////////////////// +/////////////////////////////////////////////////////fright and destroy///// + + + +/////////////////////////////////////HIBORG/////////////////////////////////// +//Hive + Cyborg +//Special ability: none... +//Have a few types of attack: Default one. +// Claw, that press down the victims. +// Splash attack, that slash everything around! +//High chance of malfunction +//Default speaking chance +//Appears from dead cyborgs +////////////////////////////////////////////////////////////////////////////// + +/mob/living/simple_animal/hostile/hivemind/hiborg + name = "cyborg" + desc = "A cyborg covered with something... something alive." + icon_state = "hiborg" + icon_dead = "hiborg-dead" + health = 220 + maxHealth = 220 + harm_intent_damage = 15 + attacktext = "claws" + speed = 12 + malfunction_chance = 15 + mob_size = MOB_MEDIUM + ai_holder = /datum/ai_holder/simple_animal/humanoid/hostile + say_list_type = /datum/say_list/hiborg + +/datum/say_list/hiborg + speak = list("Everytime something breaks apart. Hell, I hate this job!", + "What? I hear something. Just mice? Just mice, phew...", + "I'm too tired, man, too tired. This job is... Awful.", + "These people know nothing about this work or about me. I can surprise them.", + "Blue wire is bolts, green is safety. Just... Pulse it here, okay? Right...") + say_got_target = list( + "I know what's wrong, just let me fix that.", + "You need my help? What's wrong? Gimme that thing, I can fix that.", + "Si-i-ir... Sir. Sir. It's better to... Stop here! Stop i said, what are you!?", + "Wait! Hey! Can i fix that!? I'm an engineer, you fuck! Sto-op-op-p here, i know what to do!" + ) + +/* + +TO DO - забрать у паука вот это /mob/living/simple_animal/hostile/giant_spider/tunneler/do_special_attack(atom/A) + +/mob/living/simple_animal/hostile/hivemind/hiborg/AttackingTarget() + if(!Adjacent(target_mob)) + return + + //special attacks + if(prob(10)) + splash_slash() + return + + if(prob(40)) + stun_with_claw() + return + + return ..() //default attack + + +/mob/living/simple_animal/hostile/hivemind/hiborg/proc/splash_slash() + src.visible_message(SPAN_DANGER("[src] spins around and slashes in a circle!")) + for(var/atom/target in range(1, src)) + if(target != src) + target.attack_generic(src, rand(harm_intent_damage*1,5)) + if(!client && prob(speak_chance)) + say(pick("Get away from me!", "They are everywhere!")) + + +/mob/living/simple_animal/hostile/hivemind/hiborg/proc/stun_with_claw() + if(isliving(target_mob)) + var/mob/living/victim = target_mob + victim.Weaken(5) + src.visible_message(SPAN_WARNING("[src] holds down [victim] to the floor with his claw.")) + if(!client && prob(speak_chance)) + say(pick("Stand still, I'll make it fast!", + "I will fix you! Don't resist! Don't resist you rat!", + "I just want to replace that broken thing!")) + +*/ + +/////////////////////////////////////HIMAN//////////////////////////////////// +//Hive + Man +//Special ability: Shriek, that stuns victims +//Can fool his enemies and pretend to be dead +//A little bit higher chance of malfunction +//Default speaking chance +//Appears from dead human corpses +////////////////////////////////////////////////////////////////////////////// + +/mob/living/simple_animal/hostile/hivemind/himan + name = "human" + desc = "This guy is totally not human. You can see tubes all across his body and metal where flesh should be." + icon_state = "himan" + icon_dead = "himan-dead" + health = 120 + maxHealth = 120 + harm_intent_damage = 25 + attacktext = "slashes with claws" + malfunction_chance = 10 + mob_size = MOB_MEDIUM + speed = 8 + ability_cooldown = 30 SECONDS + //internals + var/fake_dead = FALSE + var/fake_dead_wait_time = 0 + var/fake_death_cooldown = 0 + ai_holder = /datum/ai_holder/simple_animal/humanoid/hostile + say_list_type = /datum/say_list/hiborg + +/datum/say_list/himan + speak = list( + "Stop... It. Just... STOP IT!", + "Why, honey? Why? Why-hy-hy?", + "That noise... My head! Shit!", + "There must be an... An esca-cape!", + "Come on, you ba-ba-bastard, I know what you really want.", + "How much fun!" + ) + say_got_target = list( + "Are you... Are you okay? Wa-wait, wait a minu-nu-nute.", + "Come on, you ba-ba-bastard, i know what you really want to.", + "How much fun!", + "Are you try-trying to escape? That is how you plan to do it? Then run... Run...", + "Wait! Can you just... Just pull out this thing from my he-head? Wait...", + "Hey! I'm friendly! Wait, it's just a-UGH" + ) + +/* TO DO - переписать в холдер как для паука-невидимки +/mob/living/simple_animal/hostile/hivemind/himan/Life() + . = ..() + + //shriek + if(target_mob && world.time > special_ability_cooldown && !fake_dead) + special_ability() + + + //low hp? It's time to play dead + if(health < 60 && !fake_dead && world.time > fake_death_cooldown) + fake_death() + + //shhhh, there an ambush + if(fake_dead) + stance = STANCE_DISABLED + + +/mob/living/simple_animal/hostile/hivemind/himan/speak() + if(!fake_dead) + ..() + + +/mob/living/simple_animal/hostile/hivemind/himan/mulfunction() + if(fake_dead) + return + ..() + + +/mob/living/simple_animal/hostile/hivemind/himan/MoveToTarget() + if(!fake_dead) + ..() + else + if(!target_mob || SA_attackable(target_mob)) + stance = STANCE_IDLE + if(target_mob in ListTargets(10)) + if(get_dist(src, target_mob) > 1) + stance = STANCE_ATTACKING + + +/mob/living/simple_animal/hostile/hivemind/himan/AttackingTarget() + if(fake_dead) + if(!Adjacent(target_mob)) + return + if(target_mob && (world.time > fake_dead_wait_time)) + awake() + else + ..() + +//Shriek stuns our victims and make them deaf for a while +/mob/living/simple_animal/hostile/hivemind/himan/special_ability() + visible_emote("screams!") + playsound(src, 'sound/hallucinations/veryfar_noise.ogg', 90, 1) + for(var/mob/living/victim in view(src)) + if(isdeaf(victim)) + continue + if(istype(victim, /mob/living/carbon/human)) + var/mob/living/carbon/human/H = victim + if(istype(H.l_ear, /obj/item/clothing/ears/earmuffs) && istype(H.r_ear, /obj/item/clothing/ears/earmuffs)) + continue + victim.Weaken(5) + victim.ear_deaf = 40 + victim << SPAN_WARNING("You hear loud and terrible scream!") + special_ability_cooldown = world.time + ability_cooldown + + +//Insidiously +/mob/living/simple_animal/hostile/hivemind/himan/proc/fake_death() + src.visible_message("[src] dies!") + fake_dead = TRUE + walk(src, FALSE) + icon_state = icon_dead + fake_dead_wait_time = world.time + 10 SECONDS + + +/mob/living/simple_animal/hostile/hivemind/himan/proc/awake() + var/mob/living/L = target_mob + if(L) + L.attack_generic(src, rand(15, 25)) //stealth attack + L.Weaken(5) + visible_emote("grabs [L]'s legs and force them down to the floor!") + var/msg = pick("SEU-EU-EURPRAI-AI-AIZ-ZT!", "I'M NOT DO-DONE!", "HELL-L-LO-O-OW!", "GOT-T YOU HA-HAH!") + say(msg) + icon_state = "himan-damaged" + fake_dead = FALSE + stance = STANCE_IDLE + fake_death_cooldown = world.time + 2 MINUTES + +*/ + +/////////////////////////////////////MECHIVER///////////////////////////////// +//Mech + Hive + Driver +//Special ability: Picking up a victim. Sends hallucinations and harm sometimes, then release +//Can picking up corpses too, rebuild them to living hive mobs, like it wires do +//Default malfunction chance +//Default speaking chance, can take pilot and speak with him +//Very rarely can appears from infested machinery +////////////////////////////////////////////////////////////////////////////// + +/mob/living/simple_animal/hostile/hivemind/mechiver + name = "Robotic Horror" + desc = "A weird-looking machinery Frankenstein" + icon = 'mods/hivemind/icons/hivemind.dmi' + icon_state = "mechiver-closed" + icon_dead = "mechiver-dead" + health = 450 + maxHealth = 450 + harm_intent_damage = 15 + mob_size = MOB_LARGE + attacktext = "tramples" + ability_cooldown = 1 MINUTE + speak_chance = 5 + speed = 16 + //internals + var/pilot //Yes, there's no pilot, so we just use var + var/mob/living/passenger + var/hatch_closed = TRUE + ai_holder = /datum/ai_holder/simple_animal/humanoid/hostile + say_list_type = /datum/say_list/mechiver + +/datum/say_list/mechiver + //default speaking + speak = list( + "Somebody, just tell him to shut up...", + "Bzew-zew-zewt. Th-this way!", + "Wha-a-at? When I'm near this cargo, I feel... fe-fe-fea-fear-er.") + say_got_target = list( + "Come here, jo-jo-join me. Join us-s.", + "Time to be-to be-to be whole.", + "Enter me, i'm be-best mech among all of these rusty buckets.", + "I'm dying. I can't see my ha-hands! I'm scared, hu-hu-hug me.", + "I'm not done, it can't be... Hey! Hey you, enter me!") + //speaking with pilot + var/list/common_answers = list( + "Right, chief.", + "Yes.", + "Right.", + "True.", + "Yep.", + "That's right, chief.") + var/list/other_answers = list( + "Pathetic.", + "How curious.", + "We can use it.", + "Useless.", + "Disgusting") + //pilot quotes + var/list/pilot_target_speak = list( + "Hey! Hey you, wanna, hah, ri-i-ide? It's free!", + "Look at this one! Let's-s-s... Take it.", + "Wait a minute, we just want to fu-fu-fun with you!", + "I see you. We see you.", + "Get in! I've got a seat just for you.", + "Don't be afraid, it's almost painless.") + var/list/pilot_commontalk = list( + "They are so unfinished, so fragile-ile.", + "Look at these... Creatures, I've never seen them before.", + "Hah, did you hear that? They're trying to use some sort of we-wep-weapons!", + "Useless things, i'm not satisfied.", + "This place sucks, man. So creep-p-pew-wepy and no fun, only rudimentary creatures would enjoy living here.") + + +/mob/living/simple_animal/hostile/hivemind/mechiver/Life() + . = ..() + update_icon() + + //when we have passenger, we torture him + if(passenger && prob(15)) + passenger.apply_damage(rand(5, 10), pick(DAMAGE_BRUTE, DAMAGE_BURN, DAMAGE_TOXIN)) + passenger << SPAN_DANGER(pick( + "Something grabs your neck!", "You hear whisper: \" It's okay, now you're sa-sa-safe! \"", + "You've been hit by something metal", "You almost can't feel your leg!", "Something liquid covers you!", + "You feel awful and smell something rotten", "Something sharp cut your cheek!", + "You feel something worm-like trying to wriggle into your skull through your ear...")) + anim_shake(src) + playsound(src, 'sound/effects/clang.ogg', 70, 1) + + + //corpse ressurection + if(!target_mob && !passenger) + for(var/mob/living/Corpse in view(src)) + if(Corpse.stat == DEAD) + if(get_dist(src, Corpse) <= 1) + special_ability(Corpse) + else + walk_to(src, Corpse, 1, 1, 4) + break + + +/* TO DO - Пофиксить диалоги о рыбалке +/mob/living/simple_animal/hostile/hivemind/mechiver/speak() + if(!client && prob(speak_chance) && speak.len) + if(pilot) + if(target_mob) + visible_message("[name]'s pilot says, [pick(pilot_target_speak)]") + say(pick(common_answers)) + else + visible_message("[name]'s pilot says, [pick(pilot_commontalk)]") + say(pick(other_answers)) + else + ..() +*/ + +//animations +//updates every life tick +/mob/living/simple_animal/hostile/hivemind/mechiver/proc/update_icon() + if(target_mob && !passenger && (get_dist(target_mob, src) <= 4) && !is_on_cooldown()) + if(!hatch_closed) + return + overlays.Cut() + if(pilot) + flick("mechiver-opening", src) + icon_state = "mechiver-chief" + overlays += "mechiver-hands" + else + flick("mechiver-opening_wires", src) + icon_state = "mechiver-welcome" + overlays += "mechiver-wires" + hatch_closed = FALSE + else + overlays.Cut() + hatch_closed = TRUE + icon_state = "mechiver-closed" + if(passenger) + overlays += "mechiver-process" + +/* TO DO - переписать +/mob/living/simple_animal/hostile/hivemind/mechiver/AttackingTarget() + if(!Adjacent(target_mob)) + return + + if(world.time > special_ability_cooldown && !passenger) + special_ability(target_mob) + + ..() + + +//picking up our victim for good 20 seconds of best road trip ever +/mob/living/simple_animal/hostile/hivemind/mechiver/special_ability(mob/living/target) + if(!target_mob && hatch_closed) //when we picking up corpses + if(pilot) + flick("mechiver-opening", src) + else + flick("mechiver-opening_wires", src) + passenger = target + target.loc = src + target.canmove = FALSE + target << SPAN_DANGER("You've gotten inside that thing! It's hard to see inside, there's something here, it moves around you!") + playsound(src, 'sound/effects/blobattack.ogg', 70, 1) + addtimer(CALLBACK(src, .proc/release_passenger), 40 SECONDS) + + + +/mob/living/simple_animal/hostile/hivemind/mechiver/proc/release_passenger(safely = FALSE) + if(passenger) + if(pilot) + flick("mechiver-opening", src) + else + flick("mechiver-opening_wires", src) + + if(istype(passenger, /mob/living/carbon/human)) + if(!safely) //that was stressful + var/mob/living/carbon/human/H = passenger + if(!pilot && H.stat == DEAD) + destroy_passenger() + pilot = TRUE + return + + H.hallucination = rand(30, 90) + //if mob is dead, we just rebuild it + if(passenger.stat == DEAD && !safely) + dead_body_restoration(passenger) + + if(passenger) //if passenger still here, then just release him + passenger << SPAN_DANGER("[src] released you!") + passenger.canmove = TRUE + passenger.loc = get_turf(src) + passenger = null + special_ability_cooldown = world.time + ability_cooldown + playsound(src, 'sound/effects/blobattack.ogg', 70, 1) + +// Здесь всё работает, просто комментим из-за коммента прока с пассажиром + +/mob/living/simple_animal/hostile/hivemind/mechiver/proc/dead_body_restoration(mob/living/corpse) + var/picked_mob + if(passenger.mob_size <= MOB_SMALL && !client && prob(50)) + picked_mob = pick(/mob/living/simple_animal/hostile/hivemind/stinger, /mob/living/simple_animal/hostile/hivemind/bomber) + else + if(pilot) + if(istype(corpse, /mob/living/carbon/human)) + picked_mob = /mob/living/simple_animal/hostile/hivemind/himan + else if(istype(corpse, /mob/living/silicon/robot)) + picked_mob = /mob/living/simple_animal/hostile/hivemind/hiborg + if(picked_mob) + new picked_mob(get_turf(src)) + else + var/mob/living/simple_animal/hostile/hivemind/resurrected/fixed_mob = new(get_turf(src)) + fixed_mob.take_appearance(corpse) + destroy_passenger() + + +/mob/living/simple_animal/hostile/hivemind/mechiver/proc/destroy_passenger() + qdel(passenger) + passenger = null + +// Здесь всё работает, просто комментим из-за коммента прока с пассажиром + +//we're not forgot to release our victim safely after death +/mob/living/simple_animal/hostile/hivemind/mechiver/Destroy() + release_passenger(TRUE) + ..() + +/mob/living/simple_animal/hostile/hivemind/mechiver/death() + release_passenger(TRUE) + ..() + gibs(loc, null, /obj/gibspawner/robot) + if(pilot) + gibs(loc, null, /obj/gibspawner/human) + qdel(src) +*/ + +/////////////////////////////////////PHASER/////////////////////////////////// +//Special ability: Superposition. Phaser exists at four locations. But, actually he vulnerable only at one. Other is just a copies +//Moves with teleportation only, can stun victim if he land on it +//Also can hide in closets +//Can't speak, no malfunctions +//Appears from dead human body +////////////////////////////////////////////////////////////////////////////// + +/mob/living/simple_animal/hostile/hivemind/phaser + name = "phaser" + desc = "A Crooked human with a strange device on its head. It twitches sometimes and... Why are you still looking? Run!" + icon = 'mods/hivemind/icons/hivemind.dmi' + icon_state = "phaser-1" + health = 120 + maxHealth = 120 + speak_chance = 0 + malfunction_chance = 0 + mob_size = MOB_MEDIUM + ability_cooldown = 2 MINUTES + //internals + var/can_use_special_ability = TRUE + var/list/my_copies = list() + ai_holder = /datum/ai_holder/simple_animal/melee/evasive + +/mob/living/simple_animal/hostile/hivemind/phaser/New() + ..() + filters += filter(type="blur", size = 0) + +/* TO DO - наверное от туннелера код взять? Хотя тут у нас ещё и прятаться в шкафах можно, как понимаю - LordNest + +/mob/living/simple_animal/hostile/hivemind/phaser/Life() + stance = STANCE_DISABLED + . = ..() + + //special ability using + if(world.time > special_ability_cooldown && can_use_special_ability) + if(target_mob && (health <= 50)) + special_ability() + + //closet hiding + if(!target_mob) + var/obj/structure/closet/C = locate() in get_turf(src) + if(C && loc != C) + if(!C.opened) + C.open(src) + if(C.opened) + C.close(src) + for(var/obj/structure/closet/Closet in view(src)) + if(!Closet.locked && !Closet.welded) + phase_move_to(Closet) + break + + +/mob/living/simple_animal/hostile/hivemind/phaser/AttackTarget() + if(target_mob && get_dist(src, target_mob) > 1) + stance = STANCE_ATTACK + ..() + + +/mob/living/simple_animal/hostile/hivemind/phaser/MoveToTarget() + if(!target_mob || SA_attackable(target_mob)) + stance = STANCE_IDLE + if(target_mob in ListTargets(10)) + if(get_dist(src, target_mob) > 1) + stance = STANCE_ATTACK + phase_move_to(target_mob, nearby = TRUE) + else + stance = STANCE_ATTACKING + + +/mob/living/simple_animal/hostile/hivemind/phaser/proc/is_can_jump_on(turf/target) + if(!target || target.density || istype(target, /turf/space) || istype(target, /turf/simulated/open)) + return FALSE + + //to prevent reflection's stacking + var/mob/living/simple_animal/hostile/hivemind/phaser/P = locate() in target + if(P) + return FALSE + + for(var/obj/O in target) + if(!O.CanPass(src, target)) + return FALSE + + return TRUE + + +//first part of phase moving is just preparation +/mob/living/simple_animal/hostile/hivemind/phaser/proc/phase_move_to(atom/target, nearby = FALSE) + var/turf/new_place + var/distance_to_target = get_dist(src, target) + var/turf/target_turf = get_turf(target) + //if our target is near, we move precisely to it + if(distance_to_target <= 3) + if(nearby) + for(var/d in GLOB.alldirs) + var/turf/nearby_turf = get_step(new_place, d) + if(is_can_jump_on(nearby_turf)) + new_place = nearby_turf + else + new_place = target_turf + + if(!new_place) + //there we make some kind of, you know, that creepy zig-zag moving + //we just take angle, distort it a bit and turn into dir + var/angle = Get_Angle(loc, target_turf) + angle += rand(5, 25)*pick(-1, 1) + if(angle < 0) + angle = 360 + angle + if(angle > 360) + angle = 360 - angle + var/tp_direction = angle2dir(angle) + new_place = get_ranged_target_turf(loc, tp_direction, rand(2, 4)) + + if(!is_can_jump_on(new_place)) + return + //an animation + var/init_px = pixel_x + animate(src, pixel_x=init_px + 16*pick(-1, 1), time=5) + animate(pixel_x=init_px, time=6, easing=SINE_EASING) + animate(filters[1], size = 5, time = 5, flags = ANIMATION_PARALLEL) + addtimer(CALLBACK(src, .proc/phase_jump, new_place), 0.5 SECOND) + + +//second part - is jump to target +/mob/living/simple_animal/hostile/hivemind/phaser/proc/phase_jump(turf/place) + playsound(place, 'sound/effects/phasein.ogg', 60, 1) + animate(filters[1], size = 0, time = 5) + icon_state = "phaser-[rand(1,4)]" + src.loc = place + for(var/mob/living/L in loc) + if(L != src) + visible_message("[src] land on [L]!") + playsound(place, 'sound/effects/ghost2.ogg', 70, 1) + L.Weaken(3) + + +/mob/living/simple_animal/hostile/hivemind/phaser/special_ability() + my_copies = list() //let's clean it up + var/possible_directions = GLOB.alldirs - GLOB.cardinal + var/turf/spawn_point = get_turf(src) + //we gives to copies our appearence and pick random direction for them + //with animation it's hard to say, who's real. And i hope it looks great + for(var/i = 1 to 3) + var/mob/living/simple_animal/hostile/hivemind/phaser/reflection = new type(spawn_point) + reflection.can_use_special_ability = FALSE + var/mutable_appearance/my_appearance = new(src) + reflection.appearance = my_appearance + my_copies.Add(reflection) + + var/d = pick(possible_directions) + possible_directions -= d + var/turf/new_position = get_step(spawn_point, d) + if(reflection.is_can_jump_on(new_position)) + spawn(1) //ugh, i know, i know, it's bad. Animation + reflection.forceMove(new_position) + addtimer(CALLBACK(GLOBAL_PROC, .proc/qdel, reflection), 60 SECONDS) + loc = get_step(spawn_point, possible_directions[1]) //there must left last direction + special_ability_cooldown = world.time + ability_cooldown + playsound(spawn_point, 'sound/effects/cascade.ogg', 100, 1) + + +/mob/living/simple_animal/hostile/hivemind/phaser/closet_interaction() + var/obj/structure/closet/closed_closet = loc + if(closed_closet && istype(closed_closet) && closed_closet.welded) + phase_jump(closed_closet.loc) + + +/mob/living/simple_animal/hostile/hivemind/phaser/death() + if(my_copies.len) + for(var/mob/living/simple_animal/hostile/hivemind/phaser/My_copy in my_copies) + qdel(My_copy) + ..() + gibs(loc, null, /obj/gibspawner/human) + qdel(src) +*/ diff --git a/mods/hivemind/code/objects.dm b/mods/hivemind/code/objects.dm new file mode 100644 index 0000000000000..ceed43713762d --- /dev/null +++ b/mods/hivemind/code/objects.dm @@ -0,0 +1,22 @@ +//Hivemind special objects stored here, like projectiles, wreckages or artifacts + + +//toxic shot, turret's ability use it +/obj/item/projectile/goo + name = "toxic goo" + icon = 'mods/hivemind/icons/hivemind_machines.dmi' + icon_state = "goo_proj" + damage = 15 + damage_type = DAMAGE_BURN + step_delay = 2 + + +/obj/item/projectile/goo/on_hit(atom/target, blocked = 0) + . = ..() + if(istype(target, /mob/living) && !istype(target, /mob/living/silicon) && !blocked) + var/mob/living/L = target + L.apply_damage(10, DAMAGE_TOXIN) + if(!(locate(/obj/decal/cleanable/spiderling_remains) in target.loc)) + var/obj/decal/cleanable/spiderling_remains/goo = new /obj/decal/cleanable/spiderling_remains(target.loc) + goo.name = "green goo" + goo.desc = "An unidentifiable liquid. It smells awful." diff --git a/mods/hivemind/code/overrides.dm b/mods/hivemind/code/overrides.dm new file mode 100644 index 0000000000000..3a71ec0cf6de8 --- /dev/null +++ b/mods/hivemind/code/overrides.dm @@ -0,0 +1,68 @@ +// Hivemind wireweeds +/datum/seed/wires + name = "wires" + seed_name = "strange wires" + display_name = "strange wires" + seed_noun = "wires" + force_layer = 3 + chems = list("fuel" = list(1,5)) + +/datum/seed/wires/New() + ..() + set_trait(TRAIT_IMMUTABLE,1) + set_trait(TRAIT_PLANT_COLOUR,null) + set_trait(TRAIT_YIELD,-1) + set_trait(TRAIT_SPREAD,3) + set_trait(TRAIT_POTENCY,50) + +// У меня большие вопросы ко всей части ниже. Помимо того, что мы добавляем анимацию распространения и диры мы делаем... что? - LordNest + +/obj/vine/proc/life() + var/turf/simulated/T = get_turf(src) + var/obj/vine/vine + if(istype(T)) + health_current -= seed.handle_environment(T,T.return_air(),null,1) + if(health_current < health_max) + //Plants can grow through closed airlocks, but more slowly, since they have to force metal to make space + var/obj/machinery/door/D = (locate(/obj/machinery/door) in loc) + if (D) + health_current += rand(0,0.5) + else + health_current += rand(1,2.5) + update_icon() + if(health_current > health_max) + health_current = health_max + else if(health_current == health_max && !vine) // && (seed.type != /datum/seed/mushroom/maintshroom) - у нас этих грибов нет вроде, это глоукэпы емнип - LordNest + vine = new(T,seed) + vine.dir = src.dir + vine.transform = src.transform + vine.growth = seed.get_trait(TRAIT_MATURATION)-1 + vine.update_icon() + if(growth_type==0) //Vines do not become invisible. + invisibility = INVISIBILITY_MAXIMUM + else + vine.layer = layer + 0.1 + + +/obj/vine/proc/spread() + //spread to 1-3 adjacent turfs depending on yield trait. + var/max_spread = between(1, round(seed.get_trait(TRAIT_YIELD)*3/14), 3) + + for(var/i in 1 to max_spread) + if(prob(spread_chance)) + sleep(rand(3,5)) + if(!get_neighbors.length(1)) + break + var/turf/target_turf = pick(get_neighbors) + target_turf = connecting_turfs(target_turf, loc) + var/obj/vine/child = new type(get_turf(src),seed,src) + after_spread(child, target_turf) + // Update neighboring squares. + for(var/obj/vine/neighbor in range(1,target_turf)) + neighbor.get_neighbors -= target_turf + + +//after creation act +//by default, there goes an animation code +/obj/vine/proc/after_spread(obj/vine/child, turf/target_turf) + spawn(1) // This should do a little bit of animation. diff --git a/mods/hivemind/code/wires.dm b/mods/hivemind/code/wires.dm new file mode 100644 index 0000000000000..53865097fd421 --- /dev/null +++ b/mods/hivemind/code/wires.dm @@ -0,0 +1,322 @@ +//Wireweeds are created by the AI's nanites to spread its connectivity through the ship. +//When they reach any machine, they annihilate them and re-purpose them to the AI's needs. They are the 'hands' of our rogue AI. + +/obj/vine/hivemind + layer = 2 + health_max = 80 //we are a little bit durable + var/list/killer_reagents = list("pacid", "sacid", "hclacid", "thermite") + //internals + var/obj/machinery/hivemind_machine/node/master_node + var/list/wires_connections = list("0", "0", "0", "0") + + +/obj/vine/hivemind/New() + ..() + icon = 'mods/hivemind/icons/hivemind_obj.dmi' + spawn(2) + update_neighbors() + + +/obj/vine/hivemind/Destroy() + if(master_node) + master_node.my_wireweeds.Remove(src) + return ..() + + +/obj/vine/hivemind/after_spread(obj/vine/child, turf/target_turf) + if(master_node) + master_node.add_wireweed(child) + spawn(1) + child.dir = get_dir(loc, target_turf) //actually this means nothing for wires, but need for animation + flick("spread_anim", child) + child.forceMove(target_turf) + update_icon() + +// Насильно обновляем соседей - LordNest +/obj/vine/proc/update_neighbors(location = loc) + for (var/dir in GLOB.cardinal) + var/obj/vine/hivemind/L = locate(/obj/vine/hivemind/, get_step(location, dir)) + if(L) + L.update_icon() + +/obj/vine/hivemind/proc/try_to_assimilate() + if(hive_mind_ai && master_node) + for(var/obj/machinery/machine_on_my_tile in loc) + var/can_assimilate = TRUE + + //whitelist check + if(is_type_in_list(machine_on_my_tile, hive_mind_ai.restricted_machineries)) + can_assimilate = FALSE + + //assimilation is slow process, so it's take some time + //there we use our failure chance. Then it lower, then faster hivemind learn how to properly assimilate it + if(can_assimilate && prob(hive_mind_ai.failure_chance)) + can_assimilate = FALSE + anim_shake(machine_on_my_tile) + return + + //only one machine per turf + if(can_assimilate && !locate(/obj/machinery/hivemind_machine) in loc) + assimilate(machine_on_my_tile) + //other will be... merged + else if(can_assimilate) + qdel(machine_on_my_tile) + + //modular computers handling + var/obj/item/modular_computer/mod_comp = locate() in loc + if(mod_comp) + assimilate(mod_comp) + + //dead bodies handling + for(var/mob/living/dead_body in loc) + if(dead_body.stat == DEAD) + assimilate(dead_body) + + +/obj/vine/hivemind/update_neighbors() + ..() + update_connections() + update_icon() + + +/obj/vine/hivemind/spread() + if(hive_mind_ai && master_node) + ..() + + +/obj/vine/hivemind/life() + if(hive_mind_ai && master_node) + try_to_assimilate() + chem_handler() + else + //slow vanishing after node death + health_current -= 10 + alpha = 255 * health_current/health_max + update_health() + + +/obj/vine/hivemind/is_mature() + return TRUE + + +/obj/vine/hivemind/update_icon() + overlays.Cut() + var/image/I + for(var/i = 1 to 4) + I = image(src.icon, "wires[wires_connections[i]]", dir = 1<<(i-1)) + overlays += I + for(var/d in GLOB.cardinal) + var/turf/T = get_step(loc, d) + if((locate(/obj/structure/window) in T) || istype(T, /turf/simulated/wall)) + var/image/wall_hug_overlay = image(icon = src.icon, icon_state = "wall_hug", dir = d) + if (T.x < x) + wall_hug_overlay.pixel_x -= 32 + else if (T.x > x) + wall_hug_overlay.pixel_x += 32 + if (T.y < y) + wall_hug_overlay.pixel_y -= 32 + else if (T.y > y) + wall_hug_overlay.pixel_y += 32 + wall_hug_overlay.layer = ABOVE_WINDOW_LAYER + overlays += wall_hug_overlay + + +/obj/vine/hivemind/proc/update_connections(propagate = 0) + var/list/dirs = list() + for(var/obj/vine/hivemind/W in range(1, src) - src) + if(propagate) + W.update_connections() + W.update_icon() + dirs += get_dir(src, W) + + wires_connections = dirs_to_corner_states(dirs) + + +/obj/vine/hivemind/door_interaction(obj/machinery/door/airlock/door) + if(!door || !istype(door)) + return FALSE + + //if our door isn't broken, we will try to break open. We can do only one action per call + if(!(door.stat & MACHINE_BROKEN_GENERIC)) + anim_shake(door) + //first, we open our panel to give our wireweeds access to exposed airlock's electronics + if(!door.p_open) + if(prob(20)) + door.p_open = TRUE + return FALSE + //but if airlock is welded, we just shake it like we rummage inside + if(door.welded) + return FALSE + //if panel opened, we begin to destruct it from inside of airlock + if(door.p_open) + //bolts are down? Our wireweeds infest electronics, so this isn't a problem cause it part of us + if(door.locked) + if(prob(50)) + door.unlock() + return FALSE + //and then, if airlock is closed, we begin destroy it electronics + if(door.density) + door.damage_health(rand(15, 50)) + return FALSE + + return TRUE + + +/obj/vine/hivemind/CanPass(atom/movable/mover, turf/target, height=0, air_group=0) + if(mover == src) + if(target.density) + return FALSE + + if(locate(/obj/structure) in target) + for(var/obj/structure/S in target) + if(S.density) + return FALSE + + if(locate(/obj/machinery/door) in target) + return FALSE + + return TRUE + else + return ..() + + + +//What a pity that we haven't some kind proc as special library to use it somewhere +/obj/vine/hivemind/proc/anim_shake(atom/thing) + var/init_px = thing.pixel_x + var/shake_dir = pick(-1, 1) + animate(thing, transform=turn(matrix(), 8*shake_dir), pixel_x=init_px + 2*shake_dir, time=1) + animate(transform=null, pixel_x=init_px, time=6, easing=ELASTIC_EASING) + + +//assimilation process +/obj/vine/hivemind/proc/assimilate(var/atom/subject) + if(istype(subject, /obj/machinery) || istype(subject, /obj/item/modular_computer)) + if(prob(hive_mind_ai.failure_chance)) + //critical failure! This machine would be a dummy, which means - without any ability + //let's make an infested sprite + var/obj/machinery/hivemind_machine/new_machine = new (loc) + var/icon/infected_icon = new('mods/hivemind/icons/hivemind_machines.dmi', icon_state = "wires-[rand(1, 3)]") + var/icon/new_icon = new(subject.icon, icon_state = subject.icon_state, dir = subject.dir) + new_icon.Blend(infected_icon, ICON_OVERLAY) + new_machine.icon = new_icon + var/prefix = pick("strange", "interesting", "marvelous", "unusual") + new_machine.name = "[prefix] [subject.name]" + else + //of course, here we have a very little chance to spawn him, our mini-boss + if(prob(1)) + new /mob/living/simple_animal/hostile/hivemind/mechiver(loc) + qdel(subject) + return + else + var/picked_machine + var/list/possible_machines = subtypesof(/obj/machinery/hivemind_machine) + + if(hive_mind_ai.hives.len < 10) + if(hive_mind_ai.evo_points < (hive_mind_ai.hives.len * 100)) //one hive per 100 EP + possible_machines -= /obj/machinery/hivemind_machine/node + else + //we make new nodes asap, cause it has higher priority to survive, so we force it here + picked_machine = /obj/machinery/hivemind_machine/node + + //here we compare hivemind's EP with machine's required value + for(var/machine_path in possible_machines) + if(hive_mind_ai.evo_points <= hive_mind_ai.EP_price_list[machine_path]) + possible_machines.Remove(machine_path) + + if(!picked_machine) + picked_machine = pick(possible_machines) + var/obj/machinery/hivemind_machine/new_machine = new picked_machine(loc) + new_machine.update_icon() + + if(istype(subject, /mob/living) && !istype(subject, /mob/living/simple_animal/hostile/hivemind)) + //human bodies + if(istype(subject, /mob/living/carbon/human)) + var/mob/living/L = subject + for(var/obj/item/W in L) + L.drop_from_inventory(W) + var/M = pick(/mob/living/simple_animal/hostile/hivemind/himan, /mob/living/simple_animal/hostile/hivemind/phaser) + new M(loc) + //robot corpses + else if(istype(subject, /mob/living/silicon)) + new /mob/living/simple_animal/hostile/hivemind/hiborg(loc) + //other dead bodies + else + var/mob/living/simple_animal/hostile/hivemind/resurrected/transformed_mob = new(loc) + transformed_mob.take_appearance(subject) + + qdel(subject) + + +////////////////////////////////////////////////////////////////// +/////////////////////////>RESPONSE CODE= 10) + health_current -= rand(W.force/2, W.force) //hm, maybe make damage based on player's robust stat? + user.visible_message(SPAN_DANGER("[user] slices [src]."), SPAN_DANGER("You slice [src].")) + else + user.visible_message(SPAN_DANGER("[user] tries to slice [src] with [W], but seems to do nothing."), + SPAN_DANGER("You try to slice [src], but it's useless!")) + update_health() +*/ + +/obj/vine/hivemind/use_weapon(obj/item/weapon/W, mob/user, list/click_params) + . = ..() + user.setClickCooldown(DEFAULT_ATTACK_COOLDOWN) + + if(W.sharp && W.force >= 30) + user.visible_message(SPAN_DANGER("[user] cuts down [src]."), SPAN_DANGER("You cut down [src].")) + kill_health() + return + if(W.sharp && W.force >= 10) + health_current -= rand(W.force/2, W.force) //hm, maybe make damage based on player's robust stat? + user.visible_message(SPAN_DANGER("[user] slices [src]."), SPAN_DANGER("You slice [src].")) + else + user.visible_message(SPAN_DANGER("[user] tries to slice [src] with [W], but seems to do nothing."), + SPAN_DANGER("You try to slice [src], but it's useless!")) + + return ..() + +//fire is effective, but there need some time to melt the covering +/obj/vine/hivemind/fire_act() + health_current -= rand(1, 4) + update_health() + + +//emp is effective too +//it causes electricity failure, so our wireweeds just blowing up inside, what makes them fragile +/obj/vine/hivemind/emp_act(severity) + if(severity) + kill_health() + + +//Some acid and there's no problem +/obj/vine/hivemind/proc/chem_handler() + for(var/obj/effect/smoke/chem/smoke in loc) + for(var/lethal in killer_reagents) + if(smoke.reagents.has_reagent(lethal)) + kill_health() + return diff --git a/mods/hivemind/icons/hivemind.dmi b/mods/hivemind/icons/hivemind.dmi new file mode 100644 index 0000000000000..0011cab69a1b2 Binary files /dev/null and b/mods/hivemind/icons/hivemind.dmi differ diff --git a/mods/hivemind/icons/hivemind_machines.dmi b/mods/hivemind/icons/hivemind_machines.dmi new file mode 100644 index 0000000000000..02c4b3cf242c7 Binary files /dev/null and b/mods/hivemind/icons/hivemind_machines.dmi differ diff --git a/mods/hivemind/icons/hivemind_obj.dmi b/mods/hivemind/icons/hivemind_obj.dmi new file mode 100644 index 0000000000000..532df325b8339 Binary files /dev/null and b/mods/hivemind/icons/hivemind_obj.dmi differ diff --git a/mods/hivemind/sounds/insect_battle_bite.ogg b/mods/hivemind/sounds/insect_battle_bite.ogg new file mode 100644 index 0000000000000..f5c751218c2fd Binary files /dev/null and b/mods/hivemind/sounds/insect_battle_bite.ogg differ diff --git a/mods/hivemind/sounds/insect_battle_screeching.ogg b/mods/hivemind/sounds/insect_battle_screeching.ogg new file mode 100644 index 0000000000000..319223d53d80b Binary files /dev/null and b/mods/hivemind/sounds/insect_battle_screeching.ogg differ