diff --git a/bubber_archive/blueshift/Blueshift.dmm b/bubber_archive/blueshift/Blueshift.dmm
index 3dd6caee06c..44c78a2faaa 100644
--- a/bubber_archive/blueshift/Blueshift.dmm
+++ b/bubber_archive/blueshift/Blueshift.dmm
@@ -41179,7 +41179,7 @@
pixel_y = 2
},
/obj/item/organ/external/tail/lizard,
-/obj/item/organ/internal/ears/cat,
+/obj/item/organ/ears/cat,
/obj/effect/decal/cleanable/dirt{
icon_state = "dirt-flat-1"
},
@@ -85176,7 +85176,7 @@
name = "organ storage"
},
/obj/item/organ/external/tail/cat,
-/obj/item/organ/internal/ears/cat,
+/obj/item/organ/ears/cat,
/obj/item/organ/internal/heart,
/obj/effect/decal/cleanable/dirt{
icon_state = "dirt-flat-1"
diff --git a/code/__DEFINES/atom_hud.dm b/code/__DEFINES/atom_hud.dm
index 1682fa4f953..91862241d69 100644
--- a/code/__DEFINES/atom_hud.dm
+++ b/code/__DEFINES/atom_hud.dm
@@ -56,6 +56,10 @@
/// If they have the DNR trait
#define DNR_HUD "27"
// SKYRAT EDIT ADDITION END
+// BUBBER EDIT ADDITION START
+#define NANITE_HUD "28"
+#define DIAG_NANITE_FULL_HUD "29"
+// BUBBER EDIT ADDITION END
//by default everything in the hud_list of an atom is an image
//a value in hud_list with one of these will change that behavior
diff --git a/code/__DEFINES/logging.dm b/code/__DEFINES/logging.dm
index 93245670782..e3d71a2d9e1 100644
--- a/code/__DEFINES/logging.dm
+++ b/code/__DEFINES/logging.dm
@@ -26,6 +26,8 @@
#define INVESTIGATE_RECORDS "records"
#define INVESTIGATE_RESEARCH "research"
#define INVESTIGATE_WIRES "wires"
+#define INVESTIGATE_NANITES "nanites" // BUBBER EDIT ADDITION
+
// Logging types for log_message()
#define LOG_ATTACK (1 << 0)
diff --git a/code/__DEFINES/machines.dm b/code/__DEFINES/machines.dm
index e5d7ce45ac5..dc7dfac3fd0 100644
--- a/code/__DEFINES/machines.dm
+++ b/code/__DEFINES/machines.dm
@@ -64,6 +64,7 @@
#define BIOGENERATOR (1<<4) //Uses biomass
#define LIMBGROWER (1<<5) //Uses synthetic flesh
#define SMELTER (1<<6) //uses various minerals
+#define NANITE_COMPILER (1<<7) //Prints nanite disks BUBBER EDIT ADDITION - NANITES
/// Protolathes for offstation roles. More limited tech tree.
#define AWAY_LATHE (1<<8)
/// Imprinters for offstation roles. More limited tech tree.
diff --git a/code/__DEFINES/research.dm b/code/__DEFINES/research.dm
index cf35d553ec4..feedbd61a88 100644
--- a/code/__DEFINES/research.dm
+++ b/code/__DEFINES/research.dm
@@ -3,6 +3,7 @@
//! Techweb names for new point types. Can be used to define specific point values for specific types of research (science, security, engineering, etc.)
#define TECHWEB_POINT_TYPE_GENERIC "General Research"
+#define TECHWEB_POINT_TYPE_NANITE "Nanite Research" // BUBBER EDIT ADDITION - NANITES
//! Amount of points required to unlock nodes of corresponding tiers
#define TECHWEB_TIER_1_POINTS 40
diff --git a/code/__DEFINES/~~bubber_defines/nanites.dm b/code/__DEFINES/~~bubber_defines/nanites.dm
new file mode 100644
index 00000000000..f99ea731b4c
--- /dev/null
+++ b/code/__DEFINES/~~bubber_defines/nanites.dm
@@ -0,0 +1,71 @@
+#define CAN_HAVE_NANITES(host_mob) (host_mob.mob_biotypes & (MOB_ORGANIC|MOB_UNDEAD|MOB_ROBOTIC) && !issilicon(host_mob))
+
+#define NANITE_SYNC_DELAY 300
+
+#define NANITE_SHOCK_IMMUNE 1
+#define NANITE_EMP_IMMUNE 2
+
+#define NANITE_PROGRAM_LIMIT 20
+
+#define NANITE_BASE_RESEARCH_PROGRESS 25
+
+#define NANITE_CLOUD_TOGGLE 1
+#define NANITE_CLOUD_DISABLE 2
+#define NANITE_CLOUD_ENABLE 3
+
+//Nanite excess thresholds
+#define NANITE_EXCESS_MINOR 25
+#define NANITE_EXCESS_VOMIT 100
+#define NANITE_EXCESS_BURST 350
+
+#define NANITE_BIO_REGENERATION 0.5
+#define NANITE_ADV_BIO_REGENERATION NANITE_BIO_REGENERATION * 4
+#define NANITE_ROBO_REGENERATION NANITE_BIO_REGENERATION * 2
+
+#define NANITE_TOX_REGENERATION 0.5
+#define NANITE_ADV_TOX_REGENERATION NANITE_TOX_REGENERATION
+
+#define NANITE_BRAIN_REGENERATION 0.2
+#define NANITE_ADV_BRAIN_REGENERATION NANITE_BRAIN_REGENERATION * 2
+
+#define HARMFUL_PROGRAM_DAMAGE_SCALE 1.25
+#define NANITE_OXYLOSS 5
+
+#define NANITE_PROTOCOL_KICKSTART_REPLICATION 3.5
+#define NANITE_PROTOCOL_FACTORY_MAX_REPLICATION 2
+
+///Nanite Protocol types
+#define NANITE_PROTOCOL_REPLICATION "nanite_replication"
+#define NANITE_PROTOCOL_STORAGE "nanite_storage"
+
+///Nanite extra settings types: used to help uis know what type an extra setting is
+#define NESTYPE_TEXT "text"
+#define NESTYPE_NUMBER "number"
+#define NESTYPE_TYPE "type"
+#define NESTYPE_BOOLEAN "boolean"
+
+///Nanite Extra Settings - Note that these will also be the names displayed in the UI
+#define NES_SENT_CODE "Sent Code"
+#define NES_DELAY "Delay"
+#define NES_MODE "Mode"
+#define NES_COMM_CODE "Comm Code"
+#define NES_RELAY_CHANNEL "Relay Channel"
+#define NES_HEALTH_PERCENT "Health Percent"
+#define NES_DIRECTION "Direction"
+#define NES_NANITE_PERCENT "Nanite Percent"
+#define NES_DAMAGE_TYPE "Damage Type"
+#define NES_DAMAGE "Damage"
+#define NES_SENTENCE "Sentence"
+#define NES_MESSAGE "Message"
+#define NES_DIRECTIVE "Directive"
+#define NES_INCLUSIVE_MODE "Inclusive Mode"
+#define NES_RACE "Race"
+#define NES_HALLUCINATION_TYPE "Hallucination Type"
+#define NES_HALLUCINATION_DETAIL "Hallucination Detail"
+#define NES_MOOD_MESSAGE "Mood Message"
+#define NES_PROGRAM_OVERWRITE "Program Overwrite"
+#define NES_CLOUD_OVERWRITE "Cloud Overwrite"
+#define NES_SCAN_TYPE "Scan Type"
+#define NES_BUTTON_NAME "Button Name"
+#define NES_ICON "Icon"
+#define NANITE_HARMONIC_ID "Nano Harmonic ID"
diff --git a/code/__DEFINES/~~bubber_defines/signals.dm b/code/__DEFINES/~~bubber_defines/signals.dm
index 49876f8d3fc..cc1c21adf6e 100644
--- a/code/__DEFINES/~~bubber_defines/signals.dm
+++ b/code/__DEFINES/~~bubber_defines/signals.dm
@@ -8,5 +8,7 @@
#define COMSIG_ORGAN_BODYPART_REMOVED "organ_bodypart_removed"
/// From /datum/controller/subsystem/economy/proc/issue_paydays()
#define COMSIG_ON_BANK_ACCOUNT_PAYOUT "bank_account_payout"
+/// identical to COMSIG_MOB_APPLY_DAMAGE, but always runs, even if there is no damage
+#define COMSIG_MOB_ALWAYS_APPLY_DAMAGE "mob_always_apply_damage"
/// From modular_zubbers/code/modules/disease/disease_transmission.dm
#define COMSIG_DISEASE_COUNT_UPDATE "disease_count_update"
diff --git a/code/__DEFINES/~~bubber_defines/traits/nanites.dm b/code/__DEFINES/~~bubber_defines/traits/nanites.dm
new file mode 100644
index 00000000000..ea1b3b7ff12
--- /dev/null
+++ b/code/__DEFINES/~~bubber_defines/traits/nanites.dm
@@ -0,0 +1,40 @@
+///() returns TRUE if nanites are found
+#define COMSIG_HAS_NANITES "has_nanites"
+///() returns TRUE if nanites have stealth
+#define COMSIG_NANITE_IS_STEALTHY "nanite_is_stealthy"
+///() deletes the nanite component
+#define COMSIG_NANITE_DELETE "nanite_delete"
+///(list/nanite_programs) - makes the input list a copy the nanites' program list
+#define COMSIG_NANITE_GET_PROGRAMS "nanite_get_programs"
+///(amount) Returns nanite amount
+#define COMSIG_NANITE_GET_VOLUME "nanite_get_volume"
+///(amount) Sets current nanite volume to the given amount
+#define COMSIG_NANITE_SET_VOLUME "nanite_set_volume"
+///(amount) Adjusts nanite volume by the given amount
+#define COMSIG_NANITE_ADJUST_VOLUME "nanite_adjust"
+///(amount) Sets maximum nanite volume to the given amount
+#define COMSIG_NANITE_SET_MAX_VOLUME "nanite_set_max_volume"
+///(amount(0-100)) Sets cloud ID to the given amount
+#define COMSIG_NANITE_SET_CLOUD "nanite_set_cloud"
+///(method) Modify cloud sync status. Method can be toggle, enable or disable
+#define COMSIG_NANITE_SET_CLOUD_SYNC "nanite_set_cloud_sync"
+///(amount) Sets safety threshold to the given amount
+#define COMSIG_NANITE_SET_SAFETY "nanite_set_safety"
+///(amount) Sets regeneration rate to the given amount
+#define COMSIG_NANITE_SET_REGEN "nanite_set_regen"
+///(code(1-9999)) Called when sending a nanite signal to a mob.
+#define COMSIG_NANITE_SIGNAL "nanite_signal"
+///(comm_code(1-9999), comm_message) Called when sending a nanite comm signal to a mob.
+#define COMSIG_NANITE_COMM_SIGNAL "nanite_comm_signal"
+///(mob/user, full_scan) - sends to chat a scan of the nanites to the user, returns TRUE if nanites are detected
+#define COMSIG_NANITE_SCAN "nanite_scan"
+///(list/data, scan_level) - adds nanite data to the given data list - made for ui_data procs
+#define COMSIG_NANITE_UI_DATA "nanite_ui_data"
+///(datum/nanite_program/new_program, datum/nanite_program/source_program) Called when adding a program to a nanite component
+#define COMSIG_NANITE_ADD_PROGRAM "nanite_add_program"
+ ///Installation successful
+ #define COMPONENT_PROGRAM_INSTALLED (1<<0)
+ ///Installation failed, but there are still nanites
+ #define COMPONENT_PROGRAM_NOT_INSTALLED (1<<1)
+///(datum/component/nanites, full_overwrite, copy_activation) Called to sync the target's nanites to a given nanite component
+#define COMSIG_NANITE_SYNC "nanite_sync"
diff --git a/code/__DEFINES/~~bubber_defines/traits/sources.dm b/code/__DEFINES/~~bubber_defines/traits/sources.dm
index fb073441fc2..6cc1f854a18 100644
--- a/code/__DEFINES/~~bubber_defines/traits/sources.dm
+++ b/code/__DEFINES/~~bubber_defines/traits/sources.dm
@@ -1,2 +1,4 @@
// Loadout trait
#define TRAIT_SOURCE_LOADOUT "loadout"
+
+#define NANITES_TRAIT "nanites"
diff --git a/code/__DEFINES/~~bubber_defines/vomit.dm b/code/__DEFINES/~~bubber_defines/vomit.dm
new file mode 100644
index 00000000000..025baa04897
--- /dev/null
+++ b/code/__DEFINES/~~bubber_defines/vomit.dm
@@ -0,0 +1,2 @@
+
+#define VOMIT_CATEGORY_NANITE MOB_VOMIT_MESSAGE
diff --git a/code/_globalvars/lists/mobs.dm b/code/_globalvars/lists/mobs.dm
index 73c75fec3ba..d88d08302ee 100644
--- a/code/_globalvars/lists/mobs.dm
+++ b/code/_globalvars/lists/mobs.dm
@@ -60,6 +60,7 @@ GLOBAL_LIST_EMPTY(spidermobs) //all sentient spider mobs
GLOBAL_LIST_EMPTY(bots_list)
GLOBAL_LIST_EMPTY(camera_eyes)
GLOBAL_LIST_EMPTY(suit_sensors_list) //all people with suit sensors on
+GLOBAL_LIST_EMPTY(nanite_sensors_list) //app people with nanite monitoring program // BUBBER EDIT ADDITION
/// All alive mobs with clients.
GLOBAL_LIST_EMPTY(alive_player_list)
diff --git a/code/controllers/subsystem/research.dm b/code/controllers/subsystem/research.dm
index 937c2e3b1d4..2beec1f1641 100644
--- a/code/controllers/subsystem/research.dm
+++ b/code/controllers/subsystem/research.dm
@@ -40,7 +40,7 @@ SUBSYSTEM_DEF(research)
)
var/list/errored_datums = list()
///Associated list of all point types that techwebs will have and their respective 'abbreviated' name.
- var/list/point_types = list(TECHWEB_POINT_TYPE_GENERIC = "Gen. Res.")
+ var/list/point_types = list(TECHWEB_POINT_TYPE_GENERIC = "Gen. Res.", TECHWEB_POINT_TYPE_NANITE = "Nanite Res.") // BUBBER EDIT - NANITES
//----------------------------------------------
var/list/single_server_income = list(
TECHWEB_POINT_TYPE_GENERIC = TECHWEB_SINGLE_SERVER_INCOME,
diff --git a/code/game/data_huds.dm b/code/game/data_huds.dm
index 55bafe9d152..34e47b5d654 100644
--- a/code/game/data_huds.dm
+++ b/code/game/data_huds.dm
@@ -18,7 +18,7 @@
/datum/atom_hud/data
/datum/atom_hud/data/human/medical
- hud_icons = list(STATUS_HUD, HEALTH_HUD, DNR_HUD) // SKYRAT EDIT ADDITION - DNR_HUD
+ hud_icons = list(STATUS_HUD, HEALTH_HUD, DNR_HUD, NANITE_HUD) // BUBBER EDIT ADDITION - DNR-HUD / NANITE_HUD
/datum/atom_hud/data/human/medical/basic
@@ -51,13 +51,13 @@
hud_icons = list(ID_HUD)
/datum/atom_hud/data/human/security/advanced
- hud_icons = list(ID_HUD, IMPSEC_FIRST_HUD, IMPLOYAL_HUD, IMPSEC_SECOND_HUD, WANTED_HUD, PERMIT_HUD, DNR_HUD) //SKYRAT EDIT ADDITION - PERMIT_HUD, DNR_HUD
+ hud_icons = list(ID_HUD, IMPSEC_FIRST_HUD, IMPLOYAL_HUD, IMPSEC_SECOND_HUD, WANTED_HUD, PERMIT_HUD, DNR_HUD, NANITE_HUD) // BUBBER EDIT ADDITION - DNR-HUD / NANITE_HUD
/datum/atom_hud/data/human/fan_hud
hud_icons = list(FAN_HUD)
/datum/atom_hud/data/diagnostic
- hud_icons = list(DIAG_HUD, DIAG_STAT_HUD, DIAG_BATT_HUD, DIAG_MECH_HUD, DIAG_BOT_HUD, DIAG_TRACK_HUD, DIAG_CAMERA_HUD, DIAG_AIRLOCK_HUD, DIAG_LAUNCHPAD_HUD)
+ hud_icons = list(DIAG_HUD, DIAG_STAT_HUD, DIAG_BATT_HUD, DIAG_MECH_HUD, DIAG_BOT_HUD, DIAG_TRACK_HUD, DIAG_CAMERA_HUD, DIAG_AIRLOCK_HUD, DIAG_LAUNCHPAD_HUD, DIAG_NANITE_FULL_HUD) // BUBBER EDIT ADDITION - NANITE_HUD
/datum/atom_hud/data/bot_path
hud_icons = list(DIAG_PATH_HUD)
diff --git a/code/game/machinery/computer/crew.dm b/code/game/machinery/computer/crew.dm
index c568c5a052a..46f6b407967 100644
--- a/code/game/machinery/computer/crew.dm
+++ b/code/game/machinery/computer/crew.dm
@@ -208,9 +208,9 @@ GLOBAL_DATUM_INIT(crewmonitor, /datum/crewmonitor, new)
return data_by_z["[z]"]
var/list/results = list()
- for(var/tracked_mob in GLOB.suit_sensors_list)
+ for(var/tracked_mob in GLOB.suit_sensors_list | GLOB.nanite_sensors_list) // BUBBER CHANGE - NANITES
if(!tracked_mob)
- stack_trace("Null entry in suit sensors list.")
+ stack_trace("Null entry in suit sensors or nanite sensors list.") // BUBBER CHANGE - NANITES
continue
var/mob/living/tracked_living_mob = tracked_mob
@@ -227,25 +227,33 @@ GLOBAL_DATUM_INIT(crewmonitor, /datum/crewmonitor, new)
if(pos.z != z && (!is_station_level(pos.z) || !is_station_level(z)) && !HAS_TRAIT(tracked_living_mob, TRAIT_MULTIZ_SUIT_SENSORS))
continue
+ var/sensor_mode // BUBBER ADDITION - NANITES
var/mob/living/carbon/human/tracked_human = tracked_living_mob
- // Check their humanity.
+ // Set sensor level based on whether we're in the nanites list or the suit sensor list.
+ if(tracked_living_mob in GLOB.nanite_sensors_list) // BUBBER CHANGE - NANITES
+ sensor_mode = SENSOR_COORDS
+
if(!ishuman(tracked_human))
stack_trace("Non-human mob is in suit_sensors_list: [tracked_living_mob] ([tracked_living_mob.type])")
continue
+ // BUBBER CHANGE START - NANITES
// Check they have a uniform
var/obj/item/clothing/under/uniform = tracked_human.w_uniform
- if (!istype(uniform))
+ if (!sensor_mode && istype(uniform))
+ // Check if their uniform is in a compatible mode.
+ if((uniform.has_sensor == NO_SENSORS) || !uniform.sensor_mode)
+ stack_trace("Human without active suit sensors is in suit_sensors_list: [tracked_human] ([tracked_human.type]) ([uniform.type])")
+ continue
+
+ sensor_mode = uniform.sensor_mode
+ // BUBBER CHANGE END - NANITES
+ else
stack_trace("Human without a suit sensors compatible uniform is in suit_sensors_list: [tracked_human] ([tracked_human.type]) ([uniform?.type])")
continue
- // Check if their uniform is in a compatible mode.
- if((uniform.has_sensor == NO_SENSORS) || !uniform.sensor_mode)
- stack_trace("Human without active suit sensors is in suit_sensors_list: [tracked_human] ([tracked_human.type]) ([uniform.type])")
- continue
- var/sensor_mode = uniform.sensor_mode
// The entry for this human
var/list/entry = list(
@@ -262,7 +270,7 @@ GLOBAL_DATUM_INIT(crewmonitor, /datum/crewmonitor, new)
var/trim_assignment = id_card.get_trim_assignment()
if (jobs[trim_assignment] != null)
entry["ijob"] = jobs[trim_assignment]
-
+
// SKYRAT EDIT BEGIN: Checking for robotic race
if (issynthetic(tracked_human))
entry["is_robot"] = TRUE
@@ -285,10 +293,9 @@ GLOBAL_DATUM_INIT(crewmonitor, /datum/crewmonitor, new)
// If sensors are above living tracking, set DNR state
if (sensor_mode >= SENSOR_LIVING)
entry["is_dnr"] = tracked_human.get_dnr()
- // BUBBERSTATION EDIT END
// Broken sensors show garbage data
- if (uniform.has_sensor == BROKEN_SENSORS)
+ if (uniform?.has_sensor == BROKEN_SENSORS)
entry["life_status"] = rand(0,1)
entry["area"] = pick_list (ION_FILE, "ionarea")
entry["oxydam"] = rand(0,175)
diff --git a/code/game/objects/items/devices/scanners/health_analyzer.dm b/code/game/objects/items/devices/scanners/health_analyzer.dm
index 69a58223899..a3eea0e3523 100644
--- a/code/game/objects/items/devices/scanners/health_analyzer.dm
+++ b/code/game/objects/items/devices/scanners/health_analyzer.dm
@@ -436,6 +436,7 @@
. = jointext(render_list, "")
if(tochat)
to_chat(user, custom_boxed_message("blue_box", .), trailing_newline = FALSE, type = MESSAGE_TYPE_INFO)
+ SEND_SIGNAL(target, COMSIG_NANITE_SCAN, user, FALSE) // BUBBER EDIT ADDITION - NANITES
return .
/obj/item/healthanalyzer/click_ctrl_shift(mob/user)
diff --git a/code/game/objects/items/pinpointer.dm b/code/game/objects/items/pinpointer.dm
index 878fd0ad3bb..3b877393031 100644
--- a/code/game/objects/items/pinpointer.dm
+++ b/code/game/objects/items/pinpointer.dm
@@ -105,6 +105,8 @@
var/turf/here = get_turf(src)
var/turf/there = get_turf(H)
if(here && there && (there.z == here.z || (is_station_level(here.z) && is_station_level(there.z)))) // Device and target should be on the same level or different levels of the same station
+ if (H in GLOB.nanite_sensors_list) // BUBBER ADDITION START - NANITES
+ return TRUE
if (istype(H.w_uniform, /obj/item/clothing/under))
var/obj/item/clothing/under/U = H.w_uniform
if(U.has_sensor && (U.sensor_mode >= SENSOR_COORDS || ignore_suit_sensor_level)) // Suit sensors must be on maximum or a contractor pinpointer
diff --git a/code/modules/admin/admin_investigate.dm b/code/modules/admin/admin_investigate.dm
index e3840198665..75c5176c844 100644
--- a/code/modules/admin/admin_investigate.dm
+++ b/code/modules/admin/admin_investigate.dm
@@ -31,6 +31,7 @@ ADMIN_VERB(investigate_show, R_NONE, "Investigate", "Browse various detailed log
INVESTIGATE_RECORDS,
INVESTIGATE_RESEARCH,
INVESTIGATE_WIRES,
+ INVESTIGATE_NANITES, // BUBBER ADDITION - NANITES
)
var/list/logs_present = list("notes, memos, watchlist")
diff --git a/code/modules/mob/living/carbon/alien/alien.dm b/code/modules/mob/living/carbon/alien/alien.dm
index 821c8167368..c0234006bb1 100644
--- a/code/modules/mob/living/carbon/alien/alien.dm
+++ b/code/modules/mob/living/carbon/alien/alien.dm
@@ -177,6 +177,13 @@ Des: Removes all infected images from the alien.
if(istype(melting_pot) && istype(frying_pan))
for (var/atom/movable/poor_sod as anything in melting_pot.stomach_contents)
frying_pan.consume_thing(poor_sod)
+ // BUBBER ADDITION START - NANITES
+ var/datum/component/nanites/nanites = GetComponent(/datum/component/nanites)
+ if(nanites)
+ new_xeno.AddComponent(/datum/component/nanites, nanites.nanite_volume)
+ SEND_SIGNAL(new_xeno, COMSIG_NANITE_SYNC, nanites)
+ // BUBBER ADDITION END - NANITES
+
qdel(src)
/// Changes the name of the xeno we are evolving into in order to keep the same numerical identifier the old xeno had.
diff --git a/code/modules/mob/living/carbon/carbon_defines.dm b/code/modules/mob/living/carbon/carbon_defines.dm
index ca6fea58393..30b37dea0ab 100644
--- a/code/modules/mob/living/carbon/carbon_defines.dm
+++ b/code/modules/mob/living/carbon/carbon_defines.dm
@@ -2,7 +2,7 @@
blood_volume = BLOOD_VOLUME_NORMAL
gender = MALE
pressure_resistance = 15
- hud_possible = list(HEALTH_HUD,STATUS_HUD,ANTAG_HUD,GLAND_HUD, DNR_HUD) // SKYRAT EDIT ADDITION - DNR ICON
+ hud_possible = list(HEALTH_HUD,STATUS_HUD,ANTAG_HUD,GLAND_HUD,DNR_HUD,NANITE_HUD,DIAG_NANITE_FULL_HUD) // BUBBER EDIT ADDITION - DNR_HUD / NANITE_HUD
has_limbs = TRUE
held_items = list(null, null)
num_legs = 0 //Populated on init through list/bodyparts
diff --git a/code/modules/mob/living/carbon/human/human_defines.dm b/code/modules/mob/living/carbon/human/human_defines.dm
index 3346a07513c..023ecb8801f 100644
--- a/code/modules/mob/living/carbon/human/human_defines.dm
+++ b/code/modules/mob/living/carbon/human/human_defines.dm
@@ -5,7 +5,7 @@
icon = 'icons/mob/human/human.dmi'
icon_state = "human_basic"
appearance_flags = KEEP_TOGETHER|TILE_BOUND|PIXEL_SCALE|LONG_GLIDE
- hud_possible = list(HEALTH_HUD,STATUS_HUD,ID_HUD,WANTED_HUD,IMPLOYAL_HUD,IMPSEC_FIRST_HUD,IMPSEC_SECOND_HUD,ANTAG_HUD,GLAND_HUD,FAN_HUD,PERMIT_HUD, DNR_HUD) //SKYRAT EDIT ADDITION - PERMIT_HUD, DNR_HUD
+ hud_possible = list(HEALTH_HUD,STATUS_HUD,ID_HUD,WANTED_HUD,IMPLOYAL_HUD,IMPSEC_FIRST_HUD,IMPSEC_SECOND_HUD,ANTAG_HUD,GLAND_HUD,FAN_HUD,PERMIT_HUD,DNR_HUD,NANITE_HUD,DIAG_NANITE_FULL_HUD) //BUBBER EDIT ADDITION - PERMIT_HUD, DNR_HUD
hud_type = /datum/hud/human
pressure_resistance = 25
buckle_lying = 0
diff --git a/code/modules/mob/living/carbon/human/species_types/jellypeople.dm b/code/modules/mob/living/carbon/human/species_types/jellypeople.dm
index 23514ef63ff..6ce75f45737 100644
--- a/code/modules/mob/living/carbon/human/species_types/jellypeople.dm
+++ b/code/modules/mob/living/carbon/human/species_types/jellypeople.dm
@@ -320,6 +320,14 @@
spare.domutcheck()
spare.Move(get_step(H.loc, pick(NORTH,SOUTH,EAST,WEST)))
+ // BUBBER ADDITION START - NANITES
+ var/datum/component/nanites/owner_nanites = H.GetComponent(/datum/component/nanites)
+ if(owner_nanites)
+ //copying over nanite programs/cloud sync with 50% saturation in host and spare
+ owner_nanites.nanite_volume *= 0.5
+ spare.AddComponent(/datum/component/nanites, owner_nanites.nanite_volume)
+ SEND_SIGNAL(spare, COMSIG_NANITE_SYNC, owner_nanites, TRUE, TRUE) //The trues are to copy activation as well
+ // BUBBER ADDITION END - NANITES
H.blood_volume *= 0.45
REMOVE_TRAIT(H, TRAIT_NO_TRANSFORM, REF(src))
diff --git a/code/modules/mob/living/damage_procs.dm b/code/modules/mob/living/damage_procs.dm
index 7f87af20d32..302dacb8bb0 100644
--- a/code/modules/mob/living/damage_procs.dm
+++ b/code/modules/mob/living/damage_procs.dm
@@ -39,6 +39,9 @@
if(!forced)
damage_amount *= ((100 - blocked) / 100)
damage_amount *= get_incoming_damage_modifier(damage_amount, damagetype, def_zone, sharpness, attack_direction, attacking_item)
+
+ SEND_SIGNAL(src, COMSIG_MOB_ALWAYS_APPLY_DAMAGE, damage_amount, damagetype, def_zone, blocked, wound_bonus, bare_wound_bonus, sharpness, attack_direction, attacking_item, wound_clothing) // BUBBER ADDITION: FLAT DAMAGE MODIFIERS
+
if(damage_amount <= 0)
return 0
diff --git a/code/modules/mob/living/living_defines.dm b/code/modules/mob/living/living_defines.dm
index 811c86c082a..25cf6e26dbe 100644
--- a/code/modules/mob/living/living_defines.dm
+++ b/code/modules/mob/living/living_defines.dm
@@ -1,6 +1,6 @@
/mob/living
see_invisible = SEE_INVISIBLE_LIVING
- hud_possible = list(HEALTH_HUD,STATUS_HUD,ANTAG_HUD, DNR_HUD) // SKYRAT EDIT ADDITION - DNR_HUD
+ hud_possible = list(HEALTH_HUD,STATUS_HUD,ANTAG_HUD,DNR_HUD,NANITE_HUD,DIAG_NANITE_FULL_HUD) // BUBBER EDIT ADDITION - DNR_HUD / NANITE_HUD
pressure_resistance = 10
hud_type = /datum/hud/living
interaction_flags_click = ALLOW_RESTING
diff --git a/code/modules/modular_computers/file_system/programs/radar.dm b/code/modules/modular_computers/file_system/programs/radar.dm
index 33dcd95e86b..77b376ba900 100644
--- a/code/modules/modular_computers/file_system/programs/radar.dm
+++ b/code/modules/modular_computers/file_system/programs/radar.dm
@@ -269,6 +269,8 @@
return .
if(!istype(humanoid))
return RADAR_NOT_TRACKABLE
+ if (humanoid in GLOB.nanite_sensors_list) // BUBBER ADDITION - NANITES
+ return TRUE
if(!istype(humanoid.w_uniform, /obj/item/clothing/under))
return RADAR_NOT_TRACKABLE
var/obj/item/clothing/under/uniform = humanoid.w_uniform
diff --git a/icons/obj/machines/computer.dmi b/icons/obj/machines/computer.dmi
index 34f632044c1..7dad1157cbb 100644
Binary files a/icons/obj/machines/computer.dmi and b/icons/obj/machines/computer.dmi differ
diff --git a/modular_zubbers/code/__DEFINES/traits.dm b/modular_zubbers/code/__DEFINES/traits.dm
index 0de6ca840a2..44d23be3f85 100644
--- a/modular_zubbers/code/__DEFINES/traits.dm
+++ b/modular_zubbers/code/__DEFINES/traits.dm
@@ -1,3 +1,6 @@
#define TRAIT_MONOPHOBIA "monophobia"
#define TRAIT_COFFIN_ENLARGED "coffin_enlarged"
+
+/// The mob's nanites are sending a monitoring signal visible on diag HUD
+#define TRAIT_NANITE_MONITORING "nanite_monitoring"
diff --git a/modular_zubbers/code/controllers/subsystem/processing/nanites.dm b/modular_zubbers/code/controllers/subsystem/processing/nanites.dm
new file mode 100644
index 00000000000..b8109a904af
--- /dev/null
+++ b/modular_zubbers/code/controllers/subsystem/processing/nanites.dm
@@ -0,0 +1,22 @@
+PROCESSING_SUBSYSTEM_DEF(nanites)
+ name = "Nanites"
+ flags = SS_BACKGROUND|SS_POST_FIRE_TIMING|SS_NO_INIT
+ wait = 1 SECONDS
+
+ var/list/datum/nanite_cloud_backup/cloud_backups = list()
+ var/list/mob/living/nanite_monitored_mobs = list()
+ var/list/datum/nanite_program/relay/nanite_relays = list()
+ var/neural_network_count = 0
+
+/datum/controller/subsystem/processing/nanites/proc/check_hardware(datum/nanite_cloud_backup/backup)
+ if(QDELETED(backup.storage) || (backup.storage.machine_stat & (NOPOWER|BROKEN)))
+ return FALSE
+ return TRUE
+
+/datum/controller/subsystem/processing/nanites/proc/get_cloud_backup(cloud_id, force = FALSE)
+ for(var/I in cloud_backups)
+ var/datum/nanite_cloud_backup/backup = I
+ if(!force && !check_hardware(backup))
+ return
+ if(backup.cloud_id == cloud_id)
+ return backup
diff --git a/modular_zubbers/code/datums/components/nanites.dm b/modular_zubbers/code/datums/components/nanites.dm
new file mode 100644
index 00000000000..98758530dad
--- /dev/null
+++ b/modular_zubbers/code/datums/components/nanites.dm
@@ -0,0 +1,531 @@
+#define HARMONIC_REGEN_BOOST 0.1
+
+/datum/component/nanites
+ dupe_mode = COMPONENT_DUPE_UNIQUE_PASSARGS
+
+ var/mob/living/host_mob
+ var/nanite_volume = 100 //amount of nanites in the system, used as fuel for nanite programs
+ var/max_nanites = 500 //maximum amount of nanites in the system
+ var/regen_rate = 0.5 //nanites generated per second
+ var/safety_threshold = 50 //how low nanites will get before they stop processing/triggering
+ var/cloud_id = 0 //0 if not connected to the cloud, 1-100 to set a determined cloud backup to draw from
+ var/cloud_active = TRUE //if false, won't sync to the cloud
+ var/next_sync = 0
+ var/list/datum/nanite_program/programs = list()
+ var/max_programs = NANITE_PROGRAM_LIMIT
+
+ var/list/datum/nanite_program/protocol/protocols = list() ///Separate list of protocol programs, to avoid looping through the whole programs list when cheking for conflicts
+ var/start_time = 0 ///Timestamp to when the nanites were first inserted in the host
+ var/stealth = FALSE //if TRUE, does not appear on HUDs and health scans
+ var/diagnostics = FALSE //if TRUE, displays program list when scanned by nanite scanners
+ var/harmonic_regenerating = FALSE //if TRUE, the host is regenerating nanites at a boosted rate
+ var/datum/techweb/techweb
+ // 0 - 100
+ var/research_progress = 0
+
+/datum/component/nanites/Initialize(amount = 100, cloud = 0)
+ if(!isliving(parent) && !istype(parent, /datum/nanite_cloud_backup))
+ return COMPONENT_INCOMPATIBLE
+
+ nanite_volume = amount
+ cloud_id = cloud
+
+ //Nanites without hosts are non-interactive through normal means
+ if(isliving(parent))
+ host_mob = parent
+
+ if(!CAN_HAVE_NANITES(host_mob)) //Shouldn't happen, but this avoids HUD runtimes in case a silicon gets them somehow.
+ return COMPONENT_INCOMPATIBLE
+
+ start_time = world.time
+
+ host_mob.hud_set_nanite_indicator()
+ START_PROCESSING(SSnanites, src)
+
+ if(cloud_id && cloud_active)
+ cloud_sync()
+
+/datum/component/nanites/RegisterWithParent()
+ RegisterSignal(parent, COMSIG_HAS_NANITES, .proc/confirm_nanites)
+ RegisterSignal(parent, COMSIG_NANITE_IS_STEALTHY, .proc/check_stealth)
+ RegisterSignal(parent, COMSIG_NANITE_DELETE, .proc/delete_nanites)
+ RegisterSignal(parent, COMSIG_NANITE_UI_DATA, .proc/nanite_ui_data)
+ RegisterSignal(parent, COMSIG_NANITE_GET_PROGRAMS, .proc/get_programs)
+ RegisterSignal(parent, COMSIG_NANITE_SET_VOLUME, .proc/set_volume)
+ RegisterSignal(parent, COMSIG_NANITE_ADJUST_VOLUME, .proc/adjust_nanites)
+ RegisterSignal(parent, COMSIG_NANITE_SET_MAX_VOLUME, .proc/set_max_volume)
+ RegisterSignal(parent, COMSIG_NANITE_SET_CLOUD, .proc/set_cloud)
+ RegisterSignal(parent, COMSIG_NANITE_SET_CLOUD_SYNC, .proc/set_cloud_sync)
+ RegisterSignal(parent, COMSIG_NANITE_SET_SAFETY, .proc/set_safety)
+ RegisterSignal(parent, COMSIG_NANITE_SET_REGEN, .proc/set_regen)
+ RegisterSignal(parent, COMSIG_NANITE_ADD_PROGRAM, .proc/add_program)
+ RegisterSignal(parent, COMSIG_NANITE_SCAN, .proc/nanite_scan)
+ RegisterSignal(parent, COMSIG_NANITE_SYNC, .proc/sync)
+
+ if(isliving(parent))
+ RegisterSignal(parent, COMSIG_ATOM_EMP_ACT, .proc/on_emp)
+ RegisterSignal(parent, COMSIG_LIVING_DEATH, .proc/on_death)
+ RegisterSignal(parent, COMSIG_MOB_TRIED_ACCESS, .proc/check_access)
+ RegisterSignal(parent, COMSIG_LIVING_ELECTROCUTE_ACT, .proc/on_shock)
+ RegisterSignal(parent, COMSIG_LIVING_MINOR_SHOCK, .proc/on_minor_shock)
+ RegisterSignal(parent, COMSIG_SPECIES_GAIN, .proc/check_viable_biotype)
+ RegisterSignal(parent, COMSIG_NANITE_SIGNAL, .proc/receive_signal)
+ RegisterSignal(parent, COMSIG_NANITE_COMM_SIGNAL, .proc/receive_comm_signal)
+
+/datum/component/nanites/UnregisterFromParent()
+ UnregisterSignal(
+ parent,
+ list(
+ COMSIG_HAS_NANITES,
+ COMSIG_NANITE_IS_STEALTHY,
+ COMSIG_NANITE_DELETE,
+ COMSIG_NANITE_UI_DATA,
+ COMSIG_NANITE_GET_PROGRAMS,
+ COMSIG_NANITE_SET_VOLUME,
+ COMSIG_NANITE_ADJUST_VOLUME,
+ COMSIG_NANITE_SET_MAX_VOLUME,
+ COMSIG_NANITE_SET_CLOUD,
+ COMSIG_NANITE_SET_CLOUD_SYNC,
+ COMSIG_NANITE_SET_SAFETY,
+ COMSIG_NANITE_SET_REGEN,
+ COMSIG_NANITE_ADD_PROGRAM,
+ COMSIG_NANITE_SCAN,
+ COMSIG_NANITE_SYNC,
+ COMSIG_ATOM_EMP_ACT,
+ COMSIG_LIVING_DEATH,
+ COMSIG_MOB_TRIED_ACCESS,
+ COMSIG_LIVING_ELECTROCUTE_ACT,
+ COMSIG_LIVING_MINOR_SHOCK,
+ COMSIG_MOVABLE_HEAR,
+ COMSIG_SPECIES_GAIN,
+ COMSIG_NANITE_SIGNAL,
+ COMSIG_NANITE_COMM_SIGNAL,
+ )
+ )
+ disconnect_techweb()
+
+/datum/component/nanites/Destroy()
+ STOP_PROCESSING(SSnanites, src)
+ QDEL_LIST(programs)
+ if(host_mob)
+ set_nanite_bar(TRUE)
+ host_mob.hud_set_nanite_indicator()
+ host_mob = null
+ return ..()
+
+/datum/component/nanites/InheritComponent(datum/component/nanites/new_nanites, i_am_original, amount, cloud)
+ if(new_nanites)
+ adjust_nanites(null, new_nanites.nanite_volume)
+ else
+ adjust_nanites(null, amount) //just add to the nanite volume
+
+/datum/component/nanites/process(delta_time)
+ if(!host_mob.has_status_effect(/datum/status_effect/grouped/stasis))
+ adjust_nanites(null, get_regen_rate() * delta_time)
+ add_research()
+ for(var/X in programs)
+ var/datum/nanite_program/NP = X
+ NP.on_process()
+ if(cloud_id && cloud_active && world.time > next_sync)
+ cloud_sync()
+ next_sync = world.time + NANITE_SYNC_DELAY
+ set_nanite_bar()
+
+///Deletes nanites!
+/datum/component/nanites/proc/delete_nanites()
+ SIGNAL_HANDLER
+ qdel(src)
+
+///Syncs the nanite component to another, making it so programs are the same with the same programming (except activation status)
+/datum/component/nanites/proc/sync(datum/signal_source, datum/component/nanites/source, full_overwrite = TRUE, copy_activation = FALSE)
+ SIGNAL_HANDLER
+
+ var/list/programs_to_remove = programs.Copy()
+ var/list/programs_to_add = source.programs.Copy()
+ for(var/X in programs)
+ var/datum/nanite_program/NP = X
+ for(var/Y in programs_to_add)
+ var/datum/nanite_program/SNP = Y
+ if(NP.type == SNP.type)
+ programs_to_remove -= NP
+ programs_to_add -= SNP
+ SNP.copy_programming(NP, copy_activation)
+ break
+ if(full_overwrite)
+ for(var/X in programs_to_remove)
+ qdel(X)
+ for(var/X in programs_to_add)
+ var/datum/nanite_program/SNP = X
+ add_program(null, SNP.copy())
+
+///Syncs the nanites to their assigned cloud copy, if it is available. If it is not, there is a small chance of a software error instead.
+/datum/component/nanites/proc/cloud_sync()
+ if(cloud_id)
+ var/datum/nanite_cloud_backup/backup = SSnanites.get_cloud_backup(cloud_id)
+ if(backup)
+ connect_techweb(backup.techweb)
+ var/datum/component/nanites/cloud_copy = backup.nanites
+ if(cloud_copy)
+ sync(null, cloud_copy)
+ return
+ //Without cloud syncing nanites can accumulate errors and/or defects
+ if(prob(8) && programs.len)
+ var/datum/nanite_program/NP = pick(programs)
+ NP.software_error()
+// todo test this
+/datum/component/nanites/proc/add_design(datum/techweb/web, datum/design/design, custom = FALSE)
+ SIGNAL_HANDLER
+ if(design.id == "nanite_harmonic")
+ harmonic_regenerating = TRUE
+
+/datum/component/nanites/proc/remove_design(datum/techweb/web, datum/design/design, custom = FALSE)
+ SIGNAL_HANDLER
+ if(design.id == "nanite_harmonic")
+ harmonic_regenerating = FALSE
+
+/datum/component/nanites/proc/connect_techweb(datum/techweb/new_web)
+ if(!new_web || new_web == techweb)
+ return
+ if(techweb)
+ disconnect_techweb()
+ techweb = new_web
+ var/list/all_nodes = techweb.get_researched_nodes()
+ harmonic_regenerating = !!all_nodes["nanite_harmonic"]
+ RegisterSignal(techweb, COMSIG_TECHWEB_ADD_DESIGN, .proc/add_design)
+ RegisterSignal(techweb, COMSIG_TECHWEB_REMOVE_DESIGN, .proc/remove_design)
+
+/datum/component/nanites/proc/disconnect_techweb()
+ if(!techweb)
+ return
+ UnregisterSignal(techweb, COMSIG_TECHWEB_ADD_DESIGN)
+ UnregisterSignal(techweb, COMSIG_TECHWEB_REMOVE_DESIGN)
+ techweb = null
+
+///Adds a nanite program, replacing existing unique programs of the same type. A source program can be specified to copy its programming onto the new one.
+/datum/component/nanites/proc/add_program(datum/source, datum/nanite_program/new_program, datum/nanite_program/source_program)
+ SIGNAL_HANDLER
+
+ for(var/X in programs)
+ var/datum/nanite_program/NP = X
+ if(NP.unique && NP.type == new_program.type)
+ qdel(NP)
+ if(programs.len >= max_programs)
+ return COMPONENT_PROGRAM_NOT_INSTALLED
+ if(source_program)
+ source_program.copy_programming(new_program)
+ programs += new_program
+ new_program.on_add(src)
+ return COMPONENT_PROGRAM_INSTALLED
+
+/datum/component/nanites/proc/consume_nanites(amount, force = FALSE)
+ if(!force && safety_threshold && (nanite_volume - amount < safety_threshold))
+ return FALSE
+ adjust_nanites(null, -amount)
+ return (nanite_volume > 0)
+
+///Modifies the current nanite volume, then checks if the nanites are depleted or exceeding the maximum amount
+/datum/component/nanites/proc/adjust_nanites(datum/source, amount)
+ SIGNAL_HANDLER
+
+ nanite_volume += amount
+ if(nanite_volume > max_nanites)
+ reject_excess_nanites()
+ if(nanite_volume <= 0) //oops we ran out
+ qdel(src)
+
+/**
+ * Handles how nanites leave the host's body if they find out that they're currently exceeding the maximum supported amount
+ *
+ * IC explanation:
+ * Normally nanites simply discard excess volume by slowing replication or 'sweating' it out in imperceptible amounts,
+ * but if there is a large excess volume, likely due to a programming change that leaves them unable to support their current volume,
+ * the nanites attempt to leave the host as fast as necessary to prevent nanite poisoning. This can range from minor oozing to nanites
+ * rapidly bursting out from every possible pathway, causing temporary inconvenience to the host.
+ */
+/datum/component/nanites/proc/reject_excess_nanites()
+ var/excess = nanite_volume - max_nanites
+ nanite_volume = max_nanites
+
+ switch(excess)
+ if(0 to NANITE_EXCESS_MINOR) //Minor excess amount, the extra nanites are quietly expelled without visible effects
+ return
+ if((NANITE_EXCESS_MINOR + 0.1) to NANITE_EXCESS_VOMIT) //Enough nanites getting rejected at once to be visible to the naked eye
+ host_mob.visible_message(span_warning("A grainy grey slurry starts oozing out of [host_mob]."), span_warning("A grainy grey slurry starts oozing out of your skin."), null, 4);
+ if((NANITE_EXCESS_VOMIT + 0.1) to NANITE_EXCESS_BURST) //Nanites getting rejected in massive amounts, but still enough to make a semi-orderly exit through vomit
+ if(iscarbon(host_mob))
+ var/mob/living/carbon/C = host_mob
+ host_mob.visible_message(span_warning("[host_mob] vomits a grainy grey slurry!"), span_warning("You suddenly vomit a metallic-tasting grainy grey slurry!"), null);
+ C.vomit(VOMIT_CATEGORY_NANITE, /obj/effect/decal/cleanable/vomit/nanites, FLOOR(excess / 100, 1))
+ else
+ host_mob.visible_message(span_warning("A metallic grey slurry bursts out of [host_mob]'s skin!"), span_userdanger("A metallic grey slurry violently bursts out of your skin!"), null);
+ if(isturf(host_mob.drop_location()))
+ var/turf/T = host_mob.drop_location()
+ T.add_vomit_floor(host_mob, /obj/effect/decal/cleanable/vomit/nanites, VOMIT_CATEGORY_NANITE, 0)
+ if((NANITE_EXCESS_BURST + 0.1) to INFINITY) //Way too many nanites, they just leave through the closest exit before they harm/poison the host
+ host_mob.visible_message(span_warning("A torrent of metallic grey slurry violently bursts out of [host_mob]'s face and floods out of [host_mob.p_their()] skin!"),
+ span_userdanger("A torrent of metallic grey slurry violently bursts out of your eyes, ears, and mouth, and floods out of your skin!"));
+ host_mob.adjust_eye_blur_up_to(15, 1 MINUTES)
+ host_mob.Paralyze(120)
+ if(iscarbon(host_mob))
+ var/mob/living/carbon/carbon = host_mob
+ var/obj/item/organ/ears/ears = carbon.get_organ_slot(ORGAN_SLOT_EARS)
+ if(ears)
+ ears.adjustEarDamage(0, 30) //nanites coming out of your ears
+ carbon.vomit(VOMIT_CATEGORY_NANITE, /obj/effect/decal/cleanable/vomit/nanites) //nanites coming out of your mouth
+
+ //nanites everywhere
+ if(isturf(host_mob.drop_location()))
+ var/turf/T = host_mob.drop_location()
+ T.add_vomit_floor(host_mob, VOMIT_CATEGORY_NANITE, 0)
+ for(var/turf/adjacent_turf in oview(host_mob, 1))
+ if(adjacent_turf.density || !adjacent_turf.Adjacent(T))
+ continue
+ adjacent_turf.add_vomit_floor(host_mob, /obj/effect/decal/cleanable/vomit/nanites, VOMIT_CATEGORY_NANITE, 0)
+
+///Updates the nanite volume bar visible in diagnostic HUDs
+/datum/component/nanites/proc/set_nanite_bar(remove = FALSE)
+ var/image/holder = host_mob.hud_list[DIAG_NANITE_FULL_HUD]
+ var/icon/I = icon(host_mob.icon, host_mob.icon_state, host_mob.dir)
+ holder.pixel_y = I.Height() - world.icon_size
+ holder.icon_state = null
+ if(remove || stealth)
+ return //bye icon
+ var/nanite_percent = (nanite_volume / max_nanites) * 100
+ nanite_percent = clamp(CEILING(nanite_percent, 10), 10, 100)
+ holder.icon_state = "nanites[nanite_percent]"
+
+/datum/component/nanites/proc/on_emp(datum/source, severity)
+ SIGNAL_HANDLER
+
+ nanite_volume *= (rand(60, 90) * 0.01) //Lose 10-40% of nanites
+ adjust_nanites(null, -(rand(5, 50))) //Lose 5-50 flat nanite volume
+ if(prob(40/severity))
+ cloud_id = 0
+ for(var/X in programs)
+ var/datum/nanite_program/NP = X
+ NP.on_emp(severity)
+
+
+/datum/component/nanites/proc/on_shock(datum/source, shock_damage, siemens_coeff = 1, flags = NONE)
+ SIGNAL_HANDLER
+
+ if(flags & SHOCK_ILLUSION || shock_damage < 1)
+ return
+
+ if(!HAS_TRAIT_NOT_FROM(host_mob, TRAIT_SHOCKIMMUNE, "nanites"))//Another shock protection must protect nanites too, but nanites protect only host
+ nanite_volume *= (rand(45, 80) * 0.01) //Lose 20-55% of nanites
+ adjust_nanites(null, -(rand(5, 50))) //Lose 5-50 flat nanite volume
+ for(var/X in programs)
+ var/datum/nanite_program/NP = X
+ NP.on_shock(shock_damage)
+
+/datum/component/nanites/proc/on_minor_shock(datum/source)
+ SIGNAL_HANDLER
+
+ adjust_nanites(null, -(rand(5, 15))) //Lose 5-15 flat nanite volume
+ for(var/X in programs)
+ var/datum/nanite_program/NP = X
+ NP.on_minor_shock()
+
+/datum/component/nanites/proc/check_stealth(datum/source)
+ SIGNAL_HANDLER
+
+ return stealth
+
+/datum/component/nanites/proc/on_death(datum/source, gibbed)
+ SIGNAL_HANDLER
+
+ for(var/X in programs)
+ var/datum/nanite_program/NP = X
+ NP.on_death(gibbed)
+
+/datum/component/nanites/proc/receive_signal(datum/source, code, source = "an unidentified source")
+ SIGNAL_HANDLER
+
+ for(var/X in programs)
+ var/datum/nanite_program/NP = X
+ NP.receive_signal(code, source)
+
+/datum/component/nanites/proc/receive_comm_signal(datum/source, comm_code, comm_message, comm_source = "an unidentified source")
+ SIGNAL_HANDLER
+
+ for(var/X in programs)
+ if(istype(X, /datum/nanite_program/comm))
+ var/datum/nanite_program/comm/NP = X
+ NP.receive_comm_signal(comm_code, comm_message, comm_source)
+
+/datum/component/nanites/proc/check_viable_biotype()
+ SIGNAL_HANDLER
+
+ if(!CAN_HAVE_NANITES(host_mob))
+ qdel(src) //bodytype no longer sustains nanites
+
+/datum/component/nanites/proc/check_access(datum/source, atom/locked_thing)
+ SIGNAL_HANDLER
+ var/obj/locked_object = locked_thing
+ if(!istype(locked_thing))
+ return FALSE
+ for(var/datum/nanite_program/access/access_program in programs)
+ if(access_program.activated)
+ return locked_object.check_access_list(access_program.access)
+ else
+ return FALSE
+ return FALSE
+
+/datum/component/nanites/proc/set_volume(datum/source, amount)
+ SIGNAL_HANDLER
+
+ nanite_volume = clamp(amount, 0, max_nanites)
+
+/datum/component/nanites/proc/set_max_volume(datum/source, amount)
+ SIGNAL_HANDLER
+
+ max_nanites = max(1, amount)
+
+/datum/component/nanites/proc/set_cloud(datum/source, amount)
+ SIGNAL_HANDLER
+
+ cloud_id = clamp(amount, 0, 100)
+
+/datum/component/nanites/proc/set_cloud_sync(datum/source, method)
+ SIGNAL_HANDLER
+
+ switch(method)
+ if(NANITE_CLOUD_TOGGLE)
+ cloud_active = !cloud_active
+ if(NANITE_CLOUD_DISABLE)
+ cloud_active = FALSE
+ if(NANITE_CLOUD_ENABLE)
+ cloud_active = TRUE
+
+/datum/component/nanites/proc/set_safety(datum/source, amount)
+ SIGNAL_HANDLER
+
+ safety_threshold = clamp(amount, 0, max_nanites)
+
+/datum/component/nanites/proc/set_regen(datum/source, amount)
+ SIGNAL_HANDLER
+
+ regen_rate = amount
+
+/datum/component/nanites/proc/confirm_nanites()
+ SIGNAL_HANDLER
+
+ return TRUE //yup i exist
+
+/datum/component/nanites/proc/get_programs(datum/source, list/nanite_programs)
+ SIGNAL_HANDLER
+
+ nanite_programs |= programs
+
+/datum/component/nanites/proc/add_research()
+ var/progress_addition = NANITE_BASE_RESEARCH_PROGRESS
+ if(!techweb)
+ return
+ if(!ishuman(host_mob))
+ if(!iscarbon(host_mob))
+ progress_addition *= 0.4
+ else
+ progress_addition *= 0.8
+ if(!host_mob.client)
+ progress_addition *= 0.5
+ if(host_mob.stat == DEAD)
+ progress_addition *= 0.75
+ research_progress += progress_addition
+ if(research_progress >= 100)
+ research_progress -= 100
+ techweb.add_point_list(
+ list(
+ TECHWEB_POINT_TYPE_GENERIC = TECHWEB_SINGLE_SERVER_INCOME,
+ TECHWEB_POINT_TYPE_NANITE = TECHWEB_SINGLE_SERVER_INCOME
+ )
+ )
+
+/datum/component/nanites/proc/nanite_scan(datum/source, mob/user, full_scan)
+ SIGNAL_HANDLER
+
+ if(!full_scan)
+ if(!stealth)
+ to_chat(user, span_notice("Nanites Detected"))
+ to_chat(user, span_notice("Saturation: [nanite_volume]/[max_nanites]"))
+ return TRUE
+ else
+ to_chat(user, span_info("NANITES DETECTED"))
+ to_chat(user, span_info("================"))
+ to_chat(user, span_info("Saturation: [nanite_volume]/[max_nanites]"))
+ to_chat(user, span_info("Safety Threshold: [safety_threshold]"))
+ to_chat(user, span_info("Cloud ID: [cloud_id ? cloud_id : "None"]"))
+ to_chat(user, span_info("Cloud Sync: [cloud_active ? "Active" : "Disabled"]"))
+ to_chat(user, span_info("================"))
+ to_chat(user, span_info("Program List:"))
+ if(!diagnostics)
+ to_chat(user, span_alert("Nanite debugging disabled."))
+ else
+ for(var/X in programs)
+ var/datum/nanite_program/NP = X
+ to_chat(user, span_info("[NP.name] | [NP.activated ? "Active" : "Inactive"]"))
+ return TRUE
+
+/datum/component/nanites/proc/get_regen_rate()
+ return regen_rate + (harmonic_regenerating ? HARMONIC_REGEN_BOOST : 0)
+
+/datum/component/nanites/proc/nanite_ui_data(datum/source, list/data, scan_level)
+ SIGNAL_HANDLER
+
+ data["has_nanites"] = TRUE
+ data["nanite_volume"] = nanite_volume
+ data["regen_rate"] = get_regen_rate()
+ data["safety_threshold"] = safety_threshold
+ data["cloud_id"] = cloud_id
+ data["cloud_active"] = cloud_active
+ var/list/mob_programs = list()
+ var/id = 1
+ for(var/X in programs)
+ var/datum/nanite_program/P = X
+ var/list/mob_program = list()
+ mob_program["name"] = P.name
+ mob_program["desc"] = P.desc
+ mob_program["id"] = id
+
+ if(scan_level >= 2)
+ mob_program["activated"] = P.activated
+ mob_program["use_rate"] = P.use_rate
+ mob_program["can_trigger"] = P.can_trigger
+ mob_program["trigger_cost"] = P.trigger_cost
+ mob_program["trigger_cooldown"] = P.trigger_cooldown / 10
+
+ if(scan_level >= 3)
+ mob_program["timer_restart"] = P.timer_restart / 10
+ mob_program["timer_shutdown"] = P.timer_shutdown / 10
+ mob_program["timer_trigger"] = P.timer_trigger / 10
+ mob_program["timer_trigger_delay"] = P.timer_trigger_delay / 10
+ var/list/extra_settings = P.get_extra_settings_frontend()
+ mob_program["extra_settings"] = extra_settings
+ if(LAZYLEN(extra_settings))
+ mob_program["has_extra_settings"] = TRUE
+ else
+ mob_program["has_extra_settings"] = FALSE
+
+ if(scan_level >= 4)
+ mob_program["activation_code"] = P.activation_code
+ mob_program["deactivation_code"] = P.deactivation_code
+ mob_program["kill_code"] = P.kill_code
+ mob_program["trigger_code"] = P.trigger_code
+ var/list/rules = list()
+ var/rule_id = 1
+ for(var/Z in P.rules)
+ var/datum/nanite_rule/nanite_rule = Z
+ var/list/rule = list()
+ rule["display"] = nanite_rule.display()
+ rule["program_id"] = id
+ rule["id"] = rule_id
+ rules += list(rule)
+ rule_id++
+ mob_program["rules"] = rules
+ if(LAZYLEN(rules))
+ mob_program["has_rules"] = TRUE
+ id++
+ mob_programs += list(mob_program)
+ data["mob_programs"] = mob_programs
+
+#undef HARMONIC_REGEN_BOOST
diff --git a/modular_zubbers/code/datums/mood_events/nanite_events.dm b/modular_zubbers/code/datums/mood_events/nanite_events.dm
new file mode 100644
index 00000000000..ae21bd84f4c
--- /dev/null
+++ b/modular_zubbers/code/datums/mood_events/nanite_events.dm
@@ -0,0 +1,13 @@
+/datum/mood_event/nanite_happiness
+ description = "+++++++HAPPINESS ENHANCEMENT+++++++\n"
+ mood_change = 7
+
+/datum/mood_event/nanite_happiness/add_effects(message)
+ description = "+++++++[message]+++++++\n"
+
+/datum/mood_event/nanite_sadness
+ description = "+++++++HAPPINESS SUPPRESSION+++++++\n"
+ mood_change = -7
+
+/datum/mood_event/nanite_sadness/add_effects(message)
+ description = "+++++++[message]+++++++\n"
diff --git a/modular_zubbers/code/game/area/nanite.dm b/modular_zubbers/code/game/area/nanite.dm
new file mode 100644
index 00000000000..a5ad3943be2
--- /dev/null
+++ b/modular_zubbers/code/game/area/nanite.dm
@@ -0,0 +1,3 @@
+/area/science/nanite
+ name = "Nanite Lab"
+ icon_state = "nanite"
diff --git a/modular_zubbers/code/modules/mob/living/carbon/human/physiology.dm b/modular_zubbers/code/modules/mob/living/carbon/human/physiology.dm
new file mode 100644
index 00000000000..49497003629
--- /dev/null
+++ b/modular_zubbers/code/modules/mob/living/carbon/human/physiology.dm
@@ -0,0 +1,28 @@
+/datum/physiology
+ var/flat_brute_mod = 0
+ var/flat_burn_mod = 0
+
+/mob/living/carbon/human/apply_damage(
+ damage,
+ damagetype,
+ def_zone,
+ blocked,
+ forced,
+ spread_damage,
+ wound_bonus,
+ bare_wound_bonus,
+ sharpness,
+ attack_direction,
+ attacking_item,
+ wound_clothing
+ )
+ if(!physiology)
+ return ..()
+
+ switch(damagetype)
+ if(BRUTE)
+ damage += physiology.flat_brute_mod
+ if(BURN)
+ damage += physiology.flat_burn_mod
+
+ . = ..()
diff --git a/modular_zubbers/code/modules/nanites/designs.dm b/modular_zubbers/code/modules/nanites/designs.dm
new file mode 100644
index 00000000000..72d46247ef9
--- /dev/null
+++ b/modular_zubbers/code/modules/nanites/designs.dm
@@ -0,0 +1,91 @@
+/datum/design/nanite_disk
+ name = "Nanite Program Disk"
+ desc = "Stores nanite programs."
+ id = "nanite_disk"
+ build_type = PROTOLATHE | AWAY_LATHE
+ materials = list(/datum/material/iron = 300, /datum/material/glass = 100)
+ build_path = /obj/item/disk/nanite_program
+ category = list("Electronics")
+ departmental_flags = DEPARTMENT_BITFLAG_SCIENCE
+
+/datum/design/board/nanite_chamber_control
+ name = "Computer Design (Nanite Chamber Control)"
+ desc = "Allows for the construction of circuit boards used to build a new nanite chamber control console."
+ id = "nanite_chamber_control"
+ build_path = /obj/item/circuitboard/computer/nanite_chamber_control
+ category = list("Computer Boards")
+ departmental_flags = DEPARTMENT_BITFLAG_SCIENCE
+
+/datum/design/board/nanite_cloud_control
+ name = "Computer Design (Nanite Cloud Control)"
+ desc = "Allows for the construction of circuit boards used to build a new nanite cloud control console."
+ id = "nanite_cloud_control"
+ build_path = /obj/item/circuitboard/computer/nanite_cloud_controller
+ category = list("Computer Boards")
+ departmental_flags = DEPARTMENT_BITFLAG_SCIENCE
+
+/datum/design/board/nanite_chamber
+ name = "Machine Design (Nanite Chamber Board)"
+ desc = "The circuit board for a Nanite Chamber."
+ id = "nanite_chamber"
+ build_path = /obj/item/circuitboard/machine/nanite_chamber
+ category = list("Research Machinery")
+ departmental_flags = DEPARTMENT_BITFLAG_SCIENCE
+
+/datum/design/board/public_nanite_chamber
+ name = "Machine Design (Public Nanite Chamber Board)"
+ desc = "The circuit board for a Public Nanite Chamber."
+ id = "public_nanite_chamber"
+ build_path = /obj/item/circuitboard/machine/public_nanite_chamber
+ category = list("Research Machinery")
+ departmental_flags = DEPARTMENT_BITFLAG_SCIENCE
+
+/datum/design/board/nanite_programmer
+ name = "Machine Design (Nanite Programmer Board)"
+ desc = "The circuit board for a Nanite Programmer."
+ id = "nanite_programmer"
+ build_path = /obj/item/circuitboard/machine/nanite_programmer
+ category = list("Research Machinery")
+ departmental_flags = DEPARTMENT_BITFLAG_SCIENCE
+
+/datum/design/board/nanite_program_hub
+ name = "Machine Design (Nanite Program Hub Board)"
+ desc = "The circuit board for a Nanite Program Hub."
+ id = "nanite_program_hub"
+ build_path = /obj/item/circuitboard/machine/nanite_program_hub
+ category = list("Research Machinery")
+ departmental_flags = DEPARTMENT_BITFLAG_SCIENCE
+
+///////////////////////////////////
+//////////Nanite Devices///////////
+///////////////////////////////////
+/datum/design/nanite_remote
+ name = "Nanite Remote"
+ desc = "Allows for the construction of a nanite remote."
+ id = "nanite_remote"
+ build_type = PROTOLATHE | AWAY_LATHE
+ materials = list(/datum/material/glass = 500, /datum/material/iron = 500)
+ build_path = /obj/item/nanite_remote
+ category = list("Electronics")
+ departmental_flags = DEPARTMENT_BITFLAG_SCIENCE
+
+/datum/design/nanite_comm_remote
+ name = "Nanite Communication Remote"
+ desc = "Allows for the construction of a nanite communication remote."
+ id = "nanite_comm_remote"
+ build_type = PROTOLATHE | AWAY_LATHE
+ materials = list(/datum/material/glass = 500, /datum/material/iron = 500)
+ build_path = /obj/item/nanite_remote/comm
+ category = list("Electronics")
+ departmental_flags = DEPARTMENT_BITFLAG_SCIENCE
+
+/datum/design/nanite_scanner
+ name = "Nanite Scanner"
+ desc = "Allows for the construction of a nanite scanner."
+ id = "nanite_scanner"
+ build_type = PROTOLATHE | AWAY_LATHE
+ materials = list(/datum/material/glass = 500, /datum/material/iron = 500)
+ build_path = /obj/item/nanite_scanner
+ category = list("Electronics")
+ departmental_flags = DEPARTMENT_BITFLAG_SCIENCE
+
diff --git a/modular_zubbers/code/modules/nanites/extra_settings/_extra_setting.dm b/modular_zubbers/code/modules/nanites/extra_settings/_extra_setting.dm
new file mode 100644
index 00000000000..b3bef77eb40
--- /dev/null
+++ b/modular_zubbers/code/modules/nanites/extra_settings/_extra_setting.dm
@@ -0,0 +1,20 @@
+/datum/nanite_extra_setting
+ var/setting_type
+ var/value
+
+/datum/nanite_extra_setting/proc/get_value()
+ return value
+
+/datum/nanite_extra_setting/proc/set_value(value)
+ src.value = value
+
+/datum/nanite_extra_setting/proc/get_copy()
+ return
+
+//I made the choice to send the name as part of the parameter instead of storing it directly on
+//this datum as a way of avoiding duplication of data between the containing assoc list
+//and this datum.
+//Also make sure to double wrap the list when implementing this as
+//+= is interpreted as a combine on lists, so the outer list gets unwrapped
+/datum/nanite_extra_setting/proc/get_frontend_list(name)
+ return
diff --git a/modular_zubbers/code/modules/nanites/extra_settings/boolean.dm b/modular_zubbers/code/modules/nanites/extra_settings/boolean.dm
new file mode 100644
index 00000000000..7d66eb988ea
--- /dev/null
+++ b/modular_zubbers/code/modules/nanites/extra_settings/boolean.dm
@@ -0,0 +1,27 @@
+/datum/nanite_extra_setting/boolean
+ setting_type = NESTYPE_BOOLEAN
+ var/true_text
+ var/false_text
+
+/datum/nanite_extra_setting/boolean/New(initial, true_text, false_text)
+ value = initial
+ src.true_text = true_text
+ src.false_text = false_text
+
+/datum/nanite_extra_setting/boolean/set_value(value)
+ if(isnull(value))
+ src.value = !src.value
+ return
+ . = ..()
+
+/datum/nanite_extra_setting/boolean/get_copy()
+ return new /datum/nanite_extra_setting/boolean(value, true_text, false_text)
+
+/datum/nanite_extra_setting/boolean/get_frontend_list(name)
+ return list(list(
+ "name" = name,
+ "type" = setting_type,
+ "value" = value,
+ "true_text" = true_text,
+ "false_text" = false_text
+ ))
diff --git a/modular_zubbers/code/modules/nanites/extra_settings/number.dm b/modular_zubbers/code/modules/nanites/extra_settings/number.dm
new file mode 100644
index 00000000000..75489635f55
--- /dev/null
+++ b/modular_zubbers/code/modules/nanites/extra_settings/number.dm
@@ -0,0 +1,32 @@
+/datum/nanite_extra_setting/number
+ setting_type = NESTYPE_NUMBER
+ var/min
+ var/max
+ var/unit = ""
+
+/datum/nanite_extra_setting/number/New(initial, min, max, unit)
+ value = initial
+ src.min = min
+ src.max = max
+ if(unit)
+ src.unit = unit
+
+/datum/nanite_extra_setting/number/set_value(value)
+ if(istext(value))
+ value = text2num(value)
+ if(!value || !isnum(value))
+ return
+ src.value = clamp(value, min, max)
+
+/datum/nanite_extra_setting/number/get_copy()
+ return new /datum/nanite_extra_setting/number(value, min, max, unit)
+
+/datum/nanite_extra_setting/number/get_frontend_list(name)
+ return list(list(
+ "name" = name,
+ "type" = setting_type,
+ "value" = value,
+ "min" = min,
+ "max" = max,
+ "unit" = unit
+ ))
diff --git a/modular_zubbers/code/modules/nanites/extra_settings/text.dm b/modular_zubbers/code/modules/nanites/extra_settings/text.dm
new file mode 100644
index 00000000000..d3cad27bcf5
--- /dev/null
+++ b/modular_zubbers/code/modules/nanites/extra_settings/text.dm
@@ -0,0 +1,21 @@
+/datum/nanite_extra_setting/text
+ setting_type = NESTYPE_TEXT
+
+/datum/nanite_extra_setting/text/New(initial)
+ value = initial
+
+/datum/nanite_extra_setting/text/set_value(value)
+ src.value = trim(value)
+
+/datum/nanite_extra_setting/text/get_copy()
+ return new /datum/nanite_extra_setting/text(value)
+
+/datum/nanite_extra_setting/text/get_value()
+ return html_encode(value)
+
+/datum/nanite_extra_setting/text/get_frontend_list(name)
+ return list(list(
+ "name" = name,
+ "type" = setting_type,
+ "value" = value
+ ))
diff --git a/modular_zubbers/code/modules/nanites/extra_settings/type.dm b/modular_zubbers/code/modules/nanites/extra_settings/type.dm
new file mode 100644
index 00000000000..2085ba510c4
--- /dev/null
+++ b/modular_zubbers/code/modules/nanites/extra_settings/type.dm
@@ -0,0 +1,18 @@
+/datum/nanite_extra_setting/type
+ setting_type = NESTYPE_TYPE
+ var/list/types
+
+/datum/nanite_extra_setting/type/New(initial, types)
+ value = initial
+ src.types = types
+
+/datum/nanite_extra_setting/type/get_copy()
+ return new /datum/nanite_extra_setting/type(value, types)
+
+/datum/nanite_extra_setting/type/get_frontend_list(name)
+ return list(list(
+ "name" = name,
+ "type" = setting_type,
+ "value" = value,
+ "types" = types
+ ))
diff --git a/modular_zubbers/code/modules/nanites/items/boards.dm b/modular_zubbers/code/modules/nanites/items/boards.dm
new file mode 100644
index 00000000000..154dec071c3
--- /dev/null
+++ b/modular_zubbers/code/modules/nanites/items/boards.dm
@@ -0,0 +1,58 @@
+/obj/item/circuitboard/machine/nanite_chamber
+ name = "Nanite Chamber (Machine Board)"
+ greyscale_colors = CIRCUIT_COLOR_SCIENCE
+ build_path = /obj/machinery/nanite_chamber
+ req_components = list(
+ /obj/item/stock_parts/scanning_module = 2,
+ /obj/item/stock_parts/micro_laser = 2,
+ /obj/item/stock_parts/servo = 1
+ )
+
+/obj/item/circuitboard/machine/nanite_program_hub
+ name = "Nanite Program Hub (Machine Board)"
+ greyscale_colors = CIRCUIT_COLOR_SCIENCE
+ build_path = /obj/machinery/nanite_program_hub
+ req_components = list(
+ /obj/item/stock_parts/matter_bin = 1,
+ /obj/item/stock_parts/servo = 1)
+
+/obj/item/circuitboard/machine/nanite_programmer
+ name = "Nanite Programmer (Machine Board)"
+ greyscale_colors = CIRCUIT_COLOR_SCIENCE
+ build_path = /obj/machinery/nanite_programmer
+ req_components = list(
+ /obj/item/stock_parts/servo = 2,
+ /obj/item/stock_parts/micro_laser = 2,
+ /obj/item/stock_parts/scanning_module = 1
+ )
+
+/obj/item/circuitboard/machine/public_nanite_chamber
+ name = "Public Nanite Chamber (Machine Board)"
+ greyscale_colors = CIRCUIT_COLOR_SCIENCE
+ build_path = /obj/machinery/public_nanite_chamber
+ req_components = list(
+ /obj/item/stock_parts/micro_laser = 2,
+ /obj/item/stock_parts/servo = 1
+ )
+ var/cloud_id = 1
+
+/obj/item/circuitboard/machine/public_nanite_chamber/multitool_act(mob/living/user)
+ . = ..()
+ var/new_cloud = tgui_input_number(user, "Set the public nanite chamber's Cloud ID (1-100).", "Cloud ID", cloud_id, 100, 1, 0,)
+ if(!isnum(new_cloud))
+ return
+ cloud_id = clamp(round(new_cloud, 1), 1, 100)
+
+/obj/item/circuitboard/machine/public_nanite_chamber/examine(mob/user)
+ . = ..()
+ . += "Cloud ID is currently set to [cloud_id]."
+
+/obj/item/circuitboard/computer/nanite_chamber_control
+ name = "Nanite Chamber Control (Computer Board)"
+ greyscale_colors = CIRCUIT_COLOR_SCIENCE
+ build_path = /obj/machinery/computer/nanite_chamber_control
+
+/obj/item/circuitboard/computer/nanite_cloud_controller
+ name = "Nanite Cloud Control (Computer Board)"
+ greyscale_colors = CIRCUIT_COLOR_SCIENCE
+ build_path = /obj/machinery/computer/nanite_cloud_controller
diff --git a/modular_zubbers/code/modules/nanites/items/items.dm b/modular_zubbers/code/modules/nanites/items/items.dm
new file mode 100644
index 00000000000..d70fa243725
--- /dev/null
+++ b/modular_zubbers/code/modules/nanites/items/items.dm
@@ -0,0 +1,47 @@
+/obj/item/nanite_scanner
+ name = "nanite scanner"
+ icon = 'modular_zubbers/icons/obj/devices/nanite_devices.dmi'
+ icon_state = "nanite_scanner"
+ inhand_icon_state = "electronic"
+ worn_icon_state = "electronic"
+ lefthand_file = 'icons/mob/inhands/items/devices_lefthand.dmi'
+ righthand_file = 'icons/mob/inhands/items/devices_righthand.dmi'
+ desc = "A hand-held body scanner able to detect nanites and their programming."
+ obj_flags = CONDUCTS_ELECTRICITY
+ item_flags = NOBLUDGEON
+ slot_flags = ITEM_SLOT_BELT
+ throwforce = 3
+ w_class = WEIGHT_CLASS_TINY
+ throw_speed = 3
+ throw_range = 7
+ custom_materials = list(/datum/material/iron=200)
+
+/obj/item/nanite_scanner/attack(mob/living/M, mob/living/carbon/human/user)
+ user.visible_message(
+ span_notice("[user] analyzes [M]'s nanites."),
+ span_notice("You analyze [M]'s nanites.")
+ )
+
+ add_fingerprint(user)
+
+ var/response = SEND_SIGNAL(M, COMSIG_NANITE_SCAN, user, TRUE)
+ if(!response)
+ to_chat(user, span_info("No nanites detected in the subject."))
+
+/obj/item/storage/box/disks_nanite
+ name = "nanite program disks box"
+ illustration = "disk_kit"
+
+/obj/item/storage/box/disks_nanite/PopulateContents()
+ for(var/i in 1 to 7)
+ new /obj/item/disk/nanite_program(src)
+
+/obj/item/nanite_injector
+ name = "nanite injector"
+ desc = "Injects nanites into the user."
+ w_class = WEIGHT_CLASS_SMALL
+ icon = 'modular_zubbers/icons/obj/devices/nanite_devices.dmi'
+ icon_state = "nanite_remote"
+
+/obj/item/nanite_injector/attack_self(mob/user)
+ user.AddComponent(/datum/component/nanites, 150)
diff --git a/modular_zubbers/code/modules/nanites/items/program_disks.dm b/modular_zubbers/code/modules/nanites/items/program_disks.dm
new file mode 100644
index 00000000000..efa6e6ad15f
--- /dev/null
+++ b/modular_zubbers/code/modules/nanites/items/program_disks.dm
@@ -0,0 +1,147 @@
+
+/obj/item/disk/nanite_program
+ name = "nanite program disk"
+ desc = "A disk capable of storing nanite programs. Can be customized using a Nanite Programming Console."
+ var/program_type
+ var/datum/nanite_program/program
+
+/obj/item/disk/nanite_program/Initialize(mapload)
+ . = ..()
+ if(program_type)
+ program = new program_type
+ update_name()
+
+/obj/item/disk/nanite_program/update_name()
+ . = ..()
+ if(!program)
+ return
+ name = "[initial(name)] \[[program.name]\]"
+
+/obj/item/disk/nanite_program/debugging
+ program_type = /datum/nanite_program/nanite_debugging
+
+/obj/item/disk/nanite_program/aggressive_replication
+ program_type = /datum/nanite_program/aggressive_replication
+
+/obj/item/disk/nanite_program/metabolic_synthesis
+ program_type = /datum/nanite_program/metabolic_synthesis
+
+/obj/item/disk/nanite_program/viral
+ program_type = /datum/nanite_program/viral
+
+/obj/item/disk/nanite_program/meltdown
+ program_type = /datum/nanite_program/meltdown
+
+/obj/item/disk/nanite_program/monitoring
+ program_type = /datum/nanite_program/monitoring
+
+/obj/item/disk/nanite_program/relay
+ program_type = /datum/nanite_program/relay
+
+/obj/item/disk/nanite_program/emp
+ program_type = /datum/nanite_program/emp
+
+/obj/item/disk/nanite_program/spreading
+ program_type = /datum/nanite_program/spreading
+
+/obj/item/disk/nanite_program/regenerative
+ program_type = /datum/nanite_program/regenerative
+
+/obj/item/disk/nanite_program/regenerative_advanced
+ program_type = /datum/nanite_program/regenerative/advanced
+
+/obj/item/disk/nanite_program/repairing
+ program_type = /datum/nanite_program/regenerative/robotic
+
+/obj/item/disk/nanite_program/temperature
+ program_type = /datum/nanite_program/temperature
+
+/obj/item/disk/nanite_program/purging
+ program_type = /datum/nanite_program/purging
+
+/obj/item/disk/nanite_program/purging_advanced
+ program_type = /datum/nanite_program/purging/advanced
+
+/obj/item/disk/nanite_program/brain_heal
+ program_type = /datum/nanite_program/brain_heal
+
+/obj/item/disk/nanite_program/brain_heal_advanced
+ program_type = /datum/nanite_program/brain_heal_advanced
+
+/obj/item/disk/nanite_program/blood_restoring
+ program_type = /datum/nanite_program/blood_restoring
+
+/obj/item/disk/nanite_program/defib
+ program_type = /datum/nanite_program/defib
+
+/obj/item/disk/nanite_program/nervous
+ program_type = /datum/nanite_program/nervous
+
+/obj/item/disk/nanite_program/hardening
+ program_type = /datum/nanite_program/dermal_armor
+
+/obj/item/disk/nanite_program/refractive
+ program_type = /datum/nanite_program/dermal_armor/refractive
+
+/obj/item/disk/nanite_program/coagulating
+ program_type = /datum/nanite_program/coagulating
+
+/obj/item/disk/nanite_program/necrotic
+ program_type = /datum/nanite_program/necrotic
+
+/obj/item/disk/nanite_program/brain_decay
+ program_type = /datum/nanite_program/brain_decay
+
+/obj/item/disk/nanite_program/pyro
+ program_type = /datum/nanite_program/pyro
+
+/obj/item/disk/nanite_program/cryo
+ program_type = /datum/nanite_program/cryo
+
+/obj/item/disk/nanite_program/toxic
+ program_type = /datum/nanite_program/toxic
+
+/obj/item/disk/nanite_program/suffocating
+ program_type = /datum/nanite_program/suffocating
+
+/obj/item/disk/nanite_program/heart_stop
+ program_type = /datum/nanite_program/heart_stop
+
+/obj/item/disk/nanite_program/explosive
+ program_type = /datum/nanite_program/explosive
+
+/obj/item/disk/nanite_program/shock
+ program_type = /datum/nanite_program/shocking
+
+/obj/item/disk/nanite_program/sleepy
+ program_type = /datum/nanite_program/sleepy
+
+/obj/item/disk/nanite_program/paralyzing
+ program_type = /datum/nanite_program/paralyzing
+
+/obj/item/disk/nanite_program/fake_death
+ program_type = /datum/nanite_program/fake_death
+
+/obj/item/disk/nanite_program/pacifying
+ program_type = /datum/nanite_program/pacifying
+
+/obj/item/disk/nanite_program/glitch
+ program_type = /datum/nanite_program/glitch
+
+/obj/item/disk/nanite_program/brain_misfire
+ program_type = /datum/nanite_program/pacifying
+
+/obj/item/disk/nanite_program/skin_decay
+ program_type = /datum/nanite_program/pacifying
+
+/obj/item/disk/nanite_program/nerve_decay
+ program_type = /datum/nanite_program/pacifying
+
+/obj/item/disk/nanite_program/conductive
+ program_type = /datum/nanite_program/pacifying
+
+/obj/item/disk/nanite_program/stun
+ program_type = /datum/nanite_program/stun
+
+/obj/item/disk/nanite_program/sting
+ program_type = /datum/nanite_program/nanite_sting
diff --git a/modular_zubbers/code/modules/nanites/items/remote.dm b/modular_zubbers/code/modules/nanites/items/remote.dm
new file mode 100644
index 00000000000..8ed634cb7cf
--- /dev/null
+++ b/modular_zubbers/code/modules/nanites/items/remote.dm
@@ -0,0 +1,247 @@
+#define REMOTE_MODE_OFF "Off"
+#define REMOTE_MODE_SELF "Local"
+#define REMOTE_MODE_TARGET "Targeted"
+#define REMOTE_MODE_AOE "Area"
+#define REMOTE_MODE_RELAY "Relay"
+
+/obj/item/nanite_remote
+ name = "nanite remote control"
+ desc = "A device that can remotely control active nanites through wireless signals."
+ w_class = WEIGHT_CLASS_SMALL
+ req_access = list(ACCESS_ROBOTICS)
+ icon = 'modular_zubbers/icons/obj/devices/nanite_devices.dmi'
+ icon_state = "nanite_remote"
+ item_flags = NOBLUDGEON
+ var/locked = FALSE //Can be locked, so it can be given to users with a set code and mode
+ var/mode = REMOTE_MODE_OFF
+ var/list/saved_settings = list()
+ var/last_id = 0
+ var/code = 0
+ var/relay_code = 0
+ var/current_program_name = "Program"
+
+/obj/item/nanite_remote/examine(mob/user)
+ . = ..()
+ if(locked)
+ . += span_notice("Alt-click to unlock.")
+
+/obj/item/nanite_remote/click_alt(mob/user)
+ . = ..()
+ if(!user.can_perform_action(src, FORBID_TELEKINESIS_REACH))
+ return
+ if(locked)
+ if(allowed(user))
+ to_chat(user, span_notice("You unlock [src]."))
+ locked = FALSE
+ update_appearance()
+ else
+ to_chat(user, span_warning("Access denied."))
+
+/obj/item/nanite_remote/emag_act(mob/user)
+ if(obj_flags & EMAGGED)
+ return
+ to_chat(user, span_warning("You override [src]'s ID lock."))
+ obj_flags |= EMAGGED
+ if(locked)
+ locked = FALSE
+ update_appearance()
+
+/obj/item/nanite_remote/update_overlays()
+ . = ..()
+ if(obj_flags & EMAGGED)
+ . += "nanite_remote_emagged"
+ if(locked)
+ . += "nanite_remote_locked"
+
+/obj/item/nanite_remote/interact_with_atom(atom/interacting_with, mob/living/user, list/modifiers)
+ return trigger_nanites(interacting_with, user, modifiers)
+
+/obj/item/nanite_remote/ranged_interact_with_atom(atom/interacting_with, mob/living/user, list/modifiers)
+ return trigger_nanites(interacting_with, user, modifiers)
+
+/obj/item/nanite_remote/proc/trigger_nanites(atom/interacting_with, mob/living/user, list/modifiers)
+ switch(mode)
+ if(REMOTE_MODE_SELF)
+ to_chat(user, span_notice("You activate [src], signaling the nanites in your bloodstream."))
+ signal_mob(user, code, key_name(user))
+ return ITEM_INTERACT_SUCCESS
+ if(REMOTE_MODE_TARGET)
+ if(!istype(interacting_with, /mob/living))
+ return NONE
+ if(isliving(interacting_with) && (get_dist(interacting_with, get_turf(src)) <= 7))
+ to_chat(user, span_notice("You activate [src], signaling the nanites inside [interacting_with]."))
+ signal_mob(interacting_with, code, key_name(user))
+ return ITEM_INTERACT_SUCCESS
+ if(REMOTE_MODE_AOE)
+ to_chat(user, span_notice("You activate [src], signaling the nanites inside every host around you."))
+ for(var/mob/living/L in view(user, 7))
+ signal_mob(L, code, key_name(user))
+ return ITEM_INTERACT_SUCCESS
+ if(REMOTE_MODE_RELAY)
+ to_chat(user, span_notice("You activate [src], signaling all connected relay nanites."))
+ signal_relay(code, relay_code, key_name(user))
+ return ITEM_INTERACT_SUCCESS
+ return NONE
+
+/obj/item/nanite_remote/proc/signal_mob(mob/living/M, code, source)
+ SEND_SIGNAL(M, COMSIG_NANITE_SIGNAL, code, source)
+
+/obj/item/nanite_remote/proc/signal_relay(code, relay_code, source)
+ for(var/X in SSnanites.nanite_relays)
+ var/datum/nanite_program/relay/N = X
+ N.relay_signal(code, relay_code, source)
+
+/obj/item/nanite_remote/ui_state(mob/user)
+ return GLOB.hands_state
+
+/obj/item/nanite_remote/ui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
+ if(!ui)
+ ui = new(user, src, "NaniteRemote", name)
+ ui.open()
+
+/obj/item/nanite_remote/ui_data()
+ var/list/data = list()
+ data["code"] = code
+ data["relay_code"] = relay_code
+ data["mode"] = mode
+ data["locked"] = locked
+ data["saved_settings"] = saved_settings
+ data["program_name"] = current_program_name
+ return data
+
+/obj/item/nanite_remote/ui_act(action, params)
+ . = ..()
+ if(.)
+ return
+ switch(action)
+ if("set_code")
+ if(locked)
+ return
+ var/new_code = text2num(params["code"])
+ if(!isnull(new_code))
+ new_code = clamp(round(new_code, 1),0,9999)
+ code = new_code
+ . = TRUE
+ if("set_relay_code")
+ if(locked)
+ return
+ var/new_code = text2num(params["code"])
+ if(!isnull(new_code))
+ new_code = clamp(round(new_code, 1),0,9999)
+ relay_code = new_code
+ . = TRUE
+ if("update_name")
+ current_program_name = params["name"]
+ . = TRUE
+ if("save")
+ if(locked)
+ return
+ var/new_save = list()
+ new_save["id"] = last_id + 1
+ last_id++
+ new_save["name"] = current_program_name
+ new_save["code"] = code
+ new_save["mode"] = mode
+ new_save["relay_code"] = relay_code
+
+ saved_settings += list(new_save)
+ . = TRUE
+ if("load")
+ var/code_id = params["save_id"]
+ var/list/setting
+ for(var/list/X in saved_settings)
+ if(X["id"] == text2num(code_id))
+ setting = X
+ break
+ if(setting)
+ code = setting["code"]
+ mode = setting["mode"]
+ relay_code = setting["relay_code"]
+ . = TRUE
+ if("remove_save")
+ if(locked)
+ return
+ var/code_id = params["save_id"]
+ for(var/list/setting in saved_settings)
+ if(setting["id"] == text2num(code_id))
+ saved_settings -= list(setting)
+ break
+ . = TRUE
+ if("select_mode")
+ if(locked)
+ return
+ mode = params["mode"]
+ . = TRUE
+ if("lock")
+ if(!(obj_flags & EMAGGED))
+ locked = TRUE
+ update_appearance()
+ . = TRUE
+
+
+/obj/item/nanite_remote/comm
+ name = "nanite communication remote"
+ desc = "A device that can send text messages to specific programs."
+ icon_state = "nanite_comm_remote"
+ var/comm_message = ""
+
+/obj/item/nanite_remote/comm/afterattack(atom/target, mob/user, etc)
+ switch(mode)
+ if(REMOTE_MODE_OFF)
+ return
+ if(REMOTE_MODE_SELF)
+ to_chat(user, span_notice("You activate [src], signaling the nanites in your bloodstream."))
+ signal_mob(user, code, key_name(user))
+ if(REMOTE_MODE_TARGET)
+ if(isliving(target) && (get_dist(target, get_turf(src)) <= 7))
+ to_chat(user, span_notice("You activate [src], signaling the nanites inside [target]."))
+ signal_mob(target, code, key_name(user))
+ if(REMOTE_MODE_AOE)
+ to_chat(user, span_notice("You activate [src], signaling the nanites inside every host around you."))
+ for(var/mob/living/L in view(user, 7))
+ signal_mob(L, code, key_name(user))
+ if(REMOTE_MODE_RELAY)
+ to_chat(user, span_notice("You activate [src], signaling all connected relay nanites."))
+ signal_relay(code, relay_code, key_name(user))
+
+/obj/item/nanite_remote/comm/signal_mob(mob/living/M, code, source)
+ SEND_SIGNAL(M, COMSIG_NANITE_COMM_SIGNAL, code, comm_message, source)
+
+/obj/item/nanite_remote/comm/signal_relay(code, relay_code, source)
+ for(var/X in SSnanites.nanite_relays)
+ var/datum/nanite_program/relay/N = X
+ N.relay_comm_signal(code, relay_code, comm_message)
+
+/obj/item/nanite_remote/comm/ui_data()
+ var/list/data = list()
+ data["comms"] = TRUE
+ data["code"] = code
+ data["relay_code"] = relay_code
+ data["message"] = comm_message
+ data["mode"] = mode
+ data["locked"] = locked
+ data["saved_settings"] = saved_settings
+ data["program_name"] = current_program_name
+
+ return data
+
+/obj/item/nanite_remote/comm/ui_act(action, params)
+ . = ..()
+ if(.)
+ return
+ switch(action)
+ if("set_message")
+ if(locked)
+ return
+ var/new_message = html_encode(params["value"])
+ if(!new_message)
+ return
+ comm_message = new_message
+ . = TRUE
+
+#undef REMOTE_MODE_OFF
+#undef REMOTE_MODE_SELF
+#undef REMOTE_MODE_TARGET
+#undef REMOTE_MODE_AOE
+#undef REMOTE_MODE_RELAY
diff --git a/modular_zubbers/code/modules/nanites/living.dm b/modular_zubbers/code/modules/nanites/living.dm
new file mode 100644
index 00000000000..3e16ae76ed6
--- /dev/null
+++ b/modular_zubbers/code/modules/nanites/living.dm
@@ -0,0 +1,7 @@
+/mob/living/proc/hud_set_nanite_indicator()
+ var/image/holder = hud_list[NANITE_HUD]
+ var/icon/I = icon(icon, icon_state, dir)
+ holder.pixel_y = I.Height() - world.icon_size
+ holder.icon_state = null
+ if(HAS_TRAIT(src, TRAIT_NANITE_MONITORING))
+ holder.icon_state = "nanite_ping"
diff --git a/modular_zubbers/code/modules/nanites/machines/nanite_chamber.dm b/modular_zubbers/code/modules/nanites/machines/nanite_chamber.dm
new file mode 100644
index 00000000000..63e425dbcc1
--- /dev/null
+++ b/modular_zubbers/code/modules/nanites/machines/nanite_chamber.dm
@@ -0,0 +1,213 @@
+/obj/machinery/nanite_chamber
+ name = "nanite chamber"
+ desc = "A device that can scan, reprogram, and inject nanites."
+ circuit = /obj/item/circuitboard/machine/nanite_chamber
+ icon = 'icons/obj/machines/bci_implanter.dmi'
+ icon_state = "bci_implanter"
+ base_icon_state = "bci_implanter"
+ layer = ABOVE_WINDOW_LAYER
+ use_power = IDLE_POWER_USE
+ anchored = TRUE
+ density = TRUE
+ idle_power_usage = 50
+ active_power_usage = 300
+
+ var/locked = FALSE
+ var/breakout_time = 1200
+ var/scan_level
+ var/busy = FALSE
+ var/busy_icon_state
+ var/busy_message
+ var/message_cooldown = 0
+
+/obj/machinery/nanite_chamber/Initialize(mapload, obj/item/circuitboard/C)
+ . = ..()
+ occupant_typecache = GLOB.typecache_living
+
+/obj/machinery/nanite_chamber/RefreshParts()
+ . = ..()
+ scan_level = 0
+ for(var/datum/stock_part/P in component_parts)
+ scan_level += P.tier
+
+/obj/machinery/nanite_chamber/examine(mob/user)
+ . = ..()
+ if(in_range(user, src) || isobserver(user))
+ . += span_notice("The status display reads: Scanning module has been upgraded to level [scan_level].")
+
+/obj/machinery/nanite_chamber/proc/set_busy(status, message, working_icon)
+ busy = status
+ busy_message = message
+ busy_icon_state = working_icon
+ update_appearance()
+
+/obj/machinery/nanite_chamber/proc/set_safety(threshold)
+ if(!occupant)
+ return
+ SEND_SIGNAL(occupant, COMSIG_NANITE_SET_SAFETY, threshold)
+
+/obj/machinery/nanite_chamber/proc/set_cloud(cloud_id)
+ if(!occupant)
+ return
+ SEND_SIGNAL(occupant, COMSIG_NANITE_SET_CLOUD, cloud_id)
+
+/obj/machinery/nanite_chamber/proc/inject_nanites()
+ if(machine_stat & (NOPOWER|BROKEN))
+ return
+ if((machine_stat & MAINT) || panel_open)
+ return
+ if(!occupant || busy)
+ return
+
+ var/locked_state = locked
+ locked = TRUE
+
+ //TODO OMINOUS MACHINE SOUNDS
+ set_busy(TRUE, "Initializing injection protocol...", "[initial(icon_state)]_raising")
+ addtimer(CALLBACK(src, .proc/set_busy, TRUE, "Analyzing host bio-structure...", "[initial(icon_state)]_active"),20)
+ addtimer(CALLBACK(src, .proc/set_busy, TRUE, "Priming nanites...", "[initial(icon_state)]_active"),40)
+ addtimer(CALLBACK(src, .proc/set_busy, TRUE, "Injecting...", "[initial(icon_state)]_active"),70)
+ addtimer(CALLBACK(src, .proc/set_busy, TRUE, "Activating nanites...", "[initial(icon_state)]_falling"),110)
+ addtimer(CALLBACK(src, .proc/complete_injection, locked_state), 130)
+
+/obj/machinery/nanite_chamber/proc/complete_injection(locked_state)
+ //TODO MACHINE DING
+ locked = locked_state
+ set_busy(FALSE)
+ if(!occupant)
+ return
+ occupant.AddComponent(/datum/component/nanites, 100)
+
+/obj/machinery/nanite_chamber/proc/remove_nanites(datum/nanite_program/NP)
+ if(machine_stat & (NOPOWER|BROKEN))
+ return
+ if((machine_stat & MAINT) || panel_open)
+ return
+ if(!occupant || busy)
+ return
+
+ var/locked_state = locked
+ locked = TRUE
+
+ //TODO OMINOUS MACHINE SOUNDS
+ set_busy(TRUE, "Initializing cleanup protocol...", "[initial(icon_state)]_raising")
+ addtimer(CALLBACK(src, .proc/set_busy, TRUE, "Analyzing host bio-structure...", "[initial(icon_state)]_active"),20)
+ addtimer(CALLBACK(src, .proc/set_busy, TRUE, "Pinging nanites...", "[initial(icon_state)]_active"),40)
+ addtimer(CALLBACK(src, .proc/set_busy, TRUE, "Initiating graceful self-destruct sequence...", "[initial(icon_state)]_active"),70)
+ addtimer(CALLBACK(src, .proc/set_busy, TRUE, "Removing debris...", "[initial(icon_state)]_falling"),110)
+ addtimer(CALLBACK(src, .proc/complete_removal, locked_state),130)
+
+/obj/machinery/nanite_chamber/proc/complete_removal(locked_state)
+ //TODO MACHINE DING
+ locked = locked_state
+ set_busy(FALSE)
+ if(!occupant)
+ return
+ SEND_SIGNAL(occupant, COMSIG_NANITE_DELETE)
+
+/obj/machinery/nanite_chamber/update_icon_state()
+ //running and someone in there
+ if(occupant)
+ icon_state = busy ? busy_icon_state : "[base_icon_state]_occupied"
+ return ..()
+ //running
+ icon_state = "[base_icon_state][state_open ? "_open" : null]"
+ return ..()
+
+/obj/machinery/nanite_chamber/update_overlays()
+ . = ..()
+
+ if((machine_stat & MAINT) || panel_open)
+ . += "maint"
+ return
+
+ if(machine_stat & (NOPOWER|BROKEN))
+ return
+
+ if(busy || locked)
+ . += "red"
+ if(locked)
+ . += "bolted"
+ return
+
+ . += "green"
+
+/obj/machinery/nanite_chamber/proc/toggle_open(mob/user)
+ if(panel_open)
+ to_chat(user, span_notice("Close the maintenance panel first."))
+ return
+
+ if(state_open)
+ close_machine()
+ return
+
+ else if(locked)
+ to_chat(user, span_notice("The bolts are locked down, securing the door shut."))
+ return
+
+ open_machine()
+
+/obj/machinery/nanite_chamber/container_resist_act(mob/living/user)
+ if(!locked)
+ open_machine()
+ return
+ if(busy)
+ return
+ user.changeNext_move(CLICK_CD_BREAKOUT)
+ user.last_special = world.time + CLICK_CD_BREAKOUT
+ user.visible_message(span_notice("You see [user] kicking against the door of [src]!"), \
+ span_notice("You lean on the back of [src] and start pushing the door open... (this will take about [DisplayTimeText(breakout_time)].)"), \
+ span_hear("You hear a metallic creaking from [src]."))
+ if(do_after(user,(breakout_time), target = src))
+ if(!user || user.stat != CONSCIOUS || user.loc != src || state_open || !locked || busy)
+ return
+ locked = FALSE
+ user.visible_message(span_warning("[user] successfully broke out of [src]!"), \
+ span_notice("You successfully break out of [src]!"))
+ open_machine()
+
+/obj/machinery/nanite_chamber/close_machine(mob/living/carbon/user, density_to_set = TRUE)
+ if(!state_open)
+ return FALSE
+
+ ..(user)
+ return TRUE
+
+/obj/machinery/nanite_chamber/open_machine(drop = TRUE, density_to_set = FALSE)
+ if(state_open)
+ return FALSE
+
+ ..()
+
+ return TRUE
+
+/obj/machinery/nanite_chamber/relaymove(mob/living/user, direction)
+ if(user.stat || locked)
+ if(message_cooldown <= world.time)
+ message_cooldown = world.time + 50
+ to_chat(user, span_warning("[src]'s door won't budge!"))
+ return
+ open_machine()
+
+/obj/machinery/nanite_chamber/attackby(obj/item/I, mob/user, params)
+ if(!occupant && default_deconstruction_screwdriver(user, icon_state, icon_state, I))//sent icon_state is irrelevant...
+ update_appearance()//..since we're updating the icon here, since the scanner can be unpowered when opened/closed
+ return
+
+ if(default_pry_open(I))
+ return
+
+ if(default_deconstruction_crowbar(I))
+ return
+
+ return ..()
+
+/obj/machinery/nanite_chamber/interact(mob/user)
+ toggle_open(user)
+
+/obj/machinery/nanite_chamber/mouse_drop_receive(atom/target, mob/user, params)
+ if(!user.can_perform_action(src, FORBID_TELEKINESIS_REACH|SILENT_ADJACENCY) || !iscarbon(target))
+ return
+ if(close_machine(target))
+ log_combat(user, target, "inserted", null, "into [src].")
+ add_fingerprint(user)
diff --git a/modular_zubbers/code/modules/nanites/machines/nanite_chamber_computer.dm b/modular_zubbers/code/modules/nanites/machines/nanite_chamber_computer.dm
new file mode 100644
index 00000000000..edec4df1511
--- /dev/null
+++ b/modular_zubbers/code/modules/nanites/machines/nanite_chamber_computer.dm
@@ -0,0 +1,109 @@
+/obj/machinery/computer/nanite_chamber_control
+ name = "nanite chamber control console"
+ desc = "Controls a connected nanite chamber. Can inoculate nanites, load programs, and analyze existing nanite swarms."
+ icon_screen = "nanite_chamber_control"
+ circuit = /obj/item/circuitboard/computer/nanite_chamber_control
+ var/obj/machinery/nanite_chamber/chamber
+
+/obj/machinery/computer/nanite_chamber_control/Initialize(mapload, obj/item/circuitboard/C)
+ . = ..()
+ find_chamber()
+
+/obj/machinery/computer/nanite_chamber_control/proc/find_chamber()
+ for(var/direction in GLOB.cardinals)
+ var/C = locate(/obj/machinery/nanite_chamber, get_step(src, direction))
+ if(C)
+ var/obj/machinery/nanite_chamber/NC = C
+ set_connected_chamber(NC)
+
+/obj/machinery/computer/nanite_chamber_control/interact()
+ if(!chamber)
+ find_chamber()
+ ..()
+
+/obj/machinery/computer/nanite_chamber_control/ui_interact(mob/user, datum/tgui/ui)
+ . = ..()
+ ui = SStgui.try_update_ui(user, src, ui)
+ if(!ui)
+ ui = new(user, src, "NaniteChamberControl", name)
+ ui.open()
+
+/obj/machinery/computer/nanite_chamber_control/ui_data()
+ var/list/data = list()
+
+ if(!chamber)
+ data["status_msg"] = "No chamber detected."
+ return data
+
+ if(!chamber.occupant)
+ data["status_msg"] = "No occupant detected."
+ return data
+
+ var/mob/living/L = chamber.occupant
+
+ if(!CAN_HAVE_NANITES(L))
+ data["status_msg"] = "Occupant not compatible with nanites."
+ return data
+
+ if(chamber.busy)
+ data["status_msg"] = chamber.busy_message
+ return data
+
+ data["status_msg"] = null
+ data["scan_level"] = chamber.scan_level
+ data["locked"] = chamber.locked
+ data["occupant_name"] = chamber.occupant.name
+
+ SEND_SIGNAL(L, COMSIG_NANITE_UI_DATA, data, chamber.scan_level)
+
+ return data
+
+/obj/machinery/computer/nanite_chamber_control/ui_act(action, params)
+ . = ..()
+ if(.)
+ return
+ switch(action)
+ if("toggle_lock")
+ chamber.locked = !chamber.locked
+ chamber.update_appearance()
+ . = TRUE
+ if("set_safety")
+ var/threshold = text2num(params["value"])
+ if(!isnull(threshold))
+ chamber.set_safety(clamp(round(threshold, 1),0,500))
+ playsound(src, "terminal_type", 25, FALSE)
+ chamber.occupant.investigate_log("'s nanites' safety threshold was set to [threshold] by [key_name(usr)] via [src] at [AREACOORD(src)].", INVESTIGATE_NANITES)
+ . = TRUE
+ if("set_cloud")
+ var/cloud_id = text2num(params["value"])
+ if(!isnull(cloud_id))
+ chamber.set_cloud(clamp(round(cloud_id, 1),0,100))
+ playsound(src, "terminal_type", 25, FALSE)
+ chamber.occupant.investigate_log("'s nanites' cloud id was set to [cloud_id] by [key_name(usr)] via [src] at [AREACOORD(src)].", INVESTIGATE_NANITES)
+ . = TRUE
+ if("connect_chamber")
+ find_chamber()
+ . = TRUE
+ if("remove_nanites")
+ playsound(src, 'sound/machines/terminal/terminal_prompt.ogg', 25, FALSE)
+ chamber.remove_nanites()
+ log_combat(usr, chamber.occupant, "cleared nanites from", null, "via [src]")
+ chamber.occupant.investigate_log("'s nanites were cleared by [key_name(usr)] via [src] at [AREACOORD(src)].", INVESTIGATE_NANITES)
+ . = TRUE
+ if("nanite_injection")
+ playsound(src, 'sound/machines/terminal/terminal_prompt.ogg', 25, FALSE)
+ chamber.inject_nanites()
+ log_combat(usr, chamber.occupant, "injected", null, "with nanites via [src]")
+ chamber.occupant.investigate_log("was injected with nanites by [key_name(usr)] via [src] at [AREACOORD(src)].", INVESTIGATE_NANITES)
+ . = TRUE
+
+/obj/machinery/computer/nanite_chamber_control/proc/set_connected_chamber(new_chamber)
+ if(chamber)
+ UnregisterSignal(chamber, COMSIG_PREQDELETED)
+ chamber = new_chamber
+ if(chamber)
+ RegisterSignal(chamber, COMSIG_PREQDELETED, .proc/react_to_chamber_del)
+
+/obj/machinery/computer/nanite_chamber_control/proc/react_to_chamber_del(datum/source)
+ SIGNAL_HANDLER
+ set_connected_chamber(null)
diff --git a/modular_zubbers/code/modules/nanites/machines/nanite_cloud_controller.dm b/modular_zubbers/code/modules/nanites/machines/nanite_cloud_controller.dm
new file mode 100644
index 00000000000..a4ea1e07de4
--- /dev/null
+++ b/modular_zubbers/code/modules/nanites/machines/nanite_cloud_controller.dm
@@ -0,0 +1,306 @@
+/obj/machinery/computer/nanite_cloud_controller
+ name = "nanite cloud controller"
+ desc = "Stores and controls nanite cloud backups."
+ icon = 'modular_zubbers/icons/obj/machines/nanite_machines.dmi'
+ icon_state = "nanite_cloud_controller"
+ circuit = /obj/item/circuitboard/computer/nanite_cloud_controller
+ icon_screen = "nanite_cloud_controller_screen"
+ icon_keyboard = null
+
+ var/obj/item/disk/nanite_program/disk
+ var/list/datum/nanite_cloud_backup/cloud_backups = list()
+ var/datum/techweb/linked_techweb
+ var/current_view = 0 //0 is the main menu, any other number is the page of the backup with that ID
+ var/new_backup_id = 1
+
+/obj/machinery/computer/nanite_cloud_controller/Initialize(mapload, obj/item/circuitboard/C)
+ . = ..()
+ if(!CONFIG_GET(flag/no_default_techweb_link) && !linked_techweb)
+ CONNECT_TO_RND_SERVER_ROUNDSTART(linked_techweb, src)
+ register_context()
+
+/obj/machinery/computer/nanite_cloud_controller/Destroy()
+ QDEL_LIST(cloud_backups) //rip backups
+ eject()
+ return ..()
+
+/obj/machinery/computer/nanite_cloud_controller/examine(mob/user)
+ . = ..()
+ . += span_notice("Use with a linked multitool to link to a techweb server.")
+
+/obj/machinery/computer/nanite_cloud_controller/add_context(atom/source, list/context, obj/item/held_item, mob/user)
+ . = ..()
+ if(isnull(held_item))
+ return
+ if(held_item.tool_behaviour == TOOL_SCREWDRIVER)
+ context[SCREENTIP_CONTEXT_LMB] = "Open Panel"
+ return CONTEXTUAL_SCREENTIP_SET
+ if(held_item.tool_behaviour == TOOL_MULTITOOL)
+ context[SCREENTIP_CONTEXT_LMB] = "Upload Techweb"
+ return CONTEXTUAL_SCREENTIP_SET
+
+/obj/machinery/computer/nanite_cloud_controller/item_interaction(mob/living/user, obj/item/tool, list/modifiers)
+ . = ..()
+ if(!istype(tool, /obj/item/disk/nanite_program))
+ return NONE
+ var/obj/item/disk/nanite_program/new_disk = tool
+ if(!user.transferItemToLoc(new_disk, src))
+ return NONE
+ to_chat(user, span_notice("You insert [new_disk] into [src]"))
+ playsound(src, 'sound/machines/terminal/terminal_insert_disc.ogg', 50, FALSE)
+ if(disk)
+ eject(user)
+ disk = new_disk
+ return ITEM_INTERACT_SUCCESS
+
+/obj/machinery/computer/nanite_cloud_controller/multitool_act(mob/living/user, obj/item/multitool/tool)
+ if(!QDELETED(tool.buffer) && istype(tool.buffer, /datum/techweb))
+ linked_techweb = tool.buffer
+ balloon_alert(user, "linked!")
+ update_static_data_for_all_viewers()
+ return TRUE
+
+/obj/machinery/computer/nanite_cloud_controller/attack_hand_secondary(mob/user, list/modifiers)
+ . = ..()
+ if(disk && user.can_perform_action(src, !issilicon(user)))
+ to_chat(user, span_notice("You take out [disk] from [src]."))
+ eject(user)
+ return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN
+
+/obj/machinery/computer/nanite_cloud_controller/proc/eject(mob/living/user)
+ if(!disk)
+ return
+ if(!istype(user) || !Adjacent(user) ||!user.put_in_active_hand(disk))
+ disk.forceMove(drop_location())
+ disk = null
+
+/obj/machinery/computer/nanite_cloud_controller/proc/get_backup(cloud_id)
+ for(var/I in cloud_backups)
+ var/datum/nanite_cloud_backup/backup = I
+ if(backup.cloud_id == cloud_id)
+ return backup
+
+/obj/machinery/computer/nanite_cloud_controller/proc/generate_backup(cloud_id, mob/user)
+ if(SSnanites.get_cloud_backup(cloud_id, TRUE))
+ to_chat(user, span_warning("Cloud ID already registered."))
+ return
+
+ var/datum/nanite_cloud_backup/backup = new(src)
+ var/datum/component/nanites/cloud_copy = backup.AddComponent(/datum/component/nanites)
+ backup.cloud_id = cloud_id
+ backup.nanites = cloud_copy
+ if(linked_techweb)
+ backup.techweb = linked_techweb
+ investigate_log("[key_name(user)] created a new nanite cloud backup with id #[cloud_id]", INVESTIGATE_NANITES)
+
+/obj/machinery/computer/nanite_cloud_controller/ui_interact(mob/user, datum/tgui/ui)
+ . = ..()
+ ui = SStgui.try_update_ui(user, src, ui)
+ if(!ui)
+ ui = new(user, src, "NaniteCloudControl", name)
+ ui.open()
+
+/obj/machinery/computer/nanite_cloud_controller/ui_static_data(mob/user)
+ . = ..()
+ var/list/data = .
+ if(linked_techweb)
+ data["techweb"] = list(
+ "name" = linked_techweb.id,
+ "organization" = linked_techweb.organization,
+ )
+
+// a lot of this should be in static data
+/obj/machinery/computer/nanite_cloud_controller/ui_data()
+ var/list/data = list()
+ if(disk)
+ data["has_disk"] = TRUE
+ var/list/disk_data = list()
+ var/datum/nanite_program/P = disk.program
+ data["has_program"] = P ? TRUE : FALSE
+ if(P)
+ disk_data["name"] = P.name
+ disk_data["desc"] = P.desc
+ disk_data["use_rate"] = P.use_rate
+ disk_data["can_trigger"] = P.can_trigger
+ disk_data["trigger_cost"] = P.trigger_cost
+ disk_data["trigger_cooldown"] = P.trigger_cooldown / 10
+
+ disk_data["activated"] = P.activated
+ disk_data["activation_code"] = P.activation_code
+ disk_data["deactivation_code"] = P.deactivation_code
+ disk_data["kill_code"] = P.kill_code
+ disk_data["trigger_code"] = P.trigger_code
+ disk_data["timer_restart"] = P.timer_restart / 10
+ disk_data["timer_shutdown"] = P.timer_shutdown / 10
+ disk_data["timer_trigger"] = P.timer_trigger / 10
+ disk_data["timer_trigger_delay"] = P.timer_trigger_delay / 10
+
+ var/list/extra_settings = P.get_extra_settings_frontend()
+ disk_data["extra_settings"] = extra_settings
+ if(LAZYLEN(extra_settings))
+ disk_data["has_extra_settings"] = TRUE
+ if(istype(P, /datum/nanite_program/sensor))
+ var/datum/nanite_program/sensor/sensor = P
+ if(sensor.can_rule)
+ disk_data["can_rule"] = TRUE
+ data["disk"] = disk_data
+ else
+ data["has_disk"] = FALSE
+
+ data["new_backup_id"] = new_backup_id
+
+ data["current_view"] = current_view
+ if(current_view)
+ var/datum/nanite_cloud_backup/backup = get_backup(current_view)
+ if(backup)
+ var/datum/component/nanites/nanites = backup.nanites
+ data["cloud_backup"] = TRUE
+ var/list/cloud_programs = list()
+ var/id = 1
+ for(var/datum/nanite_program/P in nanites.programs)
+ var/list/cloud_program = list()
+ cloud_program["name"] = P.name
+ cloud_program["desc"] = P.desc
+ cloud_program["id"] = id
+ cloud_program["use_rate"] = P.use_rate
+ cloud_program["can_trigger"] = P.can_trigger
+ cloud_program["trigger_cost"] = P.trigger_cost
+ cloud_program["trigger_cooldown"] = P.trigger_cooldown / 10
+ cloud_program["activated"] = P.activated
+ cloud_program["timer_restart"] = P.timer_restart / 10
+ cloud_program["timer_shutdown"] = P.timer_shutdown / 10
+ cloud_program["timer_trigger"] = P.timer_trigger / 10
+ cloud_program["timer_trigger_delay"] = P.timer_trigger_delay / 10
+
+ cloud_program["activation_code"] = P.activation_code
+ cloud_program["deactivation_code"] = P.deactivation_code
+ cloud_program["kill_code"] = P.kill_code
+ cloud_program["trigger_code"] = P.trigger_code
+ var/list/rules = list()
+ var/rule_id = 1
+ for(var/X in P.rules)
+ var/datum/nanite_rule/nanite_rule = X
+ var/list/rule = list()
+ rule["display"] = nanite_rule.display()
+ rule["program_id"] = id
+ rule["id"] = rule_id
+ rules += list(rule)
+ rule_id++
+ cloud_program["rules"] = rules
+ if(LAZYLEN(rules))
+ cloud_program["has_rules"] = TRUE
+ cloud_program["all_rules_required"] = P.all_rules_required
+
+ var/list/extra_settings = P.get_extra_settings_frontend()
+ cloud_program["extra_settings"] = extra_settings
+ if(LAZYLEN(extra_settings))
+ cloud_program["has_extra_settings"] = TRUE
+ id++
+ cloud_programs += list(cloud_program)
+ data["cloud_programs"] = cloud_programs
+ else
+ var/list/backup_list = list()
+ for(var/X in cloud_backups)
+ var/datum/nanite_cloud_backup/backup = X
+ var/list/cloud_backup = list()
+ cloud_backup["cloud_id"] = backup.cloud_id
+ backup_list += list(cloud_backup)
+ data["cloud_backups"] = backup_list
+ return data
+
+/obj/machinery/computer/nanite_cloud_controller/ui_act(action, params)
+ . = ..()
+ if(.)
+ return
+ switch(action)
+ if("eject")
+ eject(usr)
+ . = TRUE
+ if("set_view")
+ current_view = text2num(params["view"])
+ . = TRUE
+ if("update_new_backup_value")
+ var/backup_value = text2num(params["value"])
+ new_backup_id = backup_value
+ if("create_backup")
+ var/cloud_id = new_backup_id
+ if(!isnull(cloud_id))
+ playsound(src, 'sound/machines/terminal/terminal_prompt.ogg', 50, FALSE)
+ cloud_id = clamp(round(cloud_id, 1),1,100)
+ generate_backup(cloud_id, usr)
+ . = TRUE
+ if("delete_backup")
+ var/datum/nanite_cloud_backup/backup = get_backup(current_view)
+ if(backup)
+ playsound(src, 'sound/machines/terminal/terminal_prompt.ogg', 50, FALSE)
+ qdel(backup)
+ investigate_log("[key_name(usr)] deleted the nanite cloud backup #[current_view]", INVESTIGATE_NANITES)
+ . = TRUE
+ if("upload_program")
+ if(disk?.program)
+ var/datum/nanite_cloud_backup/backup = get_backup(current_view)
+ if(backup)
+ playsound(src, 'sound/machines/terminal/terminal_prompt.ogg', 50, FALSE)
+ var/datum/component/nanites/nanites = backup.nanites
+ nanites.add_program(null, disk.program.copy())
+ investigate_log("[key_name(usr)] uploaded program [disk.program.name] to cloud #[current_view]", INVESTIGATE_NANITES)
+ . = TRUE
+ if("remove_program")
+ var/datum/nanite_cloud_backup/backup = get_backup(current_view)
+ if(backup)
+ playsound(src, 'sound/machines/terminal/terminal_prompt.ogg', 50, FALSE)
+ var/datum/component/nanites/nanites = backup.nanites
+ var/datum/nanite_program/P = nanites.programs[text2num(params["program_id"])]
+ investigate_log("[key_name(usr)] deleted program [P.name] from cloud #[current_view]", INVESTIGATE_NANITES)
+ qdel(P)
+ . = TRUE
+ if("add_rule")
+ if(disk && disk.program && istype(disk.program, /datum/nanite_program/sensor))
+ var/datum/nanite_program/sensor/rule_template = disk.program
+ if(!rule_template.can_rule)
+ return
+ var/datum/nanite_cloud_backup/backup = get_backup(current_view)
+ if(backup)
+ playsound(src, 'sound/machines/terminal/terminal_prompt.ogg', 50, 0)
+ var/datum/component/nanites/nanites = backup.nanites
+ var/datum/nanite_program/P = nanites.programs[text2num(params["program_id"])]
+ var/datum/nanite_rule/rule = rule_template.make_rule(P)
+
+ investigate_log("[key_name(usr)] added rule [rule.display()] to program [P.name] in cloud #[current_view]", INVESTIGATE_NANITES)
+ . = TRUE
+ if("remove_rule")
+ var/datum/nanite_cloud_backup/backup = get_backup(current_view)
+ if(backup)
+ playsound(src, 'sound/machines/terminal/terminal_prompt.ogg', 50, 0)
+ var/datum/component/nanites/nanites = backup.nanites
+ var/datum/nanite_program/P = nanites.programs[text2num(params["program_id"])]
+ var/datum/nanite_rule/rule = P.rules[text2num(params["rule_id"])]
+ rule.remove()
+
+ investigate_log("[key_name(usr)] removed rule [rule.display()] from program [P.name] in cloud #[current_view]", INVESTIGATE_NANITES)
+ . = TRUE
+ if("toggle_rule_logic")
+ var/datum/nanite_cloud_backup/backup = get_backup(current_view)
+ if(backup)
+ playsound(src, 'sound/machines/terminal/terminal_prompt.ogg', 50, FALSE)
+ var/datum/component/nanites/nanites = backup.nanites
+ var/datum/nanite_program/P = nanites.programs[text2num(params["program_id"])]
+ P.all_rules_required = !P.all_rules_required
+ investigate_log("[key_name(usr)] edited rule logic for program [P.name] into [P.all_rules_required ? "All" : "Any"] in cloud #[current_view]", INVESTIGATE_NANITES)
+ . = TRUE
+
+/datum/nanite_cloud_backup
+ var/cloud_id = 0
+ var/datum/component/nanites/nanites
+ var/obj/machinery/computer/nanite_cloud_controller/storage
+ var/datum/techweb/techweb
+
+/datum/nanite_cloud_backup/New(obj/machinery/computer/nanite_cloud_controller/_storage)
+ storage = _storage
+ storage.cloud_backups += src
+ SSnanites.cloud_backups += src
+
+/datum/nanite_cloud_backup/Destroy()
+ storage.cloud_backups -= src
+ SSnanites.cloud_backups -= src
+ return ..()
diff --git a/modular_zubbers/code/modules/nanites/machines/nanite_program_hub.dm b/modular_zubbers/code/modules/nanites/machines/nanite_program_hub.dm
new file mode 100644
index 00000000000..4b3ec8d3a72
--- /dev/null
+++ b/modular_zubbers/code/modules/nanites/machines/nanite_program_hub.dm
@@ -0,0 +1,181 @@
+/obj/machinery/nanite_program_hub
+ name = "nanite program hub"
+ desc = "Compiles nanite programs from the techweb servers and downloads them into disks."
+ icon = 'modular_zubbers/icons/obj/machines/nanite_machines.dmi'
+ icon_state = "nanite_program_hub"
+ use_power = IDLE_POWER_USE
+ anchored = TRUE
+ density = TRUE
+ circuit = /obj/item/circuitboard/machine/nanite_program_hub
+
+ var/obj/item/disk/nanite_program/disk
+ var/datum/techweb/linked_techweb
+ var/current_category = "Main"
+ var/detail_view = TRUE
+ var/categories = list(
+ list(name = "Utility Nanites"),
+ list(name = "Medical Nanites"),
+ list(name = "Sensor Nanites"),
+ list(name = "Augmentation Nanites"),
+ list(name = "Suppression Nanites"),
+ list(name = "Weaponized Nanites"),
+ list(name = "Protocols"),
+ )
+
+/obj/machinery/nanite_program_hub/Initialize(mapload, obj/item/circuitboard/C)
+ . = ..()
+ if(!CONFIG_GET(flag/no_default_techweb_link) && !linked_techweb)
+ CONNECT_TO_RND_SERVER_ROUNDSTART(linked_techweb, src)
+ register_context()
+
+/obj/machinery/nanite_program_hub/examine(mob/user)
+ . = ..()
+ . += span_notice("Use with a linked multitool to link to a techweb server.")
+
+/obj/machinery/nanite_program_hub/add_context(atom/source, list/context, obj/item/held_item, mob/user)
+ . = ..()
+ if(isnull(held_item))
+ return
+ if(held_item.tool_behaviour == TOOL_SCREWDRIVER)
+ context[SCREENTIP_CONTEXT_LMB] = "Open Panel"
+ return CONTEXTUAL_SCREENTIP_SET
+ if(held_item.tool_behaviour == TOOL_MULTITOOL)
+ context[SCREENTIP_CONTEXT_LMB] = "Upload Techweb"
+ return CONTEXTUAL_SCREENTIP_SET
+
+/obj/machinery/nanite_program_hub/update_overlays()
+ . = ..()
+ if((machine_stat & (NOPOWER|MAINT|BROKEN)) || panel_open)
+ return
+ . += "nanite_program_hub_on"
+ . += emissive_appearance(icon, "nanite_program_hub_on", src, alpha = src.alpha)
+
+/obj/machinery/nanite_program_hub/item_interaction(mob/living/user, obj/item/tool, list/modifiers)
+ . = ..()
+ if(!istype(tool, /obj/item/disk/nanite_program))
+ return NONE
+ var/obj/item/disk/nanite_program/new_disk = tool
+ if(!user.transferItemToLoc(new_disk, src))
+ return NONE
+ to_chat(user, span_notice("You insert [new_disk] into [src]"))
+ playsound(src, 'sound/machines/terminal/terminal_insert_disc.ogg', 50, FALSE)
+ if(disk)
+ eject(user)
+ disk = new_disk
+ return ITEM_INTERACT_SUCCESS
+
+/obj/machinery/nanite_program_hub/multitool_act(mob/living/user, obj/item/multitool/tool)
+ if(!QDELETED(tool.buffer) && istype(tool.buffer, /datum/techweb))
+ linked_techweb = tool.buffer
+ balloon_alert(user, "linked!")
+ update_static_data_for_all_viewers()
+ return TRUE
+
+/obj/machinery/nanite_program_hub/screwdriver_act(mob/living/user, obj/item/I)
+ if(..())
+ return TRUE
+
+ return default_deconstruction_screwdriver(user, "nanite_program_hub_t", "nanite_program_hub", I)
+
+/obj/machinery/nanite_program_hub/crowbar_act(mob/living/user, obj/item/I)
+ if(..())
+ return TRUE
+
+ return default_deconstruction_crowbar(I)
+
+/obj/machinery/nanite_program_hub/proc/eject(mob/living/user)
+ if(!disk)
+ return
+ if(!istype(user) || !Adjacent(user) || !user.put_in_active_hand(disk))
+ disk.forceMove(drop_location())
+ disk = null
+
+/obj/machinery/nanite_program_hub/attack_hand_secondary(mob/user, list/modifiers)
+ . = ..()
+ if(disk && user.can_perform_action(src, !issilicon(user)))
+ to_chat(user, span_notice("You take out [disk] from [src]."))
+ eject(user)
+ return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN
+
+/obj/machinery/nanite_program_hub/ui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
+ if(!ui)
+ ui = new(user, src, "NaniteProgramHub", name)
+ ui.open()
+
+/obj/machinery/nanite_program_hub/ui_data()
+ var/list/data = list()
+ if(disk)
+ data["has_disk"] = TRUE
+ var/list/disk_data = list()
+ var/datum/nanite_program/P = disk.program
+ if(P)
+ data["has_program"] = TRUE
+ disk_data["name"] = P.name
+ disk_data["desc"] = P.desc
+ data["disk"] = disk_data
+ else
+ data["has_disk"] = FALSE
+
+ data["detail_view"] = detail_view
+
+ return data
+
+/obj/machinery/nanite_program_hub/ui_static_data(mob/user)
+ var/list/data = list()
+ if(linked_techweb)
+ data["techweb"] = list(
+ "name" = linked_techweb.id,
+ "organization" = linked_techweb.organization,
+ )
+ data["programs"] = list()
+ for(var/i in linked_techweb.researched_designs)
+ var/datum/design/nanites/D = SSresearch.techweb_design_by_id(i)
+ if(!istype(D))
+ continue
+ var/cat_name = D.category[1] //just put them in the first category fuck it
+ if(isnull(data["programs"][cat_name]))
+ data["programs"][cat_name] = list()
+ var/list/program_design = list()
+ program_design["id"] = D.id
+ program_design["name"] = D.name
+ program_design["desc"] = D.desc
+ data["programs"][cat_name] += list(program_design)
+
+ if(!length(data["programs"]))
+ data["programs"] = null
+
+ return data
+
+/obj/machinery/nanite_program_hub/ui_act(action, params)
+ . = ..()
+ if(.)
+ return
+ switch(action)
+ if("eject")
+ eject(usr)
+ . = TRUE
+ if("download")
+ if(!disk)
+ return
+ var/datum/design/nanites/downloaded = linked_techweb.isDesignResearchedID(params["program_id"]) //check if it's a valid design
+ if(!istype(downloaded))
+ return
+ if(disk.program)
+ qdel(disk.program)
+ disk.program = new downloaded.program_type
+ disk.update_name()
+ playsound(src, 'sound/machines/terminal/terminal_prompt.ogg', 25, FALSE)
+ . = TRUE
+ if("refresh")
+ update_static_data(usr)
+ . = TRUE
+ if("toggle_details")
+ detail_view = !detail_view
+ . = TRUE
+ if("clear")
+ if(disk?.program)
+ qdel(disk.program)
+ disk.program = null
+ disk.name = initial(disk.name)
+ . = TRUE
diff --git a/modular_zubbers/code/modules/nanites/machines/nanite_programmer.dm b/modular_zubbers/code/modules/nanites/machines/nanite_programmer.dm
new file mode 100644
index 00000000000..979a89f7988
--- /dev/null
+++ b/modular_zubbers/code/modules/nanites/machines/nanite_programmer.dm
@@ -0,0 +1,178 @@
+/obj/machinery/nanite_programmer
+ name = "nanite programmer"
+ desc = "A device that can edit nanite program disks to adjust their functionality."
+ icon = 'modular_zubbers/icons/obj/machines/nanite_machines.dmi'
+ icon_state = "nanite_programmer"
+ use_power = IDLE_POWER_USE
+ anchored = TRUE
+ density = TRUE
+ circuit = /obj/item/circuitboard/machine/nanite_programmer
+ var/obj/item/disk/nanite_program/disk
+ var/datum/nanite_program/program
+
+/obj/machinery/nanite_programmer/Initialize(mapload, obj/item/circuitboard/C)
+ . = ..()
+ become_hearing_sensitive(trait_source = ROUNDSTART_TRAIT)
+
+/obj/machinery/nanite_programmer/update_overlays()
+ . = ..()
+ if((machine_stat & (NOPOWER|MAINT|BROKEN)) || panel_open)
+ return
+ . += "nanite_programmer_on"
+ . += emissive_appearance(icon, "nanite_programmer_on", src, alpha = src.alpha)
+
+/obj/machinery/nanite_programmer/item_interaction(mob/living/user, obj/item/tool, list/modifiers)
+ . = ..()
+ if(!istype(tool, /obj/item/disk/nanite_program))
+ return NONE
+ var/obj/item/disk/nanite_program/new_disk = tool
+ if(!user.transferItemToLoc(new_disk, src))
+ return NONE
+ to_chat(user, span_notice("You insert [new_disk] into [src]"))
+ playsound(src, 'sound/machines/terminal/terminal_insert_disc.ogg', 50, FALSE)
+ if(disk)
+ eject(user)
+ disk = new_disk
+ program = new_disk.program
+ return ITEM_INTERACT_SUCCESS
+
+/obj/machinery/nanite_programmer/screwdriver_act(mob/living/user, obj/item/I)
+ if(..())
+ return TRUE
+
+ return default_deconstruction_screwdriver(user, "nanite_programmer_t", "nanite_programmer", I)
+
+/obj/machinery/nanite_programmer/crowbar_act(mob/living/user, obj/item/I)
+ if(..())
+ return TRUE
+
+ return default_deconstruction_crowbar(I)
+
+/obj/machinery/nanite_programmer/proc/eject(mob/living/user)
+ if(!disk)
+ return
+ if(!istype(user) || !Adjacent(user) || !user.put_in_active_hand(disk))
+ disk.forceMove(drop_location())
+ disk = null
+ program = null
+
+/obj/machinery/nanite_programmer/attack_hand_secondary(mob/user, list/modifiers)
+ . = ..()
+ if(disk && user.can_perform_action(src, !issilicon(user)))
+ to_chat(user, span_notice("You take out [disk] from [src]."))
+ eject(user)
+ return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN
+
+/obj/machinery/nanite_programmer/ui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
+ if(!ui)
+ ui = new(user, src, "NaniteProgrammer", name)
+ ui.open()
+
+/obj/machinery/nanite_programmer/ui_data()
+ var/list/data = list()
+ data["has_disk"] = istype(disk)
+ data["has_program"] = istype(program)
+ if(program)
+ data["name"] = program.name
+ data["desc"] = program.desc
+ data["use_rate"] = program.use_rate
+ data["can_trigger"] = program.can_trigger
+ data["trigger_cost"] = program.trigger_cost
+ data["trigger_cooldown"] = program.trigger_cooldown / 10
+
+ data["activated"] = program.activated
+ data["activation_code"] = program.activation_code
+ data["deactivation_code"] = program.deactivation_code
+ data["kill_code"] = program.kill_code
+ data["trigger_code"] = program.trigger_code
+ data["timer_restart"] = program.timer_restart / 10
+ data["timer_shutdown"] = program.timer_shutdown / 10
+ data["timer_trigger"] = program.timer_trigger / 10
+ data["timer_trigger_delay"] = program.timer_trigger_delay / 10
+
+ var/list/extra_settings = program.get_extra_settings_frontend()
+ data["extra_settings"] = extra_settings
+ if(LAZYLEN(extra_settings))
+ data["has_extra_settings"] = TRUE
+
+ return data
+
+/obj/machinery/nanite_programmer/ui_act(action, params)
+ . = ..()
+ if(.)
+ return
+ switch(action)
+ if("eject")
+ eject(usr)
+ . = TRUE
+ if("toggle_active")
+ playsound(src, "terminal_type", 25, FALSE)
+ program.activated = !program.activated //we don't use the activation procs since we aren't in a mob
+ investigate_log("[key_name(usr)] edited [program.name]'s initial activation status into [program.activated ? "Activated" : "Deactivated"]", INVESTIGATE_NANITES)
+ . = TRUE
+ if("set_code")
+ var/new_code = text2num(params["code"])
+ playsound(src, "terminal_type", 25, FALSE)
+ var/target_code = params["target_code"]
+ switch(target_code)
+ if("activation")
+ program.activation_code = clamp(round(new_code, 1),0,9999)
+ investigate_log("[key_name(usr)] edited [program.name]'s activation code into [program.activation_code]", INVESTIGATE_NANITES)
+ if("deactivation")
+ program.deactivation_code = clamp(round(new_code, 1),0,9999)
+ investigate_log("[key_name(usr)] edited [program.name]'s deactivation code into [program.deactivation_code]", INVESTIGATE_NANITES)
+ if("kill")
+ program.kill_code = clamp(round(new_code, 1),0,9999)
+ investigate_log("[key_name(usr)] edited [program.name]'s kill code into [program.kill_code]", INVESTIGATE_NANITES)
+ if("trigger")
+ program.trigger_code = clamp(round(new_code, 1),0,9999)
+ investigate_log("[key_name(usr)] edited [program.name]'s trigger code into [program.trigger_code]", INVESTIGATE_NANITES)
+ . = TRUE
+ if("set_extra_setting")
+ program.set_extra_setting(params["target_setting"], params["value"])
+ investigate_log("[key_name(usr)] edited [program.name]'s extra setting '[params["target_setting"]]' into [params["value"]]", INVESTIGATE_NANITES)
+ playsound(src, "terminal_type", 25, FALSE)
+ . = TRUE
+ if("set_restart_timer")
+ var/timer = text2num(params["delay"])
+ if(!isnull(timer))
+ playsound(src, "terminal_type", 25, FALSE)
+ timer = clamp(round(timer, 1), 0, 3600)
+ timer *= 10 //convert to deciseconds
+ program.timer_restart = timer
+ investigate_log("[key_name(usr)] edited [program.name]'s restart timer into [timer/10] s", INVESTIGATE_NANITES)
+ . = TRUE
+ if("set_shutdown_timer")
+ var/timer = text2num(params["delay"])
+ if(!isnull(timer))
+ playsound(src, "terminal_type", 25, FALSE)
+ timer = clamp(round(timer, 1), 0, 3600)
+ timer *= 10 //convert to deciseconds
+ program.timer_shutdown = timer
+ investigate_log("[key_name(usr)] edited [program.name]'s shutdown timer into [timer/10] s", INVESTIGATE_NANITES)
+ . = TRUE
+ if("set_trigger_timer")
+ var/timer = text2num(params["delay"])
+ if(!isnull(timer))
+ playsound(src, "terminal_type", 25, FALSE)
+ timer = clamp(round(timer, 1), 0, 3600)
+ timer *= 10 //convert to deciseconds
+ program.timer_trigger = timer
+ investigate_log("[key_name(usr)] edited [program.name]'s trigger timer into [timer/10] s", INVESTIGATE_NANITES)
+ . = TRUE
+ if("set_timer_trigger_delay")
+ var/timer = text2num(params["delay"])
+ if(!isnull(timer))
+ playsound(src, "terminal_type", 25, FALSE)
+ timer = clamp(round(timer, 1), 0, 3600)
+ timer *= 10 //convert to deciseconds
+ program.timer_trigger_delay = timer
+ investigate_log("[key_name(usr)] edited [program.name]'s trigger delay timer into [timer/10] s", INVESTIGATE_NANITES)
+ . = TRUE
+
+/obj/machinery/nanite_programmer/Hear(message, atom/movable/speaker, message_language, raw_message, radio_freq, list/spans, list/message_mods = list(), message_range=0)
+ . = ..()
+ var/static/regex/when = regex("(?:^\\W*when|when\\W*$)", "i") //starts or ends with when
+ if(findtext(raw_message, when) && !istype(speaker, /obj/machinery/nanite_programmer))
+ say("When you code it!!")
diff --git a/modular_zubbers/code/modules/nanites/machines/public_chamber.dm b/modular_zubbers/code/modules/nanites/machines/public_chamber.dm
new file mode 100644
index 00000000000..09904de4246
--- /dev/null
+++ b/modular_zubbers/code/modules/nanites/machines/public_chamber.dm
@@ -0,0 +1,207 @@
+/obj/machinery/public_nanite_chamber
+ name = "public nanite chamber"
+ desc = "A device that can rapidly implant cloud-synced nanites without an external operator."
+ circuit = /obj/item/circuitboard/machine/public_nanite_chamber
+ icon = 'icons/obj/machines/bci_implanter.dmi'
+ icon_state = "bci_implanter"
+ base_icon_state = "bci_implanter"
+ layer = ABOVE_WINDOW_LAYER
+ use_power = IDLE_POWER_USE
+ anchored = TRUE
+ density = TRUE
+ idle_power_usage = 50
+ active_power_usage = 300
+
+ var/cloud_id = 1
+ var/locked = FALSE
+ var/breakout_time = 1200
+ var/busy = FALSE
+ var/busy_icon_state
+ var/message_cooldown = 0
+
+/obj/machinery/public_nanite_chamber/Initialize(mapload, obj/item/circuitboard/C)
+ . = ..()
+ occupant_typecache = GLOB.typecache_living
+
+/obj/machinery/public_nanite_chamber/RefreshParts()
+ . = ..()
+ var/obj/item/circuitboard/machine/public_nanite_chamber/board = circuit
+ if(board)
+ cloud_id = board.cloud_id
+
+/obj/machinery/public_nanite_chamber/proc/set_busy(status, working_icon)
+ busy = status
+ busy_icon_state = working_icon
+ update_appearance()
+
+/obj/machinery/public_nanite_chamber/proc/inject_nanites(mob/living/attacker)
+ if(machine_stat & (NOPOWER|BROKEN))
+ return
+ if((machine_stat & MAINT) || panel_open)
+ return
+ if(!occupant || busy)
+ return
+
+ var/locked_state = locked
+ locked = TRUE
+
+ //TODO OMINOUS MACHINE SOUNDS
+ set_busy(TRUE, "[initial(icon_state)]_raising")
+ addtimer(CALLBACK(src, .proc/set_busy, TRUE, "[initial(icon_state)]_active"),20)
+ addtimer(CALLBACK(src, .proc/set_busy, TRUE, "[initial(icon_state)]_falling"),60)
+ addtimer(CALLBACK(src, .proc/complete_injection, locked_state, attacker),80)
+
+/obj/machinery/public_nanite_chamber/proc/complete_injection(locked_state, mob/living/attacker)
+ //TODO MACHINE DING
+ locked = locked_state
+ set_busy(FALSE)
+ if(!occupant)
+ return
+ if(attacker)
+ occupant.investigate_log("was injected with nanites with cloud ID [cloud_id] by [key_name(attacker)] using [src] at [AREACOORD(src)].", INVESTIGATE_NANITES)
+ log_combat(attacker, occupant, "injected", null, "with nanites via [src]")
+ occupant.AddComponent(/datum/component/nanites, 75, cloud_id)
+
+/obj/machinery/public_nanite_chamber/proc/change_cloud(mob/living/attacker)
+ if(machine_stat & (NOPOWER|BROKEN))
+ return
+ if((machine_stat & MAINT) || panel_open)
+ return
+ if(!occupant || busy)
+ return
+
+ var/locked_state = locked
+ locked = TRUE
+
+ set_busy(TRUE, "[initial(icon_state)]_raising")
+ addtimer(CALLBACK(src, .proc/set_busy, TRUE, "[initial(icon_state)]_active"),20)
+ addtimer(CALLBACK(src, .proc/set_busy, TRUE, "[initial(icon_state)]_falling"),40)
+ addtimer(CALLBACK(src, .proc/complete_cloud_change, locked_state, attacker),60)
+
+/obj/machinery/public_nanite_chamber/proc/complete_cloud_change(locked_state, mob/living/attacker)
+ locked = locked_state
+ set_busy(FALSE)
+ if(!occupant)
+ return
+ if(attacker)
+ occupant.investigate_log("had their nanite cloud ID changed into [cloud_id] by [key_name(attacker)] using [src] at [AREACOORD(src)].", INVESTIGATE_NANITES)
+ SEND_SIGNAL(occupant, COMSIG_NANITE_SET_CLOUD, cloud_id)
+
+/obj/machinery/public_nanite_chamber/update_icon_state()
+ //running and someone in there
+ if(occupant)
+ icon_state = busy ? busy_icon_state : "[base_icon_state]_occupied"
+ return ..()
+ //running
+ icon_state = "[base_icon_state][state_open ? "_open" : null]"
+ return ..()
+
+/obj/machinery/public_nanite_chamber/update_overlays()
+ . = ..()
+ if((machine_stat & MAINT) || panel_open)
+ . += "maint"
+ return
+ if(machine_stat & (NOPOWER|BROKEN))
+ return
+
+ if(busy || locked)
+ . += "red"
+ if(locked)
+ . += "bolted"
+ return
+ . += "green"
+
+/obj/machinery/public_nanite_chamber/proc/toggle_open(mob/user)
+ if(panel_open)
+ to_chat(user, span_notice("Close the maintenance panel first."))
+ return
+
+ if(state_open)
+ close_machine(null, user)
+ return
+
+ else if(locked)
+ to_chat(user, span_notice("The bolts are locked down, securing the door shut."))
+ return
+
+ open_machine()
+
+/obj/machinery/public_nanite_chamber/container_resist_act(mob/living/user)
+ if(!locked)
+ open_machine()
+ return
+ if(busy)
+ return
+ user.changeNext_move(CLICK_CD_BREAKOUT)
+ user.last_special = world.time + CLICK_CD_BREAKOUT
+ user.visible_message(span_notice("You see [user] kicking against the door of [src]!"), \
+ span_notice("You lean on the back of [src] and start pushing the door open... (this will take about [DisplayTimeText(breakout_time)].)"), \
+ span_hear("You hear a metallic creaking from [src]."))
+ if(do_after(user,(breakout_time), target = src))
+ if(!user || user.stat != CONSCIOUS || user.loc != src || state_open || !locked || busy)
+ return
+ locked = FALSE
+ user.visible_message(span_warning("[user] successfully broke out of [src]!"), \
+ span_notice("You successfully break out of [src]!"))
+ open_machine()
+
+/obj/machinery/public_nanite_chamber/close_machine(mob/living/carbon/user, density_to_set = TRUE)
+ if(!state_open)
+ return FALSE
+
+ ..()
+
+ . = TRUE
+
+ addtimer(CALLBACK(src, .proc/try_inject_nanites, user), 3 SECONDS) //If someone is shoved in give them a chance to get out before the injection starts
+
+/obj/machinery/public_nanite_chamber/proc/try_inject_nanites(mob/living/attacker)
+ if(occupant)
+ var/mob/living/L = occupant
+ if(SEND_SIGNAL(L, COMSIG_HAS_NANITES))
+ var/datum/component/nanites/nanites = L.GetComponent(/datum/component/nanites)
+ if(nanites && nanites.cloud_id != cloud_id)
+ change_cloud(attacker)
+ return
+ if(CAN_HAVE_NANITES(L))
+ inject_nanites(attacker)
+
+/obj/machinery/public_nanite_chamber/open_machine(drop = TRUE, density_to_set = FALSE)
+ . = ..()
+ if(state_open)
+ return FALSE
+
+ ..()
+
+ return TRUE
+
+/obj/machinery/public_nanite_chamber/relaymove(mob/living/user, direction)
+ if(user.stat || locked)
+ if(message_cooldown <= world.time)
+ message_cooldown = world.time + 50
+ to_chat(user, span_warning("[src]'s door won't budge!"))
+ return
+ open_machine()
+
+/obj/machinery/public_nanite_chamber/attackby(obj/item/I, mob/user, params)
+ if(!occupant && default_deconstruction_screwdriver(user, icon_state, icon_state, I))//sent icon_state is irrelevant...
+ update_appearance()//..since we're updating the icon here, since the scanner can be unpowered when opened/closed
+ return
+
+ if(default_pry_open(I))
+ return
+
+ if(default_deconstruction_crowbar(I))
+ return
+
+ return ..()
+
+/obj/machinery/public_nanite_chamber/interact(mob/user)
+ toggle_open(user)
+
+/obj/machinery/public_nanite_chamber/mouse_drop_receive(mob/living/M, mob/user, params)
+ if(!user.can_perform_action(src, FORBID_TELEKINESIS_REACH | SILENT_ADJACENCY) || !iscarbon(M))
+ return
+ if(close_machine(M, user))
+ log_combat(user, M, "inserted", null, "into [src].")
+ add_fingerprint(user)
diff --git a/modular_zubbers/code/modules/nanites/nanite_programs.dm b/modular_zubbers/code/modules/nanites/nanite_programs.dm
new file mode 100644
index 00000000000..0b26b4e79f0
--- /dev/null
+++ b/modular_zubbers/code/modules/nanites/nanite_programs.dm
@@ -0,0 +1,335 @@
+/datum/nanite_program
+ var/name = "Generic Nanite Program"
+ var/desc = "Warn a coder if you can read this."
+
+ var/datum/component/nanites/nanites
+ var/mob/living/host_mob
+
+ var/use_rate = 0 //Amount of nanites used while active
+ var/unique = TRUE //If there can be more than one copy in the same nanites
+ var/can_trigger = FALSE //If the nanites have a trigger function (used for the programming UI)
+ var/trigger_cost = 0 //Amount of nanites required to trigger
+ var/trigger_cooldown = 50 //Deciseconds required between each trigger activation
+ var/next_trigger = 0 //World time required for the next trigger activation
+
+ var/program_flags = NONE
+ var/passive_enabled = FALSE //If the nanites have an on/off-style effect, it's tracked by this var
+
+ var/list/rogue_types = list(/datum/nanite_program/glitch) //What this can turn into if it glitches.
+ //As a rule of thumb, these should be:
+ //A: simpler
+ //B: negative
+ //C: affecting the same parts of the body, roughly
+ //B is mostly a consequence of A: it's always going to be simpler to cause damage than to repair it, so a software bug will not randomly make the flesh eating
+ //nanites learn how to repair cells.
+ //Given enough glitch-swapping you'll end up with stuff like necrotic or toxic nanites, which are very simple as they just try to eat what's in front of them
+ //or just lie around polluting the blood
+
+
+ //The following vars are customizable
+ var/activated = TRUE //If FALSE, the program won't process, disables passive effects, can't trigger and doesn't consume nanites
+
+ var/timer_restart = 0 //When deactivated, the program will wait X deciseconds before self-reactivating. Also works if the program begins deactivated.
+ var/timer_shutdown = 0 //When activated, the program will wait X deciseconds before self-deactivating. Also works if the program begins activated.
+ var/timer_trigger = 0 //[Trigger only] While active, the program will attempt to trigger once every x deciseconds.
+ var/timer_trigger_delay = 0 //[Trigger only] While active, the program will delay trigger signals by X deciseconds.
+
+ //Indicates the next world.time tick where these timers will act
+ var/timer_restart_next = 0
+ var/timer_shutdown_next = 0
+ var/timer_trigger_next = 0
+ var/timer_trigger_delay_next = 0
+
+ //Signal codes, these handle remote input to the nanites. If set to 0 they'll ignore signals.
+ var/activation_code = 0 //Code that activates the program [1-9999]
+ var/deactivation_code = 0 //Code that deactivates the program [1-9999]
+ var/kill_code = 0 //Code that permanently removes the program [1-9999]
+ var/trigger_code = 0 //Code that triggers the program (if available) [1-9999]
+
+ //Extra settings
+ ///Don't ever override this or I will come to your house and stand menacingly behind a bush
+ VAR_FINAL/list/extra_settings = list()
+
+ //Rules
+ //Rules that automatically manage if the program's active without requiring separate sensor programs
+ var/list/datum/nanite_rule/rules = list()
+ var/all_rules_required = TRUE //Whether all rules are required for positive condition or any of specified
+
+/datum/nanite_program/New()
+ . = ..()
+ register_extra_settings()
+
+/datum/nanite_program/Destroy()
+ extra_settings = null
+ if(host_mob)
+ if(activated)
+ deactivate()
+ if(passive_enabled)
+ disable_passive_effect()
+ on_mob_remove()
+ if(nanites)
+ nanites.programs -= src
+ for(var/datum/nanite_rule/rule as anything in rules)
+ rule.remove()
+ rules.Cut()
+ return ..()
+
+/datum/nanite_program/proc/copy()
+ var/datum/nanite_program/new_program = new type()
+ copy_programming(new_program, TRUE)
+
+ return new_program
+
+/datum/nanite_program/proc/copy_programming(datum/nanite_program/target, copy_activated = TRUE)
+ if(copy_activated)
+ target.activated = activated
+ target.timer_restart = timer_restart
+ target.timer_shutdown = timer_shutdown
+ target.timer_trigger = timer_trigger
+ target.timer_trigger_delay = timer_trigger_delay
+ target.activation_code = activation_code
+ target.deactivation_code = deactivation_code
+ target.kill_code = kill_code
+ target.trigger_code = trigger_code
+
+ target.rules = list()
+ for(var/R in rules)
+ var/datum/nanite_rule/rule = R
+ rule.copy_to(target)
+ target.all_rules_required = all_rules_required
+
+ if(istype(target,src))
+ copy_extra_settings_to(target)
+
+/// Register extra settings by overriding this.
+///
+/// `extra_settings[name] = new typepath()` for each extra setting.
+/datum/nanite_program/proc/register_extra_settings()
+ return
+
+///You can override this if you need to have special behavior after setting certain settings.
+/datum/nanite_program/proc/set_extra_setting(setting, value)
+ var/datum/nanite_extra_setting/ES = extra_settings[setting]
+ return ES.set_value(value)
+
+///You probably shouldn't be overriding this one, but I'm not a cop.
+/datum/nanite_program/proc/get_extra_setting_value(setting)
+ var/datum/nanite_extra_setting/ES = extra_settings[setting]
+ return ES.get_value()
+
+///Used for getting information about the extra settings to the frontend
+/datum/nanite_program/proc/get_extra_settings_frontend()
+ var/list/out = list()
+ for(var/name in extra_settings)
+ var/datum/nanite_extra_setting/ES = extra_settings[name]
+ out += ES.get_frontend_list(name)
+ return out
+
+///Copy of the list instead of direct reference for obvious reasons
+/datum/nanite_program/proc/copy_extra_settings_to(datum/nanite_program/target)
+ var/list/copy_list = list()
+ for(var/ns_name in extra_settings)
+ var/datum/nanite_extra_setting/extra_setting = extra_settings[ns_name]
+ copy_list[ns_name] = extra_setting.get_copy()
+ target.extra_settings = copy_list
+
+/datum/nanite_program/proc/on_add(datum/component/nanites/_nanites)
+ nanites = _nanites
+ if(nanites.host_mob)
+ on_mob_add()
+
+/datum/nanite_program/proc/on_mob_add()
+ host_mob = nanites.host_mob
+ if(activated) //apply activation effects depending on initial status; starts the restart and shutdown timers
+ activate()
+ else
+ deactivate()
+
+/datum/nanite_program/proc/on_mob_remove()
+ return
+
+/datum/nanite_program/proc/toggle()
+ if(!activated)
+ activate()
+ else
+ deactivate()
+
+/datum/nanite_program/proc/activate()
+ activated = TRUE
+ if(timer_shutdown)
+ timer_shutdown_next = world.time + timer_shutdown
+
+/datum/nanite_program/proc/deactivate()
+ if(passive_enabled)
+ disable_passive_effect()
+ activated = FALSE
+ if(timer_restart)
+ timer_restart_next = world.time + timer_restart
+
+/datum/nanite_program/proc/on_process()
+ if(!activated)
+ if(timer_restart_next && world.time > timer_restart_next)
+ activate()
+ timer_restart_next = 0
+ return
+
+ if(timer_shutdown_next && world.time > timer_shutdown_next)
+ deactivate()
+ timer_shutdown_next = 0
+ return
+
+ if(timer_trigger && world.time > timer_trigger_next)
+ trigger()
+ timer_trigger_next = world.time + timer_trigger
+ return
+
+ if(timer_trigger_delay_next && world.time > timer_trigger_delay_next)
+ trigger(delayed = TRUE)
+ timer_trigger_delay_next = 0
+ return
+
+ if(check_conditions() && consume_nanites(use_rate))
+ if(!passive_enabled)
+ enable_passive_effect()
+ active_effect()
+ else
+ if(passive_enabled)
+ disable_passive_effect()
+
+//If false, disables active, passive effects, and triggers without consuming nanites
+//Can be used to avoid consuming nanites for nothing
+/datum/nanite_program/proc/check_conditions()
+ if (!LAZYLEN(rules))
+ return TRUE
+ for(var/R in rules)
+ var/datum/nanite_rule/rule = R
+ if(!all_rules_required && rule.check_rule())
+ return TRUE
+ if(all_rules_required && !rule.check_rule())
+ return FALSE
+ return all_rules_required ? TRUE : FALSE
+
+//Constantly procs as long as the program is active
+/datum/nanite_program/proc/active_effect()
+ return
+
+//Procs once when the program activates
+/datum/nanite_program/proc/enable_passive_effect()
+ passive_enabled = TRUE
+
+//Procs once when the program deactivates
+/datum/nanite_program/proc/disable_passive_effect()
+ passive_enabled = FALSE
+
+//Checks conditions then fires the nanite trigger effect
+/datum/nanite_program/proc/trigger(delayed = FALSE, comm_message)
+ if(!can_trigger)
+ return
+ if(!activated)
+ return
+ if(timer_trigger_delay && !delayed)
+ timer_trigger_delay_next = world.time + timer_trigger_delay
+ return
+ if(world.time < next_trigger)
+ return
+ if(!check_conditions())
+ return
+ if(!consume_nanites(trigger_cost))
+ return
+ next_trigger = world.time + trigger_cooldown
+ on_trigger(comm_message)
+
+//Nanite trigger effect, requires can_trigger to be used
+/datum/nanite_program/proc/on_trigger(comm_message)
+ return
+
+/datum/nanite_program/proc/consume_nanites(amount, force = FALSE)
+ return nanites.consume_nanites(amount, force)
+
+/datum/nanite_program/proc/on_emp(severity)
+ if(program_flags & NANITE_EMP_IMMUNE)
+ return
+ if(prob(80 / severity))
+ host_mob.investigate_log("[src] nanite program received a software error due to emp.", INVESTIGATE_NANITES)
+ software_error()
+
+/datum/nanite_program/proc/on_shock(shock_damage)
+ if(!(program_flags & NANITE_SHOCK_IMMUNE))
+ if(prob(10))
+ host_mob.investigate_log("[src] nanite program received a software error due to shock.", INVESTIGATE_NANITES)
+ software_error()
+ else if(prob(33))
+ host_mob.investigate_log("[src] nanite program was deleted due to shock.", INVESTIGATE_NANITES)
+ qdel(src)
+
+/datum/nanite_program/proc/on_minor_shock()
+ if(!(program_flags & NANITE_SHOCK_IMMUNE))
+ if(prob(10))
+ host_mob.investigate_log("[src] nanite program received a software error due to minor shock.", INVESTIGATE_NANITES)
+ software_error()
+
+/datum/nanite_program/proc/on_death()
+ return
+
+/datum/nanite_program/proc/software_error(type)
+ if(!type)
+ type = rand(1,5)
+ switch(type)
+ if(1)
+ host_mob.investigate_log("[src] nanite program was deleted by software error.", INVESTIGATE_NANITES)
+ qdel(src) //kill switch
+ return
+ if(2) //deprogram codes
+ host_mob.investigate_log("[src] nanite program was de-programmed by software error.", INVESTIGATE_NANITES)
+ activation_code = 0
+ deactivation_code = 0
+ kill_code = 0
+ trigger_code = 0
+ if(3)
+ host_mob.investigate_log("[src] nanite program was toggled by software error.", INVESTIGATE_NANITES)
+ toggle() //enable/disable
+ if(4)
+ if(can_trigger)
+ host_mob.investigate_log("[src] nanite program was triggered by software error.", INVESTIGATE_NANITES)
+ trigger()
+ if(5) //Program is scrambled and does something different
+ var/rogue_type = pick(rogue_types)
+ var/datum/nanite_program/rogue = new rogue_type
+ host_mob.investigate_log("[src] nanite program was converted into [rogue.name] by software error.", INVESTIGATE_NANITES)
+ nanites.add_program(null, rogue, src)
+ qdel(src)
+
+/datum/nanite_program/proc/receive_signal(code, source)
+ if(activation_code && code == activation_code && !activated)
+ activate()
+ host_mob.investigate_log("'s [name] nanite program was activated by [source] with code [code].", INVESTIGATE_NANITES)
+ else if(deactivation_code && code == deactivation_code && activated)
+ deactivate()
+ host_mob.investigate_log("'s [name] nanite program was deactivated by [source] with code [code].", INVESTIGATE_NANITES)
+ if(can_trigger && trigger_code && code == trigger_code)
+ trigger()
+ host_mob.investigate_log("'s [name] nanite program was triggered by [source] with code [code].", INVESTIGATE_NANITES)
+ if(kill_code && code == kill_code)
+ host_mob.investigate_log("'s [name] nanite program was deleted by [source] with code [code].", INVESTIGATE_NANITES)
+ qdel(src)
+
+///A nanite program containing a behaviour protocol. Only one protocol of each class can be active at once.
+/datum/nanite_program/protocol
+ name = "Nanite Protocol"
+ var/protocol_class = NONE
+
+/datum/nanite_program/protocol/check_conditions()
+ . = ..()
+ for(var/protocol in nanites.protocols)
+ var/datum/nanite_program/protocol/P = protocol
+ if(P != src && P.activated && P.protocol_class == protocol_class)
+ return FALSE
+
+/datum/nanite_program/protocol/on_add(datum/component/nanites/_nanites)
+ ..()
+ nanites.protocols += src
+
+/datum/nanite_program/protocol/Destroy()
+ if(nanites)
+ nanites.protocols -= src
+ return ..()
diff --git a/modular_zubbers/code/modules/nanites/nanite_programs/buffing.dm b/modular_zubbers/code/modules/nanites/nanite_programs/buffing.dm
new file mode 100644
index 00000000000..05e2ba311cf
--- /dev/null
+++ b/modular_zubbers/code/modules/nanites/nanite_programs/buffing.dm
@@ -0,0 +1,231 @@
+//Programs that buff the host in generally passive ways.
+
+/datum/nanite_program/nervous
+ name = "Nerve Support"
+ desc = "The nanites act as a secondary nervous system, reducing the amount of time the host is stunned."
+ use_rate = 1.5
+ rogue_types = list(/datum/nanite_program/nerve_decay)
+
+/datum/nanite_program/nervous/enable_passive_effect()
+ . = ..()
+ if(ishuman(host_mob))
+ var/mob/living/carbon/human/H = host_mob
+ H.physiology.stun_mod *= 0.5
+
+/datum/nanite_program/nervous/disable_passive_effect()
+ . = ..()
+ if(ishuman(host_mob))
+ var/mob/living/carbon/human/H = host_mob
+ H.physiology.stun_mod *= 2
+
+/datum/nanite_program/dermal_armor
+ name = "Dermal Hardening"
+ desc = "The nanites form a mesh under the host's skin, protecting them from melee and bullet impacts. \
+ Each hit costs nanites two times to damage, but the more nanites you have, the more protection you get. \
+ 50 nanites = 1 flat armor, with each hit costing two times it's damage in nanites. \
+ Will not turn on with other dermal armor programs."
+ use_rate = 2
+ rogue_types = list(/datum/nanite_program/skin_decay)
+ var/damage_type = BRUTE
+ var/signal = COMSIG_MOB_ALWAYS_APPLY_DAMAGE
+ var/max_damage_resist = 30
+ var/current_flat_mod = 0
+ var/rgb_color = list(255, 0, 0)
+ var/filter_name = "nanite_armor_outline"
+ var/hide_timer
+ var/cleanup_timer
+ /// how much to multiply the nanite cost on the damage received
+ var/cost_modifier = 2
+
+/datum/nanite_program/dermal_armor/activate()
+ if(check_duplicate_programs())
+ return
+ if(!ishuman(host_mob))
+ return
+ . = ..()
+
+/datum/nanite_program/dermal_armor/enable_passive_effect()
+ . = ..()
+ RegisterSignal(host_mob, signal, PROC_REF(pre_damage))
+
+/datum/nanite_program/dermal_armor/proc/check_duplicate_programs()
+ for(var/program in nanites.programs)
+ if(istype(program, type) && program != src)
+ return TRUE
+ return FALSE
+
+/datum/nanite_program/dermal_armor/disable_passive_effect()
+ . = ..()
+ UnregisterSignal(host_mob, signal)
+
+ delete_hide_timer()
+ delete_cleanup_timer()
+
+ update_damage_modifier(0)
+
+/datum/nanite_program/dermal_armor/active_effect()
+ // 1 for every 50 nanites, up to 30 flat armor, max of 10 at 500 nanites
+ var/nanite_armor = ceil(nanites.nanite_volume * 0.02)
+ var/clamped_armor = clamp(nanite_armor, 1, max_damage_resist)
+ update_damage_modifier(clamped_armor)
+
+// update and handle the damage modifier before taking damage, thus only when needed
+/datum/nanite_program/dermal_armor/proc/pre_damage(
+ mob/living/damaged_mob,
+ damage_amount,
+ damagetype,
+ def_zone,
+ blocked,
+ wound_bonus,
+ bare_wound_bonus,
+ sharpness,
+ attack_direction,
+ attacking_item,
+ wound_clothing
+ )
+ SIGNAL_HANDLER
+ if(damagetype != damage_type)
+ return
+ var/mob/living/carbon/human/humie = host_mob
+ if(!istype(humie) || !humie.physiology || !nanites)
+ return
+ // since we are likely to already have a damage modifier, we need to calculate the actual damage received
+ // as the damage_amount received here is already modified by the flat_brute_mod
+ var/actual_damage = damage_amount + current_flat_mod
+ if(actual_damage >= nanites.nanite_volume)
+ playsound(host_mob, SFX_SHATTER)
+ host_mob.Knockdown(1 SECONDS)
+ to_chat(host_mob, span_warning("Something shatters under your skin, and you feel a sharp pain!"))
+ disable_passive_effect()
+ return
+
+ // after this we don't need to prevent the rest of the code from running
+ INVOKE_ASYNC(src, PROC_REF(on_pre_damage), damagetype, actual_damage)
+
+/datum/nanite_program/dermal_armor/proc/on_pre_damage(damagetype, amount)
+ // 2 nanites for every 1 damage blocked, ensure that we don't spend extra nanites when armor is higher than damage
+ if(amount >= current_flat_mod)
+ consume_nanites(current_flat_mod * cost_modifier)
+ else
+ consume_nanites(amount * cost_modifier)
+
+ var/damage_duration = (amount / current_flat_mod) * 2 SECONDS
+ var/timeleft = timeleft(hide_timer)
+ var/leftover_time = !isnull(timeleft) ? timeleft : 0
+ // if we're already showing the outline, reset the timer
+
+ if(hide_timer)
+ delete_hide_timer()
+ else
+ // how long to keep the color effect, while also changing color intensity based on damage
+ // only add the filter if we're not cleaning up, as that means we're already showing the outline which is animating to hide itself
+ if(cleanup_timer)
+ delete_cleanup_timer()
+ else
+ host_mob.add_filter(filter_name, 2, list("type" = "outline", size = 0))
+ // control the intensity of the color based on how much damage we're taking vs the armor we have
+ var/rgb_color = damage_color(amount, current_flat_mod)
+ host_mob.transition_filter(filter_name, list("type" = "outline", color = rgb(rgb_color[1], rgb_color[2], rgb_color[3]), size = 0.5), 0.5 SECONDS)
+
+ var/time_to_hide = clamp((damage_duration + leftover_time * 0.5) * 0.8, 0.5 SECONDS, 2 SECONDS)
+ var/difference = max(damage_duration - time_to_hide, 0.5 SECONDS) // spend 20% of the time to hide the outline
+ hide_timer = addtimer(CALLBACK(src, PROC_REF(hide_outline), difference), time_to_hide, TIMER_STOPPABLE)
+
+/datum/nanite_program/dermal_armor/proc/delete_hide_timer()
+ deltimer(hide_timer)
+ hide_timer = null
+
+/datum/nanite_program/dermal_armor/proc/delete_cleanup_timer()
+ deltimer(cleanup_timer)
+ cleanup_timer = null
+
+// modifies the RGB color based on the damage amount, making it more or less intense based on the damage
+/datum/nanite_program/dermal_armor/proc/damage_color(amount, armor)
+ var/color_strength = clamp(amount / armor, 0.2, 1)
+ var/colors = list()
+ for(var/i in 1 to 3)
+ var/color = rgb_color[i]
+ colors += clamp(color * color_strength, 0, 255)
+ return colors
+
+// hiding the outline is a 2 step process as we have a delay before we start hiding it
+// this is to prevent the outline from flickering when we're taking damage in quick succession
+/datum/nanite_program/dermal_armor/proc/hide_outline(hide_time = 0.5 SECONDS)
+ hide_timer = null
+ host_mob.transition_filter(filter_name, list("type" = "outline", size = 0), hide_time)
+ cleanup_timer = addtimer(CALLBACK(src, PROC_REF(cleanup_outline)), hide_time, TIMER_STOPPABLE)
+
+/datum/nanite_program/dermal_armor/proc/cleanup_outline()
+ host_mob.remove_filter(filter_name)
+ cleanup_timer = null
+
+/datum/nanite_program/dermal_armor/proc/update_damage_modifier(amount)
+ var/mob/living/carbon/human/humie = host_mob
+ var/valid_change = clamp(amount, 1, 30)
+ humie.physiology.flat_brute_mod += current_flat_mod
+ current_flat_mod = valid_change
+ humie.physiology.flat_brute_mod -= valid_change
+
+/datum/nanite_program/dermal_armor/refractive
+ name = "Dermal Refractive Surface"
+ desc = "The nanites form a membrane above the host's skin, reducing the effect of laser and energy impacts. \
+ Each hit costs nanites equilavent to damage, but the more nanites you have, the more protection you get. \
+ 50 nanites = 1 flat armor, with each hit costing two times it's damage in nanites. \
+ Will not turn on with other dermal armor programs."
+ damage_type = BURN
+ rgb_color = list(255, 255, 0)
+
+/datum/nanite_program/dermal_armor/refractive/update_damage_modifier(mob/living/carbon/human/humie, amount)
+ humie.physiology.flat_burn_mod -= current_flat_mod
+ current_flat_mod = amount
+ humie.physiology.flat_burn_mod += amount
+
+/datum/nanite_program/coagulating
+ name = "Vein Repressurization"
+ desc = "The nanites re-route circulating blood away from open wounds, dramatically reducing bleeding rate."
+ use_rate = 0.20
+ rogue_types = list(/datum/nanite_program/suffocating)
+
+/datum/nanite_program/coagulating/enable_passive_effect()
+ . = ..()
+ if(ishuman(host_mob))
+ var/mob/living/carbon/human/H = host_mob
+ H.physiology.bleed_mod *= 0.5
+
+/datum/nanite_program/coagulating/disable_passive_effect()
+ . = ..()
+ if(ishuman(host_mob))
+ var/mob/living/carbon/human/H = host_mob
+ H.physiology.bleed_mod *= 2
+
+/datum/nanite_program/conductive
+ name = "Electric Conduction"
+ desc = "The nanites act as a grounding rod for electric shocks, protecting the host. Shocks can still damage the nanites themselves."
+ use_rate = 0.20
+ program_flags = NANITE_SHOCK_IMMUNE
+ rogue_types = list(/datum/nanite_program/nerve_decay)
+
+/datum/nanite_program/conductive/enable_passive_effect()
+ . = ..()
+ ADD_TRAIT(host_mob, TRAIT_SHOCKIMMUNE, NANITES_TRAIT)
+
+/datum/nanite_program/conductive/disable_passive_effect()
+ . = ..()
+ REMOVE_TRAIT(host_mob, TRAIT_SHOCKIMMUNE, NANITES_TRAIT)
+
+/datum/nanite_program/mindshield
+ name = "Mental Barrier"
+ desc = "The nanites form a protective membrane around the host's brain, shielding them from abnormal influences while they're active."
+ use_rate = 0.40
+ rogue_types = list(/datum/nanite_program/brain_decay, /datum/nanite_program/brain_misfire)
+
+/datum/nanite_program/mindshield/enable_passive_effect()
+ . = ..()
+ if(!host_mob.mind.has_antag_datum(/datum/antagonist/rev, TRUE)) //won't work if on a rev, to avoid having implanted revs.
+ ADD_TRAIT(host_mob, TRAIT_MINDSHIELD, NANITES_TRAIT)
+ host_mob.sec_hud_set_implants()
+
+/datum/nanite_program/mindshield/disable_passive_effect()
+ . = ..()
+ REMOVE_TRAIT(host_mob, TRAIT_MINDSHIELD, NANITES_TRAIT)
+ host_mob.sec_hud_set_implants()
diff --git a/modular_zubbers/code/modules/nanites/nanite_programs/healing.dm b/modular_zubbers/code/modules/nanites/nanite_programs/healing.dm
new file mode 100644
index 00000000000..b5c972ca43d
--- /dev/null
+++ b/modular_zubbers/code/modules/nanites/nanite_programs/healing.dm
@@ -0,0 +1,190 @@
+//Programs that heal the host in some way.
+
+/datum/nanite_program/regenerative
+ name = "Accelerated Regeneration"
+ desc = "The nanites boost the host's natural regeneration, increasing their healing speed. Will not consume nanites while the host is unharmed."
+ use_rate = 0.5
+ rogue_types = list(/datum/nanite_program/necrotic)
+ var/valid_bodytype = BODYTYPE_ORGANIC
+ var/valid_biotype = MOB_ORGANIC
+ var/healing_rate = NANITE_BIO_REGENERATION
+ var/always_active = FALSE
+
+/datum/nanite_program/regenerative/check_conditions()
+ if(always_active)
+ return TRUE
+ if(!host_mob.getBruteLoss() && !host_mob.getFireLoss())
+ return FALSE
+ if(iscarbon(host_mob))
+ var/mob/living/carbon/carbon = host_mob
+ var/list/parts = carbon.get_damaged_bodyparts(TRUE, TRUE, valid_bodytype)
+ if(!parts.len)
+ return FALSE
+ else if(!(host_mob.mob_biotypes & valid_biotype))
+ return FALSE
+ return ..()
+
+/datum/nanite_program/regenerative/active_effect()
+ host_mob.heal_overall_damage(healing_rate, healing_rate, required_bodytype = valid_bodytype)
+
+/datum/nanite_program/regenerative/advanced
+ name = "Bio-Reconstruction"
+ desc = "The nanites manually repair and replace organic cells, acting much faster than normal regeneration. \
+ However, this program cannot detect the difference between harmed and unharmed, causing it to consume nanites even if it has no effect."
+ use_rate = 5.5
+ rogue_types = list(/datum/nanite_program/suffocating, /datum/nanite_program/necrotic)
+ healing_rate = NANITE_ADV_BIO_REGENERATION
+ always_active = TRUE
+
+/datum/nanite_program/regenerative/robotic
+ name = "Mechanical Repair"
+ desc = "The nanites fix damage in the host's mechanical limbs. Will not consume nanites while the host's mechanical limbs are undamaged, or while the host has no mechanical limbs."
+ use_rate = 3
+ rogue_types = list(/datum/nanite_program/necrotic)
+ valid_bodytype = BODYTYPE_ROBOTIC
+ valid_biotype = MOB_ROBOTIC
+ healing_rate = NANITE_ROBO_REGENERATION
+
+/datum/nanite_program/temperature
+ name = "Temperature Adjustment"
+ desc = "The nanites adjust the host's internal temperature to an ideal level. Will not consume nanites while the host is at a normal body temperature."
+ use_rate = 3.5
+ rogue_types = list(/datum/nanite_program/skin_decay)
+
+/datum/nanite_program/temperature/check_conditions()
+ if(host_mob.bodytemperature > (host_mob.get_body_temp_normal(apply_change=FALSE) - 30) && host_mob.bodytemperature < (host_mob.get_body_temp_normal(apply_change=FALSE) + 30))
+ return FALSE
+ return ..()
+
+/datum/nanite_program/temperature/active_effect()
+ var/target_temp = host_mob.get_body_temp_normal(apply_change=FALSE)
+ if(host_mob.bodytemperature > target_temp)
+ host_mob.adjust_bodytemperature(-40 * TEMPERATURE_DAMAGE_COEFFICIENT, target_temp)
+ else if(host_mob.bodytemperature < (target_temp + 1))
+ host_mob.adjust_bodytemperature(40 * TEMPERATURE_DAMAGE_COEFFICIENT, 0, target_temp)
+
+/datum/nanite_program/purging
+ name = "Blood Purification"
+ desc = "The nanites purge toxins and chemicals from the host's bloodstream. Consumes nanites even if it has no effect."
+ use_rate = 1
+ rogue_types = list(/datum/nanite_program/suffocating, /datum/nanite_program/necrotic)
+ var/healing_rate = NANITE_TOX_REGENERATION
+ var/purge_path = /datum/reagent
+ var/force_heal = FALSE
+
+/datum/nanite_program/purging/check_conditions()
+ . = ..()
+ if(!. || !host_mob.reagents)
+ return FALSE // No trying to purge simple mobs
+
+/datum/nanite_program/purging/active_effect()
+ host_mob.adjustToxLoss(-healing_rate, forced = force_heal)
+ for(var/datum/reagent/R as anything in host_mob.reagents.reagent_list)
+ if(!istype(R, purge_path))
+ continue
+ host_mob.reagents.remove_reagent(R.type, 1)
+
+/datum/nanite_program/purging/advanced
+ name = "Selective Blood Purification"
+ desc = "The nanites purge toxins and dangerous chemicals from the host's bloodstream, while ignoring beneficial chemicals. \
+ The added processing power required to analyze the chemicals severely increases the nanite consumption rate. Consumes nanites even if it has no effect."
+ use_rate = 2
+ rogue_types = list(/datum/nanite_program/suffocating, /datum/nanite_program/necrotic)
+ healing_rate = NANITE_ADV_TOX_REGENERATION
+ purge_path = /datum/reagent/toxin
+ force_heal = TRUE
+
+/datum/nanite_program/blood_restoring
+ name = "Blood Regeneration"
+ desc = "The nanites stimulate and boost blood cell production in the host. Will not consume nanites while the host has a safe blood level."
+ use_rate = 1
+ rogue_types = list(/datum/nanite_program/suffocating)
+
+/datum/nanite_program/blood_restoring/check_conditions()
+ if(iscarbon(host_mob))
+ var/mob/living/carbon/C = host_mob
+ if(C.blood_volume >= BLOOD_VOLUME_SAFE)
+ return FALSE
+ else
+ return FALSE
+ return ..()
+
+/datum/nanite_program/blood_restoring/active_effect()
+ if(iscarbon(host_mob))
+ var/mob/living/carbon/C = host_mob
+ C.blood_volume += 2
+
+/datum/nanite_program/brain_heal
+ name = "Neural Regeneration"
+ desc = "The nanites fix neural connections in the host's brain, reversing brain damage and minor traumas. Will not consume nanites while it would not have an effect."
+ use_rate = 1.5
+ rogue_types = list(/datum/nanite_program/brain_decay)
+
+/datum/nanite_program/brain_heal/check_conditions()
+ if(host_mob.get_organ_loss(ORGAN_SLOT_BRAIN) > 0)
+ return ..()
+ if(iscarbon(host_mob))
+ var/mob/living/carbon/carbon = host_mob
+ if (carbon.has_trauma_type(resilience = TRAUMA_RESILIENCE_BASIC))
+ return ..()
+ return FALSE
+
+/datum/nanite_program/brain_heal/active_effect()
+ host_mob.adjustOrganLoss(ORGAN_SLOT_BRAIN, -NANITE_BRAIN_REGENERATION)
+ if(iscarbon(host_mob) && prob(1))
+ var/mob/living/carbon/C = host_mob
+ C.cure_trauma_type(resilience = TRAUMA_RESILIENCE_BASIC)
+
+/datum/nanite_program/brain_heal_advanced
+ name = "Neural Reimaging"
+ desc = "The nanites are able to backup and restore the host's neural connections, potentially replacing entire chunks of missing or damaged brain matter. Consumes nanites even if it has no effect."
+ use_rate = 3
+ rogue_types = list(/datum/nanite_program/brain_decay, /datum/nanite_program/brain_misfire)
+
+/datum/nanite_program/brain_heal_advanced/active_effect()
+ host_mob.adjustOrganLoss(ORGAN_SLOT_BRAIN, -NANITE_ADV_BRAIN_REGENERATION)
+ if(iscarbon(host_mob) && prob(1))
+ var/mob/living/carbon/C = host_mob
+ C.cure_trauma_type(resilience = TRAUMA_RESILIENCE_LOBOTOMY)
+
+/datum/nanite_program/defib
+ name = "Defibrillation"
+ desc = "The nanites shock the host's heart when triggered, bringing them back to life if the body can sustain it."
+ can_trigger = TRUE
+ trigger_cost = 25
+ trigger_cooldown = 120
+ rogue_types = list(/datum/nanite_program/shocking)
+
+/datum/nanite_program/defib/on_trigger(comm_message)
+ host_mob.notify_revival("Your heart is being defibrillated by nanites. Re-enter your corpse if you want to be revived!")
+ host_mob.grab_ghost()
+ addtimer(CALLBACK(src, .proc/zap), 50)
+
+/datum/nanite_program/defib/proc/check_revivable()
+ if(!iscarbon(host_mob)) //nonstandard biology
+ return FALSE
+ var/mob/living/carbon/C = host_mob
+ if(C.get_ghost())
+ return FALSE
+ return C.can_defib() == DEFIB_POSSIBLE
+
+/datum/nanite_program/defib/proc/zap()
+ var/mob/living/carbon/C = host_mob
+ playsound(C, 'sound/machines/defib/defib_charge.ogg', 50, FALSE)
+ sleep(30)
+ playsound(C, 'sound/machines/defib/defib_zap.ogg', 50, FALSE)
+ if(check_revivable())
+ if(C.stat == DEAD)
+ var/original_oxyloss = C.getOxyLoss()
+ C.setOxyLoss(OXYLOSS_PASSOUT_THRESHOLD - 5)
+ if(C.revive())
+ playsound(C, 'sound/machines/defib/defib_success.ogg', 50, FALSE)
+ C.emote("gasp")
+ C.adjust_jitter_up_to(10 SECONDS, 1 MINUTES)
+ SEND_SIGNAL(C, COMSIG_LIVING_MINOR_SHOCK)
+ log_game("[C] has been successfully defibrillated by nanites.")
+ return
+ else
+ C.setOxyLoss(original_oxyloss)
+ playsound(C, 'sound/machines/defib/defib_failed.ogg', 50, FALSE)
+
diff --git a/modular_zubbers/code/modules/nanites/nanite_programs/protocols.dm b/modular_zubbers/code/modules/nanites/nanite_programs/protocols.dm
new file mode 100644
index 00000000000..1b40e6d7f61
--- /dev/null
+++ b/modular_zubbers/code/modules/nanites/nanite_programs/protocols.dm
@@ -0,0 +1,277 @@
+//Replication Protocols
+/datum/nanite_program/protocol/kickstart
+ name = "Kickstart Protocol"
+ desc = "Replication Protocol: the nanites focus on early growth, heavily boosting replication rate for a few minutes after the initial implantation, \
+ resulting in an additional 420 nanite volume being produced during the first two minutes."
+ use_rate = 0
+ rogue_types = list(/datum/nanite_program/necrotic)
+ protocol_class = NANITE_PROTOCOL_REPLICATION
+ var/boost_duration = 1200
+
+/datum/nanite_program/protocol/kickstart/check_conditions()
+ if(!(world.time < nanites.start_time + boost_duration))
+ return FALSE
+ return ..()
+
+/datum/nanite_program/protocol/kickstart/active_effect()
+ nanites.adjust_nanites(null, NANITE_PROTOCOL_KICKSTART_REPLICATION)
+
+/datum/nanite_program/protocol/factory
+ name = "Factory Protocol"
+ desc = "Replication Protocol: the nanites build a factory matrix within the host, gradually increasing replication speed over time, \
+ granting a maximum of 2 additional nanite production after roughly 17 minutes. \
+ The factory decays if the protocol is not active, or if the nanites are disrupted by shocks or EMPs."
+ use_rate = 0
+ rogue_types = list(/datum/nanite_program/necrotic)
+ protocol_class = NANITE_PROTOCOL_REPLICATION
+ var/factory_efficiency = 0
+ var/max_efficiency = 1000 //Goes up to 2 bonus regen per tick after 16 minutes and 40 seconds
+
+/datum/nanite_program/protocol/factory/on_process()
+ if(!activated || !check_conditions())
+ factory_efficiency = max(0, factory_efficiency - 5)
+ ..()
+
+/datum/nanite_program/protocol/factory/on_emp(severity)
+ ..()
+ factory_efficiency = max(0, factory_efficiency - 300)
+
+/datum/nanite_program/protocol/factory/on_shock(shock_damage)
+ ..()
+ factory_efficiency = max(0, factory_efficiency - 200)
+
+/datum/nanite_program/protocol/factory/on_minor_shock()
+ ..()
+ factory_efficiency = max(0, factory_efficiency - 100)
+
+/datum/nanite_program/protocol/factory/active_effect()
+ factory_efficiency = min(factory_efficiency + 1, max_efficiency)
+ nanites.adjust_nanites(null, round(0.002 * factory_efficiency, 0.1))
+
+/datum/nanite_program/protocol/pyramid
+ name = "Pyramid Protocol"
+ desc = "Replication Protocol: the nanites implement an alternate cooperative replication protocol that is active as long as the nanite saturation level is above 80%, \
+ resulting in an additional volume production of 1.2 per second."
+ use_rate = 0
+ rogue_types = list(/datum/nanite_program/necrotic)
+ protocol_class = NANITE_PROTOCOL_REPLICATION
+ var/boost = 1.2
+
+/datum/nanite_program/protocol/pyramid/check_conditions()
+ if((nanites.nanite_volume / nanites.max_nanites) < 0.8)
+ return FALSE
+
+ return ..()
+
+/datum/nanite_program/protocol/pyramid/active_effect()
+ nanites.adjust_nanites(null, boost)
+
+/datum/nanite_program/protocol/offline
+ name = "Eclipse Protocol"
+ desc = "Replication Protocol: while the host is asleep or otherwise unconcious, the nanites exploit the reduced interference to replicate more quickly."
+ use_rate = 0
+ rogue_types = list(/datum/nanite_program/necrotic)
+ protocol_class = NANITE_PROTOCOL_REPLICATION
+ var/boost = 3
+
+
+/datum/nanite_program/protocol/offline/check_conditions()
+ if(nanites.host_mob.stat == CONSCIOUS)
+ return FALSE
+ return ..()
+
+
+/datum/nanite_program/protocol/offline/active_effect()
+ nanites.adjust_nanites(null, boost)
+
+/datum/nanite_program/protocol/hive
+ name = "Hive Protocol"
+ desc = "Storage Protocol: the nanites use a more efficient grid arrangment for volume storage, increasing maximum volume to 750."
+ use_rate = 0
+ rogue_types = list(/datum/nanite_program/necrotic)
+ protocol_class = NANITE_PROTOCOL_STORAGE
+ var/extra_volume = 250
+
+/datum/nanite_program/protocol/hive/enable_passive_effect()
+ . = ..()
+ nanites.set_max_volume(null, nanites.max_nanites + extra_volume)
+
+/datum/nanite_program/protocol/hive/disable_passive_effect()
+ . = ..()
+ nanites.set_max_volume(null, nanites.max_nanites - extra_volume)
+
+/datum/nanite_program/protocol/zip
+ name = "Zip Protocol"
+ desc = "Storage Protocol: the nanites are disassembled and compacted when unused, increasing the maximum volume to 1000. However, the process slows down their replication rate slightly."
+ use_rate = 0.2
+ rogue_types = list(/datum/nanite_program/necrotic)
+ protocol_class = NANITE_PROTOCOL_STORAGE
+ var/extra_volume = 500
+
+/datum/nanite_program/protocol/zip/enable_passive_effect()
+ . = ..()
+ nanites.set_max_volume(null, nanites.max_nanites + extra_volume)
+
+/datum/nanite_program/protocol/zip/disable_passive_effect()
+ . = ..()
+ nanites.set_max_volume(null, nanites.max_nanites - extra_volume)
+
+/datum/nanite_program/protocol/free_range
+ name = "Free-range Protocol"
+ desc = "Storage Protocol: the nanites discard their default storage protocols in favour of a cheaper and more organic approach. Reduces maximum volume to 250, but increases the replication rate by 0.5."
+ use_rate = 0
+ rogue_types = list(/datum/nanite_program/necrotic)
+ protocol_class = NANITE_PROTOCOL_STORAGE
+ var/boost = 0.5
+ var/extra_volume = -250
+
+/datum/nanite_program/protocol/free_range/enable_passive_effect()
+ . = ..()
+ nanites.set_max_volume(null, nanites.max_nanites + extra_volume)
+
+/datum/nanite_program/protocol/free_range/disable_passive_effect()
+ . = ..()
+ nanites.set_max_volume(null, nanites.max_nanites - extra_volume)
+
+/datum/nanite_program/protocol/free_range/active_effect()
+ nanites.adjust_nanites(null, boost)
+
+/datum/nanite_program/protocol/unsafe_storage
+ name = "S.L.O. Protocol"
+ desc = "Storage Protocol: 'S.L.O.P.', or Storage Level Override Protocol, completely disables the safety measures normally present in nanites, \
+ allowing them to reach a whopping maximum volume level of 2000, but at the risk of causing damage to the host at nanite concentrations above the standard limit of 500."
+ use_rate = 0
+ rogue_types = list(/datum/nanite_program/necrotic)
+ protocol_class = NANITE_PROTOCOL_STORAGE
+ var/extra_volume = 1500
+ var/next_warning = 0
+ var/min_warning_cooldown = 120
+ var/max_warning_cooldown = 350
+ var/volume_warnings_stage_1 = list("You feel a dull pain in your abdomen.",
+ "You feel a tickling sensation in your abdomen.")
+ var/volume_warnings_stage_2 = list("You feel a dull pain in your stomach.",
+ "You feel a dull pain when breathing.",
+ "Your stomach grumbles.",
+ "You feel a tickling sensation in your throat.",
+ "You feel a tickling sensation in your lungs.",
+ "You feel a tickling sensation in your stomach.",
+ "Your lungs feel stiff.")
+ var/volume_warnings_stage_3 = list("You feel a dull pain in your chest.",
+ "You hear a faint buzzing coming from nowhere.",
+ "You hear a faint buzzing inside your head.",
+ "Your head aches.")
+ var/volume_warnings_stage_4 = list("You feel a dull pain in your ears.",
+ "You feel a dull pain behind your eyes.",
+ "You hear a loud, echoing buzz inside your ears.",
+ "You feel dizzy.",
+ "You feel an itch coming from behind your eyes.",
+ "Your eardrums itch.",
+ "You see tiny grey motes drifting in your field of view.")
+ var/volume_warnings_stage_5 = list("You feel sick.",
+ "You feel a dull pain from every part of your body.",
+ "You feel nauseous.")
+ var/volume_warnings_stage_6 = list("Your skin itches and burns.",
+ "Your muscles ache.",
+ "You feel tired.",
+ "You feel something skittering under your skin.",)
+
+/datum/nanite_program/protocol/unsafe_storage/enable_passive_effect()
+ . = ..()
+ nanites.set_max_volume(null, nanites.max_nanites + extra_volume)
+
+/datum/nanite_program/protocol/unsafe_storage/disable_passive_effect()
+ . = ..()
+ nanites.set_max_volume(null, nanites.max_nanites - extra_volume)
+
+/datum/nanite_program/protocol/unsafe_storage/active_effect()
+ if(!iscarbon(host_mob))
+ if(prob(10))
+ host_mob.adjustBruteLoss(((max(nanites.nanite_volume - 450, 0) / 450) ** 2 ) * 0.5) // 0.5 -> 2 -> 4.5 -> 8 damage per successful tick
+ return
+
+ var/mob/living/carbon/carbon = host_mob
+
+ if(nanites.nanite_volume < 500)
+ return
+
+ var/current_stage = 0
+ if(nanites.nanite_volume > 500) //Liver is the main hub of nanite replication and the first to be threatened by excess volume
+ if(prob(10))
+ var/obj/item/organ/liver/liver = carbon.get_organ_slot(ORGAN_SLOT_LIVER)
+ if(liver)
+ liver.apply_organ_damage(0.6)
+ current_stage++
+ if(nanites.nanite_volume > 750) //Extra volume spills out in other central organs
+ if(prob(10))
+ var/obj/item/organ/stomach/stomach = carbon.get_organ_slot(ORGAN_SLOT_STOMACH)
+ if(stomach)
+ stomach.apply_organ_damage(0.75)
+ if(prob(10))
+ var/obj/item/organ/lungs/lungs = carbon.get_organ_slot(ORGAN_SLOT_LUNGS)
+ if(lungs)
+ lungs.apply_organ_damage(0.75)
+ current_stage++
+ if(nanites.nanite_volume > 1000) //Extra volume spills out in more critical organs
+ if(prob(10))
+ var/obj/item/organ/heart/heart = carbon.get_organ_slot(ORGAN_SLOT_HEART)
+ if(heart)
+ heart.apply_organ_damage(0.75)
+ if(prob(10))
+ var/obj/item/organ/brain/brain = carbon.get_organ_slot(ORGAN_SLOT_BRAIN)
+ if(brain)
+ brain.apply_organ_damage(0.75)
+ current_stage++
+ if(nanites.nanite_volume > 1250) //Excess nanites start invading smaller organs for more space, including sensory organs
+ if(prob(13))
+ var/obj/item/organ/eyes/eyes = carbon.get_organ_slot(ORGAN_SLOT_EYES)
+ if(eyes)
+ eyes.apply_organ_damage(0.75)
+ if(prob(13))
+ var/obj/item/organ/ears/ears = carbon.get_organ_slot(ORGAN_SLOT_EARS)
+ if(ears)
+ ears.apply_organ_damage(0.75)
+ current_stage++
+ if(nanites.nanite_volume > 1500) //Nanites start spilling into the bloodstream, causing toxicity
+ if(prob(15))
+ carbon.adjustToxLoss(0.5, TRUE, forced = TRUE) //Not healthy for slimepeople either
+ current_stage++
+ if(nanites.nanite_volume > 1750) //Nanites have almost reached their physical limit, and the pressure itself starts causing tissue damage
+ if(prob(15))
+ carbon.adjustBruteLoss(0.75, TRUE)
+ current_stage++
+
+ volume_warning(current_stage)
+
+/datum/nanite_program/protocol/unsafe_storage/proc/volume_warning(tier)
+ if(world.time < next_warning)
+ return
+
+ var/list/main_warnings
+ var/list/extra_warnings
+
+ switch(tier)
+ if(1)
+ main_warnings = volume_warnings_stage_1
+ extra_warnings = null
+ if(2)
+ main_warnings = volume_warnings_stage_2
+ extra_warnings = volume_warnings_stage_1
+ if(3)
+ main_warnings = volume_warnings_stage_3
+ extra_warnings = volume_warnings_stage_1 + volume_warnings_stage_2
+ if(4)
+ main_warnings = volume_warnings_stage_4
+ extra_warnings = volume_warnings_stage_1 + volume_warnings_stage_2 + volume_warnings_stage_3
+ if(5)
+ main_warnings = volume_warnings_stage_5
+ extra_warnings = volume_warnings_stage_1 + volume_warnings_stage_2 + volume_warnings_stage_3 + volume_warnings_stage_4
+ if(6)
+ main_warnings = volume_warnings_stage_6
+ extra_warnings = volume_warnings_stage_1 + volume_warnings_stage_2 + volume_warnings_stage_3 + volume_warnings_stage_4 + volume_warnings_stage_5
+
+ if(prob(35))
+ to_chat(host_mob, span_warning("[pick(main_warnings)]"))
+ next_warning = world.time + rand(min_warning_cooldown, max_warning_cooldown)
+ else if(islist(extra_warnings))
+ to_chat(host_mob, span_warning("[pick(extra_warnings)]"))
+ next_warning = world.time + rand(min_warning_cooldown, max_warning_cooldown)
diff --git a/modular_zubbers/code/modules/nanites/nanite_programs/rogue.dm b/modular_zubbers/code/modules/nanites/nanite_programs/rogue.dm
new file mode 100644
index 00000000000..affe66e1b51
--- /dev/null
+++ b/modular_zubbers/code/modules/nanites/nanite_programs/rogue.dm
@@ -0,0 +1,119 @@
+//Programs generated through degradation of other complex programs.
+//They generally cause minor damage or annoyance.
+
+//Last stop of the error train
+/datum/nanite_program/glitch
+ name = "Glitch"
+ desc = "A heavy software corruption that causes nanites to gradually break down."
+ use_rate = 1.5
+ unique = FALSE
+ rogue_types = list()
+
+//Generic body-affecting programs will decay into this
+/datum/nanite_program/necrotic
+ name = "Necrosis"
+ desc = "The nanites attack internal tissues indiscriminately, causing widespread damage."
+ use_rate = 0.75
+ unique = FALSE
+ rogue_types = list(/datum/nanite_program/glitch)
+
+/datum/nanite_program/necrotic/active_effect()
+ host_mob.adjustBruteLoss(0.75, TRUE)
+ if(prob(1))
+ to_chat(host_mob, span_warning("You feel a mild ache from somewhere inside you."))
+
+//Programs that don't directly interact with the body will decay into this
+/datum/nanite_program/toxic
+ name = "Toxin Buildup"
+ desc = "The nanites cause a slow but constant toxin buildup inside the host."
+ use_rate = 0.25
+ unique = FALSE
+ rogue_types = list(/datum/nanite_program/glitch)
+
+/datum/nanite_program/toxic/active_effect()
+ host_mob.adjustToxLoss(0.5)
+ if(prob(1))
+ to_chat(host_mob, span_warning("You feel a bit sick."))
+
+//Generic blood-affecting programs will decay into this
+/datum/nanite_program/suffocating
+ name = "Hypoxemia"
+ desc = "The nanites prevent the host's blood from absorbing oxygen efficiently."
+ use_rate = 0.75
+ unique = FALSE
+ rogue_types = list(/datum/nanite_program/glitch)
+
+/datum/nanite_program/suffocating/active_effect()
+ host_mob.adjustOxyLoss(3, 0)
+ if(prob(1))
+ to_chat(host_mob, span_warning("You feel short of breath."))
+
+//Generic brain-affecting programs will decay into this
+/datum/nanite_program/brain_decay
+ name = "Neuro-Necrosis"
+ desc = "The nanites seek and attack brain cells, causing extensive neural damage to the host."
+ use_rate = 0.75
+ unique = FALSE
+ rogue_types = list(/datum/nanite_program/necrotic)
+
+/datum/nanite_program/brain_decay/active_effect()
+ if(prob(4))
+ host_mob.set_hallucinations_if_lower(15)
+ host_mob.adjustOrganLoss(ORGAN_SLOT_BRAIN, 1)
+
+//Generic brain-affecting programs can also decay into this
+/datum/nanite_program/brain_misfire
+ name = "Brain Misfire"
+ desc = "The nanites interfere with neural pathways, causing minor psychological disturbances."
+ use_rate = 0.50
+ unique = FALSE
+ rogue_types = list(/datum/nanite_program/brain_decay)
+
+/datum/nanite_program/brain_misfire/active_effect()
+ if(prob(10))
+ switch(rand(1,4))
+ if(1)
+ host_mob.adjust_hallucinations(15)
+ if(2)
+ host_mob.adjust_confusion(10)
+ if(3)
+ host_mob.adjust_drowsiness(10)
+ if(4)
+ host_mob.adjust_slurring(10)
+
+//Generic skin-affecting programs will decay into this
+/datum/nanite_program/skin_decay
+ name = "Dermalysis"
+ desc = "The nanites attack skin cells, causing irritation, rashes, and minor damage."
+ use_rate = 0.25
+ unique = FALSE
+ rogue_types = list(/datum/nanite_program/necrotic)
+
+/datum/nanite_program/skin_decay/active_effect()
+ host_mob.adjustBruteLoss(0.25)
+ if(prob(5)) //itching
+ var/picked_bodypart = pick(BODY_ZONE_HEAD, BODY_ZONE_CHEST, BODY_ZONE_R_ARM, BODY_ZONE_L_ARM, BODY_ZONE_R_LEG, BODY_ZONE_L_LEG)
+ var/obj/item/bodypart/bodypart = host_mob.get_bodypart(picked_bodypart)
+ var/can_scratch = !host_mob.incapacitated && get_location_accessible(host_mob, picked_bodypart)
+
+ host_mob.visible_message("[can_scratch ? span_warning("[host_mob] scratches [host_mob.p_their()] [bodypart.name].") : ""]",\
+ span_warning("Your [bodypart.name] itches. [can_scratch ? " You scratch it." : ""]"))
+
+//Generic nerve-affecting programs will decay into this
+/datum/nanite_program/nerve_decay
+ name = "Nerve Decay"
+ desc = "The nanites attack the host's nerves, causing lack of coordination and short bursts of paralysis."
+ use_rate = 1
+ unique = FALSE
+ rogue_types = list(/datum/nanite_program/necrotic)
+
+/datum/nanite_program/nerve_decay/active_effect()
+ if(prob(5))
+ to_chat(host_mob, span_warning("You feel unbalanced!"))
+ host_mob.adjust_confusion(10)
+ else if(prob(4))
+ to_chat(host_mob, span_warning("You can't feel your hands!"))
+ host_mob.drop_all_held_items()
+ else if(prob(4))
+ to_chat(host_mob, span_warning("You can't feel your legs!"))
+ host_mob.Paralyze(30)
diff --git a/modular_zubbers/code/modules/nanites/nanite_programs/sensor.dm b/modular_zubbers/code/modules/nanites/nanite_programs/sensor.dm
new file mode 100644
index 00000000000..916c2aa989a
--- /dev/null
+++ b/modular_zubbers/code/modules/nanites/nanite_programs/sensor.dm
@@ -0,0 +1,312 @@
+/datum/nanite_program/sensor
+ name = "Sensor Nanites"
+ desc = "These nanites send a signal code when a certain condition is met."
+ unique = FALSE
+ var/can_rule = FALSE
+
+/datum/nanite_program/sensor/register_extra_settings()
+ extra_settings[NES_SENT_CODE] = new /datum/nanite_extra_setting/number(0, 1, 9999)
+
+/datum/nanite_program/sensor/proc/check_event()
+ return FALSE
+
+/datum/nanite_program/sensor/proc/send_code()
+ if(activated)
+ var/datum/nanite_extra_setting/ES = extra_settings[NES_SENT_CODE]
+ SEND_SIGNAL(host_mob, COMSIG_NANITE_SIGNAL, ES.value, "a [name] program")
+
+/datum/nanite_program/sensor/active_effect()
+ if(check_event())
+ send_code()
+
+/datum/nanite_program/sensor/proc/make_rule(datum/nanite_program/target)
+ return
+
+/datum/nanite_program/sensor/repeat
+ name = "Signal Repeater"
+ desc = "When triggered, sends another signal to the nanites, optionally with a delay."
+ can_trigger = TRUE
+ trigger_cost = 0
+ trigger_cooldown = 10
+ var/spent = FALSE
+
+/datum/nanite_program/sensor/repeat/register_extra_settings()
+ . = ..()
+ extra_settings[NES_DELAY] = new /datum/nanite_extra_setting/number(0, 0, 3600, "s")
+
+/datum/nanite_program/sensor/repeat/on_trigger(comm_message)
+ var/datum/nanite_extra_setting/ES = extra_settings[NES_DELAY]
+ addtimer(CALLBACK(src, .proc/send_code), ES.get_value() * 10)
+
+/datum/nanite_program/sensor/relay_repeat
+ name = "Relay Signal Repeater"
+ desc = "When triggered, sends another signal to a relay channel, optionally with a delay."
+ can_trigger = TRUE
+ trigger_cost = 0
+ trigger_cooldown = 10
+ var/spent = FALSE
+
+/datum/nanite_program/sensor/relay_repeat/register_extra_settings()
+ . = ..()
+ extra_settings[NES_RELAY_CHANNEL] = new /datum/nanite_extra_setting/number(1, 1, 9999)
+ extra_settings[NES_DELAY] = new /datum/nanite_extra_setting/number(0, 0, 3600, "s")
+
+/datum/nanite_program/sensor/relay_repeat/on_trigger(comm_message)
+ var/datum/nanite_extra_setting/ES = extra_settings[NES_DELAY]
+ addtimer(CALLBACK(src, .proc/send_code), ES.get_value() * 10)
+
+/datum/nanite_program/sensor/relay_repeat/send_code()
+ var/datum/nanite_extra_setting/relay = extra_settings[NES_RELAY_CHANNEL]
+ if(activated && relay.get_value())
+ for(var/X in SSnanites.nanite_relays)
+ var/datum/nanite_program/relay/N = X
+ var/datum/nanite_extra_setting/code = extra_settings[NES_SENT_CODE]
+ N.relay_signal(code.get_value(), relay.get_value(), "a [name] program")
+
+/datum/nanite_program/sensor/health
+ name = "Health Sensor"
+ desc = "The nanites receive a signal when the host's health is above/below a target percentage."
+ can_rule = TRUE
+ var/spent = FALSE
+
+/datum/nanite_program/sensor/health/register_extra_settings()
+ . = ..()
+ extra_settings[NES_HEALTH_PERCENT] = new /datum/nanite_extra_setting/number(50, -99, 100, "%")
+ extra_settings[NES_DIRECTION] = new /datum/nanite_extra_setting/boolean(TRUE, "Above", "Below")
+
+/datum/nanite_program/sensor/health/check_event()
+ var/health_percent = host_mob.health / host_mob.maxHealth * 100
+ var/datum/nanite_extra_setting/percent = extra_settings[NES_HEALTH_PERCENT]
+ var/datum/nanite_extra_setting/direction = extra_settings[NES_DIRECTION]
+ var/detected = FALSE
+ if(direction.get_value())
+ if(health_percent >= percent.get_value())
+ detected = TRUE
+ else
+ if(health_percent < percent.get_value())
+ detected = TRUE
+
+ if(detected)
+ if(!spent)
+ spent = TRUE
+ return TRUE
+ return FALSE
+ else
+ spent = FALSE
+ return FALSE
+
+/datum/nanite_program/sensor/health/make_rule(datum/nanite_program/target)
+ var/datum/nanite_rule/health/rule = new(target)
+ var/datum/nanite_extra_setting/direction = extra_settings[NES_DIRECTION]
+ var/datum/nanite_extra_setting/percent = extra_settings[NES_HEALTH_PERCENT]
+ rule.above = direction.get_value()
+ rule.threshold = percent.get_value()
+ return rule
+
+/datum/nanite_program/sensor/crit
+ name = "Critical Health Sensor"
+ desc = "The nanites receive a signal when the host first reaches critical health."
+ can_rule = TRUE
+ var/spent = FALSE
+
+/datum/nanite_program/sensor/crit/check_event()
+ if(HAS_TRAIT(host_mob, TRAIT_CRITICAL_CONDITION))
+ if(spent)
+ return FALSE
+ spent = TRUE
+ return TRUE
+ spent = FALSE
+ return FALSE
+
+
+/datum/nanite_program/sensor/crit/make_rule(datum/nanite_program/target)
+ var/datum/nanite_rule/crit/rule = new(target)
+ return rule
+
+/datum/nanite_program/sensor/death
+ name = "Death Sensor"
+ desc = "The nanites receive a signal when they detect the host is dead."
+ can_rule = TRUE
+ var/spent = FALSE
+
+/datum/nanite_program/sensor/death/on_death()
+ send_code()
+
+/datum/nanite_program/sensor/death/make_rule(datum/nanite_program/target)
+ var/datum/nanite_rule/death/rule = new(target)
+ return rule
+
+/datum/nanite_program/sensor/nanite_volume
+ name = "Nanite Volume Sensor"
+ desc = "The nanites receive a signal when the nanite supply is above/below a certain percentage."
+ can_rule = TRUE
+ var/spent = FALSE
+
+/datum/nanite_program/sensor/nanite_volume/register_extra_settings()
+ . = ..()
+ extra_settings[NES_NANITE_PERCENT] = new /datum/nanite_extra_setting/number(50, -99, 100, "%")
+ extra_settings[NES_DIRECTION] = new /datum/nanite_extra_setting/boolean(TRUE, "Above", "Below")
+
+/datum/nanite_program/sensor/nanite_volume/check_event()
+ var/nanite_percent = (nanites.nanite_volume - nanites.safety_threshold)/(nanites.max_nanites - nanites.safety_threshold)*100
+ var/datum/nanite_extra_setting/percent = extra_settings[NES_NANITE_PERCENT]
+ var/datum/nanite_extra_setting/direction = extra_settings[NES_DIRECTION]
+ var/detected = FALSE
+ if(direction.get_value())
+ if(nanite_percent >= percent.get_value())
+ detected = TRUE
+ else
+ if(nanite_percent < percent.get_value())
+ detected = TRUE
+
+ if(detected)
+ if(!spent)
+ spent = TRUE
+ return TRUE
+ return FALSE
+ else
+ spent = FALSE
+ return FALSE
+
+/datum/nanite_program/sensor/nanite_volume/make_rule(datum/nanite_program/target)
+ var/datum/nanite_rule/nanites/rule = new(target)
+ var/datum/nanite_extra_setting/direction = extra_settings[NES_DIRECTION]
+ var/datum/nanite_extra_setting/percent = extra_settings[NES_NANITE_PERCENT]
+ rule.above = direction.get_value()
+ rule.threshold = percent.get_value()
+ return rule
+
+/datum/nanite_program/sensor/damage
+ name = "Damage Sensor"
+ desc = "The nanites receive a signal when a host's specific damage type is above/below a target value."
+ can_rule = TRUE
+ var/spent = FALSE
+
+/datum/nanite_program/sensor/damage/register_extra_settings()
+ . = ..()
+ extra_settings[NES_DAMAGE_TYPE] = new /datum/nanite_extra_setting/type(BRUTE, list(BRUTE, BURN, TOX, OXY))
+ extra_settings[NES_DAMAGE] = new /datum/nanite_extra_setting/number(50, 0, 500)
+ extra_settings[NES_DIRECTION] = new /datum/nanite_extra_setting/boolean(TRUE, "Above", "Below")
+
+/datum/nanite_program/sensor/damage/check_event()
+ var/reached_threshold = FALSE
+ var/datum/nanite_extra_setting/type = extra_settings[NES_DAMAGE_TYPE]
+ var/datum/nanite_extra_setting/damage = extra_settings[NES_DAMAGE]
+ var/datum/nanite_extra_setting/direction = extra_settings[NES_DIRECTION]
+ var/check_above = direction.get_value()
+ var/damage_amt = 0
+ switch(type.get_value())
+ if(BRUTE)
+ damage_amt = host_mob.getBruteLoss()
+ if(BURN)
+ damage_amt = host_mob.getFireLoss()
+ if(TOX)
+ damage_amt = host_mob.getToxLoss()
+ if(OXY)
+ damage_amt = host_mob.getOxyLoss()
+
+ if(check_above)
+ if(damage_amt >= damage.get_value())
+ reached_threshold = TRUE
+ else
+ if(damage_amt < damage.get_value())
+ reached_threshold = TRUE
+
+ if(reached_threshold)
+ if(!spent)
+ spent = TRUE
+ return TRUE
+ return FALSE
+ else
+ spent = FALSE
+ return FALSE
+
+/datum/nanite_program/sensor/damage/make_rule(datum/nanite_program/target)
+ var/datum/nanite_rule/damage/rule = new(target)
+ var/datum/nanite_extra_setting/direction = extra_settings[NES_DIRECTION]
+ var/datum/nanite_extra_setting/damage_type = extra_settings[NES_DAMAGE_TYPE]
+ var/datum/nanite_extra_setting/damage = extra_settings[NES_DAMAGE]
+ rule.above = direction.get_value()
+ rule.threshold = damage.get_value()
+ rule.damage_type = damage_type.get_value()
+ return rule
+
+/datum/nanite_program/sensor/voice
+ name = "Voice Sensor"
+ desc = "The nanites receive a signal when they detect a specific, preprogrammed word or phrase being said."
+
+/datum/nanite_program/sensor/voice/register_extra_settings()
+ . = ..()
+ extra_settings[NES_SENTENCE] = new /datum/nanite_extra_setting/text("")
+ extra_settings[NES_INCLUSIVE_MODE] = new /datum/nanite_extra_setting/boolean(TRUE, "Inclusive", "Exclusive")
+
+/datum/nanite_program/sensor/voice/on_mob_add()
+ . = ..()
+ RegisterSignal(host_mob, COMSIG_MOVABLE_HEAR, .proc/on_hear)
+
+/datum/nanite_program/sensor/voice/on_mob_remove()
+ UnregisterSignal(host_mob, COMSIG_MOVABLE_HEAR)
+
+/datum/nanite_program/sensor/voice/proc/on_hear(datum/source, list/hearing_args)
+ SIGNAL_HANDLER
+ var/datum/nanite_extra_setting/sentence = extra_settings[NES_SENTENCE]
+ var/datum/nanite_extra_setting/inclusive = extra_settings[NES_INCLUSIVE_MODE]
+ if(!sentence.get_value())
+ return
+ if(inclusive.get_value())
+ if(findtext(hearing_args[HEARING_RAW_MESSAGE], sentence.get_value()))
+ send_code()
+ else
+ if(LOWER_TEXT(hearing_args[HEARING_RAW_MESSAGE]) == LOWER_TEXT(sentence.get_value()))
+ send_code()
+
+/datum/nanite_program/sensor/species
+ name = "Species Sensor"
+ desc = "When triggered, the nanites scan the host to determine their species and receive a signal depending on the conditions set in the settings."
+ can_trigger = TRUE
+ trigger_cost = 0
+ trigger_cooldown = 5
+
+ var/static/list/allowed_species = list(
+ "Human" = /datum/species/human,
+ "Lizard" = /datum/species/lizard,
+ "Moth" = /datum/species/moth,
+ "Ethereal" = /datum/species/ethereal,
+ "Pod" = /datum/species/pod,
+ "Fly" = /datum/species/fly,
+ "Felinid" = /datum/species/human/felinid,
+ "Jelly" = /datum/species/jelly,
+ )
+
+/datum/nanite_program/sensor/species/register_extra_settings()
+ . = ..()
+ var/list/species_types = list()
+ for(var/name in allowed_species)
+ species_types += name
+ species_types += "Other"
+ extra_settings[NES_RACE] = new /datum/nanite_extra_setting/type("Human", species_types)
+ extra_settings[NES_MODE] = new /datum/nanite_extra_setting/boolean(TRUE, "Is", "Is Not")
+
+/datum/nanite_program/sensor/species/on_trigger(comm_message)
+ var/datum/nanite_extra_setting/species_type = extra_settings[NES_RACE]
+ var/species = allowed_species[species_type.get_value()]
+ var/species_match = FALSE
+
+ if(species)
+ if(is_species(host_mob, species))
+ species_match = TRUE
+ else //this is the check for the "Other" option
+ species_match = TRUE
+ for(var/name in allowed_species)
+ var/species_other = allowed_species[name]
+ if(is_species(host_mob, species_other))
+ species_match = FALSE
+ break
+
+ var/datum/nanite_extra_setting/mode = extra_settings[NES_MODE]
+ if(mode.get_value())
+ if(species_match)
+ send_code()
+ else
+ if(!species_match)
+ send_code()
diff --git a/modular_zubbers/code/modules/nanites/nanite_programs/suppression.dm b/modular_zubbers/code/modules/nanites/nanite_programs/suppression.dm
new file mode 100644
index 00000000000..ddd38bc9790
--- /dev/null
+++ b/modular_zubbers/code/modules/nanites/nanite_programs/suppression.dm
@@ -0,0 +1,292 @@
+//Programs that are generally useful for population control and non-harmful suppression.
+
+/datum/nanite_program/sleepy
+ name = "Sleep Induction"
+ desc = "The nanites induce rapid narcolepsy when triggered."
+ can_trigger = TRUE
+ trigger_cost = 15
+ trigger_cooldown = 1200
+ rogue_types = list(/datum/nanite_program/brain_misfire, /datum/nanite_program/brain_decay)
+
+/datum/nanite_program/sleepy/on_trigger(comm_message)
+ to_chat(host_mob, span_warning("You start to feel very sleepy..."))
+ host_mob.adjust_drowsiness(20)
+ addtimer(CALLBACK(host_mob, /mob/living.proc/Sleeping, 200), rand(60,200))
+
+/datum/nanite_program/paralyzing
+ name = "Paralysis"
+ desc = "The nanites force muscle contraction, effectively paralyzing the host."
+ use_rate = 3
+ rogue_types = list(/datum/nanite_program/nerve_decay)
+
+/datum/nanite_program/paralyzing/active_effect()
+ host_mob.Stun(40)
+
+/datum/nanite_program/paralyzing/enable_passive_effect()
+ . = ..()
+ to_chat(host_mob, span_warning("Your muscles seize! You can't move!"))
+
+/datum/nanite_program/paralyzing/disable_passive_effect()
+ . = ..()
+ to_chat(host_mob, span_notice("Your muscles relax, and you can move again."))
+
+/datum/nanite_program/shocking
+ name = "Electric Shock"
+ desc = "The nanites shock the host when triggered. Destroys a large amount of nanites!"
+ can_trigger = TRUE
+ trigger_cost = 10
+ trigger_cooldown = 300
+ program_flags = NANITE_SHOCK_IMMUNE
+ rogue_types = list(/datum/nanite_program/toxic)
+
+/datum/nanite_program/shocking/on_trigger(comm_message)
+ host_mob.electrocute_act(rand(5,10), "shock nanites", 1, SHOCK_NOGLOVES)
+
+/datum/nanite_program/stun
+ name = "Neural Shock"
+ desc = "The nanites pulse the host's nerves when triggered, inapacitating them for a short period."
+ can_trigger = TRUE
+ trigger_cost = 4
+ trigger_cooldown = 300
+ rogue_types = list(/datum/nanite_program/shocking, /datum/nanite_program/nerve_decay)
+
+/datum/nanite_program/stun/on_trigger(comm_message)
+ playsound(host_mob, "sparks", 75, TRUE, -1, SHORT_RANGE_SOUND_EXTRARANGE)
+ host_mob.Paralyze(80)
+
+/datum/nanite_program/pacifying
+ name = "Pacification"
+ desc = "The nanites suppress the aggression center of the brain, preventing the host from causing direct harm to others."
+ use_rate = 1
+ rogue_types = list(/datum/nanite_program/brain_misfire, /datum/nanite_program/brain_decay)
+
+/datum/nanite_program/pacifying/enable_passive_effect()
+ . = ..()
+ ADD_TRAIT(host_mob, TRAIT_PACIFISM, NANITES_TRAIT)
+
+/datum/nanite_program/pacifying/disable_passive_effect()
+ . = ..()
+ REMOVE_TRAIT(host_mob, TRAIT_PACIFISM, NANITES_TRAIT)
+
+/datum/nanite_program/blinding
+ name = "Blindness"
+ desc = "The nanites suppress the host's ocular nerves, blinding them while they're active."
+ use_rate = 1.5
+ rogue_types = list(/datum/nanite_program/nerve_decay)
+
+/datum/nanite_program/blinding/enable_passive_effect()
+ . = ..()
+ host_mob.become_blind(NANITES_TRAIT)
+
+/datum/nanite_program/blinding/disable_passive_effect()
+ . = ..()
+ host_mob.cure_blind(NANITES_TRAIT)
+
+/datum/nanite_program/mute
+ name = "Mute"
+ desc = "The nanites suppress the host's speech, making them mute while they're active."
+ use_rate = 0.75
+ rogue_types = list(/datum/nanite_program/brain_decay, /datum/nanite_program/brain_misfire)
+
+/datum/nanite_program/mute/enable_passive_effect()
+ . = ..()
+ ADD_TRAIT(host_mob, TRAIT_MUTE, NANITES_TRAIT)
+
+/datum/nanite_program/mute/disable_passive_effect()
+ . = ..()
+ REMOVE_TRAIT(host_mob, TRAIT_MUTE, NANITES_TRAIT)
+
+/datum/nanite_program/fake_death
+ name = "Death Simulation"
+ desc = "The nanites induce a death-like coma into the host, able to fool most medical scans."
+ use_rate = 3.5
+ rogue_types = list(/datum/nanite_program/nerve_decay, /datum/nanite_program/necrotic, /datum/nanite_program/brain_decay)
+
+/datum/nanite_program/fake_death/enable_passive_effect()
+ . = ..()
+ host_mob.emote("deathgasp")
+ host_mob.fakedeath("nanites")
+
+/datum/nanite_program/fake_death/disable_passive_effect()
+ . = ..()
+ host_mob.cure_fakedeath("nanites")
+
+//Can receive transmissions from a nanite communication remote for customized messages
+/datum/nanite_program/comm
+ can_trigger = TRUE
+ var/comm_message = ""
+
+/datum/nanite_program/comm/register_extra_settings()
+ extra_settings[NES_COMM_CODE] = new /datum/nanite_extra_setting/number(0, 0, 9999)
+
+/datum/nanite_program/comm/proc/receive_comm_signal(signal_comm_code, comm_message, comm_source)
+ var/datum/nanite_extra_setting/comm_code = extra_settings[NES_COMM_CODE]
+ if(!activated || !comm_code.get_value())
+ return
+ if(signal_comm_code == comm_code.get_value())
+ host_mob.investigate_log("'s [name] nanite program was messaged by [comm_source] with comm code [signal_comm_code] and message '[comm_message]'.", INVESTIGATE_NANITES)
+ trigger(FALSE, comm_message)
+
+/datum/nanite_program/comm/speech
+ name = "Forced Speech"
+ desc = "The nanites force the host to say a pre-programmed sentence when triggered."
+ unique = FALSE
+ trigger_cost = 3
+ trigger_cooldown = 20
+ rogue_types = list(/datum/nanite_program/brain_misfire, /datum/nanite_program/brain_decay)
+ var/static/list/blacklist = list(
+ "*surrender",
+ "*collapse",
+ "*faint",
+ )
+
+/datum/nanite_program/comm/speech/register_extra_settings()
+ . = ..()
+ extra_settings[NES_SENTENCE] = new /datum/nanite_extra_setting/text("")
+
+/datum/nanite_program/comm/speech/on_trigger(comm_message)
+ var/sent_message = comm_message
+ if(!comm_message)
+ var/datum/nanite_extra_setting/sentence = extra_settings[NES_SENTENCE]
+ sent_message = sentence.get_value()
+ if(sent_message in blacklist)
+ return
+ if(host_mob.stat == DEAD)
+ return
+ to_chat(host_mob, span_warning("You feel compelled to speak..."))
+ host_mob.say(sent_message, forced = "nanite speech")
+
+/datum/nanite_program/comm/voice
+ name = "Skull Echo"
+ desc = "The nanites echo a synthesized message inside the host's skull."
+ unique = FALSE
+ trigger_cost = 1
+ trigger_cooldown = 20
+ rogue_types = list(/datum/nanite_program/brain_misfire, /datum/nanite_program/brain_decay)
+
+/datum/nanite_program/comm/voice/register_extra_settings()
+ . = ..()
+ extra_settings[NES_MESSAGE] = new /datum/nanite_extra_setting/text("")
+
+/datum/nanite_program/comm/voice/on_trigger(comm_message)
+ var/sent_message = comm_message
+ if(!comm_message)
+ var/datum/nanite_extra_setting/message_setting = extra_settings[NES_MESSAGE]
+ sent_message = message_setting.get_value()
+ if(host_mob.stat == DEAD)
+ return
+ to_chat(host_mob, "You hear a strange, robotic voice in your head... \"[span_robot("[sent_message]")]\"")
+
+/datum/nanite_program/comm/hallucination
+ name = "Hallucination"
+ desc = "The nanites make the host hallucinate something when triggered."
+ trigger_cost = 4
+ trigger_cooldown = 80
+ unique = FALSE
+ rogue_types = list(/datum/nanite_program/brain_misfire)
+ var/list/hallucination_options = list(
+ "Message" = /datum/hallucination/chat,
+ "Battle" = /datum/hallucination/battle,
+ "Sound" = /datum/hallucination/fake_sound,
+ "Weird Sound" = /datum/hallucination/fake_sound/weird,
+ "Station Message" = /datum/hallucination/station_message,
+ "Health" = null,
+ "Alert" = /datum/hallucination/fake_alert,
+ "Fire" = /datum/hallucination/fire,
+ "Shock" = /datum/hallucination/shock,
+ "Plasma Flood" = /datum/hallucination/fake_flood,
+ "Random" = null
+ )
+
+/datum/nanite_program/comm/hallucination/register_extra_settings()
+ . = ..()
+ var/list/options = assoc_to_keys(hallucination_options)
+ extra_settings[NES_HALLUCINATION_TYPE] = new /datum/nanite_extra_setting/type("Message", options)
+ extra_settings[NES_HALLUCINATION_DETAIL] = new /datum/nanite_extra_setting/text("")
+
+/datum/nanite_program/comm/hallucination/on_trigger(comm_message)
+ var/datum/nanite_extra_setting/hal_setting = extra_settings[NES_HALLUCINATION_TYPE]
+ var/hal_type = hal_setting.get_value()
+ var/datum/nanite_extra_setting/hal_detail_setting = extra_settings[NES_HALLUCINATION_DETAIL]
+ var/hal_details = hal_detail_setting.get_value()
+ if(comm_message && (hal_type != "Message")) //Triggered via comm remote, but not set to a message hallucination
+ return
+ var/sent_message = comm_message //Comm remotes can send custom hallucination messages for the chat hallucination
+ if(!sent_message)
+ sent_message = hal_details
+
+ if(!iscarbon(host_mob))
+ return
+ var/mob/living/carbon/carbon = host_mob
+ if(hal_details == "random")
+ hal_details = null
+ switch(hal_type)
+ if("Random")
+ carbon.adjust_hallucinations(15)
+ if("Health")
+ switch(hal_details)
+ if("critical")
+ carbon.adjust_timed_status_effect(3 MINUTES, /datum/status_effect/grouped/screwy_hud/fake_crit)
+ if("dead")
+ carbon.adjust_timed_status_effect(3 MINUTES, /datum/status_effect/grouped/screwy_hud/fake_dead)
+ if("healthy")
+ carbon.adjust_timed_status_effect(3 MINUTES, /datum/status_effect/grouped/screwy_hud/fake_healthy)
+ else if(hallucination_options[hal_type])
+ carbon.cause_hallucination(hallucination_options[hal_type], TRUE, hal_details)
+
+/datum/nanite_program/comm/hallucination/set_extra_setting(setting, value)
+ . = ..()
+ if(setting == NES_HALLUCINATION_TYPE)
+ switch(value)
+ if("Message")
+ extra_settings[NES_HALLUCINATION_DETAIL] = new /datum/nanite_extra_setting/text("")
+ if("Battle")
+ extra_settings[NES_HALLUCINATION_DETAIL] = new /datum/nanite_extra_setting/type("random", list("random","laser","disabler","esword","gun","stunprod","harmbaton","bomb"))
+ if("Sound")
+ extra_settings[NES_HALLUCINATION_DETAIL] = new /datum/nanite_extra_setting/type("random", list("random","airlock","airlock pry","console","explosion","far explosion","mech","glass","alarm","beepsky","mech","wall decon","door hack"))
+ if("Weird Sound")
+ extra_settings[NES_HALLUCINATION_DETAIL] = new /datum/nanite_extra_setting/type("random", list("random","phone","hallelujah","highlander","laughter","hyperspace","game over","creepy","tesla"))
+ if("Station Message")
+ extra_settings[NES_HALLUCINATION_DETAIL] = new /datum/nanite_extra_setting/type("random", list("random","ratvar","shuttle dock","blob alert","malf ai","meteors","supermatter"))
+ if("Health")
+ extra_settings[NES_HALLUCINATION_DETAIL] = new /datum/nanite_extra_setting/type("random", list("random","critical","dead","healthy"))
+ if("Alert")
+ extra_settings[NES_HALLUCINATION_DETAIL] = new /datum/nanite_extra_setting/type("random", list("random","not_enough_oxy","not_enough_tox","not_enough_co2","too_much_oxy","too_much_co2","too_much_tox","newlaw","nutrition","charge","gravity","fire","locked","hacked","temphot","tempcold","pressure"))
+ else
+ extra_settings.Remove(NES_HALLUCINATION_DETAIL)
+
+/datum/nanite_program/good_mood
+ name = "Happiness Enhancer"
+ desc = "The nanites synthesize serotonin inside the host's brain, creating an artificial sense of happiness."
+ use_rate = 0.1
+ rogue_types = list(/datum/nanite_program/brain_decay)
+
+/datum/nanite_program/good_mood/register_extra_settings()
+ . = ..()
+ extra_settings[NES_MOOD_MESSAGE] = new /datum/nanite_extra_setting/text("HAPPINESS ENHANCEMENT")
+
+/datum/nanite_program/good_mood/enable_passive_effect()
+ . = ..()
+ host_mob.add_mood_event("nanite_happy", /datum/mood_event/nanite_happiness, get_extra_setting_value(NES_MOOD_MESSAGE))
+
+/datum/nanite_program/good_mood/disable_passive_effect()
+ . = ..()
+ host_mob.clear_mood_event("nanite_happy")
+
+/datum/nanite_program/bad_mood
+ name = "Happiness Suppressor"
+ desc = "The nanites suppress the production of serotonin inside the host's brain, creating an artificial state of depression."
+ use_rate = 0.1
+ rogue_types = list(/datum/nanite_program/brain_decay)
+
+/datum/nanite_program/bad_mood/register_extra_settings()
+ . = ..()
+ extra_settings[NES_MOOD_MESSAGE] = new /datum/nanite_extra_setting/text("HAPPINESS SUPPRESSION")
+
+/datum/nanite_program/bad_mood/enable_passive_effect()
+ . = ..()
+ host_mob.add_mood_event("nanite_unhappy", /datum/mood_event/nanite_sadness, get_extra_setting_value(NES_MOOD_MESSAGE))
+
+/datum/nanite_program/bad_mood/disable_passive_effect()
+ . = ..()
+ host_mob.add_mood_event("nanite_unhappy")
diff --git a/modular_zubbers/code/modules/nanites/nanite_programs/utility.dm b/modular_zubbers/code/modules/nanites/nanite_programs/utility.dm
new file mode 100644
index 00000000000..77567fd5e1e
--- /dev/null
+++ b/modular_zubbers/code/modules/nanites/nanite_programs/utility.dm
@@ -0,0 +1,331 @@
+//Programs that interact with other programs or nanites directly, or have other special purposes.
+/datum/nanite_program/viral
+ name = "Viral Replica"
+ desc = "The nanites constantly send encrypted signals attempting to forcefully copy their own programming into other nanite clusters, also overriding or disabling their cloud sync."
+ use_rate = 0.5
+ rogue_types = list(/datum/nanite_program/toxic)
+ var/pulse_cooldown = 0
+
+/datum/nanite_program/viral/register_extra_settings()
+ extra_settings[NES_PROGRAM_OVERWRITE] = new /datum/nanite_extra_setting/type("Add To", list("Overwrite", "Add To", "Ignore"))
+ extra_settings[NES_CLOUD_OVERWRITE] = new /datum/nanite_extra_setting/number(0, 0, 100)
+
+/datum/nanite_program/viral/active_effect()
+ if(world.time < pulse_cooldown)
+ return
+ var/datum/nanite_extra_setting/program = extra_settings[NES_PROGRAM_OVERWRITE]
+ var/datum/nanite_extra_setting/cloud = extra_settings[NES_CLOUD_OVERWRITE]
+ for(var/mob/M in orange(host_mob, 5))
+ if(SEND_SIGNAL(M, COMSIG_NANITE_IS_STEALTHY))
+ continue
+ switch(program.get_value())
+ if("Overwrite")
+ SEND_SIGNAL(M, COMSIG_NANITE_SYNC, nanites, TRUE)
+ if("Add To")
+ SEND_SIGNAL(M, COMSIG_NANITE_SYNC, nanites, FALSE)
+ SEND_SIGNAL(M, COMSIG_NANITE_SET_CLOUD, cloud.get_value())
+ pulse_cooldown = world.time + 75
+
+/datum/nanite_program/monitoring
+ name = "Monitoring"
+ desc = "The nanites monitor the host's vitals and location, sending them to the suit sensor network."
+ rogue_types = list(/datum/nanite_program/toxic)
+
+/datum/nanite_program/monitoring/enable_passive_effect()
+ . = ..()
+ ADD_TRAIT(host_mob, TRAIT_NANITE_MONITORING, NANITES_TRAIT) //Shows up in diagnostic and medical HUDs as a small blinking icon
+ if(ishuman(host_mob))
+ GLOB.nanite_sensors_list |= host_mob
+ host_mob.hud_set_nanite_indicator()
+
+/datum/nanite_program/monitoring/disable_passive_effect()
+ . = ..()
+ REMOVE_TRAIT(host_mob, TRAIT_NANITE_MONITORING, "nanites")
+ if(ishuman(host_mob))
+ GLOB.nanite_sensors_list -= host_mob
+
+ host_mob.hud_set_nanite_indicator()
+
+/datum/nanite_program/self_scan
+ name = "Host Scan"
+ desc = "The nanites display a detailed readout of a body scan to the host."
+ unique = FALSE
+ can_trigger = TRUE
+ trigger_cost = 3
+ trigger_cooldown = 50
+ rogue_types = list(/datum/nanite_program/toxic)
+
+/datum/nanite_program/self_scan/register_extra_settings()
+ extra_settings[NES_SCAN_TYPE] = new /datum/nanite_extra_setting/type("Medical", list("Medical", "Chemical", "Wound", "Nanite"))
+
+/datum/nanite_program/self_scan/on_trigger(comm_message)
+ if(host_mob.stat == DEAD)
+ return
+ var/datum/nanite_extra_setting/NS = extra_settings[NES_SCAN_TYPE]
+ switch(NS.get_value())
+ if("Medical")
+ healthscan(host_mob, host_mob)
+ if("Chemical")
+ chemscan(host_mob, host_mob)
+ if("Wound")
+ woundscan(host_mob, host_mob)
+ if("Nanite")
+ SEND_SIGNAL(host_mob, COMSIG_NANITE_SCAN, host_mob, TRUE)
+
+/datum/nanite_program/stealth
+ name = "Stealth"
+ desc = "The nanites mask their activity from superficial scans, becoming undetectable by HUDs and non-specialized scanners."
+ rogue_types = list(/datum/nanite_program/toxic)
+ use_rate = 0.2
+
+/datum/nanite_program/stealth/enable_passive_effect()
+ . = ..()
+ nanites.stealth = TRUE
+
+/datum/nanite_program/stealth/disable_passive_effect()
+ . = ..()
+ nanites.stealth = FALSE
+
+/datum/nanite_program/nanite_debugging
+ name = "Nanite Debugging"
+ desc = "Enables various high-cost diagnostics in the nanites, making them able to communicate their program list to portable scanners. \
+ Doing so uses some power, slightly decreasing their replication speed."
+ rogue_types = list(/datum/nanite_program/toxic)
+ use_rate = 0.1
+
+/datum/nanite_program/nanite_debugging/enable_passive_effect()
+ . = ..()
+ nanites.diagnostics = TRUE
+
+/datum/nanite_program/nanite_debugging/disable_passive_effect()
+ . = ..()
+ nanites.diagnostics = FALSE
+
+/datum/nanite_program/relay
+ name = "Relay"
+ desc = "The nanites receive and relay long-range nanite signals."
+ rogue_types = list(/datum/nanite_program/toxic)
+
+/datum/nanite_program/relay/register_extra_settings()
+ extra_settings[NES_RELAY_CHANNEL] = new /datum/nanite_extra_setting/number(1, 1, 9999)
+
+/datum/nanite_program/relay/enable_passive_effect()
+ . = ..()
+ SSnanites.nanite_relays |= src
+
+/datum/nanite_program/relay/disable_passive_effect()
+ . = ..()
+ SSnanites.nanite_relays -= src
+
+/datum/nanite_program/relay/proc/relay_signal(code, relay_code, source)
+ if(!activated)
+ return
+ if(!host_mob)
+ return
+ var/datum/nanite_extra_setting/NS = extra_settings[NES_RELAY_CHANNEL]
+ if(relay_code != NS.get_value())
+ return
+ SEND_SIGNAL(host_mob, COMSIG_NANITE_SIGNAL, code, source)
+
+/datum/nanite_program/relay/proc/relay_comm_signal(comm_code, relay_code, comm_message)
+ if(!activated)
+ return
+ if(!host_mob)
+ return
+ var/datum/nanite_extra_setting/NS = extra_settings[NES_RELAY_CHANNEL]
+ if(relay_code != NS.get_value())
+ return
+ SEND_SIGNAL(host_mob, COMSIG_NANITE_COMM_SIGNAL, comm_code, comm_message)
+
+/datum/nanite_program/metabolic_synthesis
+ name = "Metabolic Synthesis"
+ desc = "The nanites use the metabolic cycle of the host to speed up their replication rate, using their extra nutrition as fuel."
+ use_rate = -0.5 //generates nanites
+ rogue_types = list(/datum/nanite_program/toxic)
+
+/datum/nanite_program/metabolic_synthesis/check_conditions()
+ if(!iscarbon(host_mob))
+ return FALSE
+ var/mob/living/carbon/C = host_mob
+ if(C.nutrition <= NUTRITION_LEVEL_WELL_FED)
+ return FALSE
+ return ..()
+
+/datum/nanite_program/metabolic_synthesis/active_effect()
+ host_mob.adjust_nutrition(-0.5)
+
+/datum/nanite_program/access
+ name = "Subdermal ID"
+ desc = "The nanites store the host's ID access rights in a subdermal magnetic strip. Updates when triggered, copying the host's current access."
+ can_trigger = TRUE
+ trigger_cost = 3
+ trigger_cooldown = 30
+ rogue_types = list(/datum/nanite_program/skin_decay)
+ var/access = list()
+
+//Syncs the nanites with the cumulative current mob's access level. Can potentially wipe existing access.
+/datum/nanite_program/access/on_trigger(comm_message)
+ var/list/potential_items = list()
+
+ potential_items += host_mob.get_active_held_item()
+ potential_items += host_mob.get_inactive_held_item()
+ potential_items += host_mob.pulling
+
+ if(ishuman(host_mob))
+ var/mob/living/carbon/human/H = host_mob
+ potential_items += H.wear_id
+ else if(isanimal(host_mob))
+ var/mob/living/simple_animal/A = host_mob
+ potential_items += A.access_card
+
+ var/list/new_access = list()
+ for(var/obj/item/I in potential_items)
+ new_access += I.GetAccess()
+
+ access = new_access
+
+/datum/nanite_program/spreading
+ name = "Infective Exo-Locomotion"
+ desc = "The nanites gain the ability to survive for brief periods outside of the human body, as well as the ability to start new colonies without an integration process; \
+ resulting in an extremely infective strain of nanites."
+ use_rate = 1.50
+ rogue_types = list(/datum/nanite_program/aggressive_replication, /datum/nanite_program/necrotic)
+ var/spread_cooldown = 0
+
+/datum/nanite_program/spreading/active_effect()
+ if(world.time < spread_cooldown)
+ return
+ spread_cooldown = world.time + 50
+ var/list/mob/living/target_hosts = list()
+ for(var/mob/living/L in oview(5, host_mob))
+ if(!prob(25))
+ continue
+ if(!CAN_HAVE_NANITES(L))
+ continue
+ target_hosts += L
+ if(!target_hosts.len)
+ return
+ var/mob/living/infectee = pick(target_hosts)
+ if(prob(100 - infectee.getarmor(null, BIO)))
+ //this will potentially take over existing nanites!
+ infectee.AddComponent(/datum/component/nanites, 10)
+ SEND_SIGNAL(infectee, COMSIG_NANITE_SYNC, nanites)
+ SEND_SIGNAL(infectee, COMSIG_NANITE_SET_CLOUD, nanites.cloud_id)
+ infectee.investigate_log("was infected by spreading nanites with cloud ID [nanites.cloud_id] by [key_name(host_mob)] at [AREACOORD(infectee)].", INVESTIGATE_NANITES)
+
+/datum/nanite_program/nanite_sting
+ name = "Nanite Sting"
+ desc = "When triggered, builds a invisible spikes of nanites on the host's skin that \
+ can infect a nearby non-host with a copy of the host's nanites cluster \
+ that is unlinked from the cloud. \
+ Will not work on hosts or those already infected."
+ can_trigger = TRUE
+ trigger_cost = 5
+ trigger_cooldown = 100
+ rogue_types = list(/datum/nanite_program/glitch, /datum/nanite_program/toxic)
+ var/decay_timer
+
+/datum/nanite_program/nanite_sting/Destroy()
+ . = ..()
+ if(!decay_timer)
+ return
+ decay_sting()
+
+/datum/nanite_program/nanite_sting/on_trigger(comm_message)
+ consume_nanites(-15)
+ to_chat(host_mob, span_warning("Your hands becomes sharp and prickly."))
+ RegisterSignal(host_mob, COMSIG_LIVING_EARLY_UNARMED_ATTACK, PROC_REF(on_attack_hand))
+ decay_timer = addtimer(CALLBACK(src, PROC_REF(decay_sting)), 30 SECONDS, TIMER_STOPPABLE)
+
+/datum/nanite_program/nanite_sting/proc/on_attack_hand(atom/source, mob/user, modifiers)
+ SIGNAL_HANDLER
+ var/mob/living/living = source
+ if(!istype(living)) return
+ if(!CAN_HAVE_NANITES(living) || SEND_SIGNAL(living, COMSIG_HAS_NANITES) || !living.Adjacent(host_mob)) return
+
+ if(prob(100 - living.getarmor(null, BIO)))
+ //unlike with Infective Exo-Locomotion, this can't take over existing nanites, because Nanite Sting only targets non-hosts.
+ living.AddComponent(/datum/component/nanites, 5)
+ SEND_SIGNAL(living, COMSIG_NANITE_SYNC, nanites)
+ // SEND_SIGNAL(living, COMSIG_NANITE_SET_CLOUD, nanites.cloud_id) won't set the cloud
+ // SEND_SIGNAL(living, COMSIG_NANITE_SET_CLOUD_SYNC, NANITE_CLOUD_DISABLE)
+ living.investigate_log("was infected by a nanite cluster with cloud ID [nanites.cloud_id] by [key_name(host_mob)] at [AREACOORD(living)].", INVESTIGATE_NANITES)
+ to_chat(living, span_warning("You feel a tiny prick."))
+ decay_sting()
+
+/datum/nanite_program/nanite_sting/proc/decay_sting()
+ to_chat(host_mob, span_warning("Your hands no longer feel like they're covered in spines."))
+ deltimer(decay_timer)
+ decay_timer = null
+ UnregisterSignal(host_mob, COMSIG_LIVING_EARLY_UNARMED_ATTACK)
+
+/datum/nanite_program/mitosis
+ name = "Mitosis"
+ desc = "The nanites gain the ability to self-replicate, using bluespace to power the process. Becomes more effective the more nanites are already in the host; \
+ For every 50 nanite volume in the host, the production rate is increased by 0.5. The replication has also a chance to corrupt the nanite programming \
+ due to copy faults - constant cloud sync is highly recommended."
+ use_rate = 0
+ rogue_types = list(/datum/nanite_program/toxic)
+
+/datum/nanite_program/mitosis/active_effect()
+ var/rep_rate = round(nanites.nanite_volume / 50, 1) //0.5 per 50 nanite volume
+ rep_rate *= 0.5
+ nanites.adjust_nanites(null, rep_rate)
+ if(prob(rep_rate))
+ var/datum/nanite_program/fault = pick(nanites.programs)
+ if(fault == src)
+ return
+ fault.software_error()
+ host_mob.investigate_log("[fault] nanite program received a software error due to Mitosis program.", INVESTIGATE_NANITES)
+
+/datum/nanite_program/dermal_button
+ name = "Dermal Button"
+ desc = "Displays a button on the host's skin, which can be used to send a signal to the nanites."
+ unique = FALSE
+ var/datum/action/innate/nanite_button/button
+
+/datum/nanite_program/dermal_button/register_extra_settings()
+ extra_settings[NES_SENT_CODE] = new /datum/nanite_extra_setting/number(1, 1, 9999)
+ extra_settings[NES_BUTTON_NAME] = new /datum/nanite_extra_setting/text("Button")
+ extra_settings[NES_ICON] = new /datum/nanite_extra_setting/type("power", list("blank","one","two","three","four","five","plus","minus","exclamation","question","cross","info","heart","skull","brain","brain_damage","injection","blood","shield","reaction","network","power","radioactive","electricity","magnetism","scan","repair","id","wireless","say","sleep","bomb"))
+
+/datum/nanite_program/dermal_button/enable_passive_effect()
+ . = ..()
+ var/datum/nanite_extra_setting/bn_name = extra_settings[NES_BUTTON_NAME]
+ var/datum/nanite_extra_setting/bn_icon = extra_settings[NES_ICON]
+ if(!button)
+ button = new(src, bn_name.get_value(), bn_icon.get_value())
+ button.target = host_mob
+ button.Grant(host_mob)
+
+/datum/nanite_program/dermal_button/disable_passive_effect()
+ . = ..()
+ if(button)
+ button.Remove(host_mob)
+
+/datum/nanite_program/dermal_button/on_mob_remove()
+ . = ..()
+ QDEL_NULL(button)
+
+/datum/nanite_program/dermal_button/proc/press()
+ if(activated)
+ host_mob.visible_message(span_notice("[host_mob] presses a button on [host_mob.p_their()] forearm."),
+ span_notice("You press the nanite button on your forearm."), null, 2)
+ var/datum/nanite_extra_setting/sent_code = extra_settings[NES_SENT_CODE]
+ SEND_SIGNAL(host_mob, COMSIG_NANITE_SIGNAL, sent_code.get_value(), "a [name] program")
+
+/datum/action/innate/nanite_button
+ name = "Button"
+ button_icon = 'icons/mob/actions/actions_items.dmi'
+ check_flags = AB_CHECK_HANDS_BLOCKED|AB_CHECK_IMMOBILE|AB_CHECK_CONSCIOUS
+ button_icon_state = "bci_power"
+ var/datum/nanite_program/dermal_button/program
+
+/datum/action/innate/nanite_button/New(datum/nanite_program/dermal_button/_program, _name, _icon)
+ ..()
+ program = _program
+ name = _name
+ button_icon_state = "bci_[_icon]"
+
+/datum/action/innate/nanite_button/Activate()
+ program.press()
diff --git a/modular_zubbers/code/modules/nanites/nanite_programs/weapon.dm b/modular_zubbers/code/modules/nanites/nanite_programs/weapon.dm
new file mode 100644
index 00000000000..f71a101ad97
--- /dev/null
+++ b/modular_zubbers/code/modules/nanites/nanite_programs/weapon.dm
@@ -0,0 +1,186 @@
+//Programs specifically engineered to cause harm to either the user or its surroundings (as opposed to ones that only do it due to broken programming)
+//Very dangerous!
+
+/datum/nanite_program/flesh_eating
+ name = "Cellular Breakdown"
+ desc = "The nanites destroy cellular structures in the host's body, causing brute damage."
+ use_rate = 1.5
+ rogue_types = list(/datum/nanite_program/necrotic)
+
+/datum/nanite_program/flesh_eating/active_effect()
+ if(iscarbon(host_mob))
+ var/mob/living/carbon/C = host_mob
+ C.take_bodypart_damage(1, 0, TRUE)
+ else
+ host_mob.adjustBruteLoss(1, TRUE)
+ if(prob(3))
+ to_chat(host_mob, span_warning("You feel a stab of pain from somewhere inside you."))
+
+/datum/nanite_program/poison
+ name = "Poisoning"
+ desc = "The nanites deliver poisonous chemicals to the host's internal organs, causing toxin damage and vomiting."
+ use_rate = 1.5
+ rogue_types = list(/datum/nanite_program/toxic)
+
+/datum/nanite_program/poison/active_effect()
+ host_mob.adjustToxLoss(1)
+ if(prob(2))
+ to_chat(host_mob, span_warning("You feel nauseous."))
+ if(iscarbon(host_mob))
+ var/mob/living/carbon/C = host_mob
+ C.vomit(20)
+
+/datum/nanite_program/memory_leak
+ name = "Memory Leak"
+ desc = "This program invades the memory space used by other programs, causing frequent corruptions and errors."
+ use_rate = 0
+ rogue_types = list(/datum/nanite_program/toxic)
+
+/datum/nanite_program/memory_leak/active_effect()
+ if(prob(6))
+ var/datum/nanite_program/target = pick(nanites.programs)
+ if(target == src)
+ return
+ target.software_error()
+ host_mob.investigate_log("[target] nanite program received a software error due to Memory Leak program.", INVESTIGATE_NANITES)
+
+/datum/nanite_program/aggressive_replication
+ name = "Aggressive Replication"
+ desc = "Nanites will consume organic matter to improve their replication rate, damaging the host. The efficiency increases with the volume of nanites, requiring 200 to break even, \
+ and scaling linearly for a net positive of 0.1 production rate per 20 nanite volume beyond that."
+ use_rate = 1
+ rogue_types = list(/datum/nanite_program/necrotic)
+
+/datum/nanite_program/aggressive_replication/active_effect()
+ var/extra_regen = round(nanites.nanite_volume / 200, 0.1)
+ nanites.adjust_nanites(null, extra_regen)
+ host_mob.adjustBruteLoss(extra_regen / 2, TRUE)
+
+/datum/nanite_program/meltdown
+ name = "Meltdown"
+ desc = "Causes an internal meltdown inside the nanites, causing internal burns inside the host as well as rapidly destroying the nanite population.\
+ Sets the nanites' safety threshold to 0 when activated."
+ use_rate = 10
+ rogue_types = list(/datum/nanite_program/glitch)
+
+/datum/nanite_program/meltdown/active_effect()
+ host_mob.adjustFireLoss(3.5)
+
+/datum/nanite_program/meltdown/enable_passive_effect()
+ . = ..()
+ to_chat(host_mob, span_userdanger("Your blood is burning!"))
+ nanites.safety_threshold = 0
+
+/datum/nanite_program/meltdown/disable_passive_effect()
+ . = ..()
+ to_chat(host_mob, span_warning("Your blood cools down, and the pain gradually fades."))
+
+/datum/nanite_program/explosive
+ name = "Chain Detonation"
+ desc = "Detonates all the nanites inside the host in a chain reaction when triggered."
+ can_trigger = TRUE
+ trigger_cost = 25 //plus every idle nanite left afterwards
+ trigger_cooldown = 100 //Just to avoid double-triggering
+ rogue_types = list(/datum/nanite_program/toxic)
+
+/datum/nanite_program/explosive/on_trigger(comm_message)
+ host_mob.visible_message(span_warning("[host_mob] starts emitting a high-pitched buzzing, and [host_mob.p_their()] skin begins to glow..."),\
+ span_userdanger("You start emitting a high-pitched buzzing, and your skin begins to glow..."))
+ addtimer(CALLBACK(src, .proc/boom), clamp((nanites.nanite_volume * 0.35), 25, 150))
+
+/datum/nanite_program/explosive/proc/boom()
+ dyn_explosion(host_mob, nanites.nanite_volume / 50)
+ qdel(nanites)
+
+//TODO make it defuse if triggered again
+
+/datum/nanite_program/heart_stop
+ name = "Heart-Stopper"
+ desc = "Stops the host's heart when triggered; restarts it if triggered again."
+ can_trigger = TRUE
+ trigger_cost = 12
+ trigger_cooldown = 10
+ rogue_types = list(/datum/nanite_program/nerve_decay)
+
+/datum/nanite_program/heart_stop/on_trigger(comm_message)
+ if(iscarbon(host_mob))
+ var/mob/living/carbon/carbon = host_mob
+ var/obj/item/organ/heart/heart = carbon.get_organ_slot(ORGAN_SLOT_HEART)
+ if(heart)
+ if(heart.is_beating())
+ heart.Stop()
+ else
+ heart.Restart()
+
+/datum/nanite_program/emp
+ name = "Electromagnetic Resonance"
+ desc = "The nanites cause an electromagnetic pulse around the host when triggered. Will corrupt other nanite programs!"
+ can_trigger = TRUE
+ trigger_cost = 10
+ trigger_cooldown = 50
+ program_flags = NANITE_EMP_IMMUNE
+ rogue_types = list(/datum/nanite_program/toxic)
+
+/datum/nanite_program/emp/on_trigger(comm_message)
+ empulse(host_mob, 1, 2)
+
+/datum/nanite_program/pyro
+ name = "Sub-Dermal Combustion"
+ desc = "The nanites cause buildup of flammable fluids under the host's skin, then ignites them."
+ use_rate = 4
+ rogue_types = list(/datum/nanite_program/skin_decay, /datum/nanite_program/cryo)
+
+/datum/nanite_program/pyro/check_conditions()
+ if(host_mob.fire_stacks >= 10 && host_mob.on_fire)
+ return FALSE
+ return ..()
+
+/datum/nanite_program/pyro/active_effect()
+ host_mob.adjust_fire_stacks(1)
+ host_mob.ignite_mob()
+
+/datum/nanite_program/cryo
+ name = "Cryogenic Treatment"
+ desc = "The nanites rapidly sink heat through the host's skin, lowering their temperature."
+ use_rate = 1
+ rogue_types = list(/datum/nanite_program/skin_decay, /datum/nanite_program/pyro)
+
+/datum/nanite_program/cryo/check_conditions()
+ if(host_mob.bodytemperature <= 70)
+ return FALSE
+ return ..()
+
+/datum/nanite_program/cryo/active_effect()
+ host_mob.adjust_bodytemperature(-rand(15,25), 50)
+
+/datum/nanite_program/comm/mind_control
+ name = "Mind Control"
+ desc = "The nanites imprint an absolute directive onto the host's brain for one minute when triggered."
+ trigger_cost = 30
+ trigger_cooldown = 1800
+ rogue_types = list(/datum/nanite_program/brain_decay, /datum/nanite_program/brain_misfire)
+
+/datum/nanite_program/comm/mind_control/register_extra_settings()
+ . = ..()
+ extra_settings[NES_DIRECTIVE] = new /datum/nanite_extra_setting/text("...")
+
+/datum/nanite_program/comm/mind_control/on_trigger(comm_message)
+ if(host_mob.stat == DEAD)
+ return
+ var/sent_directive = comm_message
+ if(!comm_message)
+ var/datum/nanite_extra_setting/ES = extra_settings[NES_DIRECTIVE]
+ sent_directive = ES.get_value()
+ brainwash(host_mob, sent_directive)
+ log_game("A mind control nanite program brainwashed [key_name(host_mob)] with the objective '[sent_directive]'.")
+ host_mob.log_message("has been brainwashed with the objective '[sent_directive]' triggered by a mind control nanite program.", LOG_ATTACK)
+ addtimer(CALLBACK(src, .proc/end_brainwashing), 600)
+
+/datum/nanite_program/comm/mind_control/proc/end_brainwashing()
+ if(host_mob.mind && host_mob.mind.has_antag_datum(/datum/antagonist/brainwashed))
+ host_mob.mind.remove_antag_datum(/datum/antagonist/brainwashed)
+ log_game("[key_name(host_mob)] is no longer brainwashed by nanites.")
+
+/datum/nanite_program/comm/mind_control/disable_passive_effect()
+ . = ..()
+ end_brainwashing()
diff --git a/modular_zubbers/code/modules/nanites/nanite_sign.dm b/modular_zubbers/code/modules/nanites/nanite_sign.dm
new file mode 100644
index 00000000000..f849aca4e1c
--- /dev/null
+++ b/modular_zubbers/code/modules/nanites/nanite_sign.dm
@@ -0,0 +1,9 @@
+/obj/structure/sign/departments/nanites
+ name = "\improper Nanite Lab sign"
+ icon = 'modular_zubbers/icons/obj/signs/nanite_sign.dmi'
+ icon_state = "nanites"
+ sign_change_name = "Department - Science: Nanites"
+ desc = "A sign labelling an area where testing and development of nanites is performed."
+ is_editable = TRUE
+
+MAPPING_DIRECTIONAL_HELPERS(/obj/structure/sign/departments/nanites, 32)
diff --git a/modular_zubbers/code/modules/nanites/nanite_symptom.dm b/modular_zubbers/code/modules/nanites/nanite_symptom.dm
new file mode 100644
index 00000000000..1ca11b28e27
--- /dev/null
+++ b/modular_zubbers/code/modules/nanites/nanite_symptom.dm
@@ -0,0 +1,71 @@
+/datum/symptom/nano_boost
+ name = "Nano-symbiosis"
+ desc = "The virus reacts to nanites in the host's bloodstream by enhancing their replication cycle."
+ stealth = 0
+ resistance = 2
+ stage_speed = 2
+ transmittable = -1
+ level = 7
+ severity = 0
+ symptom_delay_min = 1
+ symptom_delay_max = 1
+ var/reverse_boost = FALSE
+ threshold_descs = list(
+ "Transmission 5" = "Increases the virus' growth rate while nanites are present.",
+ "Stage Speed 7" = "Increases the replication boost."
+ )
+
+/datum/symptom/nano_boost/Start(datum/disease/advance/A)
+ . = ..()
+ if(!.)
+ return
+ if(A.totalTransmittable() >= 5) //reverse boost
+ reverse_boost = TRUE
+ if(A.totalStageSpeed() >= 7) //more nanites
+ power = 2
+
+/datum/symptom/nano_boost/Activate(datum/disease/advance/A)
+ . = ..()
+ if(!.)
+ return
+ var/mob/living/carbon/M = A.affected_mob
+ SEND_SIGNAL(M, COMSIG_NANITE_ADJUST_VOLUME, 0.5 * power)
+ if(reverse_boost && SEND_SIGNAL(M, COMSIG_HAS_NANITES))
+ if(prob(A.stage_prob))
+ A.stage = min(A.stage + 1,A.max_stages)
+
+/datum/symptom/nano_destroy
+ name = "Silicolysis"
+ desc = "The virus reacts to nanites in the host's bloodstream by attacking and consuming them."
+ stealth = 0
+ resistance = 4
+ stage_speed = -1
+ transmittable = 1
+ level = 7
+ severity = 0
+ symptom_delay_min = 1
+ symptom_delay_max = 1
+ var/reverse_boost = FALSE
+ threshold_descs = list(
+ "Stage Speed 5" = "Increases the virus' growth rate while nanites are present.",
+ "Resistance 7" = "Severely increases the rate at which the nanites are destroyed."
+ )
+
+/datum/symptom/nano_destroy/Start(datum/disease/advance/A)
+ . = ..()
+ if(!.)
+ return
+ if(A.totalStageSpeed() >= 5) //reverse boost
+ reverse_boost = TRUE
+ if(A.totalResistance() >= 7) //more nanites
+ power = 3
+
+/datum/symptom/nano_destroy/Activate(datum/disease/advance/A)
+ . = ..()
+ if(!.)
+ return
+ var/mob/living/carbon/M = A.affected_mob
+ SEND_SIGNAL(M, COMSIG_NANITE_ADJUST_VOLUME, -0.4 * power)
+ if(reverse_boost && SEND_SIGNAL(M, COMSIG_HAS_NANITES))
+ if(prob(A.stage_prob))
+ A.stage = min(A.stage + 1,A.max_stages)
diff --git a/modular_zubbers/code/modules/nanites/program_designs.dm b/modular_zubbers/code/modules/nanites/program_designs.dm
new file mode 100644
index 00000000000..6dbfb469664
--- /dev/null
+++ b/modular_zubbers/code/modules/nanites/program_designs.dm
@@ -0,0 +1,573 @@
+/datum/design/nanites
+ name = "None"
+ desc = "Warn a coder if you see this."
+ id = "default_nanites"
+ build_type = NANITE_COMPILER
+ construction_time = 50
+ category = list()
+ research_icon = 'modular_zubbers/icons/obj/devices/nanite_devices.dmi'
+ research_icon_state = "nanite_program"
+ var/program_type = /datum/nanite_program
+
+////////////////////UTILITY NANITES//////////////////////////////////////
+
+/datum/design/nanites/metabolic_synthesis
+ name = "Metabolic Synthesis"
+ desc = "The nanites use the metabolic cycle of the host to speed up their replication rate, using their extra nutrition as fuel."
+ id = "metabolic_nanites"
+ program_type = /datum/nanite_program/metabolic_synthesis
+ category = list("Utility Nanites")
+
+/datum/design/nanites/viral
+ name = "Viral Replica"
+ desc = "The nanites constantly send encrypted signals attempting to forcefully copy their own programming into other nanite clusters."
+ id = "viral_nanites"
+ program_type = /datum/nanite_program/viral
+ category = list("Utility Nanites")
+
+/datum/design/nanites/monitoring
+ name = "Monitoring"
+ desc = "The nanites monitor the host's vitals and location, sending them to the suit sensor network."
+ id = "monitoring_nanites"
+ program_type = /datum/nanite_program/monitoring
+ category = list("Utility Nanites")
+
+/datum/design/nanites/self_scan
+ name = "Host Scan"
+ desc = "The nanites display a detailed readout of a body scan to the host."
+ id = "selfscan_nanites"
+ program_type = /datum/nanite_program/self_scan
+ category = list("Utility Nanites")
+
+/datum/design/nanites/dermal_button
+ name = "Dermal Button"
+ desc = "Displays a button on the host's skin, which can be used to send a signal to the nanites."
+ id = "dermal_button_nanites"
+ program_type = /datum/nanite_program/dermal_button
+ category = list("Utility Nanites")
+
+/datum/design/nanites/stealth
+ name = "Stealth"
+ desc = "The nanites hide their activity and programming from superficial scans."
+ id = "stealth_nanites"
+ program_type = /datum/nanite_program/stealth
+ category = list("Utility Nanites")
+
+/datum/design/nanites/nanite_debugging
+ name = "Nanite Debugging"
+ desc = "Enables various high-cost diagnostics in the nanites, making them able to communicate their program list to portable scanners. \
+ Doing so uses some power, slightly decreasing their replication speed."
+ id = "debugging_nanites"
+ program_type = /datum/nanite_program/nanite_debugging
+ category = list("Utility Nanites")
+
+/datum/design/nanites/access
+ name = "Subdermal ID"
+ desc = "The nanites store the host's ID access rights in a subdermal magnetic strip. Updates when triggered, copying the host's current access."
+ id = "access_nanites"
+ program_type = /datum/nanite_program/access
+ category = list("Utility Nanites")
+
+/datum/design/nanites/relay
+ name = "Relay"
+ desc = "The nanites receive and relay long-range nanite signals."
+ id = "relay_nanites"
+ program_type = /datum/nanite_program/relay
+ category = list("Utility Nanites")
+
+/datum/design/nanites/repeater
+ name = "Signal Repeater"
+ desc = "When triggered, sends another signal to the nanites, optionally with a delay."
+ id = "repeater_nanites"
+ program_type = /datum/nanite_program/sensor/repeat
+ category = list("Utility Nanites")
+
+/datum/design/nanites/relay_repeater
+ name = "Relay Signal Repeater"
+ desc = "When triggered, sends another signal to a relay channel, optionally with a delay."
+ id = "relay_repeater_nanites"
+ program_type = /datum/nanite_program/sensor/relay_repeat
+ category = list("Utility Nanites")
+
+/datum/design/nanites/emp
+ name = "Electromagnetic Resonance"
+ desc = "The nanites cause an electromagnetic pulse around the host when triggered. Will corrupt other nanite programs!"
+ id = "emp_nanites"
+ program_type = /datum/nanite_program/emp
+ category = list("Utility Nanites")
+
+/datum/design/nanites/spreading
+ name = "Infective Exo-Locomotion"
+ desc = "The nanites gain the ability to survive for brief periods outside of the human body, as well as the ability to start new colonies without an integration process; \
+ resulting in an extremely infective strain of nanites."
+ id = "spreading_nanites"
+ program_type = /datum/nanite_program/spreading
+ category = list("Utility Nanites")
+
+/datum/design/nanites/nanite_sting
+ name = "Nanite Sting"
+ desc = "When triggered, projects a nearly invisible spike of nanites that attempts to infect a nearby non-host with a copy of the host's nanites cluster."
+ id = "nanite_sting_nanites"
+ program_type = /datum/nanite_program/nanite_sting
+ category = list("Utility Nanites")
+
+/datum/design/nanites/mitosis
+ name = "Mitosis"
+ desc = "The nanites gain the ability to self-replicate, using bluespace to power the process, instead of drawing from a template. This rapidly speeds up the replication rate,\
+ but it causes occasional software errors due to faulty copies."
+ id = "mitosis_nanites"
+ program_type = /datum/nanite_program/mitosis
+ category = list("Utility Nanites")
+
+////////////////////MEDICAL NANITES//////////////////////////////////////
+/datum/design/nanites/regenerative
+ name = "Accelerated Regeneration"
+ desc = "The nanites boost the host's natural regeneration, increasing their healing speed."
+ id = "regenerative_nanites"
+ program_type = /datum/nanite_program/regenerative
+ category = list("Medical Nanites")
+
+/datum/design/nanites/regenerative_advanced
+ name = "Bio-Reconstruction"
+ desc = "The nanites manually repair and replace organic cells, acting much faster than normal regeneration. \
+ However, this program cannot detect the difference between harmed and unharmed, causing it to consume nanites even if it has no effect."
+ id = "regenerative_plus_nanites"
+ program_type = /datum/nanite_program/regenerative/advanced
+ category = list("Medical Nanites")
+
+/datum/design/nanites/temperature
+ name = "Temperature Adjustment"
+ desc = "The nanites adjust the host's internal temperature to an ideal level."
+ id = "temperature_nanites"
+ program_type = /datum/nanite_program/temperature
+ category = list("Medical Nanites")
+
+/datum/design/nanites/purging
+ name = "Blood Purification"
+ desc = "The nanites purge toxins and chemicals from the host's bloodstream."
+ id = "purging_nanites"
+ program_type = /datum/nanite_program/purging
+ category = list("Medical Nanites")
+
+/datum/design/nanites/purging_advanced
+ name = "Selective Blood Purification"
+ desc = "The nanites purge toxins and dangerous chemicals from the host's bloodstream, while ignoring beneficial chemicals. \
+ The added processing power required to analyze the chemicals severely increases the nanite consumption rate."
+ id = "purging_plus_nanites"
+ program_type = /datum/nanite_program/purging/advanced
+ category = list("Medical Nanites")
+
+/datum/design/nanites/brain_heal
+ name = "Neural Regeneration"
+ desc = "The nanites fix neural connections in the host's brain, reversing brain damage and minor traumas."
+ id = "brainheal_nanites"
+ program_type = /datum/nanite_program/brain_heal
+ category = list("Medical Nanites")
+
+/datum/design/nanites/brain_heal_advanced
+ name = "Neural Reimaging"
+ desc = "The nanites are able to backup and restore the host's neural connections, potentially replacing entire chunks of missing or damaged brain matter."
+ id = "brainheal_plus_nanites"
+ program_type = /datum/nanite_program/brain_heal_advanced
+ category = list("Medical Nanites")
+
+/datum/design/nanites/blood_restoring
+ name = "Blood Regeneration"
+ desc = "The nanites stimulate and boost blood cell production in the host."
+ id = "bloodheal_nanites"
+ program_type = /datum/nanite_program/blood_restoring
+ category = list("Medical Nanites")
+
+/datum/design/nanites/repairing
+ name = "Mechanical Repair"
+ desc = "The nanites fix damage in the host's mechanical limbs."
+ id = "repairing_nanites"
+ program_type = /datum/nanite_program/regenerative/robotic
+ category = list("Medical Nanites")
+
+/datum/design/nanites/defib
+ name = "Defibrillation"
+ desc = "The nanites, when triggered, send a defibrillating shock to the host's heart."
+ id = "defib_nanites"
+ program_type = /datum/nanite_program/defib
+ category = list("Medical Nanites")
+
+
+////////////////////AUGMENTATION NANITES//////////////////////////////////////
+
+/datum/design/nanites/nervous
+ name = "Nerve Support"
+ desc = "The nanites act as a secondary nervous system, reducing the amount of time the host is stunned."
+ id = "nervous_nanites"
+ program_type = /datum/nanite_program/nervous
+ category = list("Augmentation Nanites")
+
+/datum/design/nanites/hardening
+ name = "Dermal Hardening"
+ desc = "The nanites form a mesh under the host's skin, protecting them from melee and bullet impacts."
+ id = "hardening_nanites"
+ program_type = /datum/nanite_program/dermal_armor
+ category = list("Augmentation Nanites")
+
+/datum/design/nanites/refractive
+ name = "Dermal Refractive Surface"
+ desc = "The nanites form a membrane above the host's skin, reducing the effect of laser and energy impacts."
+ id = "refractive_nanites"
+ program_type = /datum/nanite_program/dermal_armor/refractive
+ category = list("Augmentation Nanites")
+
+/datum/design/nanites/coagulating
+ name = "Vein Repressurization"
+ desc = "The nanites re-route circulating blood away from open wounds, dramatically reducing bleeding rate."
+ id = "coagulating_nanites"
+ program_type = /datum/nanite_program/coagulating
+ category = list("Augmentation Nanites")
+
+/datum/design/nanites/conductive
+ name = "Electric Conduction"
+ desc = "The nanites act as a grounding rod for electric shocks, protecting the host. Shocks can still damage the nanites themselves."
+ id = "conductive_nanites"
+ program_type = /datum/nanite_program/conductive
+ category = list("Augmentation Nanites")
+
+/datum/design/nanites/mindshield
+ name = "Mental Barrier"
+ desc = "The nanites form a protective membrane around the host's brain, shielding them from abnormal influences while they're active."
+ id = "mindshield_nanites"
+ program_type = /datum/nanite_program/mindshield
+ category = list("Augmentation Nanites")
+
+////////////////////DEFECTIVE NANITES//////////////////////////////////////
+
+/datum/design/nanites/glitch
+ name = "Glitch"
+ desc = "A heavy software corruption that causes nanites to gradually break down."
+ id = "glitch_nanites"
+ program_type = /datum/nanite_program/glitch
+ category = list("Defective Nanites")
+
+/datum/design/nanites/necrotic
+ name = "Necrosis"
+ desc = "The nanites attack internal tissues indiscriminately, causing widespread damage."
+ id = "necrotic_nanites"
+ program_type = /datum/nanite_program/necrotic
+ category = list("Defective Nanites")
+
+/datum/design/nanites/toxic
+ name = "Toxin Buildup"
+ desc = "The nanites cause a slow but constant toxin buildup inside the host."
+ id = "toxic_nanites"
+ program_type = /datum/nanite_program/toxic
+ category = list("Defective Nanites")
+
+/datum/design/nanites/suffocating
+ name = "Hypoxemia"
+ desc = "The nanites prevent the host's blood from absorbing oxygen efficiently."
+ id = "suffocating_nanites"
+ program_type = /datum/nanite_program/suffocating
+ category = list("Defective Nanites")
+
+/datum/design/nanites/brain_misfire
+ name = "Brain Misfire"
+ desc = "The nanites interfere with neural pathways, causing minor psychological disturbances."
+ id = "brainmisfire_nanites"
+ program_type = /datum/nanite_program/brain_misfire
+ category = list("Defective Nanites")
+
+/datum/design/nanites/skin_decay
+ name = "Dermalysis"
+ desc = "The nanites attack skin cells, causing irritation, rashes, and minor damage."
+ id = "skindecay_nanites"
+ program_type = /datum/nanite_program/skin_decay
+ category = list("Defective Nanites")
+
+/datum/design/nanites/nerve_decay
+ name = "Nerve Decay"
+ desc = "The nanites attack the host's nerves, causing lack of coordination and short bursts of paralysis."
+ id = "nervedecay_nanites"
+ program_type = /datum/nanite_program/nerve_decay
+ category = list("Defective Nanites")
+
+/datum/design/nanites/brain_decay
+ name = "Brain-Eating Nanites"
+ desc = "Damages brain cells, gradually decreasing the host's cognitive functions."
+ id = "braindecay_nanites"
+ program_type = /datum/nanite_program/brain_decay
+ category = list("Defective Nanites")
+
+////////////////////WEAPONIZED NANITES/////////////////////////////////////
+
+/datum/design/nanites/flesh_eating
+ name = "Cellular Breakdown"
+ desc = "The nanites destroy cellular structures in the host's body, causing brute damage."
+ id = "flesheating_nanites"
+ program_type = /datum/nanite_program/flesh_eating
+ category = list("Weaponized Nanites")
+
+/datum/design/nanites/poison
+ name = "Poisoning"
+ desc = "The nanites deliver poisonous chemicals to the host's internal organs, causing toxin damage and vomiting."
+ id = "poison_nanites"
+ program_type = /datum/nanite_program/poison
+ category = list("Weaponized Nanites")
+
+/datum/design/nanites/memory_leak
+ name = "Memory Leak"
+ desc = "This program invades the memory space used by other programs, causing frequent corruptions and errors."
+ id = "memleak_nanites"
+ program_type = /datum/nanite_program/memory_leak
+ category = list("Weaponized Nanites")
+
+/datum/design/nanites/aggressive_replication
+ name = "Aggressive Replication"
+ desc = "Nanites will consume organic matter to improve their replication rate, damaging the host."
+ id = "aggressive_nanites"
+ program_type = /datum/nanite_program/aggressive_replication
+ category = list("Weaponized Nanites")
+
+/datum/design/nanites/meltdown
+ name = "Meltdown"
+ desc = "Causes an internal meltdown inside the nanites, causing internal burns inside the host as well as rapidly destroying the nanite population.\
+ Sets the nanites' safety threshold to 0 when activated."
+ id = "meltdown_nanites"
+ program_type = /datum/nanite_program/meltdown
+ category = list("Weaponized Nanites")
+
+/datum/design/nanites/cryo
+ name = "Cryogenic Treatment"
+ desc = "The nanites rapidly skin heat through the host's skin, lowering their temperature."
+ id = "cryo_nanites"
+ program_type = /datum/nanite_program/cryo
+ category = list("Weaponized Nanites")
+
+/datum/design/nanites/pyro
+ name = "Sub-Dermal Combustion"
+ desc = "The nanites cause buildup of flammable fluids under the host's skin, then ignites them."
+ id = "pyro_nanites"
+ program_type = /datum/nanite_program/pyro
+ category = list("Weaponized Nanites")
+
+/datum/design/nanites/heart_stop
+ name = "Heart-Stopper"
+ desc = "Stops the host's heart when triggered; restarts it if triggered again."
+ id = "heartstop_nanites"
+ program_type = /datum/nanite_program/heart_stop
+ category = list("Weaponized Nanites")
+
+/datum/design/nanites/explosive
+ name = "Chain Detonation"
+ desc = "Blows up all the nanites inside the host in a chain reaction when triggered."
+ id = "explosive_nanites"
+ program_type = /datum/nanite_program/explosive
+ category = list("Weaponized Nanites")
+
+/datum/design/nanites/mind_control
+ name = "Mind Control"
+ desc = "The nanites imprint an absolute directive onto the host's brain while they're active."
+ id = "mindcontrol_nanites"
+ program_type = /datum/nanite_program/comm/mind_control
+ category = list("Weaponized Nanites")
+
+////////////////////SUPPRESSION NANITES//////////////////////////////////////
+
+/datum/design/nanites/shock
+ name = "Electric Shock"
+ desc = "The nanites shock the host when triggered. Destroys a large amount of nanites!"
+ id = "shock_nanites"
+ program_type = /datum/nanite_program/shocking
+ category = list("Suppression Nanites")
+
+/datum/design/nanites/stun
+ name = "Neural Shock"
+ desc = "The nanites pulse the host's nerves when triggered, inapacitating them for a short period."
+ id = "stun_nanites"
+ program_type = /datum/nanite_program/stun
+ category = list("Suppression Nanites")
+
+/datum/design/nanites/sleepy
+ name = "Sleep Induction"
+ desc = "The nanites cause rapid narcolepsy when triggered."
+ id = "sleep_nanites"
+ program_type = /datum/nanite_program/sleepy
+ category = list("Suppression Nanites")
+
+/datum/design/nanites/paralyzing
+ name = "Paralysis"
+ desc = "The nanites actively suppress nervous pulses, effectively paralyzing the host."
+ id = "paralyzing_nanites"
+ program_type = /datum/nanite_program/paralyzing
+ category = list("Suppression Nanites")
+
+/datum/design/nanites/fake_death
+ name = "Death Simulation"
+ desc = "The nanites induce a death-like coma into the host, able to fool most medical scans."
+ id = "fakedeath_nanites"
+ program_type = /datum/nanite_program/fake_death
+ category = list("Suppression Nanites")
+
+/datum/design/nanites/pacifying
+ name = "Pacification"
+ desc = "The nanites suppress the aggression center of the brain, preventing the host from causing direct harm to others."
+ id = "pacifying_nanites"
+ program_type = /datum/nanite_program/pacifying
+ category = list("Suppression Nanites")
+
+/datum/design/nanites/blinding
+ name = "Blindness"
+ desc = "The nanites suppress the host's ocular nerves, blinding them while they're active."
+ id = "blinding_nanites"
+ program_type = /datum/nanite_program/blinding
+ category = list("Suppression Nanites")
+
+/datum/design/nanites/mute
+ name = "Mute"
+ desc = "The nanites suppress the host's speech, making them mute while they're active."
+ id = "mute_nanites"
+ program_type = /datum/nanite_program/mute
+ category = list("Suppression Nanites")
+
+/datum/design/nanites/voice
+ name = "Skull Echo"
+ desc = "The nanites echo a synthesized message inside the host's skull."
+ id = "voice_nanites"
+ program_type = /datum/nanite_program/comm/voice
+ category = list("Suppression Nanites")
+
+/datum/design/nanites/speech
+ name = "Forced Speech"
+ desc = "The nanites force the host to say a pre-programmed sentence when triggered."
+ id = "speech_nanites"
+ program_type = /datum/nanite_program/comm/speech
+ category = list("Suppression Nanites")
+
+/datum/design/nanites/hallucination
+ name = "Hallucination"
+ desc = "The nanites make the host see and hear things that aren't real."
+ id = "hallucination_nanites"
+ program_type = /datum/nanite_program/comm/hallucination
+ category = list("Suppression Nanites")
+
+/datum/design/nanites/good_mood
+ name = "Happiness Enhancer"
+ desc = "The nanites synthesize serotonin inside the host's brain, creating an artificial sense of happiness."
+ id = "good_mood_nanites"
+ program_type = /datum/nanite_program/good_mood
+ category = list("Suppression Nanites")
+
+/datum/design/nanites/bad_mood
+ name = "Happiness Suppressor"
+ desc = "The nanites suppress the production of serotonin inside the host's brain, creating an artificial state of depression."
+ id = "bad_mood_nanites"
+ program_type = /datum/nanite_program/bad_mood
+ category = list("Suppression Nanites")
+
+////////////////////SENSOR NANITES//////////////////////////////////////
+
+/datum/design/nanites/sensor_health
+ name = "Health Sensor"
+ desc = "The nanites receive a signal when the host's health is above/below a certain percentage."
+ id = "sensor_health_nanites"
+ program_type = /datum/nanite_program/sensor/health
+ category = list("Sensor Nanites")
+
+/datum/design/nanites/sensor_damage
+ name = "Damage Sensor"
+ desc = "The nanites receive a signal when a host's specific damage type is above/below a target value."
+ id = "sensor_damage_nanites"
+ program_type = /datum/nanite_program/sensor/damage
+ category = list("Sensor Nanites")
+
+/datum/design/nanites/sensor_crit
+ name = "Critical Health Sensor"
+ desc = "The nanites receive a signal when the host first reaches critical health."
+ id = "sensor_crit_nanites"
+ program_type = /datum/nanite_program/sensor/crit
+ category = list("Sensor Nanites")
+
+/datum/design/nanites/sensor_death
+ name = "Death Sensor"
+ desc = "The nanites receive a signal when they detect the host is dead."
+ id = "sensor_death_nanites"
+ program_type = /datum/nanite_program/sensor/death
+ category = list("Sensor Nanites")
+
+/datum/design/nanites/sensor_voice
+ name = "Voice Sensor"
+ desc = "Sends a signal when the nanites hear a determined word or sentence."
+ id = "sensor_voice_nanites"
+ program_type = /datum/nanite_program/sensor/voice
+ category = list("Sensor Nanites")
+
+/datum/design/nanites/sensor_nanite_volume
+ name = "Nanite Volume Sensor"
+ desc = "The nanites receive a signal when the nanite supply is above/below a certain percentage."
+ id = "sensor_nanite_volume"
+ program_type = /datum/nanite_program/sensor/nanite_volume
+ category = list("Sensor Nanites")
+
+/datum/design/nanites/sensor_species
+ name = "Species Sensor"
+ desc = "When triggered, the nanites scan the host to determine their species and output a signal depending on the conditions set in the settings."
+ id = "sensor_species_nanites"
+ program_type = /datum/nanite_program/sensor/species
+ category = list("Sensor Nanites")
+
+////////////////////NANITE PROTOCOLS//////////////////////////////////////
+//Note about the category name: The UI cuts the last 8 characters from the category name to remove the " Nanites" in the other categories
+//Because of this, Protocols was getting cut down to "P", so i had to add some padding
+/datum/design/nanites/kickstart
+ name = "Kickstart Protocol"
+ desc = "Replication Protocol: the nanites focus on early growth, heavily boosting replication rate for a few minutes after the initial implantation."
+ id = "kickstart_nanites"
+ program_type = /datum/nanite_program/protocol/kickstart
+ category = list("Protocols_Nanites")
+
+/datum/design/nanites/factory
+ name = "Factory Protocol"
+ desc = "Replication Protocol: the nanites build a factory matrix within the host, gradually increasing replication speed over time. The factory decays if the protocol is not active."
+ id = "factory_nanites"
+ program_type = /datum/nanite_program/protocol/factory
+ category = list("Protocols_Nanites")
+
+/datum/design/nanites/pyramid
+ name = "Pyramid Protocol"
+ desc = "Replication Protocol: the nanites implement an alternate cooperative replication protocol that is more efficient as long as the saturation level is above 80%."
+ id = "pyramid_nanites"
+ program_type = /datum/nanite_program/protocol/pyramid
+ category = list("Protocols_Nanites")
+
+/datum/design/nanites/offline
+ name = "Eclipse Protocol"
+ desc = "Replication Protocol: while the host is asleep or otherwise unconcious, the nanites exploit the reduced interference to replicate more quickly."
+ id = "offline_nanites"
+ program_type = /datum/nanite_program/protocol/offline
+ category = list("Protocols_Nanites")
+
+/datum/design/nanites/hive
+ name = "Hive Protocol"
+ desc = "Storage Protocol: the nanites use a more efficient grid arrangment for volume storage, increasing maximum volume in a host."
+ id = "hive_nanites"
+ program_type = /datum/nanite_program/protocol/hive
+ category = list("Protocols_Nanites")
+
+/datum/design/nanites/zip
+ name = "Zip Protocol"
+ desc = "Storage Protocol: the nanites are disassembled and compacted when unused, greatly increasing the maximum volume while in a host. However, the process slows down the replication rate slightly."
+ id = "zip_nanites"
+ program_type = /datum/nanite_program/protocol/zip
+ category = list("Protocols_Nanites")
+
+/datum/design/nanites/free_range
+ name = "Free-range Protocol"
+ desc = "Storage Protocol: the nanites discard their default storage protocols in favour of a cheaper and more organic approach. Reduces maximum volume, but increases the replication rate."
+ id = "free_range_nanites"
+ program_type = /datum/nanite_program/protocol/free_range
+ category = list("Protocols_Nanites")
+
+/datum/design/nanites/unsafe_storage
+ name = "S.L.O. Protocol"
+ desc = "Storage Protocol: 'S.L.O.P.', or Storage Level Override Protocol, completely disables the safety measures normally present in nanites,\
+ allowing them to reach much higher saturation levels, but at the risk of causing internal damage to the host."
+ id = "unsafe_storage_nanites"
+ program_type = /datum/nanite_program/protocol/unsafe_storage
+ category = list("Protocols_Nanites")
diff --git a/modular_zubbers/code/modules/nanites/rules.dm b/modular_zubbers/code/modules/nanites/rules.dm
new file mode 100644
index 00000000000..79723ca5b03
--- /dev/null
+++ b/modular_zubbers/code/modules/nanites/rules.dm
@@ -0,0 +1,148 @@
+/datum/nanite_rule
+ var/name = "Generic Condition"
+ var/desc = "When triggered, the program is active"
+ var/datum/nanite_program/program
+
+/datum/nanite_rule/New(datum/nanite_program/new_program)
+ program = new_program
+ if(LAZYLEN(new_program.rules) <= 5) //Avoid infinite stacking rules
+ new_program.rules += src
+ else
+ qdel(src)
+
+/datum/nanite_rule/proc/remove()
+ program.rules -= src
+ program = null
+ qdel(src)
+
+/datum/nanite_rule/proc/check_rule()
+ return TRUE
+
+/datum/nanite_rule/proc/display()
+ return name
+
+/datum/nanite_rule/proc/copy_to(datum/nanite_program/new_program)
+ new type(new_program)
+
+/datum/nanite_rule/health
+ name = "Health"
+ desc = "Checks the host's health status."
+
+ var/threshold = 50
+ var/above = TRUE
+
+/datum/nanite_rule/health/check_rule()
+ var/health_percent = program.host_mob.health / program.host_mob.maxHealth * 100
+ if(above)
+ if(health_percent >= threshold)
+ return TRUE
+ else
+ if(health_percent < threshold)
+ return TRUE
+ return FALSE
+
+/datum/nanite_rule/health/display()
+ return "[name] [above ? ">" : "<"] [threshold]%"
+
+/datum/nanite_rule/health/copy_to(datum/nanite_program/new_program)
+ var/datum/nanite_rule/health/rule = new(new_program)
+ rule.above = above
+ rule.threshold = threshold
+
+//TODO allow inversion
+/datum/nanite_rule/crit
+ name = "Crit"
+ desc = "Checks if the host is in critical condition."
+
+/datum/nanite_rule/crit/check_rule()
+ return HAS_TRAIT(program.host_mob, TRAIT_CRITICAL_CONDITION)
+
+
+/datum/nanite_rule/death
+ name = "Death"
+ desc = "Checks if the host is dead."
+
+/datum/nanite_rule/death/check_rule()
+ if(program.host_mob.stat == DEAD || HAS_TRAIT(program.host_mob, TRAIT_FAKEDEATH))
+ return TRUE
+ return FALSE
+
+/datum/nanite_rule/cloud_sync
+ name = "Cloud Sync"
+ desc = "Checks if the nanites have cloud sync enabled or disabled."
+ var/check_type = "Enabled"
+
+/datum/nanite_rule/cloud_sync/check_rule()
+ if(check_type == "Enabled")
+ return program.nanites.cloud_active
+ else
+ return !program.nanites.cloud_active
+
+/datum/nanite_rule/cloud_sync/copy_to(datum/nanite_program/new_program)
+ var/datum/nanite_rule/cloud_sync/rule = new(new_program)
+ rule.check_type = check_type
+
+/datum/nanite_rule/cloud_sync/display()
+ return "[name]:[check_type]"
+
+/datum/nanite_rule/nanites
+ name = "Nanite Volume"
+ desc = "Checks the host's nanite volume."
+
+ var/threshold = 50
+ var/above = TRUE
+
+/datum/nanite_rule/nanites/check_rule()
+ var/nanite_percent = (program.nanites.nanite_volume - program.nanites.safety_threshold)/(program.nanites.max_nanites - program.nanites.safety_threshold)*100
+ if(above)
+ if(nanite_percent >= threshold)
+ return TRUE
+ else
+ if(nanite_percent < threshold)
+ return TRUE
+ return FALSE
+
+/datum/nanite_rule/nanites/copy_to(datum/nanite_program/new_program)
+ var/datum/nanite_rule/nanites/rule = new(new_program)
+ rule.above = above
+ rule.threshold = threshold
+
+/datum/nanite_rule/nanites/display()
+ return "[name] [above ? ">" : "<"] [threshold]%"
+
+/datum/nanite_rule/damage
+ name = "Damage"
+ desc = "Checks the host's damage."
+
+ var/threshold = 50
+ var/above = TRUE
+ var/damage_type = BRUTE
+
+/datum/nanite_rule/damage/check_rule()
+ var/damage_amt = 0
+ switch(damage_type)
+ if(BRUTE)
+ damage_amt = program.host_mob.getBruteLoss()
+ if(BURN)
+ damage_amt = program.host_mob.getFireLoss()
+ if(TOX)
+ damage_amt = program.host_mob.getToxLoss()
+ if(OXY)
+ damage_amt = program.host_mob.getOxyLoss()
+
+ if(above)
+ if(damage_amt >= threshold)
+ return TRUE
+ else
+ if(damage_amt < threshold)
+ return TRUE
+ return FALSE
+
+/datum/nanite_rule/damage/copy_to(datum/nanite_program/new_program)
+ var/datum/nanite_rule/damage/rule = new(new_program)
+ rule.above = above
+ rule.threshold = threshold
+ rule.damage_type = damage_type
+
+/datum/nanite_rule/damage/display()
+ return "[damage_type] [above ? ">" : "<"] [threshold]"
diff --git a/modular_zubbers/code/modules/nanites/spawners.dm b/modular_zubbers/code/modules/nanites/spawners.dm
new file mode 100644
index 00000000000..d0a775cfa6a
--- /dev/null
+++ b/modular_zubbers/code/modules/nanites/spawners.dm
@@ -0,0 +1,11 @@
+
+/obj/effect/spawner/random/techstorage/rnd_all/Initialize(mapload)
+ loot += list(
+ /obj/item/circuitboard/computer/nanite_chamber_control,
+ /obj/item/circuitboard/computer/nanite_cloud_controller,
+ /obj/item/circuitboard/machine/nanite_chamber,
+ /obj/item/circuitboard/machine/nanite_programmer,
+ /obj/item/circuitboard/machine/nanite_program_hub,
+ /obj/item/circuitboard/computer/scan_consolenew,
+ )
+ . = ..()
diff --git a/modular_zubbers/code/modules/nanites/techweb_nodes.dm b/modular_zubbers/code/modules/nanites/techweb_nodes.dm
new file mode 100644
index 00000000000..8aebe364c94
--- /dev/null
+++ b/modular_zubbers/code/modules/nanites/techweb_nodes.dm
@@ -0,0 +1,180 @@
+/datum/techweb_node/nanite_base
+ id = "nanite_base"
+ display_name = "Basic Nanite Programming"
+ description = "The basics of nanite construction and programming. Nanites will passively generate science points based on how many are hosting them."
+ prereq_ids = list(TECHWEB_NODE_BCI)
+ design_ids = list(
+ // "access_nanites",
+ "debugging_nanites",
+ "monitoring_nanites",
+ "nanite_chamber",
+ "nanite_chamber_control",
+ "nanite_cloud_control",
+ "nanite_comm_remote",
+ "nanite_disk",
+ "nanite_program_hub",
+ "nanite_programmer",
+ "nanite_remote",
+ "nanite_scanner",
+ "public_nanite_chamber",
+ "relay_nanites",
+ "relay_repeater_nanites",
+ "repairing_nanites",
+ "repeater_nanites",
+ "sensor_nanite_volume",
+ )
+ research_costs = list(TECHWEB_POINT_TYPE_GENERIC = TECHWEB_TIER_2_POINTS)
+
+/datum/techweb_node/nanite_smart
+ id = "nanite_smart"
+ display_name = "Smart Nanite Programming"
+ description = "Nanite programs that require nanites to perform complex actions, act independently, roam or seek targets."
+ prereq_ids = list("nanite_base", TECHWEB_NODE_PROGRAMMING)
+ design_ids = list(
+ "memleak_nanites",
+ "metabolic_nanites",
+ "purging_nanites",
+ "sensor_voice_nanites",
+ "stealth_nanites",
+ "voice_nanites",
+ )
+ research_costs = list(TECHWEB_POINT_TYPE_GENERIC = TECHWEB_TIER_3_POINTS, TECHWEB_POINT_TYPE_NANITE = TECHWEB_TIER_1_POINTS)
+
+/datum/techweb_node/nanite_mesh
+ id = "nanite_mesh"
+ display_name = "Mesh Nanite Programming"
+ description = "Nanite programs that require static structures and membranes."
+ prereq_ids = list("nanite_base", TECHWEB_NODE_MOD_ENGI_ADV)
+ design_ids = list(
+ "conductive_nanites",
+ "cryo_nanites",
+ "dermal_button_nanites",
+ "emp_nanites",
+ "hardening_nanites",
+ "refractive_nanites",
+ "shock_nanites",
+ "temperature_nanites",
+ )
+ research_costs = list(TECHWEB_POINT_TYPE_GENERIC = TECHWEB_TIER_4_POINTS, TECHWEB_POINT_TYPE_NANITE = TECHWEB_TIER_2_POINTS)
+
+/datum/techweb_node/nanite_bio
+ id = "nanite_bio"
+ display_name = "Biological Nanite Programming"
+ description = "Nanite programs that require complex biological interaction."
+ prereq_ids = list("nanite_base", TECHWEB_NODE_MOD_MEDICAL_ADV)
+ design_ids = list(
+ "bloodheal_nanites",
+ "coagulating_nanites",
+ "flesheating_nanites",
+ "poison_nanites",
+ "sensor_crit_nanites",
+ "sensor_damage_nanites",
+ "sensor_death_nanites",
+ "sensor_health_nanites",
+ "sensor_species_nanites",
+ )
+ research_costs = list(TECHWEB_POINT_TYPE_GENERIC = TECHWEB_TIER_3_POINTS, TECHWEB_POINT_TYPE_NANITE = TECHWEB_TIER_3_POINTS)
+
+/datum/techweb_node/nanite_neural
+ id = "nanite_neural"
+ display_name = "Neural Nanite Programming"
+ description = "Nanite programs affecting nerves and brain matter."
+ prereq_ids = list("nanite_bio")
+ design_ids = list(
+ "bad_mood_nanites",
+ "brainheal_nanites",
+ "good_mood_nanites",
+ "nervous_nanites",
+ "paralyzing_nanites",
+ "selfscan_nanites",
+ "stun_nanites",
+ )
+ research_costs = list(TECHWEB_POINT_TYPE_GENERIC = TECHWEB_TIER_3_POINTS, TECHWEB_POINT_TYPE_NANITE = TECHWEB_TIER_1_POINTS)
+
+/datum/techweb_node/nanite_synaptic
+ id = "nanite_synaptic"
+ display_name = "Synaptic Nanite Programming"
+ description = "Nanite programs affecting mind and thoughts."
+ prereq_ids = list("nanite_neural", TECHWEB_NODE_SURGERY_EXP)
+ design_ids = list(
+ "blinding_nanites",
+ "hallucination_nanites",
+ "mindshield_nanites",
+ "mute_nanites",
+ "pacifying_nanites",
+ "sleep_nanites",
+ // "speech_nanites",
+ )
+ research_costs = list(TECHWEB_POINT_TYPE_GENERIC = TECHWEB_TIER_3_POINTS, TECHWEB_POINT_TYPE_NANITE = TECHWEB_TIER_1_POINTS)
+
+/datum/techweb_node/nanite_harmonic
+ id = "nanite_harmonic"
+ display_name = "Harmonic Nanite Programming"
+ description = "Nanite programs that require seamless integration between nanites and biology. Passively increases nanite regeneration rate for all clouds upon researching."
+ prereq_ids = list("nanite_bio","nanite_smart","nanite_mesh")
+ design_ids = list(
+ "regenerative_nanites",
+ "aggressive_nanites",
+ "brainheal_plus_nanites",
+ "defib_nanites",
+ "fakedeath_nanites",
+ "purging_plus_nanites",
+ // "regenerative_plus_nanites",
+ )
+ research_costs = list(TECHWEB_POINT_TYPE_GENERIC = TECHWEB_TIER_4_POINTS, TECHWEB_POINT_TYPE_NANITE = TECHWEB_TIER_2_POINTS)
+
+/datum/techweb_node/nanite_combat
+ id = "nanite_military"
+ display_name = "Military Nanite Programming"
+ description = "Nanite programs that perform military-grade functions."
+ prereq_ids = list("nanite_harmonic", TECHWEB_NODE_SYNDICATE_BASIC)
+ design_ids = list(
+ // "explosive_nanites",
+ "meltdown_nanites",
+ "nanite_sting_nanites",
+ "pyro_nanites",
+ "viral_nanites",
+ )
+ research_costs = list(TECHWEB_POINT_TYPE_GENERIC = TECHWEB_TIER_5_POINTS, TECHWEB_POINT_TYPE_NANITE = TECHWEB_TIER_3_POINTS)
+
+/datum/techweb_node/nanite_hazard
+ id = "nanite_hazard"
+ display_name = "Hazard Nanite Programs"
+ description = "Extremely advanced Nanite programs with the potential of being extremely dangerous."
+ prereq_ids = list("nanite_harmonic", TECHWEB_NODE_ALIENTECH)
+ design_ids = list(
+ "mindcontrol_nanites",
+ "mitosis_nanites",
+ // "spreading_nanites",
+ )
+ research_costs = list(TECHWEB_POINT_TYPE_GENERIC = TECHWEB_TIER_4_POINTS, TECHWEB_POINT_TYPE_NANITE = TECHWEB_TIER_2_POINTS)
+
+/datum/techweb_node/nanite_replication_protocols
+ id = "nanite_replication_protocols"
+ display_name = "Nanite Replication Protocols"
+ description = "Protocols that overwrite the default nanite replication routine to achieve more efficiency in certain circumstances."
+ prereq_ids = list("nanite_smart")
+ design_ids = list(
+ "factory_nanites",
+ "kickstart_nanites",
+ "offline_nanites",
+ "pyramid_nanites",
+ )
+ research_costs = list(TECHWEB_POINT_TYPE_GENERIC = TECHWEB_TIER_5_POINTS, TECHWEB_POINT_TYPE_NANITE = TECHWEB_TIER_3_POINTS)
+ hidden = TRUE
+ experimental = TRUE
+
+/datum/techweb_node/nanite_storage_protocols
+ id = "nanite_storage_protocols"
+ display_name = "Nanite Storage Protocols"
+ description = "Protocols that overwrite the default nanite storage routine to achieve more efficiency or greater capacity."
+ prereq_ids = list("nanite_smart")
+ design_ids = list(
+ "free_range_nanites",
+ "hive_nanites",
+ "unsafe_storage_nanites",
+ "zip_nanites",
+ )
+ research_costs = list(TECHWEB_POINT_TYPE_GENERIC = TECHWEB_TIER_5_POINTS, TECHWEB_POINT_TYPE_NANITE = TECHWEB_TIER_3_POINTS)
+ hidden = TRUE
+ experimental = TRUE
diff --git a/modular_zubbers/code/modules/nanites/unit_test.dm b/modular_zubbers/code/modules/nanites/unit_test.dm
new file mode 100644
index 00000000000..1b725f6ac91
--- /dev/null
+++ b/modular_zubbers/code/modules/nanites/unit_test.dm
@@ -0,0 +1,9 @@
+
+/datum/unit_test/nanite_designs/Run()
+//Can't use allocate because of bug with certain datums
+ var/datum/design/nanites/default_design_nanites = new /datum/design/nanites()
+
+ for(var/path in subtypesof(/datum/design))
+ var/datum/design/nanites/current_design = new path //Create an instance of each design
+ if (isnull(current_design.program_type) || current_design.program_type == default_design_nanites.program_type) //Check if the Nanite design provides a program
+ Fail("Nanite Design [current_design.type] does not have have any program_type set")
diff --git a/modular_zubbers/icons/areas/nanite_area.dmi b/modular_zubbers/icons/areas/nanite_area.dmi
new file mode 100644
index 00000000000..132b954307e
Binary files /dev/null and b/modular_zubbers/icons/areas/nanite_area.dmi differ
diff --git a/modular_zubbers/icons/effects/decals/nanite_decal.dmi b/modular_zubbers/icons/effects/decals/nanite_decal.dmi
new file mode 100644
index 00000000000..a4384c24bd0
Binary files /dev/null and b/modular_zubbers/icons/effects/decals/nanite_decal.dmi differ
diff --git a/modular_zubbers/icons/mob/huds/hud.dmi b/modular_zubbers/icons/mob/huds/hud.dmi
index e123a7b9966..3c31a58122d 100644
Binary files a/modular_zubbers/icons/mob/huds/hud.dmi and b/modular_zubbers/icons/mob/huds/hud.dmi differ
diff --git a/modular_zubbers/icons/obj/devices/nanite_devices.dmi b/modular_zubbers/icons/obj/devices/nanite_devices.dmi
new file mode 100644
index 00000000000..e9cec8222bf
Binary files /dev/null and b/modular_zubbers/icons/obj/devices/nanite_devices.dmi differ
diff --git a/modular_zubbers/icons/obj/machines/nanite_machines.dmi b/modular_zubbers/icons/obj/machines/nanite_machines.dmi
new file mode 100644
index 00000000000..815d3924c60
Binary files /dev/null and b/modular_zubbers/icons/obj/machines/nanite_machines.dmi differ
diff --git a/modular_zubbers/icons/obj/signs/nanite_sign.dmi b/modular_zubbers/icons/obj/signs/nanite_sign.dmi
new file mode 100644
index 00000000000..a4384c24bd0
Binary files /dev/null and b/modular_zubbers/icons/obj/signs/nanite_sign.dmi differ
diff --git a/strings/cas_white.txt b/strings/cas_white.txt
index f8f324272df..58c5b96e931 100644
--- a/strings/cas_white.txt
+++ b/strings/cas_white.txt
@@ -92,6 +92,7 @@ Bluespace artillery.
Brain cakes.
Breaking spacetime with thousands of bluespace tomatoes.
Buttbots.
+The primal, nerve-slapping sex that nanites are having inside you.
Button spam.
BWOINK!
Calling a shuttle because I broke a toenail.
diff --git a/strings/locations.json b/strings/locations.json
index 00befdaee7c..867060cda2a 100644
--- a/strings/locations.json
+++ b/strings/locations.json
@@ -92,6 +92,7 @@
"Virology",
"Warehouse",
"Waste Disposal",
- "Xenobiology Lab"
+ "Xenobiology Lab",
+ "Nanite Lab"
]
}
diff --git a/tgstation.dme b/tgstation.dme
index a0bfd6a121b..7cf552d9c2a 100644
--- a/tgstation.dme
+++ b/tgstation.dme
@@ -521,6 +521,7 @@
#include "code\__DEFINES\~~bubber_defines\loadout.dm"
#include "code\__DEFINES\~~bubber_defines\misc.dm"
#include "code\__DEFINES\~~bubber_defines\mood.dm"
+#include "code\__DEFINES\~~bubber_defines\nanites.dm"
#include "code\__DEFINES\~~bubber_defines\research_categories.dm"
#include "code\__DEFINES\~~bubber_defines\say.dm"
#include "code\__DEFINES\~~bubber_defines\signals.dm"
@@ -531,9 +532,11 @@
#include "code\__DEFINES\~~bubber_defines\surgery.dm"
#include "code\__DEFINES\~~bubber_defines\transport.dm"
#include "code\__DEFINES\~~bubber_defines\vehicles.dm"
+#include "code\__DEFINES\~~bubber_defines\vomit.dm"
#include "code\__DEFINES\~~bubber_defines\___HELPERS\global_lists.dm"
#include "code\__DEFINES\~~bubber_defines\research\techweb_nodes.dm"
#include "code\__DEFINES\~~bubber_defines\traits\declarations.dm"
+#include "code\__DEFINES\~~bubber_defines\traits\nanites.dm"
#include "code\__DEFINES\~~bubber_defines\traits\sources.dm"
#include "code\__HELPERS\_auxtools_api.dm"
#include "code\__HELPERS\_dreamluau.dm"
@@ -8783,6 +8786,7 @@
#include "modular_zubbers\code\controllers\subsystem\mapping.dm"
#include "modular_zubbers\code\controllers\subsystem\research.dm"
#include "modular_zubbers\code\controllers\subsystem\vote.dm"
+#include "modular_zubbers\code\controllers\subsystem\processing\nanites.dm"
#include "modular_zubbers\code\controllers\subsystem\processing\sol_subsystem.dm"
#include "modular_zubbers\code\datums\action.dm"
#include "modular_zubbers\code\datums\akula_overrides.dm"
@@ -8793,6 +8797,7 @@
#include "modular_zubbers\code\datums\ai_laws\laws_neutral.dm"
#include "modular_zubbers\code\datums\ai_laws\laws_station_sided.dm"
#include "modular_zubbers\code\datums\brain_damage\magic.dm"
+#include "modular_zubbers\code\datums\components\nanites.dm"
#include "modular_zubbers\code\datums\components\sign_language.dm"
#include "modular_zubbers\code\datums\components\squeak.dm"
#include "modular_zubbers\code\datums\components\surgery_initiator.dm"
@@ -8823,6 +8828,7 @@
#include "modular_zubbers\code\datums\mood_events\dominant_mood.dm"
#include "modular_zubbers\code\datums\mood_events\food_events.dm"
#include "modular_zubbers\code\datums\mood_events\miasma_events.dm"
+#include "modular_zubbers\code\datums\mood_events\nanite_events.dm"
#include "modular_zubbers\code\datums\mutations\visuals_override.dm"
#include "modular_zubbers\code\datums\quirks\_quirk.dm"
#include "modular_zubbers\code\datums\quirks\negative_quirks\all_nighter.dm"
@@ -8869,6 +8875,7 @@
#include "modular_zubbers\code\datums\weather\weather_types\sand_storm.dm"
#include "modular_zubbers\code\datums\wounds\permanent_limp.dm"
#include "modular_zubbers\code\game\area\areas.dm"
+#include "modular_zubbers\code\game\area\nanite.dm"
#include "modular_zubbers\code\game\area\areas\centcom.dm"
#include "modular_zubbers\code\game\area\areas\moonstation.dm"
#include "modular_zubbers\code\game\area\areas\station.dm"
@@ -9294,6 +9301,7 @@
#include "modular_zubbers\code\modules\mob\living\carbon\alien\adult\adult.dm"
#include "modular_zubbers\code\modules\mob\living\carbon\human\_species.dm"
#include "modular_zubbers\code\modules\mob\living\carbon\human\human.dm"
+#include "modular_zubbers\code\modules\mob\living\carbon\human\physiology.dm"
#include "modular_zubbers\code\modules\mob\living\carbon\human\species\akula.dm"
#include "modular_zubbers\code\modules\mob\living\carbon\human\species\tajaran.dm"
#include "modular_zubbers\code\modules\mob\living\carbon\human\species\xeno.dm"
@@ -9357,6 +9365,38 @@
#include "modular_zubbers\code\modules\more_renaming\renaming_overrides.dm"
#include "modular_zubbers\code\modules\movespeed\_movespeed_modifier.dm"
#include "modular_zubbers\code\modules\movespeed\modifiers\status_effects.dm"
+#include "modular_zubbers\code\modules\nanites\designs.dm"
+#include "modular_zubbers\code\modules\nanites\living.dm"
+#include "modular_zubbers\code\modules\nanites\nanite_programs.dm"
+#include "modular_zubbers\code\modules\nanites\nanite_sign.dm"
+#include "modular_zubbers\code\modules\nanites\nanite_symptom.dm"
+#include "modular_zubbers\code\modules\nanites\program_designs.dm"
+#include "modular_zubbers\code\modules\nanites\rules.dm"
+#include "modular_zubbers\code\modules\nanites\spawners.dm"
+#include "modular_zubbers\code\modules\nanites\techweb_nodes.dm"
+#include "modular_zubbers\code\modules\nanites\extra_settings\_extra_setting.dm"
+#include "modular_zubbers\code\modules\nanites\extra_settings\boolean.dm"
+#include "modular_zubbers\code\modules\nanites\extra_settings\number.dm"
+#include "modular_zubbers\code\modules\nanites\extra_settings\text.dm"
+#include "modular_zubbers\code\modules\nanites\extra_settings\type.dm"
+#include "modular_zubbers\code\modules\nanites\items\boards.dm"
+#include "modular_zubbers\code\modules\nanites\items\items.dm"
+#include "modular_zubbers\code\modules\nanites\items\program_disks.dm"
+#include "modular_zubbers\code\modules\nanites\items\remote.dm"
+#include "modular_zubbers\code\modules\nanites\machines\nanite_chamber.dm"
+#include "modular_zubbers\code\modules\nanites\machines\nanite_chamber_computer.dm"
+#include "modular_zubbers\code\modules\nanites\machines\nanite_cloud_controller.dm"
+#include "modular_zubbers\code\modules\nanites\machines\nanite_program_hub.dm"
+#include "modular_zubbers\code\modules\nanites\machines\nanite_programmer.dm"
+#include "modular_zubbers\code\modules\nanites\machines\public_chamber.dm"
+#include "modular_zubbers\code\modules\nanites\nanite_programs\buffing.dm"
+#include "modular_zubbers\code\modules\nanites\nanite_programs\healing.dm"
+#include "modular_zubbers\code\modules\nanites\nanite_programs\protocols.dm"
+#include "modular_zubbers\code\modules\nanites\nanite_programs\rogue.dm"
+#include "modular_zubbers\code\modules\nanites\nanite_programs\sensor.dm"
+#include "modular_zubbers\code\modules\nanites\nanite_programs\suppression.dm"
+#include "modular_zubbers\code\modules\nanites\nanite_programs\utility.dm"
+#include "modular_zubbers\code\modules\nanites\nanite_programs\weapon.dm"
#include "modular_zubbers\code\modules\nyamagotchi\nyamagotchi.dm"
#include "modular_zubbers\code\modules\opposing_force\code\items.dm"
#include "modular_zubbers\code\modules\opposing_force\code\equipment\antagonist_powers.dm"
diff --git a/tgui/packages/tgui/interfaces/NaniteChamberControl.tsx b/tgui/packages/tgui/interfaces/NaniteChamberControl.tsx
new file mode 100644
index 00000000000..31d36eaaf53
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/NaniteChamberControl.tsx
@@ -0,0 +1,286 @@
+import {
+ Box,
+ Button,
+ Collapsible,
+ LabeledList,
+ NoticeBox,
+ NumberInput,
+ Section,
+ Stack,
+} from 'tgui-core/components';
+
+import { useBackend } from '../backend';
+import { Window } from '../layouts';
+import { NaniteProgram } from './Nanites/types';
+
+export const NaniteChamberControl = (props, context) => {
+ return (
+
+
+
+
+
+ );
+};
+
+interface NaniteChamberProps {
+ status_msg: string;
+ locked: boolean;
+ occupant_name: string;
+ has_nanites: boolean;
+ nanite_volume: number;
+ regen_rate: number;
+ safety_threshold: number;
+ cloud_id: number;
+ scan_level: number;
+ mob_programs: NaniteProgram[];
+}
+
+export const NaniteChamberControlContent = () => {
+ const { act, data } = useBackend();
+ const {
+ status_msg,
+ locked,
+ occupant_name,
+ has_nanites,
+ nanite_volume,
+ regen_rate,
+ safety_threshold,
+ cloud_id,
+ scan_level,
+ mob_programs = [],
+ } = data;
+
+ if (status_msg) {
+ return {status_msg};
+ }
+
+ return (
+ act('toggle_lock')}
+ >
+ {locked ? 'Locked' : 'Unlocked'}
+
+ }
+ >
+ {!has_nanites ? (
+ <>
+
+ No Nanites Detected
+
+
+ >
+ ) : (
+ <>
+ act('remove_nanites')}
+ >
+ Destroy Nanites
+
+ }
+ >
+
+
+
+
+ {nanite_volume}
+
+
+ {regen_rate}
+
+
+
+
+
+
+
+ act('set_safety', {
+ value: value,
+ })
+ }
+ />
+
+
+
+ act('set_cloud', {
+ value: value,
+ })
+ }
+ />
+
+
+
+
+
+
+ {mob_programs?.map((program) => {
+ const extra_settings = program.extra_settings || [];
+ const rules = program.rules || [];
+ return (
+
+
+
+ {scan_level >= 2 && (
+
+
+
+
+ {program.activated ? 'Active' : 'Inactive'}
+
+
+
+ {program.use_rate}/s
+
+
+
+ )}
+ {program.desc}
+
+ {scan_level >= 2 && (
+
+ {!!program.can_trigger && (
+
+
+
+
+ {program.trigger_cost}
+
+
+ {program.trigger_cooldown}
+
+ {!!program.timer_trigger_delay && (
+
+ {program.timer_trigger_delay} s
+
+ )}
+ {!!program.timer_trigger && (
+
+ {program.timer_trigger} s
+
+ )}
+
+
+
+ )}
+ {!!(
+ program.timer_restart || program.timer_shutdown
+ ) && (
+
+
+
+ {/* I mean, bruh, this indentation level
+ is ABSOLUTELY INSANE!!! */}
+ {program.timer_restart && (
+
+ {program.timer_restart} s
+
+ )}
+ {program.timer_shutdown && (
+
+ {program.timer_shutdown} s
+
+ )}
+
+
+
+ )}
+
+ )}
+ {scan_level >= 3 && !!program.has_extra_settings && (
+
+
+ {extra_settings.map((extra_setting) => (
+
+ {extra_setting.value}
+
+ ))}
+
+
+ )}
+ {scan_level >= 4 && (
+
+
+
+
+ {!!program.activation_code && (
+
+ {program.activation_code}
+
+ )}
+ {!!program.deactivation_code && (
+
+ {program.deactivation_code}
+
+ )}
+ {!!program.kill_code && (
+
+ {program.kill_code}
+
+ )}
+ {!!program.can_trigger &&
+ !!program.trigger_code && (
+
+ {program.trigger_code}
+
+ )}
+
+
+
+ {program.has_rules && (
+
+
+ {rules.map((rule) => (
+ {rule.display}
+ ))}
+
+
+ )}
+
+ )}
+
+
+ );
+ })}
+
+ >
+ )}
+
+ );
+};
diff --git a/tgui/packages/tgui/interfaces/NaniteCloudControl.tsx b/tgui/packages/tgui/interfaces/NaniteCloudControl.tsx
new file mode 100644
index 00000000000..ab3095c4ffc
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/NaniteCloudControl.tsx
@@ -0,0 +1,366 @@
+import {
+ Box,
+ Button,
+ Collapsible,
+ LabeledList,
+ NoticeBox,
+ NumberInput,
+ Section,
+ Stack,
+} from 'tgui-core/components';
+
+import { useBackend } from '../backend';
+import { Window } from '../layouts';
+import { TechwebWarning } from './Nanites/NoTechwebWarning';
+import { NaniteProgram, Techweb } from './Nanites/types';
+
+interface NaniteInfoBoxProps {
+ has_disk: boolean;
+ has_program: boolean;
+ disk: NaniteProgram;
+ current_view: number;
+ backup_id: number;
+ new_backup_id: number;
+ techweb: Techweb;
+}
+
+export const NaniteDiskBox = (props, context) => {
+ const { data } = useBackend();
+ const { has_disk, has_program, disk } = data;
+ if (!has_disk) {
+ return No disk inserted;
+ }
+ if (!has_program) {
+ return Inserted disk has no program;
+ }
+ return ;
+};
+
+export const NaniteInfoBox = (props) => {
+ const { program } = props;
+ const {
+ name,
+ desc,
+ activated,
+ use_rate,
+ can_trigger,
+ trigger_cost,
+ trigger_cooldown,
+ activation_code,
+ deactivation_code,
+ kill_code,
+ trigger_code,
+ timer_restart,
+ timer_shutdown,
+ timer_trigger,
+ timer_trigger_delay,
+ extra_settings,
+ }: NaniteProgram = program;
+ return (
+
+ {activated ? 'Activated' : 'Deactivated'}
+
+ }
+ >
+
+ {desc}
+
+
+
+ {use_rate}
+ {!!can_trigger && (
+ <>
+
+ {trigger_cost}
+
+
+ {trigger_cooldown}
+
+ >
+ )}
+
+
+
+
+
+
+
+
+
+ {activation_code}
+
+
+ {deactivation_code}
+
+ {kill_code}
+ {!!can_trigger && (
+
+ {trigger_code}
+
+ )}
+
+
+
+
+
+
+
+ {timer_restart} s
+
+
+ {timer_shutdown} s
+
+ {!!can_trigger && (
+ <>
+
+ {timer_trigger} s
+
+
+ {timer_trigger_delay} s
+
+ >
+ )}
+
+
+
+
+
+
+ {extra_settings?.map((setting) => {
+ const naniteTypesDisplayMap = {
+ number: (
+ <>
+ {setting.value}
+ {setting.unit}
+ >
+ ),
+ text: setting.value,
+ type: setting.value,
+ boolean: setting.value ? setting.true_text : setting.false_text,
+ };
+ return (
+
+ {naniteTypesDisplayMap[setting.type]}
+
+ );
+ })}
+
+
+
+ );
+};
+
+interface NaniteCloudBackupProps {
+ cloud_id: number;
+ cloud_programs: NaniteProgram[];
+ cloud_backups: CloudBackups[];
+}
+
+interface CloudBackups {
+ cloud_id: number;
+ cloud_programs: NaniteProgram[];
+}
+
+export const NaniteCloudBackupList = () => {
+ const { act, data } = useBackend();
+ const { cloud_backups = [] } = data;
+
+ return cloud_backups.map((backup) => (
+
+ ));
+};
+
+interface NaniteCloudBackupDetailsProps {
+ cloud_programs: NaniteProgram[];
+ cloud_backup: CloudBackups;
+ current_view: number;
+ disk: NaniteProgram;
+ has_program: boolean;
+}
+
+export const NaniteCloudBackupDetails = () => {
+ const { act, data } = useBackend();
+ const { current_view, disk, has_program, cloud_backup } = data;
+ const can_rule = (disk && disk.can_rule) || false;
+ if (!cloud_backup) {
+ return ERROR: Backup not found;
+ }
+ const cloud_programs = data.cloud_programs || [];
+ return (
+ act('upload_program')}
+ >
+ Upload Program from Disk
+
+ )
+ }
+ >
+ {cloud_programs.map((program) => {
+ const rules = program.rules || [];
+ return (
+
+ act('remove_program', {
+ program_id: program.id,
+ })
+ }
+ />
+ }
+ >
+
+
+ {(!!can_rule || !!program.has_rules) && (
+
+ {!!can_rule && (
+
+ )}
+
+ >
+ }
+ >
+ {program.has_rules ? (
+ rules.map((rule) => (
+
+
+ ))
+ ) : (
+ No Active Rules
+ )}
+
+ )}
+
+
+ );
+ })}
+
+ );
+};
+
+export const NaniteCloudControl = () => {
+ const { act, data } = useBackend();
+ const { has_disk, current_view, new_backup_id, techweb } = data;
+ return (
+
+
+
+ act('eject')}
+ >
+ Eject
+
+ }
+ >
+
+
+
+ act('set_view', {
+ view: 0,
+ })
+ }
+ >
+ Return
+
+ ) : (
+ <>
+ {'New Backup: '}
+
+ act('update_new_backup_value', {
+ value: value,
+ })
+ }
+ />
+
+
+
+ );
+};
diff --git a/tgui/packages/tgui/interfaces/NaniteProgramHub.tsx b/tgui/packages/tgui/interfaces/NaniteProgramHub.tsx
new file mode 100644
index 00000000000..e57cfa71ebb
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/NaniteProgramHub.tsx
@@ -0,0 +1,177 @@
+import { map } from 'common/collections';
+import React from 'react';
+import {
+ Button,
+ Flex,
+ LabeledList,
+ NoticeBox,
+ Section,
+ Tabs,
+} from 'tgui-core/components';
+
+import { useBackend, useSharedState } from '../backend';
+import { Window } from '../layouts';
+import { TechwebWarning } from './Nanites/NoTechwebWarning';
+import { NaniteProgram, Techweb } from './Nanites/types';
+
+interface NaniteProgramHubProps {
+ detail_view: boolean;
+ disk: {
+ name: string;
+ desc: string;
+ };
+ techweb: Techweb;
+ has_disk: boolean;
+ has_program: boolean;
+ programs: Record>;
+}
+
+export const NaniteProgramHub = (props, context) => {
+ const { act, data } = useBackend();
+ const {
+ detail_view,
+ disk,
+ has_disk,
+ has_program,
+ programs = {},
+ techweb,
+ } = data;
+ const [selectedCategory, setSelectedCategory] = useSharedState(
+ context,
+ 'category',
+ );
+ const programsInCategory = (programs && programs[selectedCategory]) || [];
+ return (
+
+
+
+
+
+
+ >
+ }
+ >
+ {has_disk ? (
+ has_program ? (
+
+
+ {disk.name}
+
+
+ {disk.desc}
+
+
+ ) : (
+ No Program Installed
+ )
+ ) : (
+ Insert Disk
+ )}
+
+
+
+
+ >
+ }
+ >
+ {programs !== null ? (
+
+
+
+ {map(programs, (cat_contents, category) => {
+ // Backend was sending stupid data that would have been
+ // annoying to fix
+ const tabLabel = category.substring(0, category.length - 8);
+ return (
+ setSelectedCategory(category)}
+ >
+ {tabLabel}
+
+ );
+ })}
+
+
+
+ {detail_view ? (
+ programsInCategory.map((program) => (
+
+ act('download', {
+ program_id: program.id,
+ })
+ }
+ >
+ Download
+
+ }
+ >
+ {program.desc}
+
+ ))
+ ) : (
+
+ {programsInCategory.map((program) => (
+
+ act('download', {
+ program_id: program.id,
+ })
+ }
+ >
+ Download
+
+ }
+ />
+ ))}
+
+ )}
+
+
+ ) : (
+ No nanite programs are currently researched.
+ )}
+
+
+
+ );
+};
diff --git a/tgui/packages/tgui/interfaces/NaniteProgrammer.tsx b/tgui/packages/tgui/interfaces/NaniteProgrammer.tsx
new file mode 100644
index 00000000000..a4dbc80aef3
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/NaniteProgrammer.tsx
@@ -0,0 +1,402 @@
+import {
+ Button,
+ Dropdown,
+ Input,
+ LabeledList,
+ NoticeBox,
+ NumberInput,
+ Section,
+ Stack,
+} from 'tgui-core/components';
+
+import { useBackend } from '../backend';
+import { Window } from '../layouts';
+import { NaniteProgram } from './Nanites/types';
+
+interface ExtraSetting {
+ name: string;
+ value: string;
+ types: string[];
+ unit: string;
+ true_text: string;
+ false_text: string;
+ type: string;
+}
+
+export const NaniteCodes = (props, context) => {
+ const { act, data } = useBackend();
+ const { activation_code, deactivation_code, kill_code, trigger_code } = data;
+ return (
+
+
+
+
+ act('set_code', {
+ target_code: 'activation',
+ code: value,
+ })
+ }
+ />
+
+
+
+ act('set_code', {
+ target_code: 'deactivation',
+ code: value,
+ })
+ }
+ />
+
+
+
+ act('set_code', {
+ target_code: 'kill',
+ code: value,
+ })
+ }
+ />
+
+ {!!data.can_trigger && (
+
+
+ act('set_code', {
+ target_code: 'trigger',
+ code: value,
+ })
+ }
+ />
+
+ )}
+
+
+ );
+};
+
+export const NaniteDelays = (props, context) => {
+ const { act, data } = useBackend();
+
+ const { timer_restart, timer_shutdown, timer_trigger, timer_trigger_delay } =
+ data;
+
+ return (
+
+
+
+
+ act('set_restart_timer', {
+ delay: value,
+ })
+ }
+ />
+
+
+
+ act('set_shutdown_timer', {
+ delay: value,
+ })
+ }
+ />
+
+ {!!data.can_trigger && (
+ <>
+
+
+ act('set_trigger_timer', {
+ delay: value,
+ })
+ }
+ />
+
+
+
+ act('set_timer_trigger_delay', {
+ delay: value,
+ })
+ }
+ />
+
+ >
+ )}
+
+
+ );
+};
+
+export const NaniteExtraEntry = (props, context) => {
+ const { extra_setting } = props;
+ const { name, type } = extra_setting;
+ const typeComponentMap = {
+ number: ,
+ text: ,
+ type: ,
+ boolean: ,
+ };
+ return (
+ {typeComponentMap[type]}
+ );
+};
+
+export const NaniteExtraNumber = (props, context) => {
+ const { extra_setting } = props;
+ const { act } = useBackend();
+ const { name, value, min, max, unit } = extra_setting;
+ return (
+
+ act('set_extra_setting', {
+ target_setting: name,
+ value: val,
+ })
+ }
+ />
+ );
+};
+
+export const NaniteExtraText = (props, context) => {
+ const { extra_setting } = props;
+ const { act } = useBackend();
+ const { name, value }: ExtraSetting = extra_setting;
+ return (
+
+ act('set_extra_setting', {
+ target_setting: name,
+ value: val,
+ })
+ }
+ />
+ );
+};
+
+export const NaniteExtraType = (props, context) => {
+ const { extra_setting } = props;
+ const { act } = useBackend();
+ const { name, value, types }: ExtraSetting = extra_setting;
+ return (
+
+ act('set_extra_setting', {
+ target_setting: name,
+ value: val,
+ })
+ }
+ />
+ );
+};
+
+interface ExtraSettingBoolean {
+ name: string;
+ true_text: string;
+ false_text: string;
+ value: boolean;
+}
+
+export const NaniteExtraBoolean = (props, context) => {
+ const { extra_setting } = props;
+ const { act } = useBackend();
+ const { name, value, true_text, false_text }: ExtraSettingBoolean =
+ extra_setting;
+ return (
+
+ act('set_extra_setting', {
+ target_setting: name,
+ })
+ }
+ >
+ {value ? true_text : false_text}
+
+ );
+};
+
+export const NaniteProgrammer = (props, context) => {
+ return (
+
+
+
+
+
+ );
+};
+
+interface NaniteProgrammerContentProps {
+ has_disk: boolean;
+ has_program: boolean;
+ name: string;
+ desc: string;
+ use_rate: string;
+ can_trigger: boolean;
+ trigger_cost: number;
+ trigger_cooldown: number;
+ activated: boolean;
+ has_extra_settings: boolean;
+ extra_settings: ExtraSetting[];
+}
+
+export const NaniteProgrammerContent = (props, context) => {
+ const { act, data } = useBackend();
+ const {
+ has_disk,
+ has_program,
+ name,
+ desc,
+ use_rate,
+ can_trigger,
+ trigger_cost,
+ trigger_cooldown,
+ activated,
+ has_extra_settings,
+ extra_settings,
+ } = data;
+ if (!has_disk) {
+ return (
+ Insert a nanite program disk
+ );
+ }
+ if (!has_program) {
+ return (
+ act('eject')}
+ >
+ Eject
+
+ }
+ />
+ );
+ }
+ return (
+ act('eject')}>
+ Eject
+
+ }
+ >
+
+
+ {desc}
+
+
+
+ {use_rate}
+ {!!can_trigger && (
+ <>
+
+ {trigger_cost}
+
+
+ {trigger_cooldown}
+
+ >
+ )}
+
+
+
+
+
+ act('toggle_active')}
+ >
+ {activated ? 'Active' : 'Inactive'}
+
+ }
+ >
+
+
+
+
+
+
+
+
+ {!!has_extra_settings && (
+
+
+ {extra_settings.map((setting) => (
+
+ ))}
+
+
+ )}
+
+
+ );
+};
diff --git a/tgui/packages/tgui/interfaces/NaniteRemote.tsx b/tgui/packages/tgui/interfaces/NaniteRemote.tsx
new file mode 100644
index 00000000000..fbba3c1f56f
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/NaniteRemote.tsx
@@ -0,0 +1,198 @@
+import {
+ Button,
+ Input,
+ LabeledList,
+ NoticeBox,
+ NumberInput,
+ Section,
+ Table,
+} from 'tgui-core/components';
+
+import { useBackend } from '../backend';
+import { Window } from '../layouts';
+
+export const NaniteRemote = (props, context) => {
+ return (
+
+
+
+
+
+ );
+};
+
+interface NaniteProgramProps {
+ code: number;
+ locked: boolean;
+ mode: string;
+ program_name: string;
+ relay_code: number;
+ comms: boolean;
+ message: string;
+ saved_settings: Array;
+}
+
+interface SavedSetting {
+ id: string;
+ name: string;
+ mode: string;
+ code: number;
+ relay_code: number;
+}
+
+export const NaniteRemoteContent = () => {
+ const { act, data } = useBackend();
+ const {
+ code,
+ locked,
+ mode,
+ program_name,
+ relay_code,
+ comms,
+ message,
+ saved_settings = [],
+ } = data;
+
+ const modes = ['Off', 'Local', 'Targeted', 'Area', 'Relay'];
+
+ if (locked) {
+ return This interface is locked.;
+ }
+
+ return (
+ <>
+ act('lock')}>
+ Lock Interface
+
+ }
+ >
+
+
+
+ act('update_name', {
+ name: value,
+ })
+ }
+ />
+
+
+
+
+ act('set_code', {
+ code: value,
+ })
+ }
+ />
+
+ {!!comms && (
+
+
+ act('set_message', {
+ value: value,
+ })
+ }
+ />
+
+ )}
+ {mode === 'Relay' && (
+
+
+ act('set_relay_code', {
+ code: value,
+ })
+ }
+ />
+
+ )}
+
+ {modes.map((key) => (
+
+ ))}
+
+
+
+
+ {saved_settings.length > 0 ? (
+
+
+ Name
+ Mode
+ Code
+ Relay
+
+ {saved_settings.map((setting) => (
+
+
+ {setting.name}:
+
+ {setting.mode}
+ {setting.code}
+
+ {setting.mode === 'Relay' && setting.relay_code}
+
+
+
+
+ ))}
+
+ ) : (
+ No settings currently saved
+ )}
+
+ >
+ );
+};
diff --git a/tgui/packages/tgui/interfaces/Nanites/NoTechwebWarning.tsx b/tgui/packages/tgui/interfaces/Nanites/NoTechwebWarning.tsx
new file mode 100644
index 00000000000..d84c5336d18
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/Nanites/NoTechwebWarning.tsx
@@ -0,0 +1,17 @@
+import { NoticeBox } from 'tgui-core/components';
+
+interface TechwebWarningProps {
+ display: boolean;
+}
+
+export const TechwebWarning = (props: TechwebWarningProps) => {
+ const { display } = props;
+ if (!display) {
+ return null;
+ }
+ return (
+
+ Warning: No techweb connected!
+
+ );
+};
diff --git a/tgui/packages/tgui/interfaces/Nanites/types.d.ts b/tgui/packages/tgui/interfaces/Nanites/types.d.ts
new file mode 100644
index 00000000000..6105f8acea9
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/Nanites/types.d.ts
@@ -0,0 +1,34 @@
+export interface NaniteProgram {
+ name: string;
+ id: string;
+ desc: string;
+ activated: boolean;
+ activation_code: number;
+ use_rate: number;
+ deactivation_code: number;
+ kill_code: number;
+ trigger_cost: number;
+ trigger_cooldown: number;
+ trigger_code: number;
+ can_trigger: boolean;
+ timer_restart: number;
+ timer_shutdown: number;
+ timer_trigger: number;
+ timer_trigger_delay: number;
+ has_extra_settings: boolean;
+ extra_settings: ExtraSetting[];
+ rules: NaniteRules[];
+ can_rule?: boolean;
+ has_rules?: boolean;
+ all_rules_required?: boolean;
+}
+
+export interface NaniteRules {
+ display: string;
+ id: string;
+}
+
+export interface Techweb {
+ name: string;
+ organization: string;
+}
diff --git a/tgui/packages/tgui/interfaces/ScannerGate.jsx b/tgui/packages/tgui/interfaces/ScannerGate.jsx
index ca2c4f02968..a9a9f246283 100644
--- a/tgui/packages/tgui/interfaces/ScannerGate.jsx
+++ b/tgui/packages/tgui/interfaces/ScannerGate.jsx
@@ -1,4 +1,10 @@
-import { Box, Button, LabeledList, Section } from 'tgui-core/components';
+import {
+ Box,
+ Button,
+ LabeledList,
+ NumberInput,
+ Section,
+} from 'tgui-core/components';
import { useBackend } from '../backend';
import { Window } from '../layouts';
@@ -87,6 +93,16 @@ const SCANNER_GATE_ROUTES = {
component: () => ScannerGateGender,
},
// SKYRAT EDIT END - MORE SCANNER GATE OPTIONS
+ Contraband: {
+ title: 'Scanner Mode: Contraband',
+ component: () => ScannerGateContraband,
+ },
+ // BUBBER EDIT START - NANITES
+ Nanites: {
+ title: 'Scanner Mode: Nanites',
+ component: () => ScannerGateNanites,
+ },
+ // BUBBER EDIT END - NANITES
};
const ScannerGateControl = (props) => {
@@ -146,6 +162,18 @@ const ScannerGateOff = (props) => {
content="Nutrition"
onClick={() => act('set_mode', { new_mode: 'Nutrition' })}
/>
+ {/* BUBBER EDIT START - NANITES */}
+ act('set_mode', { new_mode: 'Nanites' })}
+ />
+ {/* BUBBER EDIT END - NANITES */}
+
+ act('set_mode', { new_mode: 'Contraband' })}
+ />
>
);
@@ -286,6 +314,39 @@ const ScannerGateNutrition = (props) => {
>
);
};
+// BUBBER EDIT START - NANITES
+const ScannerGateNanites = (props, context) => {
+ const { act, data } = useBackend(context);
+ const { reverse, nanite_cloud } = data;
+ return (
+ <>
+
+ Trigger if the person scanned {reverse ? 'does not have' : 'has'} nanite
+ cloud {nanite_cloud}.
+
+
+
+
+
+ act('set_nanite_cloud', {
+ new_cloud: value,
+ })
+ }
+ />
+
+
+
+
+ >
+ );
+};
+// BUBBER EDIT END - NANITES
// SKYRAT EDIT START - MORE SCANNER GATE OPTIONS
const ScannerGateGender = (props) => {
diff --git a/tgui/packages/tgui/interfaces/Techweb/Content.tsx b/tgui/packages/tgui/interfaces/Techweb/Content.tsx
index 93a97f439b6..c628dcc6bc0 100644
--- a/tgui/packages/tgui/interfaces/Techweb/Content.tsx
+++ b/tgui/packages/tgui/interfaces/Techweb/Content.tsx
@@ -32,12 +32,15 @@ export function TechwebContent(props) {
{sec_protocols ? 'Engaged' : 'Disengaged'}
+ {/* BUBBER CHANGE START: ADD POINT IDENTIFICATION */}
{Object.keys(points).map((k) => (
-
+
+ {' '}
{points[k]}
{!!points_last_tick[k] && ` (+${points_last_tick[k]}/sec)`}
))}
+ {/* BUBBER CHANGE END: ADD POINT IDENTIFICATION */}
{queue_nodes.length !== 0
? Object.keys(queue_nodes).map((node_id) => (