diff --git a/baystation12.dme b/baystation12.dme
index e49f506a2f4e7..13ca0278f8dc1 100644
--- a/baystation12.dme
+++ b/baystation12.dme
@@ -492,6 +492,7 @@
#include "code\datums\wires\fabricator.dm"
#include "code\datums\wires\nuclearbomb.dm"
#include "code\datums\wires\particle_accelerator.dm"
+#include "code\datums\wires\quantum_pads.dm"
#include "code\datums\wires\radio.dm"
#include "code\datums\wires\robot.dm"
#include "code\datums\wires\shield_generator.dm"
@@ -1744,6 +1745,7 @@
#include "code\modules\error_handler\error_handler.dm"
#include "code\modules\error_handler\error_reporting.dm"
#include "code\modules\error_handler\error_viewer.dm"
+#include "code\modules\events\abominable_infestation.dm"
#include "code\modules\events\apc_damage.dm"
#include "code\modules\events\blob.dm"
#include "code\modules\events\brain_expansion.dm"
@@ -1980,6 +1982,7 @@
#include "code\modules\mining\mine_turfs.dm"
#include "code\modules\mining\satchel_ore_boxdm.dm"
#include "code\modules\mining\drilling\drill.dm"
+#include "code\modules\mining\drilling\drill_upgrades.dm"
#include "code\modules\mining\machinery\_mineral.dm"
#include "code\modules\mining\machinery\mineral_console.dm"
#include "code\modules\mining\machinery\mineral_processor.dm"
@@ -2006,6 +2009,10 @@
#include "code\modules\mob\grab\grab_datum.dm"
#include "code\modules\mob\grab\grab_object.dm"
#include "code\modules\mob\grab\grab_readme.dm"
+#include "code\modules\mob\grab\abnomination\abom_agressive.dm"
+#include "code\modules\mob\grab\abnomination\abom_kill.dm"
+#include "code\modules\mob\grab\abnomination\abom_passive.dm"
+#include "code\modules\mob\grab\abnomination\grab_abom.dm"
#include "code\modules\mob\grab\nab\grab_nab.dm"
#include "code\modules\mob\grab\nab\nab_aggressive.dm"
#include "code\modules\mob\grab\nab\nab_kill.dm"
@@ -2298,6 +2305,14 @@
#include "code\modules\mob\living\simple_animal\hostile\hivebot\hivebot.dm"
#include "code\modules\mob\living\simple_animal\hostile\hivebot\ranged.dm"
#include "code\modules\mob\living\simple_animal\hostile\hivebot\tank.dm"
+#include "code\modules\mob\living\simple_animal\hostile\infestation\_infestation.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\rhino.dm"
+#include "code\modules\mob\living\simple_animal\hostile\infestation\spitter.dm"
#include "code\modules\mob\living\simple_animal\hostile\retaliate\clown.dm"
#include "code\modules\mob\living\simple_animal\hostile\retaliate\drone.dm"
#include "code\modules\mob\living\simple_animal\hostile\retaliate\exoplanet.dm"
@@ -2527,6 +2542,7 @@
#include "code\modules\organs\internal\lungs.dm"
#include "code\modules\organs\internal\stomach.dm"
#include "code\modules\organs\internal\voice.dm"
+#include "code\modules\organs\internal\species\abomination.dm"
#include "code\modules\organs\internal\species\adherent.dm"
#include "code\modules\organs\internal\species\blueforged.dm"
#include "code\modules\organs\internal\species\borer.dm"
@@ -2570,6 +2586,7 @@
#include "code\modules\overmap\exoplanets\planet_types\chlorine.dm"
#include "code\modules\overmap\exoplanets\planet_types\desert.dm"
#include "code\modules\overmap\exoplanets\planet_types\grass.dm"
+#include "code\modules\overmap\exoplanets\planet_types\infested.dm"
#include "code\modules\overmap\exoplanets\planet_types\shrouded.dm"
#include "code\modules\overmap\exoplanets\planet_types\snow.dm"
#include "code\modules\overmap\exoplanets\planet_types\volcanic.dm"
@@ -2927,6 +2944,7 @@
#include "code\modules\species\species_hud.dm"
#include "code\modules\species\species_random.dm"
#include "code\modules\species\species_shapeshift.dm"
+#include "code\modules\species\outsider\abomination.dm"
#include "code\modules\species\outsider\random.dm"
#include "code\modules\species\outsider\shadow.dm"
#include "code\modules\species\outsider\starlight.dm"
@@ -3263,10 +3281,13 @@
#include "proxima\code\game\machinery\remote_weapon.dm"
#include "proxima\code\game\machinery\factory\blueprint.dm"
#include "proxima\code\game\machinery\factory\factory.dm"
+#include "proxima\code\game\machinery\factory\quantum_pads.dm"
#include "proxima\code\game\machinery\factory\blueprints\generic.dm"
#include "proxima\code\game\objects\effects\particles\snow.dm"
+#include "proxima\code\game\objects\items\bouquet.dm"
#include "proxima\code\game\objects\items\circuits_remote.dm"
#include "proxima\code\game\objects\items\containers.dm"
+#include "proxima\code\game\objects\items\mgsbox.dm"
#include "proxima\code\game\objects\items\posters.dm"
#include "proxima\code\game\objects\items\remote_weapon_ammo.dm"
#include "proxima\code\game\objects\items\toys.dm"
diff --git a/code/__defines/flags.dm b/code/__defines/flags.dm
index 3eaa128a76887..2aa913e3b7ccb 100644
--- a/code/__defines/flags.dm
+++ b/code/__defines/flags.dm
@@ -57,3 +57,9 @@ GLOBAL_LIST_INIT(bitflags, list(1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 204
#define TANK_FLAG_FORCED FLAG(1)
#define TANK_FLAG_LEAKING FLAG(2)
#define TANK_FLAG_WIRED FLAG(3)
+
+// Sector Flags.
+#define OVERMAP_SECTOR_BASE 0x0001 // Whether or not this sector is a starting sector. Z levels contained in this sector are added to station_levels
+#define OVERMAP_SECTOR_KNOWN 0x0002 // Makes the sector show up on nav computers
+#define OVERMAP_SECTOR_IN_SPACE 0x0004 // If the sector can be accessed by drifting off the map edge
+#define OVERMAP_SECTOR_UNTARGETABLE 0x0008 // If the sector is untargetable by missiles
diff --git a/code/__defines/guns.dm b/code/__defines/guns.dm
index b58e3cc83ce0a..9b49748ec5550 100644
--- a/code/__defines/guns.dm
+++ b/code/__defines/guns.dm
@@ -7,6 +7,7 @@
#define CALIBER_PISTOL_FAST "6mmR"
#define CALIBER_RIFLE "5mmR"
+#define CALIBER_CARABINE "6.5mmR"
#define CALIBER_RIFLE_MILITARY "7mmR"
#define CALIBER_ANTIMATERIAL "15mmR"
#define CALIBER_ANTIMATERIAL_SMALL "12mmR" //Порт с инфинити. Калибр КорпоАМР винтовки
diff --git a/code/__defines/mobs.dm b/code/__defines/mobs.dm
index 34c594c5afbe7..1d2498a962195 100644
--- a/code/__defines/mobs.dm
+++ b/code/__defines/mobs.dm
@@ -19,6 +19,7 @@
#define GRAB_NORMAL "normal"
#define GRAB_NAB "nab"
#define GRAB_NAB_SPECIAL "special nab"
+#define GRAB_ABOMINATION "terrifying grab"
// Grab levels.
#define NORM_PASSIVE "normal passive"
@@ -31,6 +32,10 @@
#define NAB_AGGRESSIVE "nab aggressive"
#define NAB_KILL "nab kill"
+#define GRAB_ABOMINATION_PASSIVE "terrifying grab passive"
+#define GRAB_ABOMINATION_AGGRESSIVE "terrifying grab aggressive"
+#define GRAB_ABOMINATION_KILL "terrifying grab kill"
+
#define BORGMESON FLAG(0)
#define BORGTHERM FLAG(1)
#define BORGXRAY FLAG(2)
@@ -222,6 +227,7 @@
#define BP_ANCHOR "anchoring ligament"
#define BP_PHORON "phoron filter"
#define BP_ACETONE "acetone reactor"
+#define BP_LARVA "larvae storage"
// Vox bits.
#define BP_HINDTONGUE "hindtongue"
@@ -378,10 +384,11 @@
#define SPECIES_MONKEY "Monkey"
#define SPECIES_NABBER "giant armoured serpentid"
#define SPECIES_FBP "Full Body Prosthesis" //just for xeno-panel //proxima
+#define SPECIES_ABOMINATION "Abomination"
#define UNRESTRICTED_SPECIES list(SPECIES_HUMAN, SPECIES_DIONA, SPECIES_IPC, SPECIES_UNATHI, SPECIES_YEOSA, SPECIES_SKRELL, SPECIES_TRITONIAN, SPECIES_SPACER, SPECIES_VATGROWN, SPECIES_GRAVWORLDER, SPECIES_MULE, SPECIES_SHELL)
-#define RESTRICTED_SPECIES list(SPECIES_VOX, SPECIES_ALIEN, SPECIES_GOLEM)
-#define HUMAN_SPECIES list(SPECIES_HUMAN, SPECIES_VATGROWN, SPECIES_SPACER, SPECIES_GRAVWORLDER, SPECIES_MULE)
+#define RESTRICTED_SPECIES list(SPECIES_VOX, SPECIES_ALIEN, SPECIES_GOLEM, SPECIES_ABOMINATION)
+#define HUMAN_SPECIES list(SPECIES_HUMAN, SPECIES_VATGROWN, SPECIES_SPACER, SPECIES_GRAVWORLDER, SPECIES_MULE, SPECIES_ABOMINATION)
#define SOUNDED_SPECIES list(SPECIES_HUMAN, SPECIES_VATGROWN, SPECIES_SPACER, SPECIES_TRITONIAN, SPECIES_GRAVWORLDER, SPECIES_MULE, SPECIES_UNATHI, SPECIES_YEOSA, SPECIES_SKRELL, SPECIES_SHELL)
#define SURGERY_CLOSED 0
@@ -486,3 +493,6 @@
#define DO_INCAPACITATED (-3)
#define FAKE_INVIS_ALPHA_THRESHOLD 127 // If something's alpha var is at or below this number, certain things will pretend it is invisible.
+
+
+#define SLEEP_CHECK_DEATH(X) sleep(X); if(QDELETED(src) || stat == DEAD) return;
diff --git a/code/__defines/overmap.dm b/code/__defines/overmap.dm
index b2dabb5f93a0d..6f0b673da2c86 100644
--- a/code/__defines/overmap.dm
+++ b/code/__defines/overmap.dm
@@ -12,3 +12,10 @@
#define OVERMAP_WEAKNESS_MINING 4
#define OVERMAP_WEAKNESS_EXPLOSIVE 8
#define OVERMAP_WEAKNESS_DROPPOD 16
+#define OVERMAP_WEAKNESS_ODST 32
+
+#define TARGET_SHIP 0
+#define TARGET_MISSILE 1
+#define TARGET_PLANET 2
+#define TARGET_PLANETCOORD 4
+#define TARGET_POINT 8
diff --git a/code/datums/wires/quantum_pads.dm b/code/datums/wires/quantum_pads.dm
new file mode 100644
index 0000000000000..0db57bed5c258
--- /dev/null
+++ b/code/datums/wires/quantum_pads.dm
@@ -0,0 +1,24 @@
+/datum/wires/quantumpad
+ holder_type = /obj/machinery/quantumpad
+ wire_count = 1
+ descriptions = list(
+ new /datum/wire_description(WIRE_TRIGGER, "This wire seems to be activating the teleportation mechanism.")
+ )
+
+var/const/WIRE_TRIGGER = 1
+
+/datum/wires/quantumpad/proc/trigger()
+ var/obj/machinery/quantumpad/QP = holder
+ QP.physical_attack_hand()
+ return
+
+/datum/wires/quantumpad/UpdatePulsed(index)
+ switch(index)
+ if(WIRE_TRIGGER)
+ trigger()
+
+/datum/wires/quantumpad/CanUse(mob/living/L)
+ var/obj/machinery/quantumpad/QP = holder
+ if(QP.panel_open)
+ return TRUE
+ return FALSE
diff --git a/code/game/atoms_movable.dm b/code/game/atoms_movable.dm
index 5e79b22653d85..e20639ae92d7d 100644
--- a/code/game/atoms_movable.dm
+++ b/code/game/atoms_movable.dm
@@ -241,3 +241,7 @@
/atom/movable/proc/get_bullet_impact_effect_type()
return BULLET_IMPACT_NONE
+
+/// Handles special effects of item being removed from "implants" of a mob
+/atom/movable/proc/ImplantRemoval(mob/user)
+ return
diff --git a/code/game/objects/items/weapons/implants/implant.dm b/code/game/objects/items/weapons/implants/implant.dm
index c6e7bd4bdc0e4..5f0943c77d6e9 100644
--- a/code/game/objects/items/weapons/implants/implant.dm
+++ b/code/game/objects/items/weapons/implants/implant.dm
@@ -69,13 +69,6 @@
return TRUE
-/obj/item/implant/proc/removed()
- imp_in = null
- if(part)
- part.implants -= src
- part = null
- implanted = 0
-
//Called in surgery when incision is retracted open / ribs are opened - basically before you can take implant out
/obj/item/implant/proc/exposed()
return
diff --git a/code/game/objects/items/weapons/implants/implants/death_alarm.dm b/code/game/objects/items/weapons/implants/implants/death_alarm.dm
index 8abf8f0dc7201..7b78e8933c6e8 100644
--- a/code/game/objects/items/weapons/implants/implants/death_alarm.dm
+++ b/code/game/objects/items/weapons/implants/implants/death_alarm.dm
@@ -66,7 +66,7 @@
START_PROCESSING(SSobj, src)
return TRUE
-/obj/item/implant/death_alarm/removed()
+/obj/item/implant/death_alarm/ImplantRemoval()
..()
STOP_PROCESSING(SSobj, src)
diff --git a/code/game/objects/items/weapons/implants/implants/explosive.dm b/code/game/objects/items/weapons/implants/implants/explosive.dm
index 31e47db1fc065..639dfdcf3ab9b 100644
--- a/code/game/objects/items/weapons/implants/implants/explosive.dm
+++ b/code/game/objects/items/weapons/implants/implants/explosive.dm
@@ -160,7 +160,7 @@
return TRUE
/obj/item/implant/explosive/Destroy()
- removed()
+ ImplantRemoval()
GLOB.listening_objects -= src
return ..()
diff --git a/code/game/objects/items/weapons/implants/implants/imprinting.dm b/code/game/objects/items/weapons/implants/implants/imprinting.dm
index 9607ddc2b367e..372b33b64ee7c 100644
--- a/code/game/objects/items/weapons/implants/implants/imprinting.dm
+++ b/code/game/objects/items/weapons/implants/implants/imprinting.dm
@@ -85,7 +85,7 @@
to_chat(imp_in, instruction)
addtimer(CALLBACK(src,.proc/activate),3000,(TIMER_UNIQUE|TIMER_OVERRIDE))
-/obj/item/implant/imprinting/removed()
+/obj/item/implant/imprinting/ImplantRemoval()
if(brainwashing && !malfunction)
to_chat(imp_in,"A wave of nausea comes over you.
You are no longer so sure of those beliefs you've had...")
..()
diff --git a/code/game/objects/items/weapons/implants/implants/translator.dm b/code/game/objects/items/weapons/implants/implants/translator.dm
index 764fe79944076..d3fa6eb743aa3 100644
--- a/code/game/objects/items/weapons/implants/implants/translator.dm
+++ b/code/game/objects/items/weapons/implants/implants/translator.dm
@@ -34,7 +34,7 @@
return TRUE
/obj/item/implant/translator/Destroy()
- removed()
+ ImplantRemoval()
GLOB.listening_objects -= src
return ..()
diff --git a/code/game/objects/objs.dm b/code/game/objects/objs.dm
index 1145888db929f..f807f667364a2 100644
--- a/code/game/objects/objs.dm
+++ b/code/game/objects/objs.dm
@@ -204,3 +204,6 @@
*/
/obj/proc/is_safe_to_step(mob/living/L)
return TRUE
+
+/obj/proc/get_additional_speed_decrease()
+ return between(0, src.w_class, ITEM_SIZE_GARGANTUAN) / 5
diff --git a/code/game/sound.dm b/code/game/sound.dm
index d422515de3c05..fb7ed0cc15109 100644
--- a/code/game/sound.dm
+++ b/code/game/sound.dm
@@ -171,6 +171,22 @@ var/global/const/FALLOFF_SOUNDS = 0.5
sound_to(src, S)
+/proc/sound_to_playing_players(soundin, volume = 100, vary = FALSE, frequency = 0, falloff, is_global = FALSE, ignore_pressure = FALSE)
+ for(var/m in GLOB.player_list)
+ if(ismob(m) && !isnewplayer(m))
+ var/mob/M = m
+ M.playsound_local(get_turf(M), soundin, volume, vary, frequency, falloff, is_global, ignore_pressure)
+
+/proc/sound_to_playing_players_on_level(soundin, volume = 100, vary = FALSE, frequency = 0, falloff, is_global = FALSE, ignore_pressure = FALSE, zlevel)
+ for(var/m in GLOB.player_list)
+ if(ismob(m) && !isnewplayer(m))
+ var/mob/M = m
+ if(!islist(zlevel))
+ zlevel = list(zlevel)
+ if((M.loc.z in zlevel) && M.client)
+ M.playsound_local(get_turf(M), soundin, volume, vary, frequency, falloff, is_global, ignore_pressure)
+
+
/client/proc/playtitlemusic()
if (get_preference_value(/datum/client_preference/play_lobby_music) == GLOB.PREF_YES)
sound_to(src, GLOB.using_map.lobby_track.get_sound())
diff --git a/code/game/turfs/flooring/flooring.dm b/code/game/turfs/flooring/flooring.dm
index c6f14f4d0ccf1..d2967ce9c368e 100644
--- a/code/game/turfs/flooring/flooring.dm
+++ b/code/game/turfs/flooring/flooring.dm
@@ -402,3 +402,8 @@
wall_smooth = SMOOTH_NONE
space_smooth = SMOOTH_NONE
height = -FLUID_OVER_MOB_HEAD * 2
+
+/decl/flooring/flesh/infested
+ name = "pulsating flesh"
+ desc = "Disgusting flooring made out of flesh, bone, eyes, and various other human bits and pieces."
+ color = "#94404e"
diff --git a/code/modules/ai/ai_holder_communication.dm b/code/modules/ai/ai_holder_communication.dm
index 688231e5568e7..5ab074502b97d 100644
--- a/code/modules/ai/ai_holder_communication.dm
+++ b/code/modules/ai/ai_holder_communication.dm
@@ -109,15 +109,25 @@
switch(pick(comm_types))
if (COMM_SAY)
holder.ISay(pick(holder.say_list.speak))
+ PlayMobSound(holder.say_list.speak_sounds)
if (COMM_AUDIBLE_EMOTE)
holder.audible_emote(pick(holder.say_list.emote_hear))
+ PlayMobSound(holder.say_list.emote_hear_sounds)
if (COMM_VISUAL_EMOTE)
holder.visible_emote(pick(holder.say_list.emote_see))
+ PlayMobSound(holder.say_list.emote_see_sounds)
#undef COMM_SAY
#undef COMM_AUDIBLE_EMOTE
#undef COMM_VISUAL_EMOTE
+// Simply plays a sound chosen out of a list
+/datum/ai_holder/proc/PlayMobSound(list/potential_sounds)
+ if(LAZYLEN(potential_sounds))
+ var/result = pickweight(holder.say_list.emote_see_sounds)
+ if(result != null)
+ playsound(holder, result, 50, 1)
+
// Handles the holder hearing a mob's say()
// Does nothing by default, override this proc for special behavior.
/datum/ai_holder/proc/on_hear_say(mob/living/speaker, message)
diff --git a/code/modules/ai/ai_holder_targeting.dm b/code/modules/ai/ai_holder_targeting.dm
index 5ac387dad6d9e..1ea2b9bb54177 100644
--- a/code/modules/ai/ai_holder_targeting.dm
+++ b/code/modules/ai/ai_holder_targeting.dm
@@ -10,7 +10,7 @@
var/atom/movable/preferred_target = null// If set, and if given the chance, we will always prefer to target this over other options.
var/turf/target_last_seen_turf = null // Where the mob last observed the target being, used if the target disappears but the mob wants to keep fighting.
- var/vision_range = 7 // How far the targeting system will look for things to kill. Note that values higher than 7 are 'offscreen' and might be unsporting.
+ var/vision_range = 8 // How far the targeting system will look for things to kill. Note that values higher than 7 are 'offscreen' and might be unsporting.
var/respect_alpha = TRUE // If true, mobs with a sufficently low alpha will be treated as invisible.
var/alpha_vision_threshold = FAKE_INVIS_ALPHA_THRESHOLD // Targets with an alpha less or equal to this will be considered invisible. Requires above var to be true.
diff --git a/code/modules/ai/say_list.dm b/code/modules/ai/say_list.dm
index 6e4a43ee6b9c4..bc90fe0c98d07 100644
--- a/code/modules/ai/say_list.dm
+++ b/code/modules/ai/say_list.dm
@@ -32,6 +32,11 @@
var/list/say_stand_down = list() // When the threatened thing goes away.
var/list/say_escalate = list() // When the threatened thing doesn't go away.
+// Lists belows are ASSOCIATIVE lists! Sound = Chance. If null is in there - it will not play sound when prompted
+ var/list/speak_sounds = list() // Sounds that can be played when anything from speak list is said
+ var/list/emote_hear_sounds = list() // Sounds that can be played when anything from emote_hear is performed
+ var/list/emote_see_sounds = list() // Sounds that can be played when anything from emote_see is performed
+
var/threaten_sound = null // Sound file played when the mob's AI calls threaten_target() for the first time.
var/stand_down_sound = null // Sound file played when the mob's AI loses sight of the threatened target.
diff --git a/code/modules/events/abominable_infestation.dm b/code/modules/events/abominable_infestation.dm
new file mode 100644
index 0000000000000..d103794090134
--- /dev/null
+++ b/code/modules/events/abominable_infestation.dm
@@ -0,0 +1,26 @@
+/datum/event/abominable_infestation
+ announceWhen = 30
+ var/spawncount = 1
+
+/datum/event/abominable_infestation/setup()
+ announceWhen = rand(announceWhen, announceWhen + 30)
+ spawncount = rand(2 * severity, 4 * severity)
+
+/datum/event/abominable_infestation/announce()
+ GLOB.using_map.unidentified_lifesigns_announcement()
+
+/datum/event/abominable_infestation/start()
+ var/list/vents = list()
+ for(var/obj/machinery/atmospherics/unary/vent_pump/temp_vent in SSmachines.machinery)
+ if(!temp_vent.welded && temp_vent.network && (temp_vent.loc.z in affecting_z))
+ if(temp_vent.network.normal_members.len > 50)
+ vents += temp_vent
+
+ while((spawncount >= 1) && LAZYLEN(vents))
+ var/obj/vent = pick(vents)
+ if(prob(33))
+ new /mob/living/simple_animal/hostile/infestation/larva/implant/implanter(get_turf(vent))
+ else
+ new /mob/living/simple_animal/hostile/infestation/larva(get_turf(vent))
+ vents -= vent
+ spawncount--
diff --git a/code/modules/events/event_container.dm b/code/modules/events/event_container.dm
index c0f46fdafc303..db8857455cb64 100644
--- a/code/modules/events/event_container.dm
+++ b/code/modules/events/event_container.dm
@@ -7,6 +7,7 @@
#define ASSIGNMENT_MEDICAL "Medical"
#define ASSIGNMENT_SCIENTIST "Scientist"
#define ASSIGNMENT_SECURITY "Security"
+#define ASSIGNMENT_EXPLORATION "Exploration"
#define ASSIGNMENT_PSYCHIATRIST "Psychiatrist"
var/global/list/severity_to_string = list(EVENT_LEVEL_MUNDANE = "Mundane", EVENT_LEVEL_MODERATE = "Moderate", EVENT_LEVEL_MAJOR = "Major", EVENT_LEVEL_EXO = "Exoplanet")
@@ -182,7 +183,8 @@ var/global/list/severity_to_string = list(EVENT_LEVEL_MUNDANE = "Mundane", EVENT
severity = EVENT_LEVEL_MAJOR
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),
+ new /datum/event_meta(EVENT_LEVEL_MAJOR, "Blob", /datum/event/blob, 0, list(ASSIGNMENT_ENGINEER = 20, ASSIGNMENT_MEDICAL = 10, ASSIGNMENT_SECURITY = 20, ASSIGNMENT_EXPLORATION = 10), 1),
+ new /datum/event_meta(EVENT_LEVEL_MAJOR, "Abominable Infestation", /datum/event/abominable_infestation,0, list(ASSIGNMENT_MEDICAL = 10, ASSIGNMENT_SECURITY = 20, ASSIGNMENT_EXPLORATION = 15), 1),
new /datum/event_meta/no_overmap(EVENT_LEVEL_MAJOR, "Carp Migration", /datum/event/carp_migration, 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),
@@ -208,4 +210,5 @@ var/global/list/severity_to_string = list(EVENT_LEVEL_MUNDANE = "Mundane", EVENT
#undef ASSIGNMENT_MEDICAL
#undef ASSIGNMENT_SCIENTIST
#undef ASSIGNMENT_SECURITY
+#undef ASSIGNMENT_EXPLORATION
#undef ASSIGNMENT_PSYCHIATRIST
diff --git a/code/modules/fabrication/designs/general/designs_arms_ammo.dm b/code/modules/fabrication/designs/general/designs_arms_ammo.dm
index 9ffccef77ddb0..06233109454d2 100644
--- a/code/modules/fabrication/designs/general/designs_arms_ammo.dm
+++ b/code/modules/fabrication/designs/general/designs_arms_ammo.dm
@@ -138,3 +138,7 @@
/datum/fabricator_recipe/arms_ammo/hidden/rifleinternalclip
name = "ammunition (rifle internal clip)"
path = /obj/item/ammo_magazine/iclipr
+
+/datum/fabricator_recipe/arms_ammo/hidden/carabinemagazine
+ name = "ammunition (spec.ops carabine magazine)"
+ path = /obj/item/ammo_magazine/carabine_rifle
diff --git a/code/modules/materials/recipes_storage.dm b/code/modules/materials/recipes_storage.dm
index a9f8293f6e540..a00afb64a058d 100644
--- a/code/modules/materials/recipes_storage.dm
+++ b/code/modules/materials/recipes_storage.dm
@@ -8,6 +8,26 @@
result_type = /obj/item/storage/box/large
req_amount = 2
+/datum/stack_recipe/box/larger
+ title = "cardboard box"
+ result_type = /obj/item/storage/mgsbox
+ req_amount = 5
+
+/datum/stack_recipe/box/larger/med
+ title = "cardboard box with medical symbols"
+ result_type = /obj/item/storage/mgsbox/med
+ req_amount = 5
+
+/datum/stack_recipe/box/larger/lpa
+ title = "cardboard box with strange symbols"
+ result_type = /obj/item/storage/mgsbox/lpa
+ req_amount = 5
+
+/datum/stack_recipe/box/larger/clear
+ title = "cardboard box without symbols"
+ result_type = /obj/item/storage/mgsbox/clear
+ req_amount = 5
+
/datum/stack_recipe/box/donut
title = "donut box"
result_type = /obj/item/storage/box/donut/empty
diff --git a/code/modules/mining/drilling/drill.dm b/code/modules/mining/drilling/drill.dm
index 48a542305cf4a..4db875e3ef5cc 100644
--- a/code/modules/mining/drilling/drill.dm
+++ b/code/modules/mining/drilling/drill.dm
@@ -1,3 +1,9 @@
+/// Maximum amount of upgrades that can be installed
+#define MAX_DRILL_UPGRADES 3
+// Upgrade flags
+#define DRILL_AUTOMATIC (1<1)
+
+
/obj/machinery/mining
icon = 'icons/obj/mining_drill.dmi'
anchored = FALSE
@@ -19,9 +25,20 @@
machine_desc = "A cell-powered industrial drill, used to crack through dirt and rock to harvest minerals beneath the surface. Requires two adjacent braces to operate."
var/braces_needed = 2
var/list/supports = list()
- var/supported = 0
+ var/supported = FALSE
var/active = FALSE
var/list/resource_field = list()
+ /// List of ores currently stored
+ var/list/stored_ores = list()
+ /// Range around drill from where we intake ores
+ var/resource_area = 3
+ /// Upgrades installed
+ var/list/drill_upgrades = list()
+ /// Upgrade flags
+ var/upgrade_flags = 0
+ /// Direction of automatic ore unloading
+ var/dispense_dir = NORTH
+
var/ore_types = list(
MATERIAL_IRON = /obj/item/ore/iron,
@@ -38,13 +55,13 @@
MATERIAL_RUTILE = /obj/item/ore/rutile
)
- //Upgrades
+ // Component upgrades
var/harvest_speed
var/capacity
- //Flags
- var/need_update_field = 0
- var/need_player_check = 0
+ // Booleans
+ var/need_update_field = FALSE
+ var/need_player_check = FALSE
/obj/machinery/mining/drill/Process()
if(need_player_check)
@@ -80,7 +97,7 @@
var/turf/simulated/floor/exoplanet/T = get_turf(src)
if(T.diggable)
new /obj/structure/pit(T)
- T.diggable = 0
+ T.diggable = FALSE
else if(istype(get_turf(src), /turf/simulated/floor))
var/turf/simulated/floor/T = get_turf(src)
T.ex_act(EX_ACT_HEAVY)
@@ -90,7 +107,7 @@
var/turf/simulated/harvesting = pick(resource_field)
while(resource_field.len && !harvesting.resources)
- harvesting.has_resources = 0
+ harvesting.has_resources = FALSE
harvesting.resources = null
resource_field -= harvesting
if(resource_field.len)
@@ -100,24 +117,32 @@
return
var/total_harvest = harvest_speed //Ore harvest-per-tick.
- var/found_resource = 0 //If this doesn't get set, the area is depleted and the drill errors out.
+ var/found_resource = FALSE //If this doesn't get set, the area is depleted and the drill errors out.
for(var/metal in ore_types)
- if(contents.len >= capacity)
- system_error("insufficient storage space")
- set_active(FALSE)
- need_player_check = 1
- update_icon()
- return
-
- if(contents.len + total_harvest >= capacity)
- total_harvest = capacity - contents.len
-
- if(total_harvest <= 0) break
+ if(stored_ores.len >= capacity)
+ if(!(upgrade_flags & DRILL_AUTOMATIC))
+ system_error("INSUFFICIENT STORAGE SPACE")
+ set_active(FALSE)
+ need_player_check = TRUE
+ on_update_icon()
+ return
+ // Full auto drill!
+ var/turf/unload_turf = get_step(src, dispense_dir)
+ if(!istype(unload_turf) || unload_turf.density)
+ unload_turf = get_turf(src)
+ UnloadTo(unload_turf, null)
+ playsound(loc,'sound/machines/ping.ogg', 50, TRUE)
+
+ if(stored_ores.len + total_harvest >= capacity)
+ total_harvest = capacity - stored_ores.len
+
+ if(total_harvest <= 0)
+ break
if(harvesting.resources[metal])
- found_resource = 1
+ found_resource = TRUE
var/create_ore = 0
if(harvesting.resources[metal] >= total_harvest)
@@ -131,7 +156,7 @@
for(var/i=1, i <= create_ore, i++)
var/oretype = ore_types[metal]
- new oretype(src)
+ stored_ores += new oretype(src)
if(!found_resource)
harvesting.has_resources = 0
@@ -139,13 +164,59 @@
resource_field -= harvesting
else
set_active(FALSE)
- need_player_check = 1
+ need_player_check = TRUE
+ on_update_icon()
+
+/obj/machinery/mining/drill/examine(mob/user)
+ . = ..()
+ for(var/obj/item/drill_upgrade/U in drill_upgrades)
+ if(!U.ExamineMessage(user, src))
+ continue
+ to_chat(user, U.ExamineMessage(user, src))
+ to_chat(user, "\The [src] is mining in a range of [resource_area].")
+ return
+
+/obj/machinery/mining/drill/attackby(obj/item/I, mob/user)
+ if(panel_open || user.a_intent == I_HURT)
+ return ..()
+
+ if(istype(I, /obj/item/drill_upgrade))
+ var/obj/item/drill_upgrade/upgrade = I
+ if(active)
+ to_chat(user, SPAN_WARNING("[src] cannot be modified while it is running."))
+ return FALSE
+ AttemptUpgrade(upgrade, user)
+ return TRUE
+
+ if(istype(I, /obj/item/device/multitool))
+ if(upgrade_flags & DRILL_AUTOMATIC)
+ dispense_dir = turn(dispense_dir, 45)
+ playsound(loc, 'sound/machines/click.ogg', 25, TRUE)
+ to_chat(user, SPAN_NOTICE("[src] is now dispensing ores to the [dir2text(dispense_dir)]."))
+ return TRUE
+
+ if(istype(I, /obj/item/crowbar))
+ if(!LAZYLEN(drill_upgrades))
+ to_chat()
+ return TRUE
+ var/obj/item/drill_upgrade/U = input(user, "Which modification would you like to uninstall from \the [src]?", "Modification Removal") as null|anything in drill_upgrades
+ if(!istype(U) || QDELETED(src) || !Adjacent(user) || user.incapacitated())
+ return TRUE
+ user.visible_message(SPAN_NOTICE("[user] begins removing [U] from [src]'s sockets."),
+ SPAN_NOTICE("You begin removing [U] from the [src]."))
+ playsound(loc, 'sound/items/Crowbar.ogg', 25, TRUE)
+ if(!do_after(user, 10 SECONDS, src))
+ return TRUE
+ if(!(U in drill_upgrades) || QDELETED(src) || !Adjacent(user))
+ return TRUE
+ user.visible_message(SPAN_NOTICE("[user] sucessfuly removes [U] from [src]."),
+ SPAN_NOTICE("You successfuly uninstall [U] from the [src]."))
+ playsound(loc, 'sound/items/Deconstruct.ogg', 50, TRUE)
+ U.Uninstall(src, user)
update_icon()
+ return TRUE
-/obj/machinery/mining/drill/proc/set_active(var/new_active)
- if(active != new_active)
- active = new_active
- update_use_power(active ? POWER_USE_ACTIVE : POWER_USE_OFF)
+ return ..()
/obj/machinery/mining/drill/cannot_transition_to(state_path)
if(active)
@@ -163,7 +234,7 @@
else if(anchored)
get_resource_field()
to_chat(user, "You hit the manual override and reset the drill's error checking.")
- need_player_check = 0
+ need_player_check = TRUE
update_icon()
return TRUE
if(supported && !panel_open)
@@ -171,7 +242,7 @@
set_active(!active)
if(active)
visible_message("\The [src] lurches downwards, grinding noisily.")
- need_update_field = 1
+ need_update_field = TRUE
else
visible_message("\The [src] shudders to a grinding halt.")
else
@@ -186,12 +257,16 @@
if(need_player_check)
icon_state = "mining_drill_error"
else if(active)
- var/status = clamp(round( (contents.len / capacity) * 4 ), 0, 3)
+ var/status = clamp(round((stored_ores.len / capacity) * 4 ), 0, 3)
icon_state = "mining_drill_active[status]"
else if(supported)
icon_state = "mining_drill_braced"
else
icon_state = "mining_drill"
+
+ overlays.Cut()
+ for(var/obj/item/upgrade in drill_upgrades)
+ overlays += image(icon, upgrade.icon_state)
return
/obj/machinery/mining/drill/RefreshParts()
@@ -201,9 +276,13 @@
var/charge_multiplier = clamp(total_component_rating_of_type(/obj/item/stock_parts/capacitor), 0.1, 10)
change_power_consumption(initial(active_power_usage) / charge_multiplier, POWER_USE_ACTIVE)
-/obj/machinery/mining/drill/proc/check_supports()
+/obj/machinery/mining/drill/proc/set_active(new_active)
+ if(active != new_active)
+ active = new_active
+ update_use_power(active ? POWER_USE_ACTIVE : POWER_USE_OFF)
- supported = 0
+/obj/machinery/mining/drill/proc/check_supports()
+ supported = FALSE
if((!supports || !supports.len) && initial(anchored) == 0)
anchored = FALSE
@@ -212,28 +291,53 @@
anchored = TRUE
if(supports && supports.len >= braces_needed)
- supported = 1
+ supported = TRUE
update_icon()
-/obj/machinery/mining/drill/proc/system_error(var/error)
+/obj/machinery/mining/drill/proc/system_error(error = "UNKNOWN ERROR")
if(error)
src.visible_message("\The [src] flashes a '[error]' warning.")
- need_player_check = 1
+ need_player_check = TRUE
set_active(FALSE)
update_icon()
/obj/machinery/mining/drill/proc/get_resource_field()
resource_field = list()
- need_update_field = 0
+ need_update_field = FALSE
- for (var/turf/simulated/T in range(2, src))
- if (T.has_resources)
+ for(var/turf/simulated/T in range(resource_area, src))
+ if(T.has_resources)
resource_field += T
- if (!length(resource_field))
- system_error("resources depleted")
+ if(!length(resource_field))
+ system_error("RESOURCES DEPLETED")
+
+/obj/machinery/mining/drill/proc/AttemptUpgrade(obj/item/drill_upgrade/upgrade, mob/user)
+ if(drill_upgrades.len >= MAX_DRILL_UPGRADES)
+ to_chat(user, SPAN_WARNING("The [src] has maximum amount of modifications installed already!"))
+ return FALSE
+ if((locate(upgrade.type) in drill_upgrades) && !upgrade.allow_multiple)
+ to_chat(user, SPAN_WARNING("The [src] has same kind of modification installed already!"))
+ return FALSE
+ if(!user.skill_check(SKILL_ELECTRICAL, SKILL_ADEPT) || !user.skill_check(SKILL_DEVICES, SKILL_ADEPT))
+ user.visible_message(SPAN_NOTICE("[user] fumbles around figuring out how to install the modification module on [src]."),
+ SPAN_NOTICE("You fumble around figuring out how to install the module on [src]."))
+ var/fumbling_time = 15 SECONDS - 2 SECONDS * (user.get_skill_value(SKILL_ELECTRICAL) + user.get_skill_value(SKILL_DEVICES))
+ if(!do_after(user, fumbling_time, src))
+ return FALSE
+ user.visible_message(SPAN_NOTICE("[user] begins attaching a module to [src]'s sockets."),
+ SPAN_NOTICE("You begin installing the [upgrade] into the [src]."))
+ if(!do_after(user, 10 SECONDS, src))
+ return FALSE
+ if(QDELETED(upgrade)) // It could have somehow happened, idk
+ return FALSE
+ user.visible_message(SPAN_NOTICE("[user] installs [upgrade] into the [src]!"))
+ upgrade.Install(src, user) // Handles the effects of it and puts into the list
+ playsound(loc,'sound/items/screwdriver.ogg', 25, TRUE)
+ update_icon()
+
/obj/machinery/mining/drill/verb/unload()
set name = "Unload Drill"
@@ -242,13 +346,24 @@
if(usr.stat) return
+ UnloadTo(get_turf(usr), usr)
+
+/obj/machinery/mining/drill/proc/UnloadTo(turf/T, mob/living/user)
var/obj/structure/ore_box/B = locate() in orange(1)
- if(B)
- for(var/obj/item/ore/O in contents)
+ if(B && istype(user))
+ for(var/obj/item/ore/O in stored_ores)
O.forceMove(B)
+ stored_ores = list()
to_chat(usr, "You unload the drill's storage cache into the ore box.")
- else
- to_chat(usr, "You must move an ore box up to the drill before you can unload it.")
+ return
+
+ if(!istype(T))
+ T = get_turf(src)
+ for(var/obj/item/ore/O in stored_ores)
+ O.forceMove(T)
+ stored_ores = list()
+ update_icon()
+ return
/obj/machinery/mining/brace
@@ -269,6 +384,8 @@
return ..()
/obj/machinery/mining/brace/attackby(obj/item/W as obj, mob/user as mob)
+ if(!isWrench(W))
+ return ..()
if(connected && connected.active)
to_chat(user, "You can't work with the brace of a running drill!")
return TRUE
@@ -289,8 +406,9 @@
else
disconnect()
-/obj/machinery/mining/brace/proc/connect()
+ return
+/obj/machinery/mining/brace/proc/connect()
var/turf/T = get_step(get_turf(src), src.dir)
for(var/thing in T.contents)
diff --git a/code/modules/mining/drilling/drill_upgrades.dm b/code/modules/mining/drilling/drill_upgrades.dm
new file mode 100644
index 0000000000000..55c6116ba89aa
--- /dev/null
+++ b/code/modules/mining/drilling/drill_upgrades.dm
@@ -0,0 +1,51 @@
+/obj/item/drill_upgrade
+ name = "drill modification kit"
+ desc = "Does nothing. Yell at coders."
+ icon = 'icons/obj/mining.dmi'
+ icon_state = "upgrade_automatic"
+ w_class = ITEM_SIZE_NORMAL
+ matter = list(MATERIAL_STEEL = 1000, MATERIAL_PLASTEEL = 750, MATERIAL_GLASS = 500, MATERIAL_ALUMINIUM = 250)
+ /// If more than one can be installed on the same drill
+ var/allow_multiple = FALSE
+
+/obj/item/drill_upgrade/proc/Install(obj/machinery/mining/drill/drill, mob/user)
+ drill.drill_upgrades += src
+ if(user)
+ user.unEquip(src, drill)
+ return
+
+/obj/item/drill_upgrade/proc/Uninstall(obj/machinery/mining/drill/drill, mob/user)
+ drill.drill_upgrades -= src
+ forceMove(get_turf(drill))
+ return
+
+/obj/item/drill_upgrade/proc/ExamineMessage(mob/user, obj/machinery/mining/drill/drill)
+ return null
+
+/obj/item/drill_upgrade/auto_dispense
+ name = "automatic dispenser modification kit"
+ desc = "A modification kit for the mining drills. This one forces drills to dispense its stored ores whenever at full capacity automatically."
+
+/obj/item/drill_upgrade/auto_dispense/Install(obj/machinery/mining/drill/drill)
+ . = ..()
+ drill.upgrade_flags |= DRILL_AUTOMATIC
+
+/obj/item/drill_upgrade/auto_dispense/Uninstall(obj/machinery/mining/drill/drill)
+ . = ..()
+ drill.upgrade_flags &= ~DRILL_AUTOMATIC
+
+/obj/item/drill_upgrade/auto_dispense/ExamineMessage(mob/user, obj/machinery/mining/drill/drill)
+ return SPAN_NOTICE("The drill is fully automated and will dispense ores to the [dir2text(drill.dispense_dir)].")
+
+/obj/item/drill_upgrade/range_increase
+ name = "deep mining modification kit"
+ desc = "A modification kit for the mining drills. This one increases range of drilling by 1."
+ allow_multiple = TRUE
+
+/obj/item/drill_upgrade/range_increase/Install(obj/machinery/mining/drill/drill)
+ . = ..()
+ drill.resource_area += 1
+
+/obj/item/drill_upgrade/range_increase/Uninstall(obj/machinery/mining/drill/drill)
+ . = ..()
+ drill.resource_area -= 1
diff --git a/code/modules/mob/grab/abnomination/abom_agressive.dm b/code/modules/mob/grab/abnomination/abom_agressive.dm
new file mode 100644
index 0000000000000..a877392b599ef
--- /dev/null
+++ b/code/modules/mob/grab/abnomination/abom_agressive.dm
@@ -0,0 +1,24 @@
+/datum/grab/abomination/aggressive
+ state_name = GRAB_ABOMINATION_AGGRESSIVE
+ upgrab_name = GRAB_ABOMINATION_KILL
+
+ shift = 12
+
+ point_blank_mult = 1.5
+ can_throw = 1
+ stop_move = 1
+
+ icon_state = "reinforce1"
+
+ break_chance_table = list(5, 10, 25, 50, 75, 100)
+
+/datum/grab/abomination/aggressive/process_effect(obj/item/grab/G)
+ var/mob/living/carbon/human/target = G.affecting
+
+ if(G.target_zone in list(BP_L_HAND, BP_R_HAND))
+ target.drop_l_hand()
+ target.drop_r_hand()
+
+ // Keeps those who are on the ground down
+ if(target.lying)
+ target.Weaken(4)
diff --git a/code/modules/mob/grab/abnomination/abom_kill.dm b/code/modules/mob/grab/abnomination/abom_kill.dm
new file mode 100644
index 0000000000000..8da119fb4fc93
--- /dev/null
+++ b/code/modules/mob/grab/abnomination/abom_kill.dm
@@ -0,0 +1,30 @@
+/datum/grab/abomination/kill
+ state_name = GRAB_ABOMINATION_KILL
+ downgrab_name = GRAB_ABOMINATION_AGGRESSIVE
+
+ shift = 0
+ same_tile = 1
+
+ icon_state = "kill1"
+
+ downgrade_on_action = 1
+ downgrade_on_move = 1
+ stop_move = 1
+ restrains = 1
+
+ break_chance_table = list(1, 3, 5, 10, 20, 35, 50)
+
+/datum/grab/abomination/kill/upgrade_effect(obj/item/grab/G)
+ process_effect(G)
+
+/datum/grab/abomination/kill/process_effect(obj/item/grab/G)
+ var/mob/living/carbon/human/user = G.assailant
+ var/mob/living/carbon/human/target = G.affecting
+
+ target.Stun(3)
+
+ switch(user.a_intent)
+ if(I_GRAB)
+ on_hit_grab(G)
+ if(I_HURT)
+ on_hit_harm(G)
diff --git a/code/modules/mob/grab/abnomination/abom_passive.dm b/code/modules/mob/grab/abnomination/abom_passive.dm
new file mode 100644
index 0000000000000..400201925e43f
--- /dev/null
+++ b/code/modules/mob/grab/abnomination/abom_passive.dm
@@ -0,0 +1,21 @@
+/datum/grab/abomination/passive
+ state_name = GRAB_ABOMINATION_PASSIVE
+ fancy_desc = "holding"
+
+ upgrab_name = GRAB_ABOMINATION_AGGRESSIVE
+
+ shift = 8
+
+ point_blank_mult = 1.1
+
+ icon_state = "reinforce"
+
+ break_chance_table = list(15, 30, 50, 75, 100)
+
+/datum/grab/abomination/passive/on_hit_grab(obj/item/grab/normal/G)
+ to_chat(G.assailant, SPAN_WARNING("Your grip isn't strong enough to crush."))
+ return FALSE
+
+/datum/grab/abomination/passive/on_hit_harm(obj/item/grab/normal/G)
+ to_chat(G.assailant, SPAN_WARNING("Your grip isn't strong enough to devour."))
+ return FALSE
diff --git a/code/modules/mob/grab/abnomination/grab_abom.dm b/code/modules/mob/grab/abnomination/grab_abom.dm
new file mode 100644
index 0000000000000..a03137c2b43cc
--- /dev/null
+++ b/code/modules/mob/grab/abnomination/grab_abom.dm
@@ -0,0 +1,115 @@
+/obj/item/grab/abomination
+ type_name = GRAB_ABOMINATION
+ start_grab_name = GRAB_ABOMINATION_PASSIVE
+
+/obj/item/grab/abomination/init()
+ . = ..()
+ if(!.)
+ return
+ visible_message("[assailant] has grabbed [affecting] passively!")
+
+/datum/grab/abomination
+ type_name = GRAB_ABOMINATION
+ icon = 'icons/mob/screen1.dmi'
+
+ can_absorb = 1
+ point_blank_mult = 1
+ ladder_carry = 1
+ force_danger = 1
+ can_grab_self = 0
+
+/datum/grab/abomination/on_hit_grab(obj/item/grab/G)
+ var/mob/living/carbon/human/user = G.assailant
+ var/mob/living/carbon/human/target = G.affecting
+
+ target.visible_message(
+ SPAN_DANGER("[user] begins crushing [target]!"),
+ SPAN_DANGER("[user] is trying to crush your bones!"),
+ )
+ G.attacking = TRUE
+ if(do_after(user, action_cooldown * 0.5, target))
+ G.attacking = FALSE
+ G.action_used()
+ Crush(G)
+ return TRUE
+ else
+ G.attacking = FALSE
+ target.visible_message(SPAN_WARNING("[user] stops crushing [target]!"))
+ return FALSE
+
+/datum/grab/abomination/on_hit_harm(obj/item/grab/G)
+ var/mob/living/carbon/human/user = G.assailant
+ var/mob/living/carbon/human/target = G.affecting
+ var/obj/item/organ/external/damaging = target.get_organ(user.zone_sel.selecting)
+
+ if(!istype(damaging))
+ to_chat(user, SPAN_WARNING("This limb is missing!"))
+ return FALSE
+
+ target.visible_message(
+ SPAN_DANGER("[user] begins eating [target]'s [damaging.name]!"),
+ SPAN_DANGER("[user] is eating your [damaging.name]!"),
+ )
+ G.attacking = TRUE
+
+ if(do_after(user, action_cooldown, target))
+ G.attacking = FALSE
+ G.action_used()
+ Devour(G)
+ return TRUE
+ else
+ G.attacking = FALSE
+ target.visible_message(SPAN_WARNING("[user] stops eating [target]'s limbs."))
+ return FALSE
+
+// This causes the user to temporarily stun and weaken the target, dealing pain and slight brute damage
+/datum/grab/abomination/proc/Crush(obj/item/grab/G)
+ var/mob/living/carbon/human/user = G.assailant
+ var/mob/living/carbon/human/target = G.affecting
+ var/hit_zone = user.zone_sel.selecting
+ var/obj/item/organ/external/damaging = target.get_organ(hit_zone)
+
+ target.visible_message(SPAN_DANGER("[user] crushes [target]'s [damaging.name]!"))
+ target.custom_pain("You feel your bones painfuly compress in [damaging.name]!", 50, affecting = damaging)
+ target.apply_damage(4, DAMAGE_BRUTE, hit_zone, used_weapon = "crushing")
+ target.Stun(10)
+ target.Weaken(15)
+ playsound(get_turf(target), 'sound/weapons/pierce.ogg', 25, TRUE, -3)
+
+ admin_attack_log(user, target, "Crushed their victim.", "Was crushed.", "crushed")
+
+// This causes the user to heavily damage targeted limb while also restoring its own nutrition
+/datum/grab/abomination/proc/Devour(obj/item/grab/G)
+ var/mob/living/carbon/human/user = G.assailant
+ var/mob/living/carbon/human/target = G.affecting
+ var/hit_zone = user.zone_sel.selecting
+ var/obj/item/organ/external/damaging = target.get_organ(hit_zone)
+
+ if(!istype(damaging))
+ to_chat(user, SPAN_WARNING("This limb is missing!"))
+ return
+
+ if(!damaging.how_open())
+ damaging.createwound(INJURY_TYPE_CUT, damaging.min_broken_damage*0.75, 1)
+ playsound(get_turf(target), 'sound/weapons/alien_tail_attack.ogg', 50, TRUE, 5)
+ target.visible_message(
+ SPAN_DANGER("[user] cuts [target]'s [damaging.name] open!"),
+ SPAN_DANGER("[user] cuts your [damaging.name] open!"),
+ )
+ admin_attack_log(user, target, "Cuts open their victim.", "Has been cut.", "cut")
+ return
+
+ if(user.get_fullness() > 380) // Just slash them
+ target.apply_damage(rand(16, 24), DAMAGE_BRUTE, hit_zone, used_weapon = "claws")
+ target.visible_message(SPAN_DANGER("[user] slashes [target]'s [damaging.name]!"))
+ playsound(get_turf(target), 'sound/weapons/alien_claw_flesh3.ogg', 25, TRUE)
+ else // Food!
+ var/datum/reagents/R = new /datum/reagents(3, GLOB.temp_reagents_holder)
+ R.add_reagent(/datum/reagent/nutriment, 3)
+ R.trans_to_mob(user, 3, CHEM_INGEST)
+ qdel(R)
+ target.apply_damage(rand(8, 14), DAMAGE_BRUTE, hit_zone, used_weapon = "fangs")
+ target.visible_message(SPAN_DANGER("[user] eats [target]'s [damaging.name]!"))
+ playsound(get_turf(target), 'sound/weapons/alien_claw_flesh1.ogg', 25, TRUE)
+
+ admin_attack_log(user, target, "Devours their victim.", "Was chewed.", "chewed")
diff --git a/code/modules/mob/living/simple_animal/borer/borer.dm b/code/modules/mob/living/simple_animal/borer/borer.dm
index ec7c2de465599..2538ab0ca6dfc 100644
--- a/code/modules/mob/living/simple_animal/borer/borer.dm
+++ b/code/modules/mob/living/simple_animal/borer/borer.dm
@@ -289,3 +289,9 @@
/mob/living/simple_animal/borer/flash_eyes(intensity, override_blindness_check, affect_silicon, visual, type)
intensity *= 1.5
. = ..()
+
+/mob/living/simple_animal/borer/ImplantRemoval()
+ if(controlling)
+ host.release_control()
+ detatch()
+ leave_host()
diff --git a/code/modules/mob/living/simple_animal/hostile/infestation/_infestation.dm b/code/modules/mob/living/simple_animal/hostile/infestation/_infestation.dm
new file mode 100644
index 0000000000000..bb9ac7010f6d8
--- /dev/null
+++ b/code/modules/mob/living/simple_animal/hostile/infestation/_infestation.dm
@@ -0,0 +1,99 @@
+/mob/living/simple_animal/hostile/infestation
+ name = "abominable infestation"
+ desc = "AUGH! A BUG! A LITERAL ONE! REPORT TO CODERS BEFORE IT SPREADS!"
+ icon = 'icons/mob/simple_animal/abominable_infestation/32x32.dmi'
+ // Icon states for egg
+ var/icon_egg = "egg"
+ var/icon_egg_dead = "egg_dead"
+ faction = "abominable_infestation"
+ color = "#c84444"
+ bleed_colour = COLOR_MAROON
+ see_in_dark = 5
+ min_gas = null
+ max_gas = null
+ minbodytemp = 0
+ status_flags = CANPUSH // Cannot be stunned or otherwise incapacitated
+ melee_attack_delay = 0
+ /// Reference to structure that effectively controls the colony
+ var/overmind = null
+ /// World time at which we will begin transforming into another mob
+ var/transformation_time
+ /// Assoc list of potential transformations if none are set by outer forces
+ // Type = Time
+ var/list/transformation_types = list()
+ /// Which mob we will be transformed into
+ var/transformation_target_type = null
+ /// If TRUE - will evolve despite having a target mob
+ var/ignore_combat = FALSE
+
+/mob/living/simple_animal/hostile/infestation/Life()
+ . = ..()
+ if(!.)
+ return
+ if(isnull(transformation_time))
+ return
+ if(isnull(transformation_target_type) && !LAZYLEN(transformation_types))
+ return
+ if(world.time >= transformation_time)
+ if(istype(loc, /obj/item/organ/external))
+ var/obj/item/organ/external/O = loc
+ if(O.owner)
+ to_chat(O.owner, SPAN_DANGER("Something has wriggled out of your body!"))
+ O.createwound(INJURY_TYPE_PIERCE, O.min_broken_damage, FALSE)
+ O.owner.Weaken(5) // Gives some time for larva to turn into egg
+ O.owner.apply_damage(15, DAMAGE_BRUTE, O.organ_tag)
+ O.implants -= src
+ forceMove(get_turf(O))
+ playsound(src, 'sound/effects/splat.ogg', 50, TRUE)
+ return
+ if(icon_state != icon_egg) // Become egg
+ // We're in combat, forget evolving for a moment
+ if(!ignore_combat && ai_holder.target)
+ transformation_time = world.time + 10 SECONDS
+ return
+ BecomeEgg()
+ return
+ Evolve()
+
+/mob/living/simple_animal/hostile/infestation/death()
+ if(icon_state == icon_egg)
+ animate(src, alpha = 0, time = (5 SECONDS))
+ QDEL_IN(src, (5 SECONDS))
+ return ..()
+
+
+/mob/living/simple_animal/hostile/infestation/proc/BecomeEgg()
+ name = "egg"
+ desc = "A weird egg..?"
+ icon_state = icon_egg
+ icon_living = icon_egg
+ icon_dead = icon_egg_dead
+ anchored = TRUE
+ QDEL_NULL(ai_holder)
+ QDEL_NULL(say_list)
+ ai_holder = null
+ say_list = null
+ death_sounds = list()
+ maxHealth = max(200, maxHealth * 2)
+ health = maxHealth
+ if(isnull(transformation_target_type) && LAZYLEN(transformation_types))
+ transformation_target_type = pick(transformation_types)
+ if(transformation_target_type in transformation_types)
+ transformation_time = world.time + transformation_types[transformation_target_type]
+ return
+ transformation_time = world.time + rand(45 SECONDS, 60 SECONDS)
+
+/mob/living/simple_animal/hostile/infestation/proc/Evolve()
+ var/mob/living/simple_animal/broodling = new transformation_target_type(get_turf(src))
+ broodling.color = color
+ broodling.faction = faction
+ if(ckey) // We're player controlled
+ broodling.ckey = ckey
+ gib()
+
+// Shared AI behavior
+/datum/ai_holder/simple_animal/infestation
+ hostile = TRUE
+ retaliate = TRUE
+ cooperative = TRUE
+ respect_confusion = FALSE
diff --git a/code/modules/mob/living/simple_animal/hostile/infestation/assembler.dm b/code/modules/mob/living/simple_animal/hostile/infestation/assembler.dm
new file mode 100644
index 0000000000000..f2187d90d78e4
--- /dev/null
+++ b/code/modules/mob/living/simple_animal/hostile/infestation/assembler.dm
@@ -0,0 +1,208 @@
+/mob/living/simple_animal/hostile/infestation/assembler
+ name = "assembler"
+ desc = "A large monstrosity with many appendages that it uses to 'assemble' things."
+ icon = 'icons/mob/simple_animal/abominable_infestation/48x48.dmi'
+ icon_state = "assembler"
+ icon_living = "assembler"
+ icon_dead = "assembler_dead"
+ mob_size = MOB_MEDIUM
+ move_to_delay = 3.5
+ default_pixel_x = -8
+ pixel_x = -8
+
+ natural_weapon = /obj/item/natural_weapon/assembler
+
+ health = 200
+ maxHealth = 200
+
+ move_to_delay = 5
+
+ meat_type = /obj/item/reagent_containers/food/snacks/abominationmeat
+ meat_amount = 8
+ skin_material = MATERIAL_SKIN_CHITIN
+ skin_amount = 2
+ bone_material = MATERIAL_BONE_CARTILAGE
+ bone_amount = 2
+
+ ai_holder = /datum/ai_holder/simple_animal/infestation/assembler
+ //say_list_type = /datum/say_list/infestation_assembler
+ death_sounds = list(
+ 'sound/simple_mob/abominable_infestation/eviscerator/death_1.ogg',
+ 'sound/simple_mob/abominable_infestation/eviscerator/death_2.ogg',
+ )
+
+ // Organs add 1; Non-human mobs add 5.
+ var/nutrient_stored = 0
+ /// Assoc list Type = Nutrient Required; This is responsible for forcing larva's evolution target type
+ var/larva_types = list(
+ /mob/living/simple_animal/hostile/infestation/broodling = 4,
+ /mob/living/simple_animal/hostile/infestation/floatfly = 6,
+ /mob/living/simple_animal/hostile/infestation/spitter = 8,
+ /mob/living/simple_animal/hostile/infestation/eviscerator = 12,
+ /mob/living/simple_animal/hostile/infestation/assembler = 16,
+ )
+ /// Not actually the limit, but the point where it will forcefully spawn larva without waiting for time
+ var/nutrient_max = 25
+ /// World time by which we will spawn an larva if we have no target
+ var/larva_time
+
+/obj/item/natural_weapon/assembler
+ name = "sharp tentacle"
+ attack_verb = list("stabbed", "pierced")
+ force = 20
+ armor_penetration = 10
+ hitsound = 'sound/weapons/rapidslice.ogg'
+ sharp = TRUE
+ edge = TRUE
+
+
+/datum/ai_holder/simple_animal/infestation/assembler
+ cooperative = FALSE
+ mauling = TRUE
+ handle_corpse = TRUE
+ returns_home = FALSE
+ home_low_priority = TRUE
+ speak_chance = 1
+ wander = TRUE
+ base_wander_delay = 20
+
+/datum/ai_holder/simple_animal/infestation/assembler/list_targets()
+ . = ..()
+
+ var/static/alternative_targets = typecacheof(list(/obj/item/organ))
+ for(var/obj/item/organ/O in typecache_filter_list(range(vision_range, holder), alternative_targets))
+ if(can_see(holder, O, vision_range) && !BP_IS_ROBOTIC(O))
+ . += O
+
+/datum/ai_holder/simple_animal/infestation/assembler/pick_target(list/targets)
+ var/mobs_only = locate(/mob/living) in targets // If a mob is in the list of targets, then ignore objects.
+ if(mobs_only)
+ for(var/A in targets)
+ if(!isliving(A))
+ targets -= A
+
+ return ..(targets)
+
+/mob/living/simple_animal/hostile/infestation/assembler/Initialize()
+ . = ..()
+ larva_time = world.time + 10 SECONDS
+
+/mob/living/simple_animal/hostile/infestation/assembler/Life()
+ . = ..()
+ if(!.)
+ return
+ if(nutrient_stored >= nutrient_max || world.time >= larva_time)
+ AttemptLarva()
+ return
+
+/mob/living/simple_animal/hostile/infestation/assembler/attack_target(atom/A)
+ return UnarmedAttack(A)
+
+/mob/living/simple_animal/hostile/infestation/assembler/UnarmedAttack(atom/A)
+
+ if(istype(A, /obj/item/organ))
+ ConsumeOrgan(A)
+ ai_holder.target = null
+ return
+
+ if(isliving(A))
+ var/mob/living/L = A
+ if(L.stat)
+ ConsumeDead(L)
+ return
+
+ return ..()
+
+/mob/living/simple_animal/hostile/infestation/assembler/proc/ConsumeOrgan(obj/item/organ/O)
+ visible_message(SPAN_WARNING("[src] consumes \the [O]."))
+ qdel(O)
+ nutrient_stored += 1
+ larva_time = world.time + 5 SECONDS
+
+/mob/living/simple_animal/hostile/infestation/assembler/proc/ConsumeDead(mob/living/L)
+ if(ishuman(L))
+ ConsumeHuman(L)
+ return
+
+ visible_message(SPAN_DANGER("[src] starts to consume \the [L]!"))
+ playsound(src, 'sound/simple_mob/abominable_infestation/assembler/ambient_1.ogg', 50, TRUE)
+ set_AI_busy(TRUE)
+
+ if(!do_after(src, min(10, L.mob_size) SECONDS, L))
+ set_AI_busy(FALSE)
+ return FALSE
+
+ set_AI_busy(FALSE)
+ visible_message(SPAN_DANGER("[src] consumes \the [L]!"))
+ nutrient_stored += min(10, L.mob_size)
+ larva_time = world.time + 10 SECONDS
+ L.gib()
+
+/mob/living/simple_animal/hostile/infestation/assembler/proc/ConsumeHuman(mob/living/carbon/human/H)
+ visible_message(SPAN_DANGER("[src] starts to tear [H] apart!"))
+ playsound(src, 'sound/simple_mob/abominable_infestation/assembler/ambient_1.ogg', 50, TRUE)
+ set_AI_busy(TRUE)
+
+ if(!do_after(src, 3 SECONDS, H))
+ set_AI_busy(FALSE)
+ return FALSE
+
+ if(QDELETED(H))
+ set_AI_busy(FALSE)
+ return FALSE
+
+ var/list/potential_organs = list()
+ for(var/obj/item/organ/external/O in H.organs)
+ if(istype(O, /obj/item/organ/external/stump))
+ continue
+ if(O.organ_tag in (BP_LEGS_FEET | BP_ARMS_HANDS))
+ potential_organs += O
+ continue
+ if(!LAZYLEN(potential_organs) && (O.organ_tag in list(BP_HEAD, BP_GROIN)))
+ potential_organs += O
+ continue
+
+ if(!LAZYLEN(potential_organs)) // Most likely only chest left
+ for(var/obj/item/organ/internal/I in H.internal_organs)
+ I.removed()
+ I.forceMove(get_turf(H))
+ if(!QDELETED(I) && isturf(loc))
+ I.throw_at(get_edge_target_turf(get_turf(H), pick(GLOB.alldirs)), rand(1,2), 5)
+ H.gib()
+ set_AI_busy(FALSE)
+ return
+
+ var/obj/item/organ/external/target_organ = pick(potential_organs)
+ target_organ.droplimb(FALSE, DROPLIMB_EDGE, FALSE, FALSE)
+ for(var/obj/item/organ/I in target_organ.internal_organs)
+ I.removed()
+ I.forceMove(get_turf(target_organ))
+ if(!QDELETED(I) && isturf(loc))
+ I.throw_at(get_edge_target_turf(target_organ, pick(GLOB.alldirs)), rand(1,2), 5)
+
+ larva_time = world.time + 5 SECONDS
+ set_AI_busy(FALSE)
+
+/mob/living/simple_animal/hostile/infestation/assembler/proc/AttemptLarva(forced_type = null)
+ if(ai_holder.target)
+ return
+
+ larva_time = world.time + 5 SECONDS
+ var/target_type = forced_type
+ if(isnull(target_type))
+ for(var/thing_type in larva_types)
+ if(nutrient_stored < larva_types[thing_type])
+ break
+ target_type = thing_type
+
+ if(isnull(target_type))
+ return
+
+ var/mob/living/simple_animal/hostile/infestation/larva/L = new(get_turf(src))
+ L.transformation_target_type = target_type
+ L.transformation_time = world.time + 30 SECONDS // Assembled larvas hatch faster
+ L.color = color
+ L.faction = faction
+ playsound(L, 'sound/simple_mob/abominable_infestation/larva/spawn.ogg', rand(35, 50), TRUE)
+ visible_message(SPAN_WARNING("[src] assembles a new [L.name]!"))
+ nutrient_stored -= larva_types[target_type]
diff --git a/code/modules/mob/living/simple_animal/hostile/infestation/broodling.dm b/code/modules/mob/living/simple_animal/hostile/infestation/broodling.dm
new file mode 100644
index 0000000000000..7985b0a918428
--- /dev/null
+++ b/code/modules/mob/living/simple_animal/hostile/infestation/broodling.dm
@@ -0,0 +1,56 @@
+/mob/living/simple_animal/hostile/infestation/broodling
+ name = "broodling"
+ desc = "An abominable creature, fast and vicious."
+ icon_state = "broodling"
+ icon_living = "broodling"
+ icon_dead = "broodling_dead"
+ mob_size = MOB_SMALL
+ move_to_delay = 2
+
+ natural_weapon = /obj/item/natural_weapon/claws/broodling
+ melee_attack_delay = 0
+
+ health = 100
+ maxHealth = 100
+
+ meat_type = /obj/item/reagent_containers/food/snacks/abominationmeat
+ meat_amount = 2
+ skin_material = MATERIAL_SKIN_CHITIN
+ skin_amount = 1
+ bone_material = MATERIAL_BONE_CARTILAGE
+ bone_amount = 1
+
+ ai_holder = /datum/ai_holder/simple_animal/infestation/broodling
+ say_list_type = /datum/say_list/infestation_broodling
+ death_sounds = list('sound/simple_mob/abominable_infestation/broodling/death.ogg')
+
+/obj/item/natural_weapon/claws/broodling
+ force = 7
+ armor_penetration = 10
+ hitsound = 'sound/weapons/slashmiss.ogg'
+ attack_cooldown = 2
+
+/datum/say_list/infestation_broodling
+ emote_hear = list("hisses", "attempts to bark", "breathes heavily", "gurgles")
+ emote_see = list("jumps from place to place")
+
+ emote_hear_sounds = list(
+ 'sound/simple_mob/abominable_infestation/broodling/ambient_1.ogg',
+ 'sound/simple_mob/abominable_infestation/broodling/ambient_2.ogg',
+ )
+ emote_see_sounds = list(
+ 'sound/simple_mob/abominable_infestation/broodling/ambient_1.ogg',
+ 'sound/simple_mob/abominable_infestation/broodling/ambient_2.ogg',
+ )
+
+/datum/ai_holder/simple_animal/infestation/broodling
+ returns_home = FALSE
+ home_low_priority = TRUE
+ speak_chance = 3
+ wander = TRUE
+ base_wander_delay = 2
+
+/datum/ai_holder/simple_animal/infestation/broodling/post_melee_attack(atom/A)
+ if(holder.Adjacent(A))
+ holder.IMove(get_step(holder, pick(GLOB.alldirs)))
+ holder.face_atom(A)
diff --git a/code/modules/mob/living/simple_animal/hostile/infestation/eviscerator.dm b/code/modules/mob/living/simple_animal/hostile/infestation/eviscerator.dm
new file mode 100644
index 0000000000000..9aa45f59c0d52
--- /dev/null
+++ b/code/modules/mob/living/simple_animal/hostile/infestation/eviscerator.dm
@@ -0,0 +1,63 @@
+/mob/living/simple_animal/hostile/infestation/eviscerator
+ name = "eviscerator"
+ desc = "A large monster with very sharp spear-like limbs. You better start running..."
+ icon = 'icons/mob/simple_animal/abominable_infestation/48x48.dmi'
+ icon_state = "eviscerator"
+ icon_living = "eviscerator"
+ icon_dead = "eviscerator_dead"
+ mob_size = MOB_MEDIUM
+ move_to_delay = 3.5
+ default_pixel_x = -8
+ pixel_x = -8
+
+ natural_weapon = /obj/item/natural_weapon/claws/strong/eviscerator
+
+ health = 300
+ maxHealth = 300
+
+ move_to_delay = 3.5
+ movement_sound = 'sound/simple_mob/abominable_infestation/eviscerator/step.ogg'
+ movement_shake_radius = 2
+
+ meat_type = /obj/item/reagent_containers/food/snacks/abominationmeat
+ meat_amount = 6
+ skin_material = MATERIAL_SKIN_CHITIN
+ skin_amount = 4
+ bone_material = MATERIAL_BONE_CARTILAGE
+ bone_amount = 4
+
+ ai_holder = /datum/ai_holder/simple_animal/infestation/eviscerator
+ //say_list_type = /datum/say_list/infestation_eviscerator
+ death_sounds = list(
+ 'sound/simple_mob/abominable_infestation/eviscerator/death_1.ogg',
+ 'sound/simple_mob/abominable_infestation/eviscerator/death_2.ogg',
+ )
+
+ transformation_types = list(
+ /mob/living/simple_animal/hostile/infestation/rhino = 150 SECONDS,
+ )
+
+/obj/item/natural_weapon/claws/strong/eviscerator
+ armor_penetration = 10
+ hitsound = 'sound/simple_mob/abominable_infestation/eviscerator/attack.ogg'
+
+/datum/ai_holder/simple_animal/infestation/eviscerator
+ returns_home = FALSE
+ home_low_priority = TRUE
+ speak_chance = 1
+ wander = TRUE
+ base_wander_delay = 20
+ var/list/aggro_sounds = list(
+ 'sound/simple_mob/abominable_infestation/eviscerator/aggro_1.ogg',
+ 'sound/simple_mob/abominable_infestation/eviscerator/aggro_2.ogg',
+ 'sound/simple_mob/abominable_infestation/eviscerator/aggro_3.ogg',
+ )
+
+/datum/ai_holder/simple_animal/infestation/eviscerator/give_target(new_target, urgent = FALSE)
+ . = ..()
+ if(. && prob(30))
+ playsound(holder, pick(aggro_sounds), rand(35,75), TRUE)
+
+/mob/living/simple_animal/hostile/infestation/eviscerator/Initialize()
+ . = ..()
+ transformation_time = world.time + rand(120 SECONDS, 240 SECONDS)
diff --git a/code/modules/mob/living/simple_animal/hostile/infestation/floatfly.dm b/code/modules/mob/living/simple_animal/hostile/infestation/floatfly.dm
new file mode 100644
index 0000000000000..13edb5b3233f3
--- /dev/null
+++ b/code/modules/mob/living/simple_animal/hostile/infestation/floatfly.dm
@@ -0,0 +1,86 @@
+// Medium damage, Low health, Very high mobility
+// Ignores gravity and can "fly", temporarily turning non-dense
+// When attacked, can change its pixel position slightly, making it more difficult to hit
+
+/mob/living/simple_animal/hostile/infestation/floatfly
+ name = "floatfly"
+ desc = "A flesh creature resembling a big fly."
+ icon_state = "fly"
+ icon_living = "fly"
+ icon_dead = "fly_dead"
+ mob_size = MOB_SMALL
+ movement_cooldown = 2.5
+
+ natural_weapon = /obj/item/natural_weapon/claws/floatfly
+
+ health = 80
+ maxHealth = 80
+
+ meat_type = /obj/item/reagent_containers/food/snacks/abominationmeat
+ meat_amount = 3
+ skin_material = MATERIAL_SKIN_CHITIN
+ skin_amount = 1
+ bone_material = MATERIAL_BONE_CARTILAGE
+ bone_amount = 1
+
+ ai_holder = /datum/ai_holder/simple_animal/infestation/floatfly
+ say_list_type = /datum/say_list/infestation_floatfly
+ death_sounds = list('sound/simple_mob/abominable_infestation/floatfly/death.ogg')
+
+ var/fly_cooldown
+ var/fly_cooldown_time = 5 SECONDS
+ var/fly_duration = 5 SECONDS
+
+/obj/item/natural_weapon/claws/floatfly
+ force = 14
+ armor_penetration = 10
+ hitsound = 'sound/weapons/alien_claw_flesh1.ogg'
+
+/datum/say_list/infestation_floatfly
+ emote_hear = list("buzzes", "hisses")
+ emote_see = list("flies all over the place")
+
+ emote_hear_sounds = list()
+ emote_see_sounds = list()
+
+/mob/living/simple_animal/hostile/infestation/floatfly/death()
+ animate(src, pixel_z = 0, time = 3)
+ return ..()
+
+/mob/living/simple_animal/hostile/infestation/floatfly/adjustBruteLoss(amount)
+ . = ..()
+ if(world.time > fly_cooldown && prob(amount * 5))
+ animate(src, pixel_x = default_pixel_x + rand(-10, 10), pixel_y = default_pixel_y + rand(-10, 10), time = 2)
+
+
+/mob/living/simple_animal/hostile/infestation/floatfly/proc/StartFlight()
+ if(!density || fly_cooldown >= world.time)
+ return FALSE
+ density = FALSE
+ playsound(src, 'sound/simple_mob/abominable_infestation/floatfly/fly.ogg', 75, TRUE, 6)
+ visible_message(SPAN_DANGER("\The [src] flies upwards!"))
+ animate(src, pixel_z = 16, time = 5)
+ default_pixel_z = 16
+ addtimer(CALLBACK(src, .proc/EndFlight), fly_duration)
+
+/mob/living/simple_animal/hostile/infestation/floatfly/proc/EndFlight()
+ if(QDELETED(src) || stat == DEAD)
+ return FALSE
+ fly_cooldown = world.time + fly_cooldown_time
+ density = TRUE
+ visible_message(SPAN_WARNING("\The [src] stops its flight."))
+ animate(src, pixel_z = 0, time = 5)
+ default_pixel_z = 0
+
+/datum/ai_holder/simple_animal/infestation/floatfly
+ returns_home = FALSE
+ home_low_priority = TRUE
+ speak_chance = 3
+ wander = TRUE
+ base_wander_delay = 1
+
+/datum/ai_holder/simple_animal/infestation/floatfly/give_target(new_target, urgent = FALSE)
+ . = ..()
+ var/mob/living/simple_animal/hostile/infestation/floatfly/F = holder
+ if(. && prob(25) && world.time > F.fly_cooldown)
+ F.StartFlight()
diff --git a/code/modules/mob/living/simple_animal/hostile/infestation/larva.dm b/code/modules/mob/living/simple_animal/hostile/infestation/larva.dm
new file mode 100644
index 0000000000000..6ff6480133b3a
--- /dev/null
+++ b/code/modules/mob/living/simple_animal/hostile/infestation/larva.dm
@@ -0,0 +1,133 @@
+/mob/living/simple_animal/hostile/infestation/larva
+ name = "larva"
+ desc = "A weird insect-like creature."
+ icon_state = "larva"
+ icon_living = "larva"
+ icon_dead = "larva_dead"
+ response_help = "pets"
+ response_disarm = "pushes aside"
+ response_harm = "stomps on"
+ destroy_surroundings = FALSE
+ mob_size = MOB_MINISCULE
+ density = FALSE
+ layer = LYING_MOB_LAYER
+ speak_emote = list("gurgles")
+ pass_flags = PASS_FLAG_TABLE | PASS_FLAG_GRILLE
+ move_to_delay = 1.5
+
+ health = 20
+ maxHealth = 20
+
+ meat_type = /obj/item/reagent_containers/food/snacks/abominationmeat
+ meat_amount = 1
+
+ ai_holder = /datum/ai_holder/simple_animal/infestation/larva
+ say_list_type = /datum/say_list/infestation_larva
+ death_sounds = list(
+ 'sound/simple_mob/abominable_infestation/larva/death_1.ogg',
+ 'sound/simple_mob/abominable_infestation/larva/death_2.ogg',
+ )
+
+ transformation_types = list(
+ /mob/living/simple_animal/hostile/infestation/broodling = 30 SECONDS,
+ /mob/living/simple_animal/hostile/infestation/spitter = 45 SECONDS,
+ /mob/living/simple_animal/hostile/infestation/eviscerator = 60 SECONDS,
+ /mob/living/simple_animal/hostile/infestation/assembler = 75 SECONDS,
+ )
+ ignore_combat = TRUE
+
+/datum/say_list/infestation_larva
+ emote_hear = list("gurgles", "hisses", "attempts to make a sound")
+ emote_see = list("wriggles around")
+
+ emote_hear_sounds = list(
+ 'sound/simple_mob/abominable_infestation/larva/ambient_1.ogg',
+ 'sound/simple_mob/abominable_infestation/larva/ambient_2.ogg',
+ 'sound/simple_mob/abominable_infestation/larva/ambient_3.ogg',
+ )
+ emote_see_sounds = list(
+ 'sound/simple_mob/abominable_infestation/larva/ambient_1.ogg',
+ 'sound/simple_mob/abominable_infestation/larva/ambient_2.ogg',
+ 'sound/simple_mob/abominable_infestation/larva/ambient_3.ogg',
+ )
+
+/datum/ai_holder/simple_animal/infestation/larva
+ hostile = FALSE
+ retaliate = FALSE
+ returns_home = FALSE
+ can_flee = TRUE
+ speak_chance = 4
+ wander = TRUE
+ base_wander_delay = 4
+
+/mob/living/simple_animal/hostile/infestation/larva/Initialize()
+ . = ..()
+ transformation_time = world.time + rand(60 SECONDS, 90 SECONDS)
+
+
+// Neutral faction and slightly different color
+/mob/living/simple_animal/hostile/infestation/larva/friendly
+ faction = "neutral"
+ color = "#c87d44"
+ transformation_types = list(
+ /mob/living/simple_animal/hostile/infestation/broodling = 60 SECONDS,
+ /mob/living/simple_animal/hostile/infestation/eviscerator = 90 SECONDS,
+ /mob/living/simple_animal/hostile/infestation/assembler = 120 SECONDS,
+ )
+
+// Implanted subtype of larva transforms much faster
+/mob/living/simple_animal/hostile/infestation/larva/implant/BecomeEgg()
+ . = ..()
+ transformation_time = world.time + rand(5 SECONDS, 10 SECONDS)
+
+/mob/living/simple_animal/hostile/infestation/larva/implant/ImplantRemoval()
+ playsound(src, pick(say_list.emote_hear_sounds), 50, TRUE)
+ transformation_time = world.time + rand(10 SECONDS, 15 SECONDS)
+
+// Aggressive larva that will rush to humans and implant into them
+/mob/living/simple_animal/hostile/infestation/larva/implant/implanter
+ icon_state = "larva_implanter"
+ icon_living = "larva_implanter"
+ ai_holder = /datum/ai_holder/simple_animal/infestation/larva/implanter
+
+/datum/ai_holder/simple_animal/infestation/larva/implanter
+ hostile = TRUE
+ retaliate = TRUE
+ can_flee = FALSE
+
+/datum/ai_holder/simple_animal/infestation/larva/implanter/list_targets()
+ var/mob/living/simple_animal/hostile/infestation/larva/implant/implanter/L = holder
+ if(L.transformation_time != null) // Already implanted once
+ return
+
+ var/list/humans = list()
+ for(var/mob/living/carbon/human/H in view(vision_range, holder))
+ humans += H
+
+ return humans
+
+/mob/living/simple_animal/hostile/infestation/larva/implant/implanter/Initialize()
+ . = ..()
+ transformation_time = null // We only evolve after implanting ourselves
+
+/mob/living/simple_animal/hostile/infestation/larva/implant/implanter/attack_target(atom/A)
+ var/mob/living/carbon/human/H = A
+ var/list/valid_organs = list()
+ for(var/obj/item/organ/external/O in H.organs)
+ if(istype(O, /obj/item/organ/external/stump))
+ continue
+ if(locate(/mob/living/simple_animal/hostile/infestation/larva) in O.implants)
+ continue
+ valid_organs += O
+
+ if(!LAZYLEN(valid_organs))
+ return
+
+ visible_message(SPAN_DANGER("[src] bites through [H]'s clothes and skin and wriggles inside!"))
+ playsound(src, 'sound/simple_mob/abominable_infestation/larva/implant.ogg', 50, TRUE)
+ var/obj/item/organ/external/target_organ = pick(valid_organs)
+ target_organ.owner.apply_damage(15, DAMAGE_BRUTE, target_organ.organ_tag)
+ forceMove(target_organ)
+ target_organ.implants += src
+ transformation_time = world.time + rand(120 SECONDS, 240 SECONDS)
+ ai_holder.speak_chance = 0
diff --git a/code/modules/mob/living/simple_animal/hostile/infestation/rhino.dm b/code/modules/mob/living/simple_animal/hostile/infestation/rhino.dm
new file mode 100644
index 0000000000000..74386f0fcfeb2
--- /dev/null
+++ b/code/modules/mob/living/simple_animal/hostile/infestation/rhino.dm
@@ -0,0 +1,71 @@
+/mob/living/simple_animal/hostile/infestation/rhino
+ name = "rhino"
+ desc = "A large heavily-armored monster."
+ icon = 'icons/mob/simple_animal/abominable_infestation/48x48.dmi'
+ icon_state = "rhino"
+ icon_living = "rhino"
+ icon_dead = "rhino_dead"
+ mob_size = MOB_MEDIUM
+ default_pixel_x = -8
+ pixel_x = -8
+
+ natural_weapon = /obj/item/natural_weapon/hooves/rhino
+
+ health = 1000
+ maxHealth = 1000
+ resistance = 15
+
+ movement_cooldown = 5
+ movement_sound = 'sound/simple_mob/abominable_infestation/rhino/step.ogg'
+ movement_shake_radius = 3
+
+ meat_type = /obj/item/reagent_containers/food/snacks/abominationmeat
+ meat_amount = 6
+ skin_material = MATERIAL_SKIN_CHITIN
+ skin_amount = 8
+ bone_material = MATERIAL_BONE_CARTILAGE
+ bone_amount = 8
+
+ ai_holder = /datum/ai_holder/simple_animal/infestation/rhino
+ death_sounds = list('sound/simple_mob/abominable_infestation/rhino/death.ogg')
+
+ /// Grants increased movement speed
+ var/enraged = FALSE
+ var/enraged_end_time
+ var/enraged_cooldown
+ var/enraged_cooldown_time = 20 SECONDS
+ var/enraged_movement_sound = 'sound/simple_mob/abominable_infestation/rhino/step_angry.ogg'
+
+/obj/item/natural_weapon/hooves/rhino
+ force = 50
+ armor_penetration = 10
+ hitsound = 'sound/weapons/heavysmash.ogg'
+
+/mob/living/simple_animal/hostile/infestation/rhino/Life()
+ . = ..()
+ if(!.)
+ return
+ if(enraged && world.time > enraged_end_time)
+ enraged = FALSE
+ visible_message(SPAN_WARNING("\The [src] calms down."))
+
+/mob/living/simple_animal/hostile/infestation/rhino/movement_delay()
+ . = ..()
+ . -= enraged * 2
+
+
+/datum/ai_holder/simple_animal/infestation/rhino
+ returns_home = TRUE
+ home_low_priority = TRUE
+ wander = TRUE
+ base_wander_delay = 10
+
+/datum/ai_holder/simple_animal/infestation/rhino/give_target(new_target, urgent = FALSE)
+ . = ..()
+ var/mob/living/simple_animal/hostile/infestation/rhino/R = holder
+ if(. && prob(25) && world.time > R.enraged_cooldown)
+ R.enraged_cooldown = world.time + R.enraged_cooldown_time
+ R.enraged_end_time = world.time + 10 SECONDS
+ R.enraged = TRUE
+ playsound(holder, 'sound/simple_mob/abominable_infestation/rhino/roar.ogg', 75, TRUE, 6)
+ holder.visible_message(SPAN_DANGER("\The [holder] charges at [new_target]!"))
diff --git a/code/modules/mob/living/simple_animal/hostile/infestation/spitter.dm b/code/modules/mob/living/simple_animal/hostile/infestation/spitter.dm
new file mode 100644
index 0000000000000..a6d630120d8c7
--- /dev/null
+++ b/code/modules/mob/living/simple_animal/hostile/infestation/spitter.dm
@@ -0,0 +1,54 @@
+/mob/living/simple_animal/hostile/infestation/spitter
+ name = "spitter"
+ desc = "A weird wriggling creature. Some sort of corrosive substance is dripping from its maw."
+ icon_state = "spitter"
+ icon_living = "spitter"
+ icon_dead = "spitter_dead"
+ mob_size = MOB_SMALL
+ move_to_delay = 3
+
+ natural_weapon = /obj/item/natural_weapon/bite/abomination_spitter
+
+ base_attack_cooldown = 1 SECONDS
+ ranged = TRUE
+ projectiletype = /obj/item/projectile/energy/acid_spit
+ projectilesound = 'sound/weapons/alien_spit.ogg'
+ fire_desc = "spits acid"
+ ranged_range = 7
+
+ health = 150
+ maxHealth = 150
+
+ meat_type = /obj/item/reagent_containers/food/snacks/abominationmeat
+ meat_amount = 4
+ skin_material = MATERIAL_SKIN_CHITIN
+ skin_amount = 2
+ bone_material = MATERIAL_BONE_CARTILAGE
+ bone_amount = 2
+
+ ai_holder = /datum/ai_holder/simple_animal/infestation/spitter
+ say_list_type = /datum/say_list/infestation_spitter
+ death_sounds = list('sound/simple_mob/abominable_infestation/spitter/death.ogg')
+
+/obj/item/natural_weapon/bite/abomination_spitter
+ hitsound = 'sound/simple_mob/abominable_infestation/spitter/attack.ogg'
+
+/datum/say_list/infestation_spitter
+ emote_hear = list("gurgles")
+ emote_see = list("looks around", "wriggles around")
+
+ emote_hear_sounds = list(
+ 'sound/simple_mob/abominable_infestation/spitter/ambient_1.ogg',
+ 'sound/simple_mob/abominable_infestation/spitter/ambient_2.ogg',
+ )
+ emote_see_sounds = list(
+ 'sound/simple_mob/abominable_infestation/spitter/ambient_1.ogg',
+ 'sound/simple_mob/abominable_infestation/spitter/ambient_2.ogg',
+ )
+
+/datum/ai_holder/simple_animal/infestation/spitter
+ returns_home = FALSE
+ home_low_priority = TRUE
+ speak_chance = 2
+ wander = TRUE
+ base_wander_delay = 15
diff --git a/code/modules/mob/living/simple_animal/life.dm b/code/modules/mob/living/simple_animal/life.dm
index 0411270a7f39f..9500fc7121200 100644
--- a/code/modules/mob/living/simple_animal/life.dm
+++ b/code/modules/mob/living/simple_animal/life.dm
@@ -8,7 +8,7 @@
icon_state = icon_living
switch_from_dead_to_living_mob_list()
set_stat(CONSCIOUS)
- set_density(1)
+ set_density(initial(density))
return 0
handle_atmos()
diff --git a/code/modules/mob/living/simple_animal/natural_weapons.dm b/code/modules/mob/living/simple_animal/natural_weapons.dm
index a5b2c6e4967d3..262217d31270b 100644
--- a/code/modules/mob/living/simple_animal/natural_weapons.dm
+++ b/code/modules/mob/living/simple_animal/natural_weapons.dm
@@ -30,6 +30,9 @@
attack_verb = list("nibbled")
hitsound = null
+/obj/item/natural_weapon/bite/abomination_spitter
+ hitsound = 'sound/simple_mob/abominable_infestation/spitter/attack.ogg'
+
/obj/item/natural_weapon/bite/strong
force = 25
@@ -40,9 +43,17 @@
sharp = TRUE
edge = TRUE
+/obj/item/natural_weapon/claws/broodling
+ force = 4
+ hitsound = 'sound/weapons/slashmiss.ogg'
+ attack_cooldown = 2
+
/obj/item/natural_weapon/claws/strong
force = 25
+/obj/item/natural_weapon/claws/strong/eviscerator
+ hitsound = 'sound/simple_mob/abominable_infestation/eviscerator/attack.ogg'
+
/obj/item/natural_weapon/claws/weak
force = 5
attack_verb = list("clawed", "scratched")
diff --git a/code/modules/mob/living/simple_animal/simple_animal.dm b/code/modules/mob/living/simple_animal/simple_animal.dm
index 999533259b5f0..8e56de0eb6e13 100644
--- a/code/modules/mob/living/simple_animal/simple_animal.dm
+++ b/code/modules/mob/living/simple_animal/simple_animal.dm
@@ -152,6 +152,9 @@
var/return_damage_min
var/return_damage_max
+// List of potential death sounds, if any
+ var/list/death_sounds = list()
+
/mob/living/simple_animal/Initialize()
. = ..()
if(LAZYLEN(natural_armor))
@@ -175,8 +178,11 @@
density = FALSE
adjustBruteLoss(maxHealth) //Make sure dey dead.
walk_to(src,0)
+ if(LAZYLEN(death_sounds))
+ playsound(src, pick(death_sounds), 50, TRUE)
return ..(gibbed,deathmessage,show_dead_message)
+
/mob/living/simple_animal/ex_act(severity)
if (status_flags & GODMODE)
return
diff --git a/code/modules/mob/mob.dm b/code/modules/mob/mob.dm
index e97b9e7f003a3..c3924a1296706 100644
--- a/code/modules/mob/mob.dm
+++ b/code/modules/mob/mob.dm
@@ -210,7 +210,7 @@
if(pulling)
if(istype(pulling, /obj))
var/obj/O = pulling
- . += clamp(O.w_class, 0, ITEM_SIZE_GARGANTUAN) / 5
+ . += O.get_additional_speed_decrease()
else if(istype(pulling, /mob))
var/mob/M = pulling
. += max(0, M.mob_size) / MOB_MEDIUM
@@ -887,7 +887,7 @@
implant.update_icon()
if(istype(implant,/obj/item/implant))
var/obj/item/implant/imp = implant
- imp.removed()
+ imp.ImplantRemoval()
if (istype(implant, /obj/item/holder/voxslug))
var/obj/item/holder/voxslug/holder = implant
var/mob/living/simple_animal/hostile/voxslug/V = holder.contents[1]
diff --git a/code/modules/organs/external/_external.dm b/code/modules/organs/external/_external.dm
index 1d14c63d814da..e00f92b1b4182 100644
--- a/code/modules/organs/external/_external.dm
+++ b/code/modules/organs/external/_external.dm
@@ -1252,7 +1252,7 @@ Note that amputating the affected organ does in fact remove the infection from t
// let actual implants still inside know they're no longer implanted
if(istype(I, /obj/item/implant))
var/obj/item/implant/imp_device = I
- imp_device.removed()
+ imp_device.ImplantRemoval()
else
implants.Remove(implant)
implant.forceMove(get_turf(src))
diff --git a/code/modules/organs/internal/species/abomination.dm b/code/modules/organs/internal/species/abomination.dm
new file mode 100644
index 0000000000000..932fe7c613300
--- /dev/null
+++ b/code/modules/organs/internal/species/abomination.dm
@@ -0,0 +1,57 @@
+/obj/item/organ/internal/heart/abomination
+ name = "blood pump"
+
+/obj/item/organ/internal/stomach/abomination
+ name = "nutrient refinery"
+
+/obj/item/organ/internal/lungs/abomination
+ name = "gas pump"
+
+/obj/item/organ/internal/liver/abomination
+ name = "toxin filter"
+
+/obj/item/organ/internal/kidneys/abomination
+ name = "secondary filter"
+
+/obj/item/organ/internal/brain/abomination
+ name = "swarm link"
+
+/obj/item/organ/internal/eyes/abomination
+ name = "eyes" // Eyes, yes.
+
+// Actual stuff
+/obj/item/organ/internal/larva_producer
+ name = "larvae storage"
+ icon_state = "stomach"
+ color = COLOR_MAROON
+ organ_tag = BP_LARVA
+ parent_organ = BP_CHEST
+ can_be_printed = FALSE
+ var/larva_cooldown = 0
+ var/larva_cooldown_time = 300
+
+/obj/item/organ/internal/larva_producer/Initialize()
+ . = ..()
+ larva_cooldown = larva_cooldown_time * rand(2, 3)
+
+/obj/item/organ/internal/larva_producer/Process()
+ if(owner)
+ larva_cooldown = larva_cooldown <= 0 ? larva_cooldown_time : larva_cooldown - 1
+ if(larva_cooldown == round(larva_cooldown_time * 0.3))
+ to_chat(owner, SPAN_WARNING("Your [parent_organ] is vibrating ever so slighty..."))
+ else if(larva_cooldown == round(larva_cooldown_time * 0.2))
+ to_chat(owner, SPAN_DANGER("Something inside of your [parent_organ] is moving!"))
+ else if(larva_cooldown == round(larva_cooldown_time * 0.1))
+ to_chat(owner, SPAN_DANGER("\The [src] is about to produce a new larva!"))
+ else if(larva_cooldown <= 0)
+ var/mob/living/simple_animal/hostile/infestation/larva/L = new(get_turf(owner))
+ owner.Stun(2)
+ playsound(L, 'sound/effects/splat.ogg', 50, TRUE)
+ var/obj/item/organ/internal/stomach/stomach = owner.internal_organs_by_name[BP_STOMACH]
+ var/obj/effect/decal/cleanable/vomit/splat = new /obj/effect/decal/cleanable/vomit(get_turf(owner))
+ splat.color = COLOR_MAROON
+ if(stomach.ingested.total_volume)
+ stomach.ingested.trans_to_obj(splat, min(15, stomach.ingested.total_volume))
+ visible_message(SPAN_DANGER("\The [owner] throws up, as something crawls out!"),
+ SPAN_DANGER("You throw up, leading \the [L] outside."))
+ return ..()
diff --git a/code/modules/overmap/contacts/_contact.dm b/code/modules/overmap/contacts/_contact.dm
new file mode 100644
index 0000000000000..37add6643e8d2
--- /dev/null
+++ b/code/modules/overmap/contacts/_contact.dm
@@ -0,0 +1,140 @@
+var/list/phonetic_alphabet_prefix = list("ALPHA", "BRAVO", "CHARLIE", "DELTA", "ECHO", "FOXTROT", "GOLF", "HOTEL", "INDIA",
+ "JULIETT", "KILO", "LIMA", "MIKE", "NOVEMBER", "OSCAR", "PAPA", "QUEBEC", "ROMEO", "SIERRA", "TANGO",
+ "UNIFORM", "VICTOR", "WHISKEY", "XRAY", "YANKEE", "ZULU")
+var/list/phonetic_alphabet_suffix = list("ALPHA", "BETA", "GAMMA", "DELTA", "EPSILON", "ZETA", "ETA", "THETA", "IOTA", "KAPPA",
+ "LAMBDA", "MU", "NU", "XI", "OMICRON", "PI", "RHO", "SIGMA", "TAU", "UPSILON", "PHI", "CHI", "PSI", "OMEGA")
+#define SENSOR_TIME_DELAY 0.2 SECONDS
+/datum/overmap_contact
+
+ var/name = "Unknown" // Contact name.
+ var/temp_designation = "Unknown" // Temporary designation given by the computer to this object.
+ var/class = "UNKWN" // Class of the ship
+ var/class_long = "Unknown" // Human-readable name of the class.
+ var/information = "Unknown" // Any information we have on the contact.
+ var/identified = FALSE // Have we identified it?
+ var/identification_progress // Progress towards identification, increased by identification_amount of the ship effect.
+ var/seen // Have we seen this recently?
+ var/image/marker // Image overlay attached to the contact.
+ var/is_overmap_event = FALSE // Pretty much used to skip a bunch of stuff so we can ues the same system for overmap effects.
+ var/pinged = FALSE // Used to animate overmap effects.
+ var/list/images = list() // Our list of images to cast to users.
+
+ // The sensor console holding this data.
+ var/obj/machinery/computer/ship/sensors/owner
+
+ // The actual overmap effect associated with this.
+ var/obj/effect/overmap/visitable/ship/effect
+
+/datum/overmap_contact/proc/handle_being_identified()
+ if(!identified)
+ hide()
+ identified = TRUE
+ identification_progress = effect.identification_difficulty
+ marker.icon = effect.icon
+ marker.appearance_flags |= RESET_COLOR
+ marker.icon_state = effect.contact_icon_state || "ship"
+ marker.color = effect.color
+ show()
+
+/datum/overmap_contact/New(var/obj/machinery/computer/ship/sensors/creator, var/obj/effect/overmap/source, var/is_event = FALSE)
+ is_overmap_event = is_event
+ // Update local tracking information.
+ owner = creator
+ effect = source
+ name = effect.scanner_name
+ owner.contact_datums[effect] = src
+
+ if(!is_overmap_event)
+ var/obj/effect/overmap/visitable/ship/source_ship = source
+ information = source_ship.desc
+ temp_designation = "[pick(global.phonetic_alphabet_prefix)]-[pick(global.phonetic_alphabet_suffix)]-[random_id(type, 1, 999)]"
+ marker = image(loc = source_ship, icon = 'icons/obj/overmap.dmi', icon_state = "unidentified_ship")
+
+ if(source_ship.transponder_active)
+ handle_being_identified()
+ else
+ identified = TRUE
+ var/obj/effect/overmap/event/source_event = source
+ marker = image(loc = source_event, icon = 'icons/obj/overmap.dmi', icon_state = source_event.overmap_effect_state)
+ marker.color = source_event.color
+ marker.filters = filter(type="drop_shadow", color = marker.color + "F0", size = 2, offset = 1,x = 0, y = 0)
+ marker.alpha = 0
+
+ images += marker
+
+/datum/overmap_contact/proc/update_marker_icon(var/range = 0)
+ if(identified)
+ marker.icon_state = effect.contact_icon_state
+
+ marker.overlays.Cut()
+
+ if(check_effect_shield())
+ var/image/shield_image = image(icon = 'icons/obj/overmap.dmi', icon_state = "shield")
+ shield_image.pixel_x = 8
+ marker.overlays += shield_image
+
+ if(range > 1)
+ var/image/radar
+
+ for(var/image/I in images)
+ if(I.tag == "radar")
+ radar = I
+ if(radar)
+ return
+
+ radar = image(loc = effect, icon = 'icons/obj/overmap.dmi', icon_state = "sensor_range")
+ radar.pixel_x = -2
+ radar.tag = "radar"
+ radar.filters = filter(type="blur", size = 0.5)
+ images += radar
+
+ var/matrix/M = matrix()
+ M.Scale(range*2.6)
+ animate(radar, transform = M, alpha = 0, time = (0.25 SECONDS*range), 1, SINE_EASING)
+ addtimer(CALLBACK(src, .proc/reset_radar, radar), (0.25 SECONDS *range+0.1))
+ QDEL_IN(radar, (0.25 SECONDS *range+0.3))
+
+/datum/overmap_contact/proc/reset_radar(var/image/radar)
+ images -= radar
+
+/datum/overmap_contact/proc/show()
+ for(var/weakref/W in owner?.viewers)
+ var/mob/M = W.resolve()
+ if(istype(M))
+ M.client?.images |= images
+
+/datum/overmap_contact/proc/hide()
+ for(var/weakref/W in owner?.viewers)
+ var/mob/M = W.resolve()
+ if(istype(M))
+ M.client?.images -= images
+
+/datum/overmap_contact/proc/ping()
+ if(pinged)
+ return
+ pinged = TRUE
+ show()
+ animate(marker, alpha=255, 0.5 SECOND, 1, LINEAR_EASING)
+ addtimer(CALLBACK(src, .proc/unping), 1 SECOND)
+
+
+/datum/overmap_contact/proc/unping()
+ animate(marker, alpha=230, 2 SECOND, 1, LINEAR_EASING)
+
+/datum/overmap_contact/proc/check_effect_shield()
+ var/shield_active = FALSE
+ for(var/obj/machinery/power/shield_generator/S in SSmachines.machinery)
+ if(S.z in effect.map_z)
+ if(S.running == SHIELD_RUNNING)
+ shield_active = TRUE
+ return shield_active
+
+/datum/overmap_contact/Destroy()
+ if(owner)
+ hide()
+ if(effect)
+ owner.contact_datums[effect] = null
+ owner.contact_datums -= null
+ owner = null
+ effect = null
+ . = ..()
diff --git a/code/modules/overmap/contacts/_contacts.dm b/code/modules/overmap/contacts/_contacts.dm
new file mode 100644
index 0000000000000..f9e22ee42875f
--- /dev/null
+++ b/code/modules/overmap/contacts/_contacts.dm
@@ -0,0 +1,141 @@
+var/list/phonetic_alphabet_prefix = list("ALPHA", "BRAVO", "CHARLIE", "DELTA", "ECHO", "FOXTROT", "GOLF", "HOTEL", "INDIA",
+ "JULIETT", "KILO", "LIMA", "MIKE", "NOVEMBER", "OSCAR", "PAPA", "QUEBEC", "ROMEO", "SIERRA", "TANGO",
+ "UNIFORM", "VICTOR", "WHISKEY", "XRAY", "YANKEE", "ZULU")
+var/list/phonetic_alphabet_suffix = list("ALPHA", "BETA", "GAMMA", "DELTA", "EPSILON", "ZETA", "ETA", "THETA", "IOTA", "KAPPA",
+ "LAMBDA", "MU", "NU", "XI", "OMICRON", "PI", "RHO", "SIGMA", "TAU", "UPSILON", "PHI", "CHI", "PSI", "OMEGA")
+#define SENSOR_TIME_DELAY 0.2 SECONDS
+
+/datum/overmap_contact
+
+ var/name = "Unknown" // Contact name.
+ var/temp_designation = "Unknown" // Temporary designation given by the computer to this object.
+ var/class = "UNKWN" // Class of the ship
+ var/class_long = "Unknown" // Human-readable name of the class.
+ var/information = "Unknown" // Any information we have on the contact.
+ var/identified = FALSE // Have we identified it?
+ var/identification_progress // Progress towards identification, increased by identification_amount of the ship effect.
+ var/seen // Have we seen this recently?
+ var/image/marker // Image overlay attached to the contact.
+ var/is_overmap_event = FALSE // Pretty much used to skip a bunch of stuff so we can ues the same system for overmap effects.
+ var/pinged = FALSE // Used to animate overmap effects.
+ var/list/images = list() // Our list of images to cast to users.
+
+ // The sensor console holding this data.
+ var/obj/machinery/computer/ship/sensors/owner
+
+ // The actual overmap effect associated with this.
+ var/obj/effect/overmap/visitable/ship/effect
+
+/datum/overmap_contact/proc/handle_being_identified()
+ if(!identified)
+ hide()
+ identified = TRUE
+ identification_progress = effect.identification_difficulty
+ marker.icon = effect.icon
+ marker.appearance_flags |= RESET_COLOR
+ marker.icon_state = effect.contact_icon_state || "ship"
+ marker.color = effect.color
+ show()
+
+/datum/overmap_contact/New(var/obj/machinery/computer/ship/sensors/creator, var/obj/effect/overmap/source, var/is_event = FALSE)
+ is_overmap_event = is_event
+ // Update local tracking information.
+ owner = creator
+ effect = source
+ name = effect.scanner_name
+ owner.contact_datums[effect] = src
+
+ if(!is_overmap_event)
+ var/obj/effect/overmap/visitable/ship/source_ship = source
+ information = source_ship.desc
+ temp_designation = "[pick(global.phonetic_alphabet_prefix)]-[pick(global.phonetic_alphabet_suffix)]-[random_id(type, 1, 999)]"
+ marker = image(loc = source_ship, icon = 'icons/obj/overmap.dmi', icon_state = "unidentified_ship")
+
+ if(source_ship.transponder_active)
+ handle_being_identified()
+ else
+ identified = TRUE
+ var/obj/effect/overmap/event/source_event = source
+ marker = image(loc = source_event, icon = 'icons/obj/overmap.dmi', icon_state = source_event.overmap_effect_state)
+ marker.color = source_event.color
+ marker.filters = filter(type="drop_shadow", color = marker.color + "F0", size = 2, offset = 1,x = 0, y = 0)
+ marker.alpha = 0
+
+ images += marker
+
+/datum/overmap_contact/proc/update_marker_icon(var/range = 0)
+ if(identified)
+ marker.icon_state = effect.contact_icon_state
+
+ marker.overlays.Cut()
+
+ if(check_effect_shield())
+ var/image/shield_image = image(icon = 'icons/obj/overmap.dmi', icon_state = "shield")
+ shield_image.pixel_x = 8
+ marker.overlays += shield_image
+
+ if(range > 1)
+ var/image/radar
+
+ for(var/image/I in images)
+ if(I.tag == "radar")
+ radar = I
+ if(radar)
+ return
+
+ radar = image(loc = effect, icon = 'icons/obj/overmap.dmi', icon_state = "sensor_range")
+ radar.pixel_x = -2
+ radar.tag = "radar"
+ radar.filters = filter(type="blur", size = 0.5)
+ images += radar
+
+ var/matrix/M = matrix()
+ M.Scale(range*2.6)
+ animate(radar, transform = M, alpha = 0, time = (0.25 SECONDS*range), 1, SINE_EASING)
+ addtimer(CALLBACK(src, .proc/reset_radar, radar), (0.25 SECONDS *range+0.1))
+ QDEL_IN(radar, (0.25 SECONDS *range+0.3))
+
+/datum/overmap_contact/proc/reset_radar(var/image/radar)
+ images -= radar
+
+/datum/overmap_contact/proc/show()
+ for(var/weakref/W in owner?.viewers)
+ var/mob/M = W.resolve()
+ if(istype(M))
+ M.client?.images |= images
+
+/datum/overmap_contact/proc/hide()
+ for(var/weakref/W in owner?.viewers)
+ var/mob/M = W.resolve()
+ if(istype(M))
+ M.client?.images -= images
+
+/datum/overmap_contact/proc/ping()
+ if(pinged)
+ return
+ pinged = TRUE
+ show()
+ animate(marker, alpha=255, 0.5 SECOND, 1, LINEAR_EASING)
+ addtimer(CALLBACK(src, .proc/unping), 1 SECOND)
+
+
+/datum/overmap_contact/proc/unping()
+ animate(marker, alpha=230, 2 SECOND, 1, LINEAR_EASING)
+
+/datum/overmap_contact/proc/check_effect_shield()
+ var/shield_active = FALSE
+ for(var/obj/machinery/power/shield_generator/S in SSmachines.machinery)
+ if(S.z in effect.map_z)
+ if(S.running == SHIELD_RUNNING)
+ shield_active = TRUE
+ return shield_active
+
+/datum/overmap_contact/Destroy()
+ if(owner)
+ hide()
+ if(effect)
+ owner.contact_datums[effect] = null
+ owner.contact_datums -= null
+ owner = null
+ effect = null
+ . = ..()
diff --git a/code/modules/overmap/contacts/contact_class.dm b/code/modules/overmap/contacts/contact_class.dm
new file mode 100644
index 0000000000000..2079bb94e29c5
--- /dev/null
+++ b/code/modules/overmap/contacts/contact_class.dm
@@ -0,0 +1,41 @@
+/obj/effect/overmap/visitable/ship
+ var/contact_class = /decl/ship_contact_class
+
+/decl/ship_contact_class
+ var/class_short = "Ship"
+ var/class_long = "Unknown Ship Class"
+ var/min_ship_mass = 0
+ var/max_ship_mass = INFINITY
+
+/decl/ship_contact_class/ship
+ class_short = "SH"
+ class_long = "Ship"
+ max_ship_mass = 10000
+
+/decl/ship_contact_class/shuttle
+ class_short = "SHUTTLE"
+ class_long = "Shuttle"
+ max_ship_mass = 10000
+
+/decl/ship_contact_class/destroyer_escort
+ class_short = "DE"
+ class_long = "Destroyer Escort"
+ min_ship_mass = 10000
+ max_ship_mass = 50000
+
+/decl/ship_contact_class/destroyer
+ class_short = "DD"
+ class_long = "Destroyer"
+ min_ship_mass = 50000
+ max_ship_mass = 100000
+
+/decl/ship_contact_class/cruiser
+ class_short = "CA"
+ class_long = "Cruiser"
+ min_ship_mass = 100000
+ max_ship_mass = 250000
+
+/decl/ship_contact_class/capital_ship
+ class_short = "CAP"
+ class_long = "Capital Ship"
+ min_ship_mass = 250000
diff --git a/code/modules/overmap/contacts/contact_sensors.dm b/code/modules/overmap/contacts/contact_sensors.dm
new file mode 100644
index 0000000000000..0c3d59d5e12a0
--- /dev/null
+++ b/code/modules/overmap/contacts/contact_sensors.dm
@@ -0,0 +1,143 @@
+/obj/machinery/computer/ship/sensors
+ var/list/objects_in_view = list()
+ var/list/contact_datums = list()
+
+/obj/machinery/computer/ship/sensors/Destroy()
+ objects_in_view.Cut()
+ for(var/key in contact_datums)
+ var/datum/overmap_contact/record = contact_datums[key]
+ qdel(record)
+ contact_datums.Cut()
+ . = ..()
+
+/obj/machinery/computer/ship/sensors/attempt_hook_up(obj/effect/overmap/visitable/ship/sector)
+ . = ..()
+ if(. && linked && !contact_datums[linked])
+ var/datum/overmap_contact/record = new(src, linked)
+ record.handle_being_identified()
+
+/obj/machinery/computer/ship/sensors/proc/reveal_contacts(var/mob/user)
+ if(user && user.client)
+ for(var/key in contact_datums)
+ var/datum/overmap_contact/record = contact_datums[key]
+ if(record)
+ user.client.images |= record.marker
+
+/obj/machinery/computer/ship/sensors/proc/hide_contacts(var/mob/user)
+ if(user && user.client)
+ for(var/key in contact_datums)
+ var/datum/overmap_contact/record = contact_datums[key]
+ if(record)
+ user.client.images -= record.marker
+
+/obj/machinery/computer/ship/sensors/Process()
+ ..()
+ update_sound()
+ if(!linked)
+ return
+
+ // Update our 'sensor range' (ie. overmap lighting)
+ if(!sensors || !sensors.use_power || !sensors.powered())
+ linked.set_light(0)
+ // TODO move to on_power_change()?
+ for(var/key in contact_datums)
+ var/datum/overmap_contact/record = contact_datums[key]
+ if(record.effect == linked)
+ continue
+ animate(record.marker, alpha=0, 1 SECOND, 1, LINEAR_EASING)
+ record.hide()
+
+ objects_in_view.Cut()
+ return
+
+ var/sensor_range = round(sensors.range,1)
+ linked.set_light(1, sensor_range, sensor_range+1)
+
+ // What can we see?
+ var/list/new_objects_in_view = list()
+ var/list/objects_in_current_view = list()
+ for(var/obj/effect/overmap/contact in view(sensor_range, linked)-linked)
+ objects_in_current_view[contact] = TRUE
+ if(!objects_in_view[contact])
+ new_objects_in_view[contact] = TRUE
+
+ // Fade out and remove anything that is out of range.
+ for(var/obj/effect/overmap/contact in objects_in_view)
+ if(!QDELETED(contact) && objects_in_current_view[contact])
+ continue
+ var/datum/overmap_contact/record = contact_datums[contact]
+ var/bearing = round(90 - Atan2(contact.x - linked.x, contact.y - linked.y),5)
+ if(bearing < 0)
+ bearing += 360
+ if(record)
+ animate(record.marker, alpha=0, 2 SECOND, 1, LINEAR_EASING)
+ addtimer(CALLBACK(record, /datum/overmap_contact/proc/hide), 2 SECOND)
+ if(record && !record.is_overmap_event)
+ if(record.identified)
+ visible_message(SPAN_NOTICE("[src] states, 'Contact lost with [record.name], bearing [bearing].'"))
+ else
+ visible_message(SPAN_NOTICE("[src] states, 'Contact lost with [record.temp_designation], bearing [bearing].'"))
+ playsound(loc, "sound/machines/sensors/contact_lost.ogg", 30, 1)
+ objects_in_view -= contact
+ // Refresh or update contacts and markers for anything new.
+ objects_in_view |= new_objects_in_view
+
+ for(var/obj/effect/overmap/contact in new_objects_in_view)
+ var/datum/overmap_contact/record = contact_datums[contact]
+ var/bearing = round(90 - Atan2(contact.x - linked.x, contact.y - linked.y),5)
+ if(bearing < 0)
+ bearing += 360
+ if(record)
+ if(!record.is_overmap_event)
+ if(record.identified)
+ visible_message(SPAN_NOTICE("[src] states, 'Contact regained with [record.name], bearing [bearing].'"))
+ else
+ visible_message(SPAN_NOTICE("[src] states, 'Contact regained with [record.temp_designation], bearing [bearing].'"))
+ playsound(loc, "sound/machines/sensors/contact_regained.ogg", 30, 1)
+ record.show()
+ if(record.is_overmap_event)
+ animate(record.marker, alpha=255, 2 SECOND, 1, LINEAR_EASING)
+ continue
+ animate(record.marker, alpha=255, 2 SECOND, 1, LINEAR_EASING)
+
+ for(var/obj/effect/overmap/visitable/ship/contact in objects_in_view) //Update everything.
+ // Have we seen this ship before?
+ var/datum/overmap_contact/record = contact_datums[contact]
+ // Generate contact information for this overmap object.
+ var/bearing = round(90 - Atan2(contact.x - linked.x, contact.y - linked.y),5)
+ if(bearing < 0)
+ bearing += 360
+ if(!record)
+ record = new /datum/overmap_contact(src, contact)
+ if(record.effect.type in linked.known_ships)
+ record.handle_being_identified()
+ visible_message(SPAN_NOTICE("\The [src] states, \"Known contact registered, [record.name].\""))
+ else
+ playsound(loc, "sound/machines/sensors/newcontact.ogg", 30, 1)
+ visible_message(SPAN_NOTICE("\The [src] states, \"New contact detected, temporary designation [record.temp_designation], bearing [bearing]. Identification in progress.\""))
+
+ // Update identification information for this record.
+ if(prob(record.effect.sensor_visiblity))
+ record.update_marker_icon()
+ if(!record.identified)
+ if(record.identification_progress < record.effect.identification_difficulty)
+ record.identification_progress += 5
+ if(record.identification_progress == record.effect.identification_difficulty)
+ record.handle_being_identified()
+ playsound(loc, "sound/machines/sensors/contact_identified.ogg", 30, 1)
+ var/decl/ship_contact_class/class = decls_repository.get_decl(record.effect.contact_class)
+ visible_message(SPAN_NOTICE("[src] states, 'Contact [record.temp_designation] identified as [record.name], [class.class_long], bearing [bearing].'"))
+
+ for(var/obj/effect/overmap/event in objects_in_view)
+ var/datum/overmap_contact/record = contact_datums[event]
+ if(!record)
+ record = new /datum/overmap_contact(src, event, TRUE)
+ var/time_delay = max((SENSOR_TIME_DELAY * get_dist(linked, event)),1)
+ if(!record.pinged)
+ addtimer(CALLBACK(record, .proc/ping), time_delay)
+
+
+ //Update our own marker icon.
+ var/datum/overmap_contact/self_record = contact_datums[linked]
+ self_record.update_marker_icon(sensor_range)
+ self_record.show()
diff --git a/code/modules/overmap/contacts/contacts_class.dm b/code/modules/overmap/contacts/contacts_class.dm
new file mode 100644
index 0000000000000..e9f5d67359b1c
--- /dev/null
+++ b/code/modules/overmap/contacts/contacts_class.dm
@@ -0,0 +1,41 @@
+/obj/effect/overmap/visitable/ship
+ var/contact_class = /decl/ship_contact_class
+
+/decl/ship_contact_class
+ var/class_short = "Ship"
+ var/class_long = "Unknown Ship Class"
+ var/min_ship_mass = 0
+ var/max_ship_mass = INFINITY
+
+/decl/ship_contact_class/ship
+ class_short = "SH"
+ class_long = "Ship"
+ max_ship_mass = 10000
+
+/decl/ship_contact_class/shuttle
+ class_short = "SHUTTLE"
+ class_long = "Shuttle"
+ max_ship_mass = 10000
+
+/decl/ship_contact_class/destroyer_escort
+ class_short = "DE"
+ class_long = "Destroyer Escort"
+ min_ship_mass = 10000
+ max_ship_mass = 50000
+
+/decl/ship_contact_class/destroyer
+ class_short = "DD"
+ class_long = "Destroyer"
+ min_ship_mass = 50000
+ max_ship_mass = 100000
+
+/decl/ship_contact_class/cruiser
+ class_short = "CA"
+ class_long = "Cruiser"
+ min_ship_mass = 100000
+ max_ship_mass = 250000
+
+/decl/ship_contact_class/capital_ship
+ class_short = "CAP"
+ class_long = "Capital Ship"
+ min_ship_mass = 250000
diff --git a/code/modules/overmap/contacts/contacts_sensors.dm b/code/modules/overmap/contacts/contacts_sensors.dm
new file mode 100644
index 0000000000000..0c3d59d5e12a0
--- /dev/null
+++ b/code/modules/overmap/contacts/contacts_sensors.dm
@@ -0,0 +1,143 @@
+/obj/machinery/computer/ship/sensors
+ var/list/objects_in_view = list()
+ var/list/contact_datums = list()
+
+/obj/machinery/computer/ship/sensors/Destroy()
+ objects_in_view.Cut()
+ for(var/key in contact_datums)
+ var/datum/overmap_contact/record = contact_datums[key]
+ qdel(record)
+ contact_datums.Cut()
+ . = ..()
+
+/obj/machinery/computer/ship/sensors/attempt_hook_up(obj/effect/overmap/visitable/ship/sector)
+ . = ..()
+ if(. && linked && !contact_datums[linked])
+ var/datum/overmap_contact/record = new(src, linked)
+ record.handle_being_identified()
+
+/obj/machinery/computer/ship/sensors/proc/reveal_contacts(var/mob/user)
+ if(user && user.client)
+ for(var/key in contact_datums)
+ var/datum/overmap_contact/record = contact_datums[key]
+ if(record)
+ user.client.images |= record.marker
+
+/obj/machinery/computer/ship/sensors/proc/hide_contacts(var/mob/user)
+ if(user && user.client)
+ for(var/key in contact_datums)
+ var/datum/overmap_contact/record = contact_datums[key]
+ if(record)
+ user.client.images -= record.marker
+
+/obj/machinery/computer/ship/sensors/Process()
+ ..()
+ update_sound()
+ if(!linked)
+ return
+
+ // Update our 'sensor range' (ie. overmap lighting)
+ if(!sensors || !sensors.use_power || !sensors.powered())
+ linked.set_light(0)
+ // TODO move to on_power_change()?
+ for(var/key in contact_datums)
+ var/datum/overmap_contact/record = contact_datums[key]
+ if(record.effect == linked)
+ continue
+ animate(record.marker, alpha=0, 1 SECOND, 1, LINEAR_EASING)
+ record.hide()
+
+ objects_in_view.Cut()
+ return
+
+ var/sensor_range = round(sensors.range,1)
+ linked.set_light(1, sensor_range, sensor_range+1)
+
+ // What can we see?
+ var/list/new_objects_in_view = list()
+ var/list/objects_in_current_view = list()
+ for(var/obj/effect/overmap/contact in view(sensor_range, linked)-linked)
+ objects_in_current_view[contact] = TRUE
+ if(!objects_in_view[contact])
+ new_objects_in_view[contact] = TRUE
+
+ // Fade out and remove anything that is out of range.
+ for(var/obj/effect/overmap/contact in objects_in_view)
+ if(!QDELETED(contact) && objects_in_current_view[contact])
+ continue
+ var/datum/overmap_contact/record = contact_datums[contact]
+ var/bearing = round(90 - Atan2(contact.x - linked.x, contact.y - linked.y),5)
+ if(bearing < 0)
+ bearing += 360
+ if(record)
+ animate(record.marker, alpha=0, 2 SECOND, 1, LINEAR_EASING)
+ addtimer(CALLBACK(record, /datum/overmap_contact/proc/hide), 2 SECOND)
+ if(record && !record.is_overmap_event)
+ if(record.identified)
+ visible_message(SPAN_NOTICE("[src] states, 'Contact lost with [record.name], bearing [bearing].'"))
+ else
+ visible_message(SPAN_NOTICE("[src] states, 'Contact lost with [record.temp_designation], bearing [bearing].'"))
+ playsound(loc, "sound/machines/sensors/contact_lost.ogg", 30, 1)
+ objects_in_view -= contact
+ // Refresh or update contacts and markers for anything new.
+ objects_in_view |= new_objects_in_view
+
+ for(var/obj/effect/overmap/contact in new_objects_in_view)
+ var/datum/overmap_contact/record = contact_datums[contact]
+ var/bearing = round(90 - Atan2(contact.x - linked.x, contact.y - linked.y),5)
+ if(bearing < 0)
+ bearing += 360
+ if(record)
+ if(!record.is_overmap_event)
+ if(record.identified)
+ visible_message(SPAN_NOTICE("[src] states, 'Contact regained with [record.name], bearing [bearing].'"))
+ else
+ visible_message(SPAN_NOTICE("[src] states, 'Contact regained with [record.temp_designation], bearing [bearing].'"))
+ playsound(loc, "sound/machines/sensors/contact_regained.ogg", 30, 1)
+ record.show()
+ if(record.is_overmap_event)
+ animate(record.marker, alpha=255, 2 SECOND, 1, LINEAR_EASING)
+ continue
+ animate(record.marker, alpha=255, 2 SECOND, 1, LINEAR_EASING)
+
+ for(var/obj/effect/overmap/visitable/ship/contact in objects_in_view) //Update everything.
+ // Have we seen this ship before?
+ var/datum/overmap_contact/record = contact_datums[contact]
+ // Generate contact information for this overmap object.
+ var/bearing = round(90 - Atan2(contact.x - linked.x, contact.y - linked.y),5)
+ if(bearing < 0)
+ bearing += 360
+ if(!record)
+ record = new /datum/overmap_contact(src, contact)
+ if(record.effect.type in linked.known_ships)
+ record.handle_being_identified()
+ visible_message(SPAN_NOTICE("\The [src] states, \"Known contact registered, [record.name].\""))
+ else
+ playsound(loc, "sound/machines/sensors/newcontact.ogg", 30, 1)
+ visible_message(SPAN_NOTICE("\The [src] states, \"New contact detected, temporary designation [record.temp_designation], bearing [bearing]. Identification in progress.\""))
+
+ // Update identification information for this record.
+ if(prob(record.effect.sensor_visiblity))
+ record.update_marker_icon()
+ if(!record.identified)
+ if(record.identification_progress < record.effect.identification_difficulty)
+ record.identification_progress += 5
+ if(record.identification_progress == record.effect.identification_difficulty)
+ record.handle_being_identified()
+ playsound(loc, "sound/machines/sensors/contact_identified.ogg", 30, 1)
+ var/decl/ship_contact_class/class = decls_repository.get_decl(record.effect.contact_class)
+ visible_message(SPAN_NOTICE("[src] states, 'Contact [record.temp_designation] identified as [record.name], [class.class_long], bearing [bearing].'"))
+
+ for(var/obj/effect/overmap/event in objects_in_view)
+ var/datum/overmap_contact/record = contact_datums[event]
+ if(!record)
+ record = new /datum/overmap_contact(src, event, TRUE)
+ var/time_delay = max((SENSOR_TIME_DELAY * get_dist(linked, event)),1)
+ if(!record.pinged)
+ addtimer(CALLBACK(record, .proc/ping), time_delay)
+
+
+ //Update our own marker icon.
+ var/datum/overmap_contact/self_record = contact_datums[linked]
+ self_record.update_marker_icon(sensor_range)
+ self_record.show()
diff --git a/code/modules/overmap/exoplanets/planet_types/infested.dm b/code/modules/overmap/exoplanets/planet_types/infested.dm
new file mode 100644
index 0000000000000..9e1188f8539dd
--- /dev/null
+++ b/code/modules/overmap/exoplanets/planet_types/infested.dm
@@ -0,0 +1,84 @@
+/obj/effect/overmap/visitable/sector/exoplanet/infested
+ name = "infested exoplanet"
+ icon_state = "globe_infested"
+ desc = "Dangerous plante, requeired infiltration."
+
+ color = "#9c3d51"
+ water_color = null
+ surface_color = "#94404e"
+ planetary_area = /area/exoplanet/infested_forest
+ rock_colors = list(COLOR_ASTEROID_ROCK, COLOR_GRAY80, COLOR_BROWN)
+ plant_colors = list("#f5426f","#c72a51","#cc1d48","#99156f","#8c268b", "RANDOM")
+ map_generators = list(/datum/random_map/noise/exoplanet/infested, /datum/random_map/noise/ore/rich)
+ habitability_distribution = list(HABITABILITY_IDEAL = 10, HABITABILITY_OKAY = 80, HABITABILITY_BAD = 10)
+ flora_diversity = 15
+ fauna_types = list(
+ /mob/living/simple_animal/hostile/infestation/broodling = 7,
+ /mob/living/simple_animal/hostile/infestation/eviscerator = 5,
+ /mob/living/simple_animal/hostile/infestation/floatfly = 4,
+ /mob/living/simple_animal/hostile/infestation/spitter = 3,
+ /mob/living/simple_animal/hostile/infestation/assembler = 3,
+ /mob/living/simple_animal/hostile/infestation/rhino = 2,
+ /mob/living/simple_animal/hostile/infestation/larva/implant/implanter = 1,
+ )
+
+/obj/effect/overmap/visitable/sector/exoplanet/infested/generate_map()
+ if(prob(50))
+ lightlevel = pick(0.05, 0.1, 0.15)
+ return ..()
+
+/obj/effect/overmap/visitable/sector/exoplanet/infested/generate_atmosphere()
+ ..()
+ if(atmosphere)
+ atmosphere.temperature = T20C + rand(-40, 40)
+ atmosphere.update_values()
+
+/obj/effect/overmap/visitable/sector/exoplanet/infested/get_surface_color()
+ return "#9c3d51"
+
+/obj/effect/overmap/visitable/sector/exoplanet/infested/adapt_animal(mob/living/simple_animal/A, setname = TRUE)
+ if(!istype(A, /mob/living/simple_animal/hostile/infestation))
+ return ..()
+ // Planet-spawned abominations do not evolve normally
+ var/mob/living/simple_animal/hostile/infestation/abom = A
+ abom.transformation_time = null
+
+/obj/effect/overmap/visitable/sector/exoplanet/infested/adapt_seed(datum/seed/S)
+ ..()
+ var/carnivore_prob = rand(100)
+ if(carnivore_prob < 30)
+ S.set_trait(TRAIT_CARNIVOROUS,2)
+ if(prob(75))
+ S.get_trait(TRAIT_STINGS, 1)
+ else if(carnivore_prob < 60)
+ S.set_trait(TRAIT_CARNIVOROUS,1)
+ if(prob(50))
+ S.get_trait(TRAIT_STINGS)
+ if(prob(15) || (S.get_trait(TRAIT_CARNIVOROUS) && prob(40)))
+ S.set_trait(TRAIT_BIOLUM,1)
+ S.set_trait(TRAIT_BIOLUM_COLOUR,get_random_colour(0,75,190))
+
+ if(prob(30))
+ S.set_trait(TRAIT_PARASITE,1)
+ if(!S.get_trait(TRAIT_LARGE))
+ var/vine_prob = rand(100)
+ if(vine_prob < 15)
+ S.set_trait(TRAIT_SPREAD,2)
+ else if(vine_prob < 30)
+ S.set_trait(TRAIT_SPREAD,1)
+
+/area/exoplanet/infested_forest
+ base_turf = /turf/simulated/floor/exoplanet/grass
+
+/area/exoplanet/infested_forest/play_ambience(mob/living/L)
+ ..()
+ if(!L.ear_deaf && L.client)
+ L.playsound_local(get_turf(L),sound('sound/ambience/infested_forest.ogg', repeat = 1, wait = 0, volume = 25))
+
+/datum/random_map/noise/exoplanet/infested
+ descriptor = "infested exoplanet"
+ smoothing_iterations = 2
+ land_type = /turf/simulated/floor/exoplanet/flesh
+
+ flora_prob = 10
+ fauna_prob = 20
diff --git a/code/modules/overmap/exoplanets/turfs.dm b/code/modules/overmap/exoplanets/turfs.dm
index 257bd4f9fff9d..8bd74ebc5431b 100644
--- a/code/modules/overmap/exoplanets/turfs.dm
+++ b/code/modules/overmap/exoplanets/turfs.dm
@@ -184,6 +184,27 @@
icon_state = "sandglass"
diggable = 0
+// Flesh
+/turf/simulated/floor/exoplanet/flesh
+ name = "flesh"
+ icon = 'icons/turf/flooring/flesh.dmi'
+ icon_state = "flesh0"
+ color = "#94404e"
+ footstep_type = /decl/footsteps/blank
+
+/turf/simulated/floor/exoplanet/flesh/Initialize()
+ . = ..()
+ icon_state = "flesh[pick(0,1,2,3)]"
+
+/turf/simulated/floor/exoplanet/flesh/fire_act(datum/gas_mixture/air, exposed_temperature, exposed_volume)
+ if((temperature > T0C + 200 && prob(5)) || temperature > T0C + 1000)
+ melt()
+
+/turf/simulated/floor/exoplanet/flesh/melt()
+ SetName("scorched flesh")
+ footstep_type = /decl/footsteps/asteroid
+ color = "#70353a"
+
//Concrete
/turf/simulated/floor/exoplanet/concrete
name = "concrete"
diff --git a/code/modules/overmap/radio_beacon.dm b/code/modules/overmap/radio_beacon.dm
new file mode 100644
index 0000000000000..d00ca713a6ba2
--- /dev/null
+++ b/code/modules/overmap/radio_beacon.dm
@@ -0,0 +1,187 @@
+/obj/effect/overmap/radio
+ name = "radio signal"
+ scanner_name = "radio signal"
+ icon_state = "radio"
+ scannable = TRUE
+ color = COLOR_AMBER
+ var/message
+ var/obj/effect/overmap/source
+
+/obj/effect/overmap/radio/get_scan_data(mob/user)
+ return "Радиосигнал, передаваемый [source].
\
+ ---НАЧАЛО ПЕРЕДАЧИ---
\
+ [message] \
+
---КОНЕЦ ПЕРЕДАЧИ---"
+
+/obj/effect/overmap/radio/proc/set_origin(obj/effect/overmap/origin)
+ GLOB.moved_event.register(origin, src, /obj/effect/overmap/radio/proc/follow)
+ GLOB.destroyed_event.register(origin, src, /datum/proc/qdel_self)
+ forceMove(origin.loc)
+ source = origin
+ pixel_x = -(origin.bound_width - 6)
+ pixel_y = origin.bound_height - 6
+
+/obj/effect/overmap/radio/proc/follow(var/atom/movable/am, var/old_loc, var/new_loc)
+ forceMove(new_loc)
+
+/obj/effect/overmap/radio/Destroy()
+ GLOB.destroyed_event.unregister(source, src)
+ GLOB.moved_event.unregister(source, src)
+ source = null
+ . = ..()
+
+/obj/item/radio_beacon
+ name = "radio beacon"
+ desc = "Device capable of continuously broadcasting a signal that can be picked up by ship sensors."
+ icon = 'icons/obj/radio.dmi'
+ icon_state = "walkietalkie"
+ var/obj/effect/overmap/radio/signal
+
+/obj/item/radio_beacon/attack_self(mob/user)
+ var/obj/effect/overmap/visitable/O = map_sectors["[get_z(src)]"]
+ if(!O)
+ to_chat(user, SPAN_WARNING("Вы не можете развернуть [src] здесь."))
+ return
+ var/message = sanitize(input("Что следует передать?") as message|null)
+
+ if(!signal)
+ signal = new()
+
+ signal.message = message
+ signal.set_origin(O)
+
+
+
+
+
+/obj/item/device/subspaceradio
+ name = "subspace radio"
+ desc = "This long range communications device has the ability to send and recieve transmissions from anywhere."
+ icon = 'icons/obj/structures/decor.dmi'
+ icon_state = "random_radio"
+ w_class = ITEM_SIZE_LARGE
+ action_button_name = "Remove/Replace Handset"
+ var/obj/item/subspacehandset/handset
+
+/obj/item/device/subspaceradio/Initialize()
+ . = ..()
+ if(ispath(handset))
+ handset = new handset(src, src)
+ else
+ handset = new(src, src)
+
+/obj/item/device/subspaceradio/Destroy()
+ . = ..()
+ QDEL_NULL(handset)
+
+/obj/item/device/subspaceradio/ui_action_click()
+ toggle_handset()
+
+/obj/item/device/subspaceradio/attack_hand(mob/user)
+ if(loc == user)
+ toggle_handset()
+ else
+ ..()
+
+/obj/item/device/subspaceradio/MouseDrop()
+ if(ismob(loc))
+ if(!CanMouseDrop(src))
+ return
+ var/mob/M = loc
+ if(!M.unEquip(src))
+ return
+ src.add_fingerprint(usr)
+ M.put_in_any_hand_if_possible(src)
+
+/obj/item/device/subspaceradio/attackby(obj/item/W, mob/user, params)
+ if(W == handset)
+ reattach_handset(user)
+ else
+ return ..()
+
+/obj/item/device/subspaceradio/AltClick(mob/user as mob)
+ toggle_handset()
+
+/obj/item/device/subspaceradio/verb/toggle_handset()
+
+ var/mob/living/carbon/human/user = usr
+ if(!handset)
+ to_chat(user, SPAN_WARNING("The handset is missing!"))
+ return
+
+ if(handset.loc != src)
+ reattach_handset(user) //Remove from their hands and back onto the defib unit
+ return
+
+ else
+ if(!usr.put_in_hands(handset)) //Detach the handset into the user's hands
+ to_chat(user, SPAN_WARNING("You need a free hand to hold the handset!"))
+ update_icon() //success
+
+//checks that the base unit is in the correct slot to be used
+/obj/item/device/subspaceradio/proc/slot_check()
+ var/mob/M = loc
+ if(!istype(M))
+ return FALSE //not equipped
+
+ if((slot_flags & SLOT_BACK) && M.get_equipped_item(slot_back) == src)
+ return TRUE
+ if((slot_flags & SLOT_BELT) && M.get_equipped_item(slot_belt) == src)
+ return TRUE
+
+ return FALSE
+
+/obj/item/device/subspaceradio/dropped(mob/user)
+ ..()
+ reattach_handset(user) //handset attached to a base unit should never exist outside of their base unit or the mob equipping the base unit
+
+/obj/item/device/subspaceradio/proc/reattach_handset(mob/user)
+ if(!handset) return
+
+ if(ismob(handset.loc))
+ var/mob/M = handset.loc
+ if(M.drop_from_inventory(handset, src))
+ to_chat(user, SPAN_NOTICE("\The [handset] snaps back into the main unit."))
+ else
+ handset.forceMove(src)
+
+//Subspace Radio Handset
+/obj/item/subspacehandset
+ name = "radio beacon"
+ desc = "Device capable of continuously broadcasting a signal that can be picked up by ship sensors."
+ icon = 'icons/obj/radio.dmi'
+ icon_state = "walkietalkie"
+ var/obj/effect/overmap/radio/signal
+
+/obj/item/subspacehandset/attack_self(mob/user)
+ var/obj/effect/overmap/visitable/O = map_sectors["[get_z(src)]"]
+ if(!O)
+ to_chat(user, SPAN_WARNING("Вы не можете развернуть [src] здесь."))
+ return
+ var/message = sanitize(input("Что следует передать?") as message|null)
+
+ if(!signal)
+ signal = new()
+
+ signal.message = message
+ signal.set_origin(O)
+
+/obj/item/subspacehandset/linked
+ var/obj/item/device/subspaceradio/base_unit
+
+/obj/item/subspacehandset/linked/New(newloc, obj/item/device/subspaceradio/radio)
+ base_unit = radio
+ ..(newloc)
+
+/obj/item/subspacehandset/linked/Destroy()
+ if(base_unit)
+ //ensure the base unit's icon updates
+ if(base_unit.handset == src)
+ base_unit.handset = null
+ base_unit = null
+ return ..()
+
+/obj/item/subspacehandset/linked/dropped(mob/user)
+ ..() //update twohanding
+ if(base_unit)
+ base_unit.reattach_handset(user) //handset attached to a base unit should never exist outside of their base unit or the mob equipping the base unit
diff --git a/code/modules/overmap/ships/computers/target_control.dm b/code/modules/overmap/ships/computers/target_control.dm
new file mode 100644
index 0000000000000..24d9b1e0ebb85
--- /dev/null
+++ b/code/modules/overmap/ships/computers/target_control.dm
@@ -0,0 +1,166 @@
+//Engine control and monitoring console
+
+/obj/machinery/computer/ship/missiles
+ name = "missile control console"
+ icon_keyboard = "tech_key"
+ icon_screen = "mass_driver"
+ var/display_state = "status"
+ var/handled_sensors_range = 0
+
+
+/obj/machinery/computer/ship/missiles/proc/handle_sensors_range()
+ for(var/obj/machinery/computer/ship/sensors/sensor in linked.linked_computers)
+ handled_sensors_range = sensor.get_scanner_range()
+ //message_admins(handled_sensors_range)
+
+
+/obj/machinery/computer/ship/missiles/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1)
+ if(!linked)
+ display_reconnect_dialog(user, "sensors")
+ return
+
+ handle_sensors_range()
+ var/data[0]
+ var/list/contacts_ships = list()
+ var/list/contacts_planets = list()
+ var/list/contacts_missiles = list()
+ var/obj/target_temp
+ for(var/obj/effect/overmap/O in view(src.handled_sensors_range, linked))
+ if(linked == O)
+ continue
+ if(!O.scannable)
+ continue
+ var/bearing = round(90 - Atan2(O.x - linked.x, O.y - linked.y),5)
+ if(bearing < 0)
+ bearing += 360
+
+ if(istype(O, /obj/effect/overmap/visitable/ship))
+ contacts_ships.Add(list(list("name"=O.scanner_name, "ref"="\ref[O]", "bearing"=bearing)))
+
+ else if(istype(O, /obj/effect/overmap/visitable/sector/exoplanet))
+ contacts_planets.Add(list(list("name"=O.scanner_name, "ref"="\ref[O]", "bearing"=bearing)))
+
+ else if(istype(O, /obj/effect/overmap/projectile))
+ contacts_missiles.Add(list(list("name"=O.scanner_name, "ref"="\ref[O]", "bearing"=bearing)))
+
+ if(contacts_ships.len)
+ data["contacts_ships"] = contacts_ships
+
+ if(contacts_planets.len)
+ data["contacts_planets"] = contacts_planets
+
+ if(contacts_missiles.len)
+ data["contacts_missiles"] = contacts_missiles
+
+ data["planet_x"] = linked.get_target(TARGET_PLANETCOORD)[1]
+ data["planet_y"] = linked.get_target(TARGET_PLANETCOORD)[2]
+ data["point_x"] = linked.get_target(TARGET_POINT)[1]
+ data["point_y"] = linked.get_target(TARGET_POINT)[2]
+ data["target_ship"] = null
+ if(linked.get_target(TARGET_PLANET)[1])
+ target_temp = linked.get_target(TARGET_PLANET)[1]
+ data["target_planet"] = target_temp.name
+ if(linked.get_target(TARGET_SHIP))
+ target_temp = linked.get_target(TARGET_SHIP)
+ data["target_ship"] = target_temp.name
+ if(linked.get_target(TARGET_MISSILE))
+ target_temp = linked.get_target(TARGET_MISSILE)
+ data["target_missile"] = target_temp.name
+
+ ui = SSnano.try_update_ui(user, src, ui_key, ui, data, force_open)
+ if (!ui)
+ ui = new(user, src, ui_key, "target.tmpl", "[linked.scanner_name] Target Control", 700, 545)
+ ui.set_initial_data(data)
+ ui.open()
+ ui.set_auto_update(1)
+
+
+/obj/machinery/computer/ship/missiles/OnTopic(var/mob/user, var/list/href_list, state) //TODO: FIX THIS
+ if(..())
+ return TOPIC_HANDLED
+
+ if (!linked)
+ return TOPIC_NOACTION
+
+ if (href_list["ship_lock"])
+ var/obj/effect/overmap/O = locate(href_list["ship_lock"])
+ if(istype(O) && !QDELETED(O) && (O in view(handled_sensors_range, linked)))
+ if(linked.set_target(TARGET_SHIP, O))
+ visible_message(SPAN_NOTICE("[src] states, 'TARGET LOCKED: [O.scanner_name]'"))
+ playsound(loc, "sound/machines/sensors/target_lock.ogg", 30, 1)
+ else
+ visible_message(SPAN_NOTICE("[src] states, 'TARGET LOCK FAILED'"))
+ return TOPIC_HANDLED
+
+ if (href_list["missile_lock"])
+ var/obj/effect/overmap/O = locate(href_list["missile_lock"])
+ if(istype(O) && !QDELETED(O) && (O in view(handled_sensors_range, linked)))
+ if(linked.set_target(TARGET_MISSILE, O))
+ visible_message(SPAN_NOTICE("[src] states, 'MISSILE LOCKED: [O.scanner_name]'"))
+ playsound(loc, "sound/machines/sensors/target_lock.ogg", 30, 1)
+ else
+ visible_message(SPAN_NOTICE("[src] states, 'MISSILE LOCK FAILED'"))
+ return TOPIC_HANDLED
+
+ if (href_list["planet_lock"])
+ var/obj/effect/overmap/O = locate(href_list["planet_lock"])
+ if(istype(O) && !QDELETED(O) && (O in view(handled_sensors_range, linked)))
+ if(linked.set_target(TARGET_PLANET, O, linked.get_target(TARGET_PLANET)[2], linked.get_target(TARGET_PLANET)[3]))
+ visible_message(SPAN_NOTICE("[src] states, 'PLANET LOCKED: [O.scanner_name]'"))
+ playsound(loc, "sound/machines/sensors/target_lock.ogg", 30, 1)
+ else
+ visible_message(SPAN_NOTICE("[src] states, 'PLANET LOCK FAILED'"))
+ return TOPIC_HANDLED
+
+ if (href_list["set_planetx"])
+ var/input = input("Set new planet X target", "Planet X", linked.get_target(TARGET_PLANETCOORD)[1]) as num|null
+ if(!CanInteract(user,state))
+ return TOPIC_NOACTION
+ if (input)
+ linked.set_target(TARGET_PLANET, linked.get_target(TARGET_PLANET)[1], Clamp(input,1, world.maxx-8), linked.get_target(TARGET_PLANET)[3])
+ return TOPIC_REFRESH
+
+ if (href_list["set_planety"])
+ var/input = input("Set new planet Y target", "Planet Y", linked.get_target(TARGET_PLANETCOORD)[2]) as num|null
+ if(!CanInteract(user,state))
+ return TOPIC_NOACTION
+ if (input)
+ linked.set_target(TARGET_PLANET, linked.get_target(TARGET_PLANET)[1], linked.get_target(TARGET_PLANET)[2], Clamp(input,1, world.maxy-8))
+ return TOPIC_REFRESH
+
+
+ if (href_list["set_pointx"])
+ var/input = input("Set new point X target", "Planet X", linked.get_target(TARGET_POINT)[1]) as num|null
+ if(!CanInteract(user,state))
+ return TOPIC_NOACTION
+ if (input)
+ linked.set_target(TARGET_POINT, null, Clamp(input, 1, 40), linked.get_target(TARGET_POINT)[2])
+ return TOPIC_REFRESH
+
+ if (href_list["set_pointy"])
+ var/input = input("Set new point Y target", "Planet Y", linked.get_target(TARGET_POINT)[2]) as num|null
+ if(!CanInteract(user,state))
+ return TOPIC_NOACTION
+ if (input)
+ linked.set_target(TARGET_POINT, null, linked.get_target(TARGET_POINT)[1], Clamp(input, 1, 40))
+ return TOPIC_REFRESH
+
+/obj/machinery/computer/ship/missiles/proc/announce()
+ if(prob(80))
+ for(var/obj/effect/overmap/O in view(10,linked))
+ if(linked == O)
+ continue
+ if(!O.scannable)
+ continue
+ var/bearing = round(90 - Atan2(O.x - linked.x, O.y - linked.y),5)
+ if(bearing < 0)
+ bearing += 360
+ if(istype(O, /obj/effect/overmap/projectile))
+ if(bearing == 0)
+ return
+ if(bearing <= 179)
+ src.visible_message(SPAN_WARNING("[name] states, обнаружен пуск ракеты, передняя полусфера"))
+ playsound(src.loc, 'sound/machines/defib_safetyOff.ogg', 100, 1)
+ if(bearing >= 180)
+ src.visible_message(SPAN_DANGER("[name] states, обнаружен пуск ракеты, задняя полусфера"))
+ playsound(src.loc, 'sound/machines/defib_safetyOn.ogg', 100, 1)
diff --git a/code/modules/overmap/ships/engines/electric.dm b/code/modules/overmap/ships/engines/electric.dm
new file mode 100644
index 0000000000000..26d4ac5097429
--- /dev/null
+++ b/code/modules/overmap/ships/engines/electric.dm
@@ -0,0 +1,108 @@
+//Thermal nozzle engine
+/datum/ship_engine/electric
+ name = "electric engine"
+ var/obj/machinery/power/engine/ion/nozzle
+
+/datum/ship_engine/electric/New(var/obj/machinery/_holder)
+ ..()
+ nozzle = _holder
+
+/datum/ship_engine/electric/Destroy()
+ nozzle = null
+ . = ..()
+
+/datum/ship_engine/electric/get_status()
+ return nozzle.get_status()
+
+/datum/ship_engine/electric/get_thrust()
+ return nozzle.get_thrust()
+
+/datum/ship_engine/electric/burn()
+ return nozzle.burn()
+
+/datum/ship_engine/electric/set_thrust_limit(var/new_limit)
+ nozzle.thrust_limit = new_limit
+
+/datum/ship_engine/electric/get_thrust_limit()
+ return nozzle.thrust_limit
+
+/datum/ship_engine/electric/is_on()
+ return nozzle.is_on()
+
+/datum/ship_engine/electric/toggle()
+ nozzle.on = !nozzle.on
+
+/datum/ship_engine/electric/can_burn()
+ return nozzle.is_on() && nozzle.check_power()
+
+/obj/machinery/power/engine/ion
+ name = "ion thruster nozzle"
+ desc = "Simple electrical propulsion nozzle, uses ion magic to propel the ship."
+ icon = 'icons/obj/ship_engine.dmi'
+ icon_state = "nozzle"
+ use_power = 1
+ idle_power_usage = 1500 // internal circuitry, friction losses and stuff
+ opacity = TRUE
+ density = TRUE
+
+ var/on = 1
+ var/datum/ship_engine/electric/controller
+ var/thrust_limit = 0.3 // Value between 1 and 0 to limit the resulting thrust
+
+ var/use_power_per_thrust = 250000 //INF, WAS 10000
+ var/max_draw_per_tick = 100000
+
+ // Todo: upgrades, use cells and capacitors
+ var/stored_power
+ var/max_stored_power = 1000000 // ONE MILLION KILLER WATS
+
+/obj/machinery/power/engine/ion/Process()
+ ..()
+ if(powered())
+ var/draw_amount = min(max_draw_per_tick, max_stored_power-stored_power)
+ if(surplus() < draw_amount)
+ draw_amount = surplus()
+ stored_power += draw_power(draw_amount)
+
+/obj/machinery/power/engine/ion/Initialize()
+ . = ..()
+ controller = new(src)
+ connect_to_network()
+
+/obj/machinery/power/engine/ion/Destroy()
+ ..()
+ if(controller)
+ qdel(controller)
+ controller = null
+
+/obj/machinery/power/engine/ion/proc/get_status()
+ . = list()
+ .+= "Location: [get_area(src)]."
+ if(!powered())
+ .+= "Insufficient power to operate."
+ if(!check_power())
+ .+= "Insufficient power for thrust (below [round(use_power_per_thrust * thrust_limit)] kW)."
+ .+= "Available power: [stored_power] kW."
+ . = jointext(.,"
")
+
+/obj/machinery/power/engine/ion/proc/is_on()
+ return on && powered() && check_power()
+
+/obj/machinery/power/engine/ion/proc/check_power()
+ return stored_power >= round(use_power_per_thrust * thrust_limit)
+
+/obj/machinery/power/engine/ion/proc/get_thrust()
+ if(!is_on() || !check_power())
+ return 0
+ return (use_power_per_thrust / 1000) * thrust_limit
+
+/obj/machinery/power/engine/ion/proc/burn()
+ if (!is_on())
+ return 0
+ if(!check_power())
+ audible_message(src,"[src] sparks once or twice, then goes dark!")
+ on = !on
+ return 0
+ . = get_thrust()
+ stored_power = max(0, stored_power - round(use_power_per_thrust * thrust_limit))
+//TODO: ion trail
\ No newline at end of file
diff --git a/code/modules/overmap/ships/panicbutton.dm b/code/modules/overmap/ships/panicbutton.dm
new file mode 100644
index 0000000000000..e89f8b457f7ba
--- /dev/null
+++ b/code/modules/overmap/ships/panicbutton.dm
@@ -0,0 +1,58 @@
+/obj/structure/panic_button
+ name = "distress beacon trigger"
+ desc = "WARNING: Will deploy ship's distress beacon and request help. Misuse may result in fines and jail time."
+ icon = 'icons/obj/objects.dmi'
+ icon_state = "panicbutton"
+ anchored = TRUE
+
+ var/glass = TRUE
+ var/launched = FALSE
+
+
+/obj/structure/panic_button/on_update_icon()
+ if(launched)
+ icon_state = "[initial(icon_state)]_launched"
+ else if(!glass)
+ icon_state = "[initial(icon_state)]_open"
+ else
+ icon_state = "[initial(icon_state)]"
+
+/obj/structure/panic_button/attack_hand(mob/living/user)
+ if(!istype(user))
+ return ..()
+
+ if(user.incapacitated())
+ return
+
+ // Already launched
+ if(launched)
+ to_chat(user, "The button is already depressed; the beacon has been launched already.")
+ // Glass present
+ else if(glass)
+ if(user.a_intent == I_HURT)
+ user.custom_emote(VISIBLE_MESSAGE, "smashes the glass on [src]!")
+ glass = FALSE
+ playsound(src, 'sound/effects/hit_on_shattered_glass.ogg')
+ update_icon()
+ else
+ user.custom_emote(VISIBLE_MESSAGE, "pats [src] in a friendly manner.")
+ to_chat(user, "If you're trying to break the glass, you'll have to hit it harder than that...")
+ // Must be !glass and !launched
+ else if(!glass && !launched)
+ user.custom_emote(VISIBLE_MESSAGE, "pushes the button on [src]!")
+ playsound(src, get_sfx("button"))
+ update_icon()
+ launch(usr)
+
+/obj/structure/panic_button/proc/launch(mob/living/user)
+ var/sound/SND = sound('sound/misc/emergency_beacon_launched.ogg') // Inside the loop because playsound_local modifies it for each person, so, need separate instances
+
+ if(launched)
+ return
+ launched = TRUE
+ var/obj/effect/overmap/visitable/S = get_overmap_sector(z)
+ if(!S)
+ error("Distress button hit on z[z] but that's not an overmap sector...")
+ return
+ S.distress(user)
+ playsound(src, SND, 25)
diff --git a/code/modules/projectiles/ammunition.dm b/code/modules/projectiles/ammunition.dm
index 5b94acb6537a7..9bec66fbd906a 100644
--- a/code/modules/projectiles/ammunition.dm
+++ b/code/modules/projectiles/ammunition.dm
@@ -136,6 +136,38 @@
SetName("[name] ([english_list(labels, and_text = ", ")])")
update_icon()
+/obj/item/ammo_magazine/afterattack(atom/target, mob/living/user, proximity_flag)
+ if(!proximity_flag || (!istype(target, /turf) && !istype(target, /obj/item/ammo_casing)))
+ return ..()
+
+ var/turf/T = istype(target, /turf) ? target : get_turf(target)
+ if(istype(target, /turf))
+ if(!locate(/obj/item/ammo_casing) in T)
+ return ..()
+
+ var/curr_ammo = length(stored_ammo)
+ if(curr_ammo >= max_ammo)
+ to_chat(user, "[src] is full!")
+ return
+
+ to_chat(user, SPAN_NOTICE("You begin inserting casings into \the [src]..."))
+ if(!do_after(user, (max_ammo - curr_ammo) * 2, src))
+ return
+
+ for(var/obj/item/ammo_casing/C in T)
+ if(stored_ammo.len >= max_ammo)
+ break
+ if(C.caliber != caliber)
+ continue
+ stored_ammo.Add(C)
+ C.forceMove(src)
+
+ if(length(stored_ammo) - curr_ammo)
+ to_chat(user, SPAN_NOTICE("You insert [length(stored_ammo) - curr_ammo] casings into \the [src]."))
+ update_icon()
+ else
+ to_chat(user, SPAN_WARNING("You fail to collect any casings!"))
+
/obj/item/ammo_magazine/attackby(obj/item/W as obj, mob/user as mob)
if(istype(W, /obj/item/ammo_casing))
var/obj/item/ammo_casing/C = W
@@ -155,10 +187,17 @@
if(!stored_ammo.len)
to_chat(user, "[src] is already empty!")
return
+ if(!do_after(user, 10, src))
+ return
to_chat(user, "You empty [src].")
+ var/curr_sounds = 0
+ var/max_sounds = clamp(round(length(stored_ammo) * 0.2), 1, 10)
for(var/obj/item/ammo_casing/C in stored_ammo)
C.forceMove(user.loc)
C.set_dir(pick(GLOB.alldirs))
+ if(LAZYLEN(C.fall_sounds) && curr_sounds < max_sounds)
+ playsound(user.loc, pick(C.fall_sounds), 20, TRUE)
+ curr_sounds += 1
stored_ammo.Cut()
update_icon()
diff --git a/code/modules/projectiles/projectile/energy.dm b/code/modules/projectiles/projectile/energy.dm
index dcfe54753d42b..a827ca83f3770 100644
--- a/code/modules/projectiles/projectile/energy.dm
+++ b/code/modules/projectiles/projectile/energy.dm
@@ -222,3 +222,11 @@
damage = 10
armor_penetration = 35
damage_type = DAMAGE_BRUTE
+
+/obj/item/projectile/energy/acid_spit
+ name = "acid bolt"
+ icon_state = "toxin"
+ damage = 18
+ damage_type = DAMAGE_BURN
+ fire_sound = 'sound/weapons/alien_spit.ogg'
+ pass_flags = PASS_FLAG_TABLE
diff --git a/code/modules/psionics/equipment/implant.dm b/code/modules/psionics/equipment/implant.dm
index 723a71517daca..e2cb64d200f2b 100644
--- a/code/modules/psionics/equipment/implant.dm
+++ b/code/modules/psionics/equipment/implant.dm
@@ -30,7 +30,8 @@
var/use_psi_mode = get_psi_mode()
return (!malfunction && (use_psi_mode == PSI_IMPLANT_SHOCK || use_psi_mode == PSI_IMPLANT_WARN)) ? src : FALSE
-/obj/item/implant/psi_control/removed()
+/obj/item/implant/psi_control/ImplantRemoval(mob/user)
+ . = ..()
var/mob/living/M = imp_in
if(disrupts_psionics() && istype(M) && M.psi)
to_chat(M, SPAN_NOTICE("Вы чувствуете, как исчезают холодные оковы, сковывающие ваши псионические способности."))
diff --git a/code/modules/reagents/Chemistry-Reagents/Chemistry-Reagents-Other.dm b/code/modules/reagents/Chemistry-Reagents/Chemistry-Reagents-Other.dm
index 47a4cee484ee9..d38d2a77450c2 100644
--- a/code/modules/reagents/Chemistry-Reagents/Chemistry-Reagents-Other.dm
+++ b/code/modules/reagents/Chemistry-Reagents/Chemistry-Reagents-Other.dm
@@ -578,3 +578,119 @@
/datum/reagent/colored_hair_dye/chaos/affect_touch(mob/living/carbon/human/H, alien, removed)
apply_dye_color(H, Frand(1, 254), Frand(1, 254), Frand(1, 254))
+
+
+/* Abominable Infestation reagents */
+
+// Grauel - Basically a healing chem that can implant a larva into its user.
+/datum/reagent/grauel
+ name = "Grauel"
+ description = "Reagent responsible for incubation of unknown lifeforms within its host. Has healing properties, but using it as medicine is a concerning idea."
+ taste_description = "vomit"
+ taste_mult = 1.4
+ reagent_state = LIQUID
+ color = COLOR_MAROON
+ value = 4
+ heating_products = list(/datum/reagent/nutriment/protein, /datum/reagent/laich)
+ heating_point = 120 CELSIUS
+ heating_message = "turns into a fleshy mess."
+
+/datum/reagent/grauel/affect_blood(mob/living/carbon/M, alien, removed)
+ if(alien == IS_DIONA)
+ return
+ var/heal_rate = 4
+ if(M.chem_doses[/datum/reagent/laich] >= 1)
+ heal_rate *= 2.5
+ M.heal_organ_damage(heal_rate * removed, 0) // Effectively weaker bicaridine, just with a tiny issue...
+ M.add_chemical_effect(CE_PAINKILLER, 5)
+
+ var/dosage = M.chem_doses[type]
+ if(dosage < 5) // Only implant larvas on somewhat "high" dose
+ return
+ if(!prob(max(10, dosage*0.5)))
+ return
+
+ var/list/valid_organs = list()
+ for(var/obj/item/organ/external/O in M.organs)
+ if(istype(O, /obj/item/organ/external/stump))
+ continue
+ if(locate(/mob/living/simple_animal/hostile/infestation/larva) in O.implants) // One larva per limb
+ continue
+ valid_organs += O
+ if(!LAZYLEN(valid_organs))
+ return
+ var/obj/item/organ/external/target_organ = pick(valid_organs)
+ var/mob/living/simple_animal/hostile/infestation/larva/implant/L = new(target_organ, TRUE)
+ target_organ.implants += L
+ L.transformation_time = world.time + rand(180 SECONDS, 360 SECONDS)
+ L.ai_holder.speak_chance = 0
+
+// Laich - Reagent needed for creating of larva cube.
+/datum/reagent/laich
+ name = "Laich"
+ description = "An important ingridient in creation of life. Causes mild suffocation. When consumed/injected alongside with Grauel - the later's healing properties were increased."
+ taste_description = "disgusting mess"
+ color = COLOR_ORANGE
+
+/datum/reagent/laich/affect_blood(mob/living/carbon/M, alien, removed)
+ if(alien == IS_DIONA)
+ return
+ if(prob(20))
+ M.adjustOxyLoss(8)
+ if(ishuman(M) && prob(2))
+ var/mob/living/carbon/human/H = M
+ H.vomit(2, 2, rand(2 SECONDS, 4 SECONDS))
+
+
+/datum/reagent/gottheit
+ name = "Gottheit"
+ description = "An impossibly powerful medicine, which is just as impossibly addictive."
+ taste_description = "pleasantly burning acid"
+ taste_mult = 5
+ reagent_state = LIQUID
+ color = COLOR_YELLOW
+ value = 50
+
+/datum/reagent/gottheit/affect_blood(mob/living/carbon/M, alien, removed)
+ if(alien == IS_DIONA)
+ return
+
+ // Heal all conventional damage types
+ M.adjustCloneLoss(-40 * removed)
+ M.adjustOxyLoss(-10 * removed)
+ M.heal_organ_damage(40 * removed, 40 * removed)
+ M.adjustToxLoss(-40 * removed)
+
+ // Some useful chem effects, including painkilling
+ M.add_chemical_effect(CE_PAINKILLER, 200)
+ M.add_chemical_effect(CE_SPEEDBOOST, 1)
+
+ // Reduce bad effects
+ M.drowsyness = max(M.drowsyness - 50, 0)
+ M.adjust_hallucination(-50)
+ M.AdjustParalysis(-10)
+ M.AdjustStunned(-10)
+ M.AdjustWeakened(-10)
+
+ // Super low doses won't do cool stuff
+ var/dosage = M.chem_doses[type]
+ if(dosage < 2)
+ return
+
+ // Heal organs
+ if(ishuman(M))
+ var/mob/living/carbon/human/H = M
+ for(var/obj/item/organ/internal/I in H.internal_organs)
+ if(!BP_IS_ROBOTIC(I))
+ I.heal_damage(20 * removed)
+ for(var/obj/item/organ/external/E in H.organs)
+ if(E.status & ORGAN_ARTERY_CUT)
+ E.status &= ~ORGAN_ARTERY_CUT
+ if(E.status & ORGAN_TENDON_CUT)
+ E.status &= ~ORGAN_TENDON_CUT
+ if(E.status & ORGAN_BLEEDING)
+ E.status &= ~ORGAN_BLEEDING
+ if(E.status & ORGAN_BROKEN)
+ E.status &= ~ORGAN_BROKEN
+ if(E.status & ORGAN_DEAD)
+ E.status &= ~ORGAN_DEAD
diff --git a/code/modules/reagents/Chemistry-Recipes.dm b/code/modules/reagents/Chemistry-Recipes.dm
index d75b75e78e027..a96431fe1c913 100644
--- a/code/modules/reagents/Chemistry-Recipes.dm
+++ b/code/modules/reagents/Chemistry-Recipes.dm
@@ -3185,3 +3185,34 @@
catalysts = list(
/datum/reagent/enzyme = 1
)
+
+
+/datum/chemical_reaction/bicaridine_alt
+ name = "Grauel Decomposition into Bicaridine"
+ result = /datum/reagent/bicaridine
+ required_reagents = list(/datum/reagent/grauel = 1, /datum/reagent/phosphorus = 1)
+ result_amount = 2
+
+/datum/chemical_reaction/abomination_larva
+ name = "Abominable Larva"
+ result = null
+ required_reagents = list(/datum/reagent/laich = 10, /datum/reagent/phosphorus = 20)
+
+/datum/chemical_reaction/abomination_larva/on_reaction(datum/reagents/holder)
+ . = ..()
+ if(prob(66))
+ new /obj/item/reagent_containers/food/snacks/monkeycube/abominationcube(get_turf(holder.my_atom))
+ else
+ new /obj/item/reagent_containers/food/snacks/monkeycube/abominationcube/friendly(get_turf(holder.my_atom))
+
+
+/datum/chemical_reaction/gottheit
+ name = "Gottheit"
+ result = /datum/reagent/gottheit
+ result_amount = 2
+ required_reagents = list(
+ /datum/reagent/grauel = 1,
+ /datum/reagent/rezadone = 1,
+ /datum/reagent/tramadol/oxycodone = 1,
+ /datum/reagent/peridaxon = 1,
+ )
diff --git a/code/modules/reagents/reagent_containers/food/snacks.dm b/code/modules/reagents/reagent_containers/food/snacks.dm
index 6205956edc90e..f2954a2ad12fe 100644
--- a/code/modules/reagents/reagent_containers/food/snacks.dm
+++ b/code/modules/reagents/reagent_containers/food/snacks.dm
@@ -3907,3 +3907,14 @@
name = "taco"
desc = "Interestingly, the shell has gone soft and the contents have gone stale."
icon_state = "ancient_taco"
+
+
+/obj/item/reagent_containers/food/snacks/monkeycube/abominationcube
+ name = "larva cube"
+ desc = "Requires blood to expand."
+ monkey_type = /mob/living/simple_animal/hostile/infestation/larva
+ color = COLOR_MAROON
+ filling_color = COLOR_MAROON
+
+/obj/item/reagent_containers/food/snacks/monkeycube/abominationcube/friendly
+ monkey_type = /mob/living/simple_animal/hostile/infestation/larva/friendly
diff --git a/code/modules/reagents/reagent_containers/food/snacks/meat.dm b/code/modules/reagents/reagent_containers/food/snacks/meat.dm
index 73a4cda1f63f0..38cfc26d683f6 100644
--- a/code/modules/reagents/reagent_containers/food/snacks/meat.dm
+++ b/code/modules/reagents/reagent_containers/food/snacks/meat.dm
@@ -46,3 +46,17 @@
/obj/item/reagent_containers/food/snacks/meat/chicken/game
name = "game bird piece"
desc = "Fresh game meat, harvested from some wild bird."
+
+
+/obj/item/reagent_containers/food/snacks/abominationmeat
+ name = "meat"
+ desc = "A slab of red-ish meat. Smells terribly."
+ icon_state = "rottenmeat"
+ filling_color = COLOR_MAROON
+ center_of_mass = "x=16;y=10"
+ bitesize = 5
+
+/obj/item/reagent_containers/food/snacks/abominationmeat/New()
+ ..()
+ reagents.add_reagent(/datum/reagent/nutriment/protein, 4)
+ reagents.add_reagent(/datum/reagent/grauel, 6)
diff --git a/code/modules/research/designs/designs_circuits.dm b/code/modules/research/designs/designs_circuits.dm
index 1c9e35004248a..963e2da8aeaa2 100644
--- a/code/modules/research/designs/designs_circuits.dm
+++ b/code/modules/research/designs/designs_circuits.dm
@@ -861,6 +861,19 @@
req_tech = list(TECH_ENGINEERING = 6, TECH_COMBAT = 7, TECH_POWER = 6, TECH_BLUESPACE = 6)
build_path = /obj/item/stock_parts/circuitboard/remote_weapon/loadable/bs
sort_string = "YAAAH"
+
+/datum/design/circuit/quantumpad
+ name = "quantum pad"
+ id = "quantumpad"
+ req_tech = list(TECH_BLUESPACE = 5, TECH_ENGINEERING = 5)
+ build_path = /obj/item/stock_parts/circuitboard/quantumpad
+ sort_string = "YAAAI"
+
+/datum/design/circuit/mining_quantumpad
+ name = "mining quantum pad"
+ id = "mining_quantumpad"
+ req_tech = list(TECH_BLUESPACE = 4, TECH_ENGINEERING = 4)
+ build_path = /obj/item/stock_parts/circuitboard/quantumpad/mining
/datum/design/circuit/factory
name = "automated production factory"
diff --git a/code/modules/research/designs/designs_mining.dm b/code/modules/research/designs/designs_mining.dm
index d41160d862d41..4da8a2bb1b640 100644
--- a/code/modules/research/designs/designs_mining.dm
+++ b/code/modules/research/designs/designs_mining.dm
@@ -68,3 +68,19 @@
materials = list(MATERIAL_STEEL = 1700, MATERIAL_GLASS = 1500, MATERIAL_PLASTIC = 500, MATERIAL_PHORON = 500)
build_path = /obj/item/pickaxe/xeno/drill/plasma
sort_string = "KAAAI"
+
+/datum/design/item/mining/drill_upgrade_automatic
+ desc = "Modifies mining drill to automatically dispense stored ores."
+ id = "drill_upgrade_automatic"
+ req_tech = list(TECH_ENGINEERING = 4, TECH_POWER = 3)
+ materials = list(MATERIAL_STEEL = 1000, MATERIAL_PLASTEEL = 750, MATERIAL_GLASS = 500, MATERIAL_ALUMINIUM = 250)
+ build_path = /obj/item/drill_upgrade/auto_dispense
+ sort_string = "KAAAJ"
+
+/datum/design/item/mining/drill_upgrade_range
+ desc = "Modifies mining drill to increase mining range."
+ id = "drill_upgrade_range"
+ req_tech = list(TECH_ENGINEERING = 4, TECH_POWER = 3)
+ materials = list(MATERIAL_STEEL = 1000, MATERIAL_PLASTEEL = 750, MATERIAL_GLASS = 500, MATERIAL_ALUMINIUM = 250)
+ build_path = /obj/item/drill_upgrade/range_increase
+ sort_string = "KAAAK"
diff --git a/code/modules/species/outsider/abomination.dm b/code/modules/species/outsider/abomination.dm
new file mode 100644
index 0000000000000..ecc45b73a990c
--- /dev/null
+++ b/code/modules/species/outsider/abomination.dm
@@ -0,0 +1,88 @@
+// Human subspecies that were essentially produced(assimilated?) by the infestation.
+// Very resistant to most damage sources except for burn.
+// Very incomplete at the moment and not really used anywhere
+
+/datum/species/human/abomination
+ name = SPECIES_ABOMINATION
+ name_plural = "Abominations"
+ description = "A sub-product of bio-engineered species known as the \"Infestation\" or \"Abominations\". \
+ Very dangerous and resistant to everything outside of burning things and high temperatures. \
+ In the complex caste system of the Swarm, they are known to be among the highest-ranking leaders, \
+ usually employed for more sophisticated tasks, such as infiltration and operating captured worlds."
+ show_ssd = "hybernating"
+ blood_color = COLOR_MAROON
+ flesh_color = COLOR_MAROON
+
+ speech_chance = 50
+ speech_sounds = list(
+ 'sound/voice/abomination1.ogg',
+ 'sound/voice/abomination2.ogg',
+ 'sound/voice/abomination3.ogg',
+ 'sound/voice/abomination4.ogg',
+ 'sound/voice/abomination5.ogg',
+ 'sound/voice/abomination6.ogg',
+ 'sound/voice/abomination7.ogg',
+ 'sound/voice/abomination8.ogg',
+ )
+
+ unarmed_types = list(
+ /datum/unarmed_attack/claws/strong/abomination,
+ /datum/unarmed_attack/bite/sharp,
+ )
+
+ darksight_range = 7
+ breath_pressure = 5
+ max_pressure_diff = 500
+
+ cold_level_1 = 60
+ cold_level_2 = 30
+ cold_level_3 = 0
+ heat_level_1 = 330
+ heat_level_2 = 360
+ heat_level_3 = 600
+ body_temperature = 290
+ heat_discomfort_level = 305
+ cold_discomfort_level = 75
+
+ natural_armour_values = list(
+ melee = ARMOR_MELEE_RESISTANT,
+ bullet = ARMOR_BALLISTIC_PISTOL,
+ laser = ARMOR_LASER_MAJOR,
+ energy = ARMOR_ENERGY_RESISTANT,
+ bomb = ARMOR_BOMB_PADDED,
+ bio = ARMOR_BIO_SHIELDED,
+ rad = ARMOR_RAD_SHIELDED
+ )
+
+ flash_mod = 0.5
+ oxy_mod = 0.1
+ toxins_mod = 0.2
+ burn_mod = 2
+
+ hunger_factor = DEFAULT_HUNGER_FACTOR * 20 // Very hungry
+ thirst_factor = DEFAULT_THIRST_FACTOR
+ taste_sensitivity = TASTE_DULL
+ rarity_value = 10
+ gluttonous = 2
+ strength = STR_VHIGH
+ sexybits_location = null
+ vision_flags = SEE_SELF | SEE_MOBS
+
+ has_organ = list(
+ BP_HEART = /obj/item/organ/internal/heart,
+ BP_STOMACH = /obj/item/organ/internal/stomach,
+ BP_LUNGS = /obj/item/organ/internal/lungs,
+ BP_LIVER = /obj/item/organ/internal/liver,
+ BP_KIDNEYS = /obj/item/organ/internal/kidneys,
+ BP_BRAIN = /obj/item/organ/internal/brain,
+ BP_APPENDIX = /obj/item/organ/internal/appendix,
+ BP_EYES = /obj/item/organ/internal/eyes,
+ BP_LARVA = /obj/item/organ/internal/larva_producer
+ )
+
+ appearance_flags = HAS_HAIR_COLOR | HAS_SKIN_TONE_SPCR | HAS_LIPS | HAS_UNDERWEAR | HAS_EYE_COLOR
+ species_flags = SPECIES_FLAG_NO_SCAN | SPECIES_FLAG_NO_SLIP | SPECIES_FLAG_NO_MINOR_CUT
+ spawn_flags = SPECIES_IS_RESTRICTED | SPECIES_NO_FBP_CONSTRUCTION | SPECIES_NO_FBP_CHARGEN
+
+/datum/species/human/abomination/attempt_grab(mob/living/carbon/human/user, mob/living/target)
+ return ..(user, target, GRAB_ABOMINATION)
diff --git a/code/modules/species/species_attack.dm b/code/modules/species/species_attack.dm
index 638ed0b2eaa98..6a127d54fd0bf 100644
--- a/code/modules/species/species_attack.dm
+++ b/code/modules/species/species_attack.dm
@@ -83,6 +83,13 @@
/datum/unarmed_attack/claws/strong/gloves
blocked_by_gloves = FALSE
+/datum/unarmed_attack/claws/strong/abomination
+ attack_verb = list("eviscerated", "gored")
+ damage = 8
+ attack_name = "terrifying claws"
+ attack_sound = 'sound/weapons/alien_claw_flesh2.ogg'
+
+
/datum/unarmed_attack/bite/strong
attack_verb = list("mauled")
damage = 8
diff --git a/customs/code/krabinator.dm b/customs/code/krabinator.dm
index a7732f060a1f9..82bcdbfda0683 100644
--- a/customs/code/krabinator.dm
+++ b/customs/code/krabinator.dm
@@ -11,4 +11,4 @@
desc = "Nice uniform sis."
icon_state = "janimaid_alt"
icon = 'customs/icons/obj/custom_items_obj.dmi'
- item_icons = list(slot_head_str = 'customs/icons/mob/custom_items_mob.dmi')
+ item_icons = list(slot_wear_suit_str = 'customs/icons/mob/custom_items_mob.dmi')
diff --git a/icons/mob/simple_animal/abominable_infestation/32x32.dmi b/icons/mob/simple_animal/abominable_infestation/32x32.dmi
new file mode 100644
index 0000000000000..6170a78f2471b
Binary files /dev/null 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
new file mode 100644
index 0000000000000..afb17a37ee6b4
Binary files /dev/null and b/icons/mob/simple_animal/abominable_infestation/48x48.dmi differ
diff --git a/icons/mob/simple_animal/abominable_infestation/64x64.dmi b/icons/mob/simple_animal/abominable_infestation/64x64.dmi
new file mode 100644
index 0000000000000..252b601b75d69
Binary files /dev/null and b/icons/mob/simple_animal/abominable_infestation/64x64.dmi differ
diff --git a/icons/obj/closets/bases/odst.dmi b/icons/obj/closets/bases/odst.dmi
new file mode 100644
index 0000000000000..fbd18f090c103
Binary files /dev/null and b/icons/obj/closets/bases/odst.dmi differ
diff --git a/icons/obj/mining.dmi b/icons/obj/mining.dmi
index 25e53c58d8898..f437baf1982fe 100644
Binary files a/icons/obj/mining.dmi and b/icons/obj/mining.dmi differ
diff --git a/icons/obj/mining_drill.dmi b/icons/obj/mining_drill.dmi
index 95aab5f5bea37..d8094234a0c79 100644
Binary files a/icons/obj/mining_drill.dmi and b/icons/obj/mining_drill.dmi differ
diff --git a/icons/obj/missile_equipment.dmi b/icons/obj/missile_equipment.dmi
new file mode 100644
index 0000000000000..c6f54ca70901b
Binary files /dev/null and b/icons/obj/missile_equipment.dmi differ
diff --git a/icons/obj/munitions.dmi b/icons/obj/munitions.dmi
index 4631b35ba4b62..7b5362baf397c 100644
Binary files a/icons/obj/munitions.dmi and b/icons/obj/munitions.dmi differ
diff --git a/icons/obj/overmap.dmi b/icons/obj/overmap.dmi
index 3cda65da5e1c3..a36821fc688a8 100644
Binary files a/icons/obj/overmap.dmi and b/icons/obj/overmap.dmi differ
diff --git a/icons/obj/structures/decor.dmi b/icons/obj/structures/decor.dmi
new file mode 100644
index 0000000000000..f5b5ee54609d9
Binary files /dev/null and b/icons/obj/structures/decor.dmi differ
diff --git a/icons/obj/structures/decor32x64.dmi b/icons/obj/structures/decor32x64.dmi
new file mode 100644
index 0000000000000..ceb577bb2c45b
Binary files /dev/null and b/icons/obj/structures/decor32x64.dmi differ
diff --git a/icons/obj/structures/decor64x64.dmi b/icons/obj/structures/decor64x64.dmi
new file mode 100644
index 0000000000000..e9ac905d19867
Binary files /dev/null and b/icons/obj/structures/decor64x64.dmi differ
diff --git a/icons/obj/structures/decor96x96.dmi b/icons/obj/structures/decor96x96.dmi
new file mode 100644
index 0000000000000..7229e30f238e6
Binary files /dev/null and b/icons/obj/structures/decor96x96.dmi differ
diff --git a/icons/obj/structures/decor_fences.dmi b/icons/obj/structures/decor_fences.dmi
new file mode 100644
index 0000000000000..155d5bef68c69
Binary files /dev/null and b/icons/obj/structures/decor_fences.dmi differ
diff --git a/icons/obj/telescience.dmi b/icons/obj/telescience.dmi
index dcd7f39d73846..20c9011e95e11 100644
Binary files a/icons/obj/telescience.dmi and b/icons/obj/telescience.dmi differ
diff --git a/icons/turf/flooring/flesh.dmi b/icons/turf/flooring/flesh.dmi
index 0653e6807395a..5cedd5abf01a7 100644
Binary files a/icons/turf/flooring/flesh.dmi and b/icons/turf/flooring/flesh.dmi differ
diff --git a/maps/antag_spawn/mercenary/mercenary.dm b/maps/antag_spawn/mercenary/mercenary.dm
index 4d5809b4d98a0..3efb4b7bf39ee 100644
--- a/maps/antag_spawn/mercenary/mercenary.dm
+++ b/maps/antag_spawn/mercenary/mercenary.dm
@@ -6,7 +6,6 @@
/obj/effect/overmap/visitable/sector/merc_base
name = "Tiny Asteroid"
desc = "Sensor array detects an small, insignificant asteroid. The core appears to be reflecting scans."
- in_space = TRUE
known = FALSE
place_near_main = list(2, 4)
icon_state = "meteor4"
diff --git a/maps/antag_spawn/vox/voxraider.dm b/maps/antag_spawn/vox/voxraider.dm
index 49baa5de1bbe7..97a6d826f23cb 100644
--- a/maps/antag_spawn/vox/voxraider.dm
+++ b/maps/antag_spawn/vox/voxraider.dm
@@ -6,7 +6,6 @@
/obj/effect/overmap/visitable/sector/vox_start
name = "Empty Space"
desc = "Just some empty space, with an irregular sensor echo."
- in_space = TRUE
known = FALSE
place_near_main = list(2, 4)
icon_state = "event"
diff --git a/maps/away/blueriver/blueriver.dm b/maps/away/blueriver/blueriver.dm
index b1373c79a0cca..521063ed9e37e 100644
--- a/maps/away/blueriver/blueriver.dm
+++ b/maps/away/blueriver/blueriver.dm
@@ -3,7 +3,6 @@
/obj/effect/overmap/visitable/sector/arcticplanet
name = "arctic planetoid"
desc = "Sensor array detects an arctic planet with a small vessel on the planet's surface. Scans further indicate strange energy emissions from below the planet's surface."
- in_space = FALSE
icon_state = "globe"
initial_generic_waypoints = list(
"nav_blueriv_1",
diff --git a/maps/away/skrellscoutship/skrellscoutship.dm b/maps/away/skrellscoutship/skrellscoutship.dm
index 831fb95761129..02dae80af8bce 100644
--- a/maps/away/skrellscoutship/skrellscoutship.dm
+++ b/maps/away/skrellscoutship/skrellscoutship.dm
@@ -22,7 +22,6 @@
/obj/effect/overmap/visitable/sector/skrellscoutspace
name = "Empty Sector"
desc = "Slight traces of a cloaking device are present. Unable to determine exact location."
- in_space = TRUE
icon_state = "event"
hide_from_reports = TRUE
diff --git a/maps/away/voxship/voxship.dm b/maps/away/voxship/voxship.dm
index d69000f26f6e9..6e6d2e90cfa44 100644
--- a/maps/away/voxship/voxship.dm
+++ b/maps/away/voxship/voxship.dm
@@ -20,7 +20,6 @@
/obj/effect/overmap/visitable/sector/vox_scav_ship
name = "small asteroid cluster"
desc = "Sensor array detects a small asteroid cluster."
- in_space = TRUE
icon_state = "meteor4"
hide_from_reports = TRUE
initial_generic_waypoints = list(
diff --git a/maps/torch/torch5_deck1.dmm b/maps/torch/torch5_deck1.dmm
index 9f5952993bb2c..2f59a1b161dc6 100644
--- a/maps/torch/torch5_deck1.dmm
+++ b/maps/torch/torch5_deck1.dmm
@@ -9115,14 +9115,11 @@
/turf/simulated/floor/tiled/techfloor,
/area/maintenance/firstdeck/aftport)
"aEG" = (
-/obj/effect/floor_decal/industrial/outline/grey,
/obj/structure/table/rack,
-/obj/item/clothing/shoes/dutyboots,
-/obj/item/clothing/accessory/armband/bluegold,
-/obj/item/clothing/accessory/storage/holster/thigh,
-/obj/item/clothing/glasses/tacgoggles,
-/obj/item/clothing/suit/armor/pcarrier/medium/sol,
-/obj/item/clothing/head/helmet,
+/obj/item/gun/projectile/automatic/carabine_tactical,
+/obj/item/ammo_magazine/carabine_rifle,
+/obj/item/ammo_magazine/carabine_rifle,
+/obj/item/ammo_magazine/carabine_rifle,
/turf/simulated/floor/tiled/techfloor/grid,
/area/command/armoury/tactical)
"aEH" = (
@@ -18360,9 +18357,6 @@
/obj/item/gun/energy/stunrevolver/rifle,
/obj/item/gun/energy/taser/carbine,
/obj/item/gun/energy/taser/carbine,
-/obj/machinery/light{
- dir = 1
- },
/turf/simulated/floor/tiled/techfloor/grid,
/area/command/armoury/tactical)
"hub" = (
@@ -18711,17 +18705,6 @@
/turf/simulated/floor/tiled/white,
/area/rnd/development)
"hLf" = (
-/obj/effect/floor_decal/industrial/outline/grey,
-/obj/structure/table/rack,
-/obj/item/clothing/shoes/dutyboots,
-/obj/item/clothing/accessory/armband/bluegold,
-/obj/item/clothing/accessory/storage/holster/thigh,
-/obj/item/clothing/glasses/tacgoggles,
-/obj/item/clothing/suit/armor/pcarrier/medium/sol,
-/obj/item/clothing/head/helmet,
-/obj/machinery/recharger/wallcharger{
- pixel_y = 24
- },
/turf/simulated/floor/tiled/techfloor/grid,
/area/command/armoury/tactical)
"hLD" = (
@@ -18801,6 +18784,19 @@
/obj/effect/floor_decal/industrial/hatch/yellow,
/turf/simulated/floor/tiled/dark,
/area/rnd/rnd_sec)
+"hQl" = (
+/obj/effect/floor_decal/industrial/outline/grey,
+/obj/structure/table/rack,
+/obj/item/clothing/shoes/dutyboots,
+/obj/item/clothing/accessory/armband/bluegold,
+/obj/item/clothing/accessory/storage/holster/thigh,
+/obj/item/clothing/suit/armor/pcarrier/medium/sol,
+/obj/item/clothing/head/helmet,
+/obj/item/clothing/accessory/glassesmod/nvg,
+/obj/item/clothing/glasses/ballistic/security,
+/obj/item/clothing/mask/gas/half,
+/turf/simulated/floor/tiled/techfloor/grid,
+/area/command/armoury/tactical)
"hUb" = (
/obj/machinery/door/blast/shutters{
density = 0;
@@ -21058,6 +21054,22 @@
/obj/machinery/atmospherics/pipe/simple/hidden/supply,
/turf/simulated/floor/tiled/techfloor,
/area/maintenance/firstdeck/centralstarboard)
+"kML" = (
+/obj/effect/floor_decal/industrial/outline/grey,
+/obj/structure/table/rack,
+/obj/item/clothing/shoes/dutyboots,
+/obj/item/clothing/accessory/armband/bluegold,
+/obj/item/clothing/accessory/storage/holster/thigh,
+/obj/item/clothing/suit/armor/pcarrier/medium/sol,
+/obj/item/clothing/head/helmet,
+/obj/item/clothing/accessory/glassesmod/nvg,
+/obj/item/clothing/glasses/ballistic/security,
+/obj/structure/window/reinforced{
+ dir = 4
+ },
+/obj/item/clothing/mask/gas/half,
+/turf/simulated/floor/tiled/techfloor/grid,
+/area/command/armoury/tactical)
"kOb" = (
/obj/machinery/atmospherics/pipe/simple/hidden/supply{
dir = 5
@@ -25841,9 +25853,6 @@
/obj/item/shield/riot/metal,
/obj/item/shield/riot/metal,
/obj/item/shield/riot/metal,
-/obj/machinery/light{
- dir = 1
- },
/turf/simulated/floor/tiled/techfloor/grid,
/area/command/armoury/tactical)
"qLb" = (
@@ -26258,6 +26267,22 @@
},
/turf/simulated/floor/tiled/freezer,
/area/rnd/xenobiology/xenoflora)
+"rkZ" = (
+/obj/effect/floor_decal/industrial/outline/grey,
+/obj/structure/table/rack,
+/obj/item/clothing/shoes/dutyboots,
+/obj/item/clothing/accessory/armband/bluegold,
+/obj/item/clothing/accessory/storage/holster/thigh,
+/obj/item/clothing/suit/armor/pcarrier/medium/sol,
+/obj/item/clothing/head/helmet,
+/obj/item/clothing/accessory/glassesmod/nvg,
+/obj/item/clothing/glasses/ballistic/security,
+/obj/item/clothing/mask/gas/half,
+/obj/machinery/recharger/wallcharger{
+ pixel_y = 24
+ },
+/turf/simulated/floor/tiled/techfloor/grid,
+/area/command/armoury/tactical)
"rlb" = (
/obj/machinery/atmospherics/unary/freezer{
dir = 1;
@@ -27446,6 +27471,28 @@
},
/turf/simulated/floor/tiled,
/area/hallway/primary/firstdeck/aft)
+"ssN" = (
+/obj/effect/floor_decal/industrial/outline/grey,
+/obj/structure/table/rack,
+/obj/item/clothing/shoes/dutyboots,
+/obj/item/clothing/accessory/armband/bluegold,
+/obj/item/clothing/accessory/storage/holster/thigh,
+/obj/item/clothing/suit/armor/pcarrier/medium/sol,
+/obj/item/clothing/head/helmet,
+/obj/item/clothing/accessory/glassesmod/nvg,
+/obj/item/clothing/glasses/ballistic/security,
+/obj/machinery/light{
+ dir = 1
+ },
+/obj/structure/window/reinforced{
+ dir = 1
+ },
+/obj/structure/window/reinforced{
+ dir = 8
+ },
+/obj/item/clothing/mask/gas/half,
+/turf/simulated/floor/tiled/techfloor/grid,
+/area/command/armoury/tactical)
"stb" = (
/obj/effect/floor_decal/corner/research/mono,
/obj/effect/floor_decal/industrial/outline/yellow,
@@ -27457,6 +27504,22 @@
},
/turf/simulated/floor/tiled/white/monotile,
/area/rnd/xenobiology/xenoflora)
+"stv" = (
+/obj/effect/floor_decal/industrial/outline/grey,
+/obj/structure/table/rack,
+/obj/item/clothing/shoes/dutyboots,
+/obj/item/clothing/accessory/armband/bluegold,
+/obj/item/clothing/accessory/storage/holster/thigh,
+/obj/item/clothing/suit/armor/pcarrier/medium/sol,
+/obj/item/clothing/head/helmet,
+/obj/item/clothing/accessory/glassesmod/nvg,
+/obj/item/clothing/glasses/ballistic/security,
+/obj/structure/window/reinforced{
+ dir = 8
+ },
+/obj/item/clothing/mask/gas/half,
+/turf/simulated/floor/tiled/techfloor/grid,
+/area/command/armoury/tactical)
"sub" = (
/obj/structure/closet/secure_closet/hydroponics_torch,
/obj/effect/floor_decal/corner/research/mono,
@@ -29192,6 +29255,28 @@
/obj/effect/floor_decal/industrial/outline/grey,
/turf/simulated/floor/plating,
/area/maintenance/firstdeck/centralstarboard)
+"uWS" = (
+/obj/effect/floor_decal/industrial/outline/grey,
+/obj/structure/table/rack,
+/obj/item/clothing/shoes/dutyboots,
+/obj/item/clothing/accessory/armband/bluegold,
+/obj/item/clothing/accessory/storage/holster/thigh,
+/obj/item/clothing/suit/armor/pcarrier/medium/sol,
+/obj/item/clothing/head/helmet,
+/obj/item/clothing/accessory/glassesmod/nvg,
+/obj/item/clothing/glasses/ballistic/security,
+/obj/machinery/light{
+ dir = 1
+ },
+/obj/structure/window/reinforced{
+ dir = 1
+ },
+/obj/structure/window/reinforced{
+ dir = 4
+ },
+/obj/item/clothing/mask/gas/half,
+/turf/simulated/floor/tiled/techfloor/grid,
+/area/command/armoury/tactical)
"uXe" = (
/turf/simulated/floor/tiled/techfloor,
/area/maintenance/firstdeck/aftstarboard)
@@ -57982,10 +58067,10 @@ dUZ
aaa
acd
acd
-acd
-acd
dVw
dVw
+ssN
+stv
htK
hFu
vjY
@@ -58184,13 +58269,13 @@ bhb
aaa
aaa
aaa
-acd
-acd
dVw
dVw
wSf
svH
svH
+svH
+svH
swz
aBc
iBb
@@ -58386,10 +58471,10 @@ abN
aaa
aaa
aaa
-acd
-acd
dVw
dVw
+rkZ
+hQl
hLf
aEG
aEG
@@ -58588,13 +58673,13 @@ aiI
ajJ
aaa
aaa
-acd
-acd
dVw
dVw
ryt
svH
svH
+svH
+svH
njm
ksK
nZZ
@@ -58790,10 +58875,10 @@ abN
aaa
aaa
acd
-acd
-acd
dVw
dVw
+rkZ
+hQl
hLf
aEG
aEG
@@ -58992,13 +59077,13 @@ abN
aaa
aaa
acd
-acd
-acd
dVw
dVw
sET
svH
svH
+svH
+svH
nWP
aBc
cZd
@@ -59194,10 +59279,10 @@ aaa
aaa
aaa
acd
-acd
-acd
dVw
dVw
+uWS
+kML
qKQ
iAS
bCX
@@ -59396,8 +59481,8 @@ aaa
aaa
aaa
acd
-acd
-acd
+dVw
+dVw
dVw
dVw
dVw
@@ -59600,7 +59685,7 @@ aaa
aaa
acd
acd
-acd
+dVw
dVw
dVw
dVw
diff --git a/maps/torch/torch_overmap.dm b/maps/torch/torch_overmap.dm
index 675bc24a5d04f..d639076078248 100644
--- a/maps/torch/torch_overmap.dm
+++ b/maps/torch/torch_overmap.dm
@@ -4,7 +4,6 @@
fore_dir = WEST
vessel_mass = 100000
burn_delay = 2 SECONDS
- base = TRUE
initial_restricted_waypoints = list(
"Charon" = list("nav_hangar_charon"), //can't have random shuttles popping inside the ship
diff --git a/proxima/code/game/items/weapons/gun/projectiles/bosnia.dm b/proxima/code/game/items/weapons/gun/projectiles/bosnia.dm
index b4678d34acf65..c56bf0da180fc 100644
--- a/proxima/code/game/items/weapons/gun/projectiles/bosnia.dm
+++ b/proxima/code/game/items/weapons/gun/projectiles/bosnia.dm
@@ -192,3 +192,63 @@
/obj/item/gun/projectile/automatic/bandit/on_update_icon()
..()
icon_state = (ammo_magazine)? "mpistolen" : "mpistolen-empty"
+
+
+/obj/item/gun/projectile/automatic/carabine_tactical
+ name = "Special forces carabine"
+ desc = "SC-42 carabine - These are weapons specially designed to resolve conflicts on the streets without attracting unnecessary attention. By integrating a silencer, it is mainly used by the military police of the SCG."
+ icon = 'proxima/icons/obj/guns/carabine.dmi'
+ icon_state = "arg75"
+ item_state = "arifle"
+ wielded_item_state = "arifle-wielded"
+ item_icons = list(
+ slot_r_hand_str = 'proxima/icons/mob/onmob/items/guns_r_default.dmi',
+ slot_l_hand_str = 'proxima/icons/mob/onmob/items/guns_l_default.dmi',
+ )
+ fire_delay = 4
+ caliber = CALIBER_CARABINE
+ ammo_type = /obj/item/ammo_casing/rifle/carabine
+ magazine_type = /obj/item/ammo_magazine/carabine_rifle
+ load_method = MAGAZINE
+ allowed_magazines = /obj/item/ammo_magazine/carabine_rifle
+ silenced = TRUE
+ multi_aim = 1
+ can_special_reload = TRUE
+ firemodes = list(
+ list(mode_name = "semiauto", mode_desc = "Fire as fast as you can pull the trigger", burst=1, fire_delay=0, move_delay=null),
+ list(mode_name="2-round bursts", mode_desc = "Short, controlled bursts", burst=2, fire_delay=null, move_delay=2, one_hand_penalty=2),
+ list(mode_name="3-round bursts", mode_desc = "Short, controlled bursts", burst=3, fire_delay=null, move_delay=4, one_hand_penalty=3)
+ )
+
+/obj/item/gun/projectile/automatic/carabine_tactical/on_update_icon()
+ ..()
+ icon_state = "arg75"
+ if(ammo_magazine)
+ overlays += image(icon, "arg75_mag")
+
+
+/obj/item/ammo_magazine/carabine_rifle
+ name = "carabine rifle magazine"
+ icon = 'proxima/icons/obj/ammo.dmi'
+ icon_state = "carabine"
+ origin_tech = list(TECH_COMBAT = 2)
+ mag_type = MAGAZINE
+ caliber = CALIBER_CARABINE
+ matter = list(MATERIAL_STEEL = 1400)
+ ammo_type = /obj/item/ammo_casing/rifle/carabine
+ max_ammo = 20
+ multiple_sprites = 1
+
+/obj/item/ammo_casing/rifle/carabine
+ desc = "A spec.ops carabine casing."
+ caliber = CALIBER_CARABINE
+ projectile_type = /obj/item/projectile/bullet/rifle/carabine
+ icon_state = "rifle_mil"
+ spent_icon = "rifle_mil-spent"
+
+/obj/item/projectile/bullet/rifle/carabine
+ fire_sound = 'sound/weapons/gunshot/gunshot3.ogg'
+ damage = 35
+ armor_penetration = 30
+ penetration_modifier = 1
+ penetrating = 0 //INF, WAS 1
diff --git a/proxima/code/game/machinery/factory/quantum_pads.dm b/proxima/code/game/machinery/factory/quantum_pads.dm
new file mode 100644
index 0000000000000..ff4dd93e11141
--- /dev/null
+++ b/proxima/code/game/machinery/factory/quantum_pads.dm
@@ -0,0 +1,248 @@
+/obj/machinery/quantumpad
+ name = "quantum pad"
+ desc = "A bluespace quantum-linked telepad used for teleporting objects to other quantum pads."
+ icon = 'icons/obj/telescience.dmi'
+ icon_state = "qpad-idle"
+ idle_power_usage = 10 KILOWATTS
+ construct_state = /decl/machine_construction/default/panel_closed
+ wires = /datum/wires/quantumpad
+ maximum_component_parts = list(/obj/item/stock_parts = 14)
+ var/icon_state_default = "qpad"
+ var/teleport_delay = 2 SECONDS
+ var/teleport_cooldown_time = 30 SECONDS
+ var/teleport_cooldown
+ var/teleporting = FALSE //if it's in the process of teleporting
+ /// List of whitelisted types; If set - only these things can pass through
+ var/list/teleport_only = list()
+ var/teleport_power_cost = 100 KILOWATTS
+ var/power_efficiency = 1
+ var/obj/machinery/quantumpad/linked_pad
+
+ // Mapping
+ var/static/list/mapped_quantum_pads = list()
+ var/map_pad_id = ""
+ var/map_pad_link_id = ""
+
+/obj/machinery/quantumpad/Initialize()
+ . = ..()
+ if(map_pad_id)
+ mapped_quantum_pads[map_pad_id] = src
+
+/obj/machinery/quantumpad/Destroy()
+ mapped_quantum_pads -= map_pad_id
+ return ..()
+
+/obj/machinery/quantumpad/examine(mob/user)
+ . = ..()
+ to_chat(user, SPAN_NOTICE("It is [ linked_pad ? "currently" : "not"] linked to another pad."))
+ if(!panel_open)
+ to_chat(user, SPAN_NOTICE("The panel is screwed in, obstructing the linking device."))
+ else
+ to_chat(user, SPAN_NOTICE("The linking device is now able to be scanned with a multitool."))
+
+ if(world.time < teleport_cooldown)
+ to_chat(user, SPAN_NOTICE("[src] will be operational in [round((teleport_cooldown - world.time) / 10)] seconds."))
+
+/obj/machinery/quantumpad/on_update_icon()
+ if(panel_open || (stat & NOPOWER))
+ icon_state = "[icon_state_default]-o"
+ else
+ icon_state = "[icon_state_default]-idle"
+ return
+
+/obj/machinery/quantumpad/attackby(obj/item/I, mob/user, params)
+ if(user.a_intent == I_HURT)
+ return ..()
+
+ if(istype(I, /obj/item/device/assembly/signaler) && panel_open)
+ return wires.Interact(user)
+
+ if(istype(I, /obj/item/device/multitool))
+ var/obj/item/device/multitool/M = I
+ if(panel_open)
+ M.set_buffer(src)
+ to_chat(user, SPAN_NOTICE("You save the data in [M]'s buffer. It can now be linked to pads with closed panels."))
+ return TRUE
+
+ else
+ var/obj/machinery/quantumpad/buffer = M.get_buffer(/obj/machinery/quantumpad)
+ if(!istype(buffer))
+ return TRUE
+ if(buffer == src)
+ to_chat(user, SPAN_WARNING("You cannot link a pad to itself!"))
+ return TRUE
+ else
+ linked_pad = buffer
+ to_chat(user, SPAN_NOTICE("You link [src] to the one in [I]'s buffer."))
+ return TRUE
+
+ if(isWrench(I) && !panel_open)
+ playsound(src.loc, 'sound/items/Ratchet.ogg', 50, 1)
+ to_chat(user, SPAN_NOTICE("You [anchored ? "un" : ""]anchor \the [src]."))
+ anchored = !anchored
+ return TRUE
+
+ return ..()
+
+/obj/machinery/quantumpad/physical_attack_hand(mob/user, obj/machinery/quantumpad/target_pad = linked_pad)
+ if(!target_pad || QDELETED(target_pad))
+ if(!map_pad_link_id || !initMappedLink())
+ to_chat(user, SPAN_WARNING("Target pad not found!"))
+ return
+
+ if(panel_open)
+ to_chat(user, SPAN_WARNING("[src]'s panel is open!"))
+ return
+
+ if(stat & NOPOWER)
+ to_chat(user, SPAN_WARNING("[src] is unpowered!"))
+ return
+
+ if(teleporting)
+ to_chat(user, SPAN_WARNING("[src] is charging up. Please wait."))
+ return
+
+ if(!anchored)
+ to_chat(user, SPAN_WARNING("[src] is not anchored."))
+ return
+
+ if(world.time < teleport_cooldown)
+ to_chat(user, SPAN_WARNING("[src] is recharging. Please wait for [round((teleport_cooldown - world.time) / 10)] seconds."))
+ return
+
+ if(target_pad.teleporting)
+ to_chat(user, SPAN_WARNING("Target pad is busy. Please wait."))
+ return
+
+ if(target_pad.stat & NOPOWER)
+ to_chat(user, SPAN_WARNING("Target pad is not responding to ping."))
+ return
+
+ if(!target_pad.anchored)
+ to_chat(user, SPAN_WARNING("Target pad is not anchored."))
+ return
+
+ add_fingerprint(user)
+ AttemptTeleport(user, target_pad)
+ return TRUE
+
+/obj/machinery/quantumpad/attack_ghost(mob/observer/ghost)
+ . = ..()
+ if(.)
+ return
+ if(!linked_pad && map_pad_link_id)
+ initMappedLink()
+ if(linked_pad)
+ ghost.forceMove(get_turf(linked_pad))
+
+/obj/machinery/quantumpad/RefreshParts()
+ ..()
+ teleport_delay = 15 SECONDS / clamp(total_component_rating_of_type(/obj/item/stock_parts/micro_laser), 1, 30)
+ teleport_cooldown_time = 150 SECONDS / clamp(total_component_rating_of_type(/obj/item/stock_parts/micro_laser), 1, 30)
+ power_efficiency = 0.2 * clamp(total_component_rating_of_type(/obj/item/stock_parts/capacitor), 1, 30)
+
+/obj/machinery/quantumpad/proc/sparks()
+ var/datum/effect/effect/system/spark_spread/s = new /datum/effect/effect/system/spark_spread
+ s.set_up(5, 1, get_turf(src))
+ s.start()
+
+/obj/machinery/quantumpad/proc/AttemptTeleport(mob/user, obj/machinery/quantumpad/target_pad = linked_pad)
+ if(!target_pad)
+ return
+
+ playsound(get_turf(src), 'sound/weapons/flash.ogg', 25, TRUE)
+ teleporting = TRUE
+ addtimer(CALLBACK(src, .proc/DoTeleport, user, target_pad), teleport_delay)
+
+/obj/machinery/quantumpad/proc/DoTeleport(mob/user, obj/machinery/quantumpad/target_pad = linked_pad)
+ if(!src || QDELETED(src))
+ teleporting = FALSE
+ return
+ if(stat & NOPOWER)
+ to_chat(user, SPAN_WARNING("[src] is unpowered!"))
+ teleporting = FALSE
+ return
+ if(!target_pad || QDELETED(target_pad) || target_pad.stat & NOPOWER)
+ to_chat(user, SPAN_WARNING("Linked pad is not responding to ping. Teleport aborted."))
+ teleporting = FALSE
+ return
+ if(can_use_power_oneoff(teleport_power_cost / power_efficiency, powered() ? power_channel : LOCAL))
+ to_chat(user, SPAN_WARNING("Not enough power to perform the jump. Teleport aborted."))
+ teleporting = FALSE
+ return
+
+ use_power_oneoff(teleport_power_cost / power_efficiency, powered() ? power_channel : LOCAL)
+ teleporting = FALSE
+ teleport_cooldown = world.time + teleport_cooldown_time
+
+ sparks()
+ target_pad.sparks()
+
+ flick("[icon_state_default]-beam", src)
+ playsound(get_turf(src), 'sound/weapons/emitter2.ogg', 25, TRUE)
+ flick("[target_pad.icon_state_default]-beam", target_pad)
+ playsound(get_turf(target_pad), 'sound/weapons/emitter2.ogg', 25, TRUE)
+ for(var/atom/movable/ROI in get_turf(src))
+ if(QDELETED(ROI))
+ continue // Sleeps in CHECK_TICK
+ if(LAZYLEN(teleport_only) && !(ROI.type in teleport_only))
+ continue // Cannot teleport these
+
+ // if is anchored, don't let through
+ if(ROI.anchored)
+ if(isliving(ROI))
+ var/mob/living/L = ROI
+ //only TP living mobs buckled to non anchored items
+ if(!L.buckled || L.buckled.anchored)
+ continue
+ else
+ continue
+
+ // Don't TP ghosts
+ if(isobserver(ROI))
+ continue
+
+ do_teleport(ROI, get_turf(target_pad), type = /decl/teleport)
+ CHECK_TICK
+
+/obj/machinery/quantumpad/proc/initMappedLink()
+ . = FALSE
+ var/obj/machinery/quantumpad/link = mapped_quantum_pads[map_pad_link_id]
+ if(link)
+ linked_pad = link
+ . = TRUE
+
+/obj/machinery/quantumpad/mining
+ name = "mining quantum pad"
+ desc = "A bluespace quantum-linked telepad used for teleporting ores to other quantum pads."
+ icon_state = "pad_idle"
+ icon_state_default = "pad"
+ idle_power_usage = 0
+ teleport_power_cost = 20 KILOWATTS // It only teleports ores, after all
+ maximum_component_parts = list(/obj/item/stock_parts = 12)
+
+/obj/machinery/quantumpad/mining/Initialize()
+ . = ..()
+ teleport_only = subtypesof(/obj/item/ore)
+
+
+/obj/item/stock_parts/circuitboard/quantumpad
+ name = T_BOARD("quantum pad")
+ build_path = /obj/machinery/quantumpad
+ board_type = "machine"
+ origin_tech = list(TECH_BLUESPACE = 5, TECH_ENGINEERING = 5)
+ req_components = list(
+ /obj/item/stock_parts/capacitor = 5,
+ /obj/item/stock_parts/micro_laser = 5)
+ additional_spawn_components = list(
+ /obj/item/stock_parts/power/battery/buildable/stock,
+ /obj/item/cell/standard = 1
+ )
+
+/obj/item/stock_parts/circuitboard/quantumpad/mining
+ name = T_BOARD("mining quantum pad")
+ build_path = /obj/machinery/quantumpad/mining
+ origin_tech = list(TECH_BLUESPACE = 4, TECH_ENGINEERING = 4)
+ req_components = list(
+ /obj/item/stock_parts/capacitor = 3,
+ /obj/item/stock_parts/micro_laser = 5)
diff --git a/proxima/code/game/objects/items/bouquet.dm b/proxima/code/game/objects/items/bouquet.dm
new file mode 100644
index 0000000000000..ac1422497074d
--- /dev/null
+++ b/proxima/code/game/objects/items/bouquet.dm
@@ -0,0 +1,77 @@
+/obj/item/bouquet
+ name = "Glass bouquet"
+ desc = "Beautiful bouquet with flowers. It smells indescribably delicious!"
+ icon = 'proxima/icons/obj/bouquet.dmi'
+ icon_state = "bouquet_base"
+ item_state = "bouquet_base"
+ var/list/allowedplants = list(
+ "harebells",
+ "poppies",
+ "lavender",
+ "sunflowers"
+ )
+ var/list/contentflovers = list()
+ var/filled = FALSE
+
+/obj/item/bouquet/on_update_icon()
+ for(var/obj/item/reagent_containers/food/snacks/grown/flower as anything in contentflovers)
+ switch (flower.plantname)
+ if ("harebells") overlays += image(icon, "flower_1")
+ if ("poppies") overlays += image(icon, "flower_2")
+ if ("lavender") overlays += image(icon, "flower_3")
+ if ("sunflowers") overlays += image(icon, "flower_4")
+ . = ..()
+
+/obj/item/bouquet/attackby(obj/item/weapon, mob/user)
+ if(!istype(weapon, /obj/item/reagent_containers/food/snacks/grown) || filled)
+ return ..()
+
+ var/obj/item/reagent_containers/food/snacks/grown/flower = weapon
+
+ if(flower in contentflovers)
+ to_chat(user, SPAN_WARNING("There is already this flower here."))
+ return
+
+ if(!(flower.plantname in allowedplants))
+ to_chat(user, SPAN_WARNING("This flower will look to strange in bouquet..."))
+ return
+
+ contentflovers.Add(flower)
+ qdel(flower)
+ update_icon()
+ visible_message(SPAN_INFO("[user] put's [flower.plantname] in the bouquet."))
+ if(length(contentflovers) >= 4) filled = TRUE
+
+/obj/item/bouquet/throw_impact(atom/hit_atom)
+ if(QDELETED(src))
+ return
+
+ visible_message(
+ SPAN_DANGER("\The [src] shatters from the impact!"),
+ SPAN_DANGER("You hear the sound of glass shattering!")
+ )
+ playsound(src.loc, pick(GLOB.shatter_sound), 100)
+ new /obj/item/material/shard(src.loc)
+ qdel(src)
+
+/obj/item/bouquet/premaded
+ icon_state = "bouquet_preview"
+ item_state = "bouquet_preview"
+ filled = TRUE
+
+/obj/item/reagent_containers/food/drinks/glass2/carafe/attackby(obj/item/I, mob/user)
+ if(!istype(I, /obj/item/reagent_containers/food/snacks/grown))
+ return ..()
+
+ if(reagents.total_volume)
+ to_chat(user, SPAN_WARNING("The pitcher must be empty!"))
+ return
+
+ var/obj/item/reagent_containers/food/snacks/grown/flower = I
+ if(flower.plantname != "grass")
+ return
+ qdel(flower)
+ var/obj/item/bouquet/B = new(get_turf(src))
+ qdel(src)
+ user.put_in_any_hand_if_possible(B)
+ to_chat(user, SPAN_INFO("You placed grass into the pitcher."))
diff --git a/proxima/code/game/objects/items/mgsbox.dm b/proxima/code/game/objects/items/mgsbox.dm
new file mode 100644
index 0000000000000..05df17eaf500b
--- /dev/null
+++ b/proxima/code/game/objects/items/mgsbox.dm
@@ -0,0 +1,189 @@
+//snake? snake! SNAKE?!!!!!!!!!!
+/obj/item/storage/mgsbox
+ name = "cardboard box"
+ icon = 'proxima/icons/obj/box.dmi'
+ icon_state = "box"
+ desc = "Just ordinary and absolutely unsuspicious box."
+ w_class = ITEM_SIZE_NO_CONTAINER
+ max_w_class = ITEM_SIZE_GARGANTUAN
+ max_storage_space = 40
+ allow_quick_empty = 1
+ allow_quick_gather = 1
+ randpixel = 2
+
+ var/open = 0
+ var/stealth_mode = 0
+ var/sprite = "box"
+ var/sprite_open = "box-open"
+ var/taping_level = 0
+ var/obj/effect/dummy/box/active_dummy = null
+ var/image/overlay
+
+/obj/item/storage/mgsbox/AltClick(mob/usr)
+ if(!(src in view(1, usr)))
+ return
+
+ if(stealth_mode == 1)
+ to_chat(usr, SPAN_WARNING("You can't do it while in box!"))
+ return
+
+ if(taping_level == 1)
+ to_chat(usr, SPAN_NOTICE("You begin to remove duct tape from the box..."))
+ if(do_after(usr, 6 SECONDS, src, DO_SHOW_PROGRESS | DO_PUBLIC_PROGRESS | DO_BAR_OVER_USER))
+ to_chat(usr, SPAN_NOTICE("You're done removing the duct tape, now you can open the box."))
+ taping_level = 0
+ src.overlays -= overlay
+ return
+ else if(taping_level == 2)
+ to_chat(usr, SPAN_WARNING("There so much duct tape - you even can't open the box! Maybe just break it?.."))
+ return
+
+ if(open == 0)
+ to_chat(usr, SPAN_NOTICE("You open the box."))
+ open = 1
+ icon_state = sprite_open
+ playsound(src, 'sound/effects/storage/box.ogg', 50, 1)
+ else
+ to_chat(usr, SPAN_NOTICE("You close the box."))
+ open = 0
+ icon_state = sprite
+
+/obj/item/storage/mgsbox/proc/is_open()
+ if(stealth_mode == 1)
+ to_chat(usr, SPAN_WARNING("You can't do it while in box!"))
+ return 0
+
+ if(open == 0)
+ to_chat(usr, SPAN_NOTICE("You need to open the box."))
+ return 0
+ else
+ return 1
+
+/obj/item/storage/mgsbox/proc/taping()
+ if(taping_level == 0)
+ to_chat(usr, SPAN_NOTICE("You begin to taping the box..."))
+ playsound(src, 'sound/effects/tape.ogg', 25)
+ if(do_after(usr, 5 SECONDS, src, DO_SHOW_PROGRESS | DO_PUBLIC_PROGRESS | DO_BAR_OVER_USER))
+ to_chat(usr, SPAN_NOTICE("You finish taping of the box. You can tape once more, but then it will be impossible to open this box."))
+ taping_level = 1
+ overlay = image(icon, icon_state = "taping_level1")
+ src.overlays += overlay
+ else if(taping_level == 1)
+ to_chat(usr, SPAN_NOTICE("You begin to taping the box..."))
+ playsound(src, 'sound/effects/tape.ogg', 25)
+ if(do_after(usr, 7 SECONDS, src, DO_SHOW_PROGRESS | DO_PUBLIC_PROGRESS | DO_BAR_OVER_USER))
+ to_chat(usr, SPAN_NOTICE("This box is now fully secured by duct tape."))
+ taping_level = 2
+ src.overlays -= overlay
+ overlay = image(icon, icon_state = "taping_level2")
+ src.overlays += overlay
+ else
+ to_chat(usr, SPAN_WARNING("You don't know where else possible to stick the duct tape here."))
+
+/obj/item/storage/mgsbox/attackby(obj/item/W, mob/user)
+ if((istype(W, /obj/item/tape_roll)) && (open == 0) && (stealth_mode == 0))
+ taping()
+ return
+
+ if((istype(W, /obj/item/crowbar)) || (istype(W, /obj/item/melee)) || (istype(W, /obj/item/material/twohanded)) || (istype(W, /obj/item/material/hatchet)) || (istype(W, /obj/item/material/sword)))
+ if(usr.a_intent == I_HURT)
+ playsound(src, 'sound/weapons/pierce.ogg', 75)
+ quick_empty()
+ visible_message(SPAN_WARNING("[usr] breaks the box with [W]!"))
+ new /obj/item/stack/material/cardboard(src.loc)
+ new /obj/item/stack/material/cardboard(src.loc)
+ qdel(src)
+ return
+
+ if(is_open())
+ . = ..() //When open work like ordinary storage. Same with commands below
+ else
+ return
+
+/obj/item/storage/mgsbox/MouseDrop(obj/over_object as obj)
+ if(is_open())
+ . = ..()
+ else
+ return
+
+/obj/item/storage/mgsbox/attack_hand(mob/user as mob)
+ if(is_open())
+ . = ..()
+ else
+ usr.put_in_active_hand(src)
+
+/obj/item/storage/mgsbox/quick_empty()
+ if(is_open())
+ . = ..()
+ else
+ return
+
+/obj/item/storage/mgsbox/toggle_gathering_mode()
+ if(is_open())
+ . = ..()
+ else
+ return
+
+/obj/item/storage/mgsbox/examine(mob/user, distance)
+ . = ..(user)
+ if(distance <= 2)
+ if(taping_level == 1)
+ to_chat(usr, SPAN_NOTICE("You notice that this box is secured with duct tape."))
+ else if(taping_level == 2)
+ to_chat(usr, SPAN_NOTICE("You notice that this box is taped in every possible place."))
+/*
+
+ Here's to you, Nicola and Bart
+ Rest Forever here in our hearts
+ The last and final moment is yours
+ That agony is your triumph
+*/
+
+/************
+* Other boxes
+*************/
+/obj/item/storage/mgsbox/lpa
+ name = "cardboard box"
+ icon = 'proxima/icons/obj/box.dmi'
+ icon_state = "boxlpa"
+ desc = "Not ordinary and prety suspicious box."
+
+ sprite = "boxlpa"
+ sprite_open = "boxlpa-open"
+
+/obj/item/storage/mgsbox/med
+ name = "cardboard box"
+ icon = 'proxima/icons/obj/box.dmi'
+ icon_state = "boxmed"
+ desc = "A blue cardboard box with medical symbols. Destruction of this box probably violate the Moon Convention."
+
+ sprite = "boxmed"
+ sprite_open = "boxmed-open"
+
+/obj/item/storage/mgsbox/clear
+ name = "cardboard box"
+ icon = 'proxima/icons/obj/box.dmi'
+ icon_state = "boxclear"
+ desc = "A cardboard box without any symbols. Probably hand-made."
+
+ sprite = "boxclear"
+ sprite_open = "boxclear-open"
+
+
+/****************
+* Boxes with loot
+****************/
+/obj/item/storage/mgsbox/med/loot_medicaments
+ startswith = list(/obj/random/firstaid, /obj/random/medical, /obj/random/medical)
+
+/obj/item/storage/mgsbox/loot_material
+ startswith = list(/obj/random/material, /obj/random/tool, /obj/random/maintenance, /obj/random/maintenance)
+
+/obj/item/storage/mgsbox/loot_engineering
+ startswith = list(/obj/random/toolbox, /obj/item/stack/cable_coil/random, /obj/item/stack/cable_coil/random, /obj/random/maintenance)
+
+/obj/item/storage/mgsbox/loot_accesory
+ startswith = list(/obj/random/clothing, /obj/random/accessory, /obj/random/accessory, /obj/random/masks, /obj/random/hat)
+
+/obj/item/storage/mgsbox/loot_meal
+ startswith = list(/obj/item/storage/mre/random, /obj/random/drinkbottle, /obj/random/snack, /obj/random/single/cola)
diff --git a/proxima/icons/obj/ammo.dmi b/proxima/icons/obj/ammo.dmi
new file mode 100644
index 0000000000000..e74b672d00d71
Binary files /dev/null and b/proxima/icons/obj/ammo.dmi differ
diff --git a/proxima/icons/obj/bouquet.dmi b/proxima/icons/obj/bouquet.dmi
new file mode 100644
index 0000000000000..110cf3c58c1eb
Binary files /dev/null and b/proxima/icons/obj/bouquet.dmi differ
diff --git a/proxima/icons/obj/box.dmi b/proxima/icons/obj/box.dmi
new file mode 100644
index 0000000000000..20c915ace7f04
Binary files /dev/null and b/proxima/icons/obj/box.dmi differ
diff --git a/proxima/icons/obj/cart.dmi b/proxima/icons/obj/cart.dmi
new file mode 100644
index 0000000000000..6b7b80483e988
Binary files /dev/null and b/proxima/icons/obj/cart.dmi differ
diff --git a/proxima/icons/obj/guns/carabine.dmi b/proxima/icons/obj/guns/carabine.dmi
new file mode 100644
index 0000000000000..6f760cffe925b
Binary files /dev/null and b/proxima/icons/obj/guns/carabine.dmi differ
diff --git a/sound/AI/sos.ogg b/sound/AI/sos.ogg
new file mode 100644
index 0000000000000..ded835c4784c6
Binary files /dev/null and b/sound/AI/sos.ogg differ
diff --git a/sound/ambience/infested_forest.ogg b/sound/ambience/infested_forest.ogg
new file mode 100644
index 0000000000000..4bd2321dedec7
Binary files /dev/null and b/sound/ambience/infested_forest.ogg differ
diff --git a/sound/effects/combatroll.ogg b/sound/effects/combatroll.ogg
new file mode 100644
index 0000000000000..744bcc6cf7ec8
Binary files /dev/null and b/sound/effects/combatroll.ogg differ
diff --git a/sound/effects/heartbeat_low.ogg b/sound/effects/heartbeat_low.ogg
new file mode 100644
index 0000000000000..ddac31ca51ea3
Binary files /dev/null and b/sound/effects/heartbeat_low.ogg differ
diff --git a/sound/effects/orbital_bombardment.ogg b/sound/effects/orbital_bombardment.ogg
new file mode 100644
index 0000000000000..d58e6c318fc0e
Binary files /dev/null and b/sound/effects/orbital_bombardment.ogg differ
diff --git a/sound/machines/sensors/alarm1.ogg b/sound/machines/sensors/alarm1.ogg
new file mode 100644
index 0000000000000..15e6cf5bb8fc1
Binary files /dev/null and b/sound/machines/sensors/alarm1.ogg differ
diff --git a/sound/machines/sensors/contact_identified.ogg b/sound/machines/sensors/contact_identified.ogg
new file mode 100644
index 0000000000000..531411c39ed14
Binary files /dev/null and b/sound/machines/sensors/contact_identified.ogg differ
diff --git a/sound/machines/sensors/contact_lost.ogg b/sound/machines/sensors/contact_lost.ogg
new file mode 100644
index 0000000000000..4a1b7753658a4
Binary files /dev/null and b/sound/machines/sensors/contact_lost.ogg differ
diff --git a/sound/machines/sensors/contact_regained.ogg b/sound/machines/sensors/contact_regained.ogg
new file mode 100644
index 0000000000000..9e35c81b31267
Binary files /dev/null and b/sound/machines/sensors/contact_regained.ogg differ
diff --git a/sound/machines/sensors/contactgeneric.ogg b/sound/machines/sensors/contactgeneric.ogg
new file mode 100644
index 0000000000000..ef4e04b982574
Binary files /dev/null and b/sound/machines/sensors/contactgeneric.ogg differ
diff --git a/sound/machines/sensors/dradis.ogg b/sound/machines/sensors/dradis.ogg
new file mode 100644
index 0000000000000..aa53495a6fd6d
Binary files /dev/null and b/sound/machines/sensors/dradis.ogg differ
diff --git a/sound/machines/sensors/newcontact.ogg b/sound/machines/sensors/newcontact.ogg
new file mode 100644
index 0000000000000..dd52c2d34dc91
Binary files /dev/null and b/sound/machines/sensors/newcontact.ogg differ
diff --git a/sound/machines/sensors/target_lock.ogg b/sound/machines/sensors/target_lock.ogg
new file mode 100644
index 0000000000000..c074147743a4c
Binary files /dev/null and b/sound/machines/sensors/target_lock.ogg differ
diff --git a/sound/simple_mob/abominable_infestation/alien_04.ogg b/sound/simple_mob/abominable_infestation/alien_04.ogg
new file mode 100644
index 0000000000000..f012ad7797933
Binary files /dev/null and b/sound/simple_mob/abominable_infestation/alien_04.ogg differ
diff --git a/sound/simple_mob/abominable_infestation/alien_05.ogg b/sound/simple_mob/abominable_infestation/alien_05.ogg
new file mode 100644
index 0000000000000..a762400655260
Binary files /dev/null and b/sound/simple_mob/abominable_infestation/alien_05.ogg differ
diff --git a/sound/simple_mob/abominable_infestation/alien_06.ogg b/sound/simple_mob/abominable_infestation/alien_06.ogg
new file mode 100644
index 0000000000000..512512bbf68fe
Binary files /dev/null and b/sound/simple_mob/abominable_infestation/alien_06.ogg differ
diff --git a/sound/simple_mob/abominable_infestation/alien_09.ogg b/sound/simple_mob/abominable_infestation/alien_09.ogg
new file mode 100644
index 0000000000000..765df11a70605
Binary files /dev/null and b/sound/simple_mob/abominable_infestation/alien_09.ogg differ
diff --git a/sound/simple_mob/abominable_infestation/assembler/ambient_1.ogg b/sound/simple_mob/abominable_infestation/assembler/ambient_1.ogg
new file mode 100644
index 0000000000000..25745c672af7c
Binary files /dev/null and b/sound/simple_mob/abominable_infestation/assembler/ambient_1.ogg differ
diff --git a/sound/simple_mob/abominable_infestation/broodling/ambient_1.ogg b/sound/simple_mob/abominable_infestation/broodling/ambient_1.ogg
new file mode 100644
index 0000000000000..e3662c4f30754
Binary files /dev/null and b/sound/simple_mob/abominable_infestation/broodling/ambient_1.ogg differ
diff --git a/sound/simple_mob/abominable_infestation/broodling/ambient_2.ogg b/sound/simple_mob/abominable_infestation/broodling/ambient_2.ogg
new file mode 100644
index 0000000000000..cfa36cc33c24c
Binary files /dev/null and b/sound/simple_mob/abominable_infestation/broodling/ambient_2.ogg differ
diff --git a/sound/simple_mob/abominable_infestation/broodling/death.ogg b/sound/simple_mob/abominable_infestation/broodling/death.ogg
new file mode 100644
index 0000000000000..d0184ffc1477f
Binary files /dev/null and b/sound/simple_mob/abominable_infestation/broodling/death.ogg differ
diff --git a/sound/simple_mob/abominable_infestation/bug_03.ogg b/sound/simple_mob/abominable_infestation/bug_03.ogg
new file mode 100644
index 0000000000000..1b09e54631743
Binary files /dev/null and b/sound/simple_mob/abominable_infestation/bug_03.ogg differ
diff --git a/sound/simple_mob/abominable_infestation/bug_05.ogg b/sound/simple_mob/abominable_infestation/bug_05.ogg
new file mode 100644
index 0000000000000..7188ba4083c7a
Binary files /dev/null and b/sound/simple_mob/abominable_infestation/bug_05.ogg differ
diff --git a/sound/simple_mob/abominable_infestation/bug_06.ogg b/sound/simple_mob/abominable_infestation/bug_06.ogg
new file mode 100644
index 0000000000000..a1fafd2cfc813
Binary files /dev/null and b/sound/simple_mob/abominable_infestation/bug_06.ogg differ
diff --git a/sound/simple_mob/abominable_infestation/bug_08.ogg b/sound/simple_mob/abominable_infestation/bug_08.ogg
new file mode 100644
index 0000000000000..5520b4a4327e1
Binary files /dev/null and b/sound/simple_mob/abominable_infestation/bug_08.ogg differ
diff --git a/sound/simple_mob/abominable_infestation/bug_09.ogg b/sound/simple_mob/abominable_infestation/bug_09.ogg
new file mode 100644
index 0000000000000..f4e2d95ad9cd4
Binary files /dev/null and b/sound/simple_mob/abominable_infestation/bug_09.ogg differ
diff --git a/sound/simple_mob/abominable_infestation/bug_10.ogg b/sound/simple_mob/abominable_infestation/bug_10.ogg
new file mode 100644
index 0000000000000..2b08cf872636d
Binary files /dev/null and b/sound/simple_mob/abominable_infestation/bug_10.ogg differ
diff --git a/sound/simple_mob/abominable_infestation/bug_11.ogg b/sound/simple_mob/abominable_infestation/bug_11.ogg
new file mode 100644
index 0000000000000..f46c6894dab41
Binary files /dev/null and b/sound/simple_mob/abominable_infestation/bug_11.ogg differ
diff --git a/sound/simple_mob/abominable_infestation/bug_12.ogg b/sound/simple_mob/abominable_infestation/bug_12.ogg
new file mode 100644
index 0000000000000..be89ae926c0a9
Binary files /dev/null and b/sound/simple_mob/abominable_infestation/bug_12.ogg differ
diff --git a/sound/simple_mob/abominable_infestation/bug_13.ogg b/sound/simple_mob/abominable_infestation/bug_13.ogg
new file mode 100644
index 0000000000000..6a9f56dddb654
Binary files /dev/null and b/sound/simple_mob/abominable_infestation/bug_13.ogg differ
diff --git a/sound/simple_mob/abominable_infestation/eviscerator/aggro_1.ogg b/sound/simple_mob/abominable_infestation/eviscerator/aggro_1.ogg
new file mode 100644
index 0000000000000..87a5c6535b5d7
Binary files /dev/null and b/sound/simple_mob/abominable_infestation/eviscerator/aggro_1.ogg differ
diff --git a/sound/simple_mob/abominable_infestation/eviscerator/aggro_2.ogg b/sound/simple_mob/abominable_infestation/eviscerator/aggro_2.ogg
new file mode 100644
index 0000000000000..291895ef20a95
Binary files /dev/null and b/sound/simple_mob/abominable_infestation/eviscerator/aggro_2.ogg differ
diff --git a/sound/simple_mob/abominable_infestation/eviscerator/aggro_3.ogg b/sound/simple_mob/abominable_infestation/eviscerator/aggro_3.ogg
new file mode 100644
index 0000000000000..3d1945dd02a70
Binary files /dev/null and b/sound/simple_mob/abominable_infestation/eviscerator/aggro_3.ogg differ
diff --git a/sound/simple_mob/abominable_infestation/eviscerator/attack.ogg b/sound/simple_mob/abominable_infestation/eviscerator/attack.ogg
new file mode 100644
index 0000000000000..ceddc7b1e3874
Binary files /dev/null and b/sound/simple_mob/abominable_infestation/eviscerator/attack.ogg differ
diff --git a/sound/simple_mob/abominable_infestation/eviscerator/death_1.ogg b/sound/simple_mob/abominable_infestation/eviscerator/death_1.ogg
new file mode 100644
index 0000000000000..4ca0361af09f3
Binary files /dev/null and b/sound/simple_mob/abominable_infestation/eviscerator/death_1.ogg differ
diff --git a/sound/simple_mob/abominable_infestation/eviscerator/death_2.ogg b/sound/simple_mob/abominable_infestation/eviscerator/death_2.ogg
new file mode 100644
index 0000000000000..6e213a349e950
Binary files /dev/null and b/sound/simple_mob/abominable_infestation/eviscerator/death_2.ogg differ
diff --git a/sound/simple_mob/abominable_infestation/eviscerator/step.ogg b/sound/simple_mob/abominable_infestation/eviscerator/step.ogg
new file mode 100644
index 0000000000000..6190eb6d108dd
Binary files /dev/null and b/sound/simple_mob/abominable_infestation/eviscerator/step.ogg differ
diff --git a/sound/simple_mob/abominable_infestation/floatfly/death.ogg b/sound/simple_mob/abominable_infestation/floatfly/death.ogg
new file mode 100644
index 0000000000000..2e981a561fd8f
Binary files /dev/null and b/sound/simple_mob/abominable_infestation/floatfly/death.ogg differ
diff --git a/sound/simple_mob/abominable_infestation/floatfly/fly.ogg b/sound/simple_mob/abominable_infestation/floatfly/fly.ogg
new file mode 100644
index 0000000000000..733b8190e33a1
Binary files /dev/null and b/sound/simple_mob/abominable_infestation/floatfly/fly.ogg differ
diff --git a/sound/simple_mob/abominable_infestation/heart_death.ogg b/sound/simple_mob/abominable_infestation/heart_death.ogg
new file mode 100644
index 0000000000000..24e2a3b4743f1
Binary files /dev/null and b/sound/simple_mob/abominable_infestation/heart_death.ogg differ
diff --git a/sound/simple_mob/abominable_infestation/larva/ambient_1.ogg b/sound/simple_mob/abominable_infestation/larva/ambient_1.ogg
new file mode 100644
index 0000000000000..3e7a4dbb8ed11
Binary files /dev/null and b/sound/simple_mob/abominable_infestation/larva/ambient_1.ogg differ
diff --git a/sound/simple_mob/abominable_infestation/larva/ambient_2.ogg b/sound/simple_mob/abominable_infestation/larva/ambient_2.ogg
new file mode 100644
index 0000000000000..41d87e12de265
Binary files /dev/null and b/sound/simple_mob/abominable_infestation/larva/ambient_2.ogg differ
diff --git a/sound/simple_mob/abominable_infestation/larva/ambient_3.ogg b/sound/simple_mob/abominable_infestation/larva/ambient_3.ogg
new file mode 100644
index 0000000000000..299fc01fc49ac
Binary files /dev/null and b/sound/simple_mob/abominable_infestation/larva/ambient_3.ogg differ
diff --git a/sound/simple_mob/abominable_infestation/larva/death_1.ogg b/sound/simple_mob/abominable_infestation/larva/death_1.ogg
new file mode 100644
index 0000000000000..738fd8f7c9ef2
Binary files /dev/null and b/sound/simple_mob/abominable_infestation/larva/death_1.ogg differ
diff --git a/sound/simple_mob/abominable_infestation/larva/death_2.ogg b/sound/simple_mob/abominable_infestation/larva/death_2.ogg
new file mode 100644
index 0000000000000..afa9580d3eea6
Binary files /dev/null and b/sound/simple_mob/abominable_infestation/larva/death_2.ogg differ
diff --git a/sound/simple_mob/abominable_infestation/larva/implant.ogg b/sound/simple_mob/abominable_infestation/larva/implant.ogg
new file mode 100644
index 0000000000000..0c7ba431bfd0e
Binary files /dev/null and b/sound/simple_mob/abominable_infestation/larva/implant.ogg differ
diff --git a/sound/simple_mob/abominable_infestation/larva/spawn.ogg b/sound/simple_mob/abominable_infestation/larva/spawn.ogg
new file mode 100644
index 0000000000000..820e500b33ad6
Binary files /dev/null and b/sound/simple_mob/abominable_infestation/larva/spawn.ogg differ
diff --git a/sound/simple_mob/abominable_infestation/rhino/death.ogg b/sound/simple_mob/abominable_infestation/rhino/death.ogg
new file mode 100644
index 0000000000000..3222a77c21e3c
Binary files /dev/null and b/sound/simple_mob/abominable_infestation/rhino/death.ogg differ
diff --git a/sound/simple_mob/abominable_infestation/rhino/roar.ogg b/sound/simple_mob/abominable_infestation/rhino/roar.ogg
new file mode 100644
index 0000000000000..39845a8867b08
Binary files /dev/null and b/sound/simple_mob/abominable_infestation/rhino/roar.ogg differ
diff --git a/sound/simple_mob/abominable_infestation/rhino/step.ogg b/sound/simple_mob/abominable_infestation/rhino/step.ogg
new file mode 100644
index 0000000000000..dd68658f8aef9
Binary files /dev/null and b/sound/simple_mob/abominable_infestation/rhino/step.ogg differ
diff --git a/sound/simple_mob/abominable_infestation/rhino/step_angry.ogg b/sound/simple_mob/abominable_infestation/rhino/step_angry.ogg
new file mode 100644
index 0000000000000..c5ff7040a26a0
Binary files /dev/null and b/sound/simple_mob/abominable_infestation/rhino/step_angry.ogg differ
diff --git a/sound/simple_mob/abominable_infestation/spitter/ambient_1.ogg b/sound/simple_mob/abominable_infestation/spitter/ambient_1.ogg
new file mode 100644
index 0000000000000..41d87e12de265
Binary files /dev/null and b/sound/simple_mob/abominable_infestation/spitter/ambient_1.ogg differ
diff --git a/sound/simple_mob/abominable_infestation/spitter/ambient_2.ogg b/sound/simple_mob/abominable_infestation/spitter/ambient_2.ogg
new file mode 100644
index 0000000000000..299fc01fc49ac
Binary files /dev/null and b/sound/simple_mob/abominable_infestation/spitter/ambient_2.ogg differ
diff --git a/sound/simple_mob/abominable_infestation/spitter/attack.ogg b/sound/simple_mob/abominable_infestation/spitter/attack.ogg
new file mode 100644
index 0000000000000..3b6ff6f1b0bd1
Binary files /dev/null and b/sound/simple_mob/abominable_infestation/spitter/attack.ogg differ
diff --git a/sound/simple_mob/abominable_infestation/spitter/death.ogg b/sound/simple_mob/abominable_infestation/spitter/death.ogg
new file mode 100644
index 0000000000000..8dcbeb4e55ce0
Binary files /dev/null and b/sound/simple_mob/abominable_infestation/spitter/death.ogg differ
diff --git a/sound/simple_mob/abominable_infestation/stand_down.ogg b/sound/simple_mob/abominable_infestation/stand_down.ogg
new file mode 100644
index 0000000000000..63db2c0fb7eb0
Binary files /dev/null and b/sound/simple_mob/abominable_infestation/stand_down.ogg differ
diff --git a/sound/simple_mob/abominable_infestation/threat.ogg b/sound/simple_mob/abominable_infestation/threat.ogg
new file mode 100644
index 0000000000000..79e720f3e1883
Binary files /dev/null and b/sound/simple_mob/abominable_infestation/threat.ogg differ
diff --git a/sound/voice/abomination1.ogg b/sound/voice/abomination1.ogg
new file mode 100644
index 0000000000000..f012ad7797933
Binary files /dev/null and b/sound/voice/abomination1.ogg differ
diff --git a/sound/voice/abomination2.ogg b/sound/voice/abomination2.ogg
new file mode 100644
index 0000000000000..a762400655260
Binary files /dev/null and b/sound/voice/abomination2.ogg differ
diff --git a/sound/voice/abomination3.ogg b/sound/voice/abomination3.ogg
new file mode 100644
index 0000000000000..512512bbf68fe
Binary files /dev/null and b/sound/voice/abomination3.ogg differ
diff --git a/sound/voice/abomination4.ogg b/sound/voice/abomination4.ogg
new file mode 100644
index 0000000000000..765df11a70605
Binary files /dev/null and b/sound/voice/abomination4.ogg differ
diff --git a/sound/voice/abomination5.ogg b/sound/voice/abomination5.ogg
new file mode 100644
index 0000000000000..a1fafd2cfc813
Binary files /dev/null and b/sound/voice/abomination5.ogg differ
diff --git a/sound/voice/abomination6.ogg b/sound/voice/abomination6.ogg
new file mode 100644
index 0000000000000..5520b4a4327e1
Binary files /dev/null and b/sound/voice/abomination6.ogg differ
diff --git a/sound/voice/abomination7.ogg b/sound/voice/abomination7.ogg
new file mode 100644
index 0000000000000..7188ba4083c7a
Binary files /dev/null and b/sound/voice/abomination7.ogg differ
diff --git a/sound/voice/abomination8.ogg b/sound/voice/abomination8.ogg
new file mode 100644
index 0000000000000..f4e2d95ad9cd4
Binary files /dev/null and b/sound/voice/abomination8.ogg differ
diff --git a/sound/weapons/alien_bite1.ogg b/sound/weapons/alien_bite1.ogg
new file mode 100644
index 0000000000000..3e90b90e3ba3b
Binary files /dev/null and b/sound/weapons/alien_bite1.ogg differ
diff --git a/sound/weapons/alien_bite2.ogg b/sound/weapons/alien_bite2.ogg
new file mode 100644
index 0000000000000..0f0361a7c678a
Binary files /dev/null and b/sound/weapons/alien_bite2.ogg differ
diff --git a/sound/weapons/alien_claw_block.ogg b/sound/weapons/alien_claw_block.ogg
new file mode 100644
index 0000000000000..a7c16ef022afb
Binary files /dev/null and b/sound/weapons/alien_claw_block.ogg differ
diff --git a/sound/weapons/alien_claw_flesh1.ogg b/sound/weapons/alien_claw_flesh1.ogg
new file mode 100644
index 0000000000000..7853503c8f1c8
Binary files /dev/null and b/sound/weapons/alien_claw_flesh1.ogg differ
diff --git a/sound/weapons/alien_claw_flesh2.ogg b/sound/weapons/alien_claw_flesh2.ogg
new file mode 100644
index 0000000000000..2a1ba20dc471f
Binary files /dev/null and b/sound/weapons/alien_claw_flesh2.ogg differ
diff --git a/sound/weapons/alien_claw_flesh3.ogg b/sound/weapons/alien_claw_flesh3.ogg
new file mode 100644
index 0000000000000..fe866ca7478bd
Binary files /dev/null and b/sound/weapons/alien_claw_flesh3.ogg differ
diff --git a/sound/weapons/alien_claw_metal1.ogg b/sound/weapons/alien_claw_metal1.ogg
new file mode 100644
index 0000000000000..0316d97376d6e
Binary files /dev/null and b/sound/weapons/alien_claw_metal1.ogg differ
diff --git a/sound/weapons/alien_claw_metal2.ogg b/sound/weapons/alien_claw_metal2.ogg
new file mode 100644
index 0000000000000..19d7027c65e91
Binary files /dev/null and b/sound/weapons/alien_claw_metal2.ogg differ
diff --git a/sound/weapons/alien_claw_metal3.ogg b/sound/weapons/alien_claw_metal3.ogg
new file mode 100644
index 0000000000000..47990ed0eb265
Binary files /dev/null and b/sound/weapons/alien_claw_metal3.ogg differ
diff --git a/sound/weapons/alien_claw_swipe.ogg b/sound/weapons/alien_claw_swipe.ogg
new file mode 100644
index 0000000000000..d37977f66e1a0
Binary files /dev/null and b/sound/weapons/alien_claw_swipe.ogg differ
diff --git a/sound/weapons/alien_knockdown.ogg b/sound/weapons/alien_knockdown.ogg
new file mode 100644
index 0000000000000..073f7e97f2a8e
Binary files /dev/null and b/sound/weapons/alien_knockdown.ogg differ
diff --git a/sound/weapons/alien_spit.ogg b/sound/weapons/alien_spit.ogg
new file mode 100644
index 0000000000000..243d1aa1744c3
Binary files /dev/null and b/sound/weapons/alien_spit.ogg differ
diff --git a/sound/weapons/alien_tail_attack.ogg b/sound/weapons/alien_tail_attack.ogg
new file mode 100644
index 0000000000000..1c67cbb3f2e26
Binary files /dev/null and b/sound/weapons/alien_tail_attack.ogg differ