diff --git a/code/__DEFINES/misc.dm b/code/__DEFINES/misc.dm
index 32d961d77d..86fdd23d0b 100644
--- a/code/__DEFINES/misc.dm
+++ b/code/__DEFINES/misc.dm
@@ -515,6 +515,7 @@ GLOBAL_LIST_INIT(pda_reskins, list(PDA_SKIN_CLASSIC = 'icons/obj/pda.dmi', PDA_S
#define VOMIT_TOXIC 1
#define VOMIT_PURPLE 2
+#define VOMIT_NANITE 3
// possible bitflag return values of intercept_zImpact(atom/movable/AM, levels = 1) calls
#define FALL_INTERCEPTED (1<<0) //Stops the movable from falling further and crashing on the ground
diff --git a/code/__DEFINES/nanites.dm b/code/__DEFINES/nanites.dm
index 05c3501609..388b049ad0 100644
--- a/code/__DEFINES/nanites.dm
+++ b/code/__DEFINES/nanites.dm
@@ -12,6 +12,11 @@
#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
+
///Nanite Protocol types
#define NANITE_PROTOCOL_REPLICATION "nanite_replication"
#define NANITE_PROTOCOL_STORAGE "nanite_storage"
@@ -45,4 +50,3 @@
#define NES_SCAN_TYPE "Scan Type"
#define NES_BUTTON_NAME "Button Name"
#define NES_ICON "Icon"
-#define NES_COLOR "Color"
diff --git a/code/datums/components/nanites.dm b/code/datums/components/nanites.dm
index 4e0ce3fc16..3db861d0f0 100644
--- a/code/datums/components/nanites.dm
+++ b/code/datums/components/nanites.dm
@@ -170,6 +170,7 @@
/**
* Used to rid ourselves
*/
+///Deletes nanites!
/datum/component/nanites/proc/delete_nanites()
if(can_be_deleted)
qdel(src)
@@ -214,6 +215,7 @@
return SEND_SIGNAL(src, COMSIG_NANITE_INTERNAL_VIRAL_PREVENTION_CHECK)
//Syncs the nanite component to another, making it so programs are the same with the same programming (except activation status)
+///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)
var/list/programs_to_remove = programs.Copy() - permanent_programs
var/list/programs_to_add = source.programs.Copy()
@@ -233,6 +235,7 @@
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)
@@ -246,6 +249,7 @@
var/datum/nanite_program/NP = pick(programs)
NP.software_error()
+///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)
for(var/X in programs)
var/datum/nanite_program/NP = X
@@ -268,11 +272,68 @@
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)
nanite_volume = clamp(nanite_volume + amount, 0, max_nanites)
+ SIGNAL_HANDLER
+
+ nanite_volume += amount
+ if(nanite_volume > max_nanites)
+ reject_excess_nanites()
if(nanite_volume <= 0) //oops we ran out
nanites_depleted()
+/**
+ * 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("A grainy grey slurry starts oozing out of [host_mob].", "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("[host_mob] vomits a grainy grey slurry!", "You suddenly vomit a metallic-tasting grainy grey slurry!", null);
+ C.vomit(0, FALSE, TRUE, FLOOR(excess / 100, 1), FALSE, VOMIT_NANITE, FALSE, TRUE, 0)
+ else
+ host_mob.visible_message("A metallic grey slurry bursts out of [host_mob]'s skin!", "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, VOMIT_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("A torrent of metallic grey slurry violently bursts out of [host_mob]'s face and floods out of [host_mob.p_their()] skin!",
+ "A torrent of metallic grey slurry violently bursts out of your eyes, ears, and mouth, and floods out of your skin!");
+
+ host_mob.blind_eyes(15) //nanites coming out of your eyes
+ host_mob.Paralyze(120)
+ if(iscarbon(host_mob))
+ var/mob/living/carbon/C = host_mob
+ var/obj/item/organ/ears/ears = C.getorganslot(ORGAN_SLOT_EARS)
+ if(ears)
+ ears.adjustEarDamage(0, 30) //nanites coming out of your ears
+ C.vomit(0, FALSE, TRUE, 2, FALSE, VOMIT_NANITE, FALSE, TRUE, 0) //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_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, VOMIT_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)
@@ -347,6 +408,9 @@
/datum/component/nanites/proc/set_max_volume(datum/source, amount)
max_nanites = max(1, max_nanites)
+ SIGNAL_HANDLER
+
+ max_nanites = max(1, amount)
/datum/component/nanites/proc/set_cloud(datum/source, amount)
cloud_id = clamp(amount, 0, 100)
diff --git a/code/datums/status_effects/buffs.dm b/code/datums/status_effects/buffs.dm
index 9cfec43013..bf910b3944 100644
--- a/code/datums/status_effects/buffs.dm
+++ b/code/datums/status_effects/buffs.dm
@@ -642,7 +642,7 @@
O.Remove()
if(iscarbon(owner))
var/mob/living/carbon/C = owner
- C.vomit(0, toxic = TRUE)
+ C.vomit(0)
O.forceMove(get_turf(owner))
if(isliving(owner))
var/mob/living/L = owner
diff --git a/code/game/turfs/turf.dm b/code/game/turfs/turf.dm
index fa566c5225..1a9d8146bb 100755
--- a/code/game/turfs/turf.dm
+++ b/code/game/turfs/turf.dm
@@ -579,7 +579,7 @@ GLOBAL_LIST_EMPTY(station_turfs)
/turf/AllowDrop()
return TRUE
-/turf/proc/add_vomit_floor(mob/living/M, toxvomit = NONE)
+/turf/proc/add_vomit_floor(mob/living/M, toxvomit = NONE, purge_ratio = 0.1)
var/obj/effect/decal/cleanable/vomit/V = new /obj/effect/decal/cleanable/vomit(src, M.get_static_viruses())
//if the vomit combined, apply toxicity and reagents to the old vomit
@@ -587,21 +587,24 @@ GLOBAL_LIST_EMPTY(station_turfs)
V = locate() in src
if(!V) //the decal was spawned on a wall or groundless turf and promptly qdeleted.
return
- // Make toxins and blazaam vomit look different
+ // Apply the proper icon set based on vomit type
if(toxvomit == VOMIT_PURPLE)
V.icon_state = "vomitpurp_[pick(1,4)]"
else if (toxvomit == VOMIT_TOXIC)
V.icon_state = "vomittox_[pick(1,4)]"
- if (iscarbon(M))
- var/mob/living/carbon/C = M
- if(C.reagents)
- clear_reagents_to_vomit_pool(C,V)
+ else if (toxvomit == VOMIT_NANITE)
+ V.name = "metallic slurry"
+ V.desc = "A puddle of metallic slurry that looks vaguely like very fine sand. It almost seems like it's moving..."
+ V.icon_state = "vomitnanite_[pick(1,4)]"
+ if (purge_ratio && iscarbon(M))
+ clear_reagents_to_vomit_pool(M, V, purge_ratio)
-/proc/clear_reagents_to_vomit_pool(mob/living/carbon/M, obj/effect/decal/cleanable/vomit/V)
+/proc/clear_reagents_to_vomit_pool(mob/living/carbon/M, obj/effect/decal/cleanable/vomit/V, purge_ratio = 0.1)
for(var/datum/reagent/consumable/R in M.reagents.reagent_list) //clears the stomach of anything that might be digested as food
if(R.nutriment_factor > 0)
M.reagents.del_reagent(R.type)
- M.reagents.trans_to(V, M.reagents.total_volume / 10)
+ var/chemicals_lost = M.reagents.total_volume * purge_ratio
+ M.reagents.trans_to(V, chemicals_lost)
//Whatever happens after high temperature fire dies out or thermite reaction works.
//Should return new turf
diff --git a/code/modules/mob/living/carbon/carbon.dm b/code/modules/mob/living/carbon/carbon.dm
index c1110af267..a0beb07fab 100644
--- a/code/modules/mob/living/carbon/carbon.dm
+++ b/code/modules/mob/living/carbon/carbon.dm
@@ -502,17 +502,17 @@
return 0
return ..()
-/mob/living/carbon/proc/vomit(lost_nutrition = 10, blood = FALSE, stun = TRUE, distance = 1, message = TRUE, toxic = FALSE)
- if(HAS_TRAIT(src, TRAIT_NOHUNGER))
- return 1
+/mob/living/carbon/proc/vomit(lost_nutrition = 10, blood = FALSE, stun = TRUE, distance = 1, message = TRUE, vomit_type = VOMIT_TOXIC, harm = TRUE, force = FALSE, purge_ratio = 0.1)
+ if((HAS_TRAIT(src, TRAIT_NOHUNGER) || HAS_TRAIT(src, TRAIT_TOXINLOVER)) && !force)
+ return TRUE
- if(nutrition < 100 && !blood)
+ if(nutrition < 100 && !blood && !force)
if(message)
visible_message("[src] dry heaves!", \
"You try to throw up, but there's nothing in your stomach!")
if(stun)
DefaultCombatKnockdown(200)
- return 1
+ return TRUE
if(is_mouth_covered()) //make this add a blood/vomit overlay later it'll be hilarious
if(message)
@@ -525,30 +525,29 @@
visible_message("[src] throws up!", "You throw up!")
if(!isflyperson(src))
SEND_SIGNAL(src, COMSIG_ADD_MOOD_EVENT, "vomit", /datum/mood_event/vomit)
+
if(stun)
Stun(80)
- playsound(get_turf(src), 'sound/effects/splat.ogg', 50, 1)
+ playsound(get_turf(src), 'sound/effects/splat.ogg', 50, TRUE)
var/turf/T = get_turf(src)
if(!blood)
adjust_nutrition(-lost_nutrition)
adjustToxLoss(-3)
+
for(var/i=0 to distance)
if(blood)
if(T)
add_splatter_floor(T)
- if(stun)
+ if(harm)
adjustBruteLoss(3)
- else if(src.reagents.has_reagent(/datum/reagent/consumable/ethanol/blazaam))
- if(T)
- T.add_vomit_floor(src, VOMIT_PURPLE)
else
if(T)
- T.add_vomit_floor(src, VOMIT_TOXIC)//toxic barf looks different
+ T.add_vomit_floor(src, vomit_type, purge_ratio) //toxic barf looks different || call purge when doing detoxicfication to pump more chems out of the stomach.
T = get_step(T, dir)
if (is_blocked_turf(T))
break
- return 1
+ return TRUE
/mob/living/carbon/proc/spew_organ(power = 5, amt = 1)
var/list/spillable_organs = list()
diff --git a/code/modules/mob/living/carbon/human/human.dm b/code/modules/mob/living/carbon/human/human.dm
index 41ac05b8e2..9b9c86f471 100644
--- a/code/modules/mob/living/carbon/human/human.dm
+++ b/code/modules/mob/living/carbon/human/human.dm
@@ -840,8 +840,8 @@
override = dna.species.override_float
..()
-/mob/living/carbon/human/vomit(lost_nutrition = 10, blood = 0, stun = 1, distance = 0, message = 1, toxic = 0)
- if(blood && dna?.species && (NOBLOOD in dna.species.species_traits))
+/mob/living/carbon/human/vomit(lost_nutrition = 10, blood = FALSE, stun = TRUE, distance = 1, message = TRUE, vomit_type = VOMIT_TOXIC, harm = TRUE, force = FALSE, purge_ratio = 0.1)
+ if(blood && dna?.species && (NOBLOOD in dna.species.species_traits) && !HAS_TRAIT(src, TRAIT_TOXINLOVER))
if(message)
visible_message("[src] dry heaves!", \
"You try to throw up, but there's nothing in your stomach!")
@@ -1095,7 +1095,7 @@
* * Rock / Brownish if a golem
* * Green if none of the others apply (aka, generic organic)
*/
-/mob/living/carbon/human/proc/spec_trait_examine_font()
+/mob/living/carbon/human/proc/spec_trait_examine_font()
if(HAS_TRAIT(src, TRAIT_ROBOTIC_ORGANISM))
return ""
if(HAS_TRAIT(src, TRAIT_TOXINLOVER))
diff --git a/code/modules/mob/living/carbon/human/life.dm b/code/modules/mob/living/carbon/human/life.dm
index bdf7faf10b..767250c863 100644
--- a/code/modules/mob/living/carbon/human/life.dm
+++ b/code/modules/mob/living/carbon/human/life.dm
@@ -273,7 +273,7 @@
if(getToxLoss() >= 45 && nutrition > 20 && !HAS_TRAIT(src, TRAIT_ROBOTIC_ORGANISM))
lastpuke += prob(50)
if(lastpuke >= 50) // about 25 second delay I guess
- vomit(20, toxic = TRUE)
+ vomit(20)
lastpuke = 0
diff --git a/code/modules/reagents/chemistry/reagents/alcohol_reagents.dm b/code/modules/reagents/chemistry/reagents/alcohol_reagents.dm
index 2882a85b5e..177b1c70bc 100644
--- a/code/modules/reagents/chemistry/reagents/alcohol_reagents.dm
+++ b/code/modules/reagents/chemistry/reagents/alcohol_reagents.dm
@@ -2196,7 +2196,7 @@ All effects don't start immediately, but rather get worse over time; the rate is
if(prob(10))
stored_teleports += rand(2,6)
if(prob(70))
- M.vomit()
+ M.vomit(vomit_type = VOMIT_PURPLE)
return ..()
/datum/reagent/consumable/ethanol/planet_cracker
@@ -2516,101 +2516,19 @@ All effects don't start immediately, but rather get worse over time; the rate is
color = "#FFFFFF"
boozepwr = 35
quality = DRINK_GOOD
- taste_description = "bad coding"
- can_synth = FALSE
- var/list/names = list("null fruit" = 1) //Names of the fruits used. Associative list where name is key, value is the percentage of that fruit.
- var/list/tastes = list("bad coding" = 1) //List of tastes. See above.
- pH = 4
+ taste_description = "a delightful softened punch"
+ glass_icon_state = "godfather"
+ glass_name = "Godfather"
+ glass_desc = "A classic from old Italy and enjoyed by gangsters, pray the orange peel doesnt end up in your mouth."
-/datum/reagent/consumable/ethanol/fruit_wine/on_new(list/data)
- names = data["names"]
- tastes = data["tastes"]
- boozepwr = data["boozepwr"]
- color = data["color"]
- generate_data_info(data)
+/datum/reagent/consumable/ethanol/godmother
+ name = "Godmother"
+ description = "A twist on a classic, liked more by mature women."
+ boozepwr = 50
+ color = "#E68F00"
+ quality = DRINK_GOOD
+ taste_description = "sweetness and a zesty twist"
+ glass_icon_state = "godmother"
+ glass_name = "Godmother"
+ glass_desc = "A lovely fresh smelling cocktail, a true Sicilian delight."
-/datum/reagent/consumable/ethanol/fruit_wine/on_merge(list/data, amount)
- var/diff = (amount/volume)
- if(diff < 1)
- color = BlendRGB(color, data["color"], diff/2) //The percentage difference over two, so that they take average if equal.
- else
- color = BlendRGB(color, data["color"], (1/diff)/2) //Adjust so it's always blending properly.
- var/oldvolume = volume-amount
-
- var/list/cachednames = data["names"]
- for(var/name in names | cachednames)
- names[name] = ((names[name] * oldvolume) + (cachednames[name] * amount)) / volume
-
- var/list/cachedtastes = data["tastes"]
- for(var/taste in tastes | cachedtastes)
- tastes[taste] = ((tastes[taste] * oldvolume) + (cachedtastes[taste] * amount)) / volume
-
- boozepwr *= oldvolume
- var/newzepwr = data["boozepwr"] * amount
- boozepwr += newzepwr
- boozepwr /= volume //Blending boozepwr to volume.
- generate_data_info(data)
-
-/datum/reagent/consumable/ethanol/fruit_wine/proc/generate_data_info(list/data)
- var/minimum_percent = 0.15 //Percentages measured between 0 and 1.
- var/list/primary_tastes = list()
- var/list/secondary_tastes = list()
- glass_name = "glass of [name]"
- glass_desc = description
- for(var/taste in tastes)
- switch(tastes[taste])
- if(minimum_percent*2 to INFINITY)
- primary_tastes += taste
- if(minimum_percent to minimum_percent*2)
- secondary_tastes += taste
-
- var/minimum_name_percent = 0.35
- name = ""
- var/list/names_in_order = sortTim(names, /proc/cmp_numeric_dsc, TRUE)
- var/named = FALSE
- for(var/fruit_name in names)
- if(names[fruit_name] >= minimum_name_percent)
- name += "[fruit_name] "
- named = TRUE
- if(named)
- name += "wine"
- else
- name = "mixed [names_in_order[1]] wine"
-
- var/alcohol_description
- switch(boozepwr)
- if(120 to INFINITY)
- alcohol_description = "suicidally strong"
- if(90 to 120)
- alcohol_description = "rather strong"
- if(70 to 90)
- alcohol_description = "strong"
- if(40 to 70)
- alcohol_description = "rich"
- if(20 to 40)
- alcohol_description = "mild"
- if(0 to 20)
- alcohol_description = "sweet"
- else
- alcohol_description = "watery" //How the hell did you get negative boozepwr?
-
- var/list/fruits = list()
- if(names_in_order.len <= 3)
- fruits = names_in_order
- else
- for(var/i in 1 to 3)
- fruits += names_in_order[i]
- fruits += "other plants"
- var/fruit_list = english_list(fruits)
- description = "A [alcohol_description] wine brewed from [fruit_list]."
-
- var/flavor = ""
- if(!primary_tastes.len)
- primary_tastes = list("[alcohol_description] alcohol")
- flavor += english_list(primary_tastes)
- if(secondary_tastes.len)
- flavor += ", with a hint of "
- flavor += english_list(secondary_tastes)
- taste_description = flavor
- if(holder.my_atom)
- holder.my_atom.on_reagent_change()
diff --git a/code/modules/reagents/chemistry/reagents/toxin_reagents.dm b/code/modules/reagents/chemistry/reagents/toxin_reagents.dm
index c9fbf6928a..0655faa1e8 100644
--- a/code/modules/reagents/chemistry/reagents/toxin_reagents.dm
+++ b/code/modules/reagents/chemistry/reagents/toxin_reagents.dm
@@ -735,7 +735,7 @@
/datum/reagent/toxin/spewium/on_mob_life(mob/living/carbon/C)
.=..()
if(current_cycle >=11 && prob(min(50,current_cycle)))
- C.vomit(10, prob(10), prob(50), rand(0,4), TRUE, prob(30))
+ C.vomit(10, prob(10), prob(50), rand(0,4), TRUE)
for(var/datum/reagent/toxin/R in C.reagents.reagent_list)
if(R != src)
C.reagents.remove_reagent(R.type,1)
diff --git a/code/modules/research/designs/nanite_designs.dm b/code/modules/research/designs/nanite_designs.dm
index 38cd774683..78f70721c3 100644
--- a/code/modules/research/designs/nanite_designs.dm
+++ b/code/modules/research/designs/nanite_designs.dm
@@ -246,8 +246,8 @@
category = list("Augmentation Nanites")
/datum/design/nanites/coagulating
- name = "Rapid Coagulation"
- desc = "The nanites induce rapid coagulation when the host is wounded, dramatically reducing bleeding rate."
+ 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")
@@ -558,15 +558,15 @@
program_type = /datum/nanite_program/protocol/factory
category = list("Protocols_Nanites")
-/datum/design/nanites/tinker
- name = "Tinker Protocol"
- desc = "Replication Protocol: the nanites learn to use metallic material in the host's bloodstream to speed up the replication process."
- id = "tinker_nanites"
- program_type = /datum/nanite_program/protocol/tinker
+/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 = "Offline Production Protocol"
+ 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
@@ -578,3 +578,32 @@
id = "synergy_nanites"
program_type = /datum/nanite_program/protocol/synergy
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/code/modules/research/nanites/nanite_cloud_controller.dm b/code/modules/research/nanites/nanite_cloud_controller.dm
index 44ebe11c29..eda9224bfb 100644
--- a/code/modules/research/nanites/nanite_cloud_controller.dm
+++ b/code/modules/research/nanites/nanite_cloud_controller.dm
@@ -4,6 +4,7 @@
icon = 'icons/obj/machines/research.dmi'
icon_state = "nanite_cloud_controller"
circuit = /obj/item/circuitboard/computer/nanite_cloud_controller
+ icon_screen = "nanite_cloud_controller_screen"
var/obj/item/disk/nanite_program/disk
var/list/datum/nanite_cloud_backup/cloud_backups = list()
@@ -144,6 +145,7 @@
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
@@ -232,6 +234,15 @@
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_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
diff --git a/code/modules/research/nanites/nanite_program_hub.dm b/code/modules/research/nanites/nanite_program_hub.dm
index 495c788845..85a117f53f 100644
--- a/code/modules/research/nanites/nanite_program_hub.dm
+++ b/code/modules/research/nanites/nanite_program_hub.dm
@@ -26,6 +26,14 @@
. = ..()
linked_techweb = SSresearch.science_tech
+/obj/machinery/nanite_program_hub/update_overlays()
+ . = ..()
+ SSvis_overlays.remove_vis_overlay(src, managed_vis_overlays)
+ if((stat & (NOPOWER|MAINT|BROKEN)) || panel_open)
+ return
+ SSvis_overlays.add_vis_overlay(src, icon, "nanite_program_hub_on", layer, plane)
+ SSvis_overlays.add_vis_overlay(src, icon, "nanite_program_hub_on", EMISSIVE_LAYER, EMISSIVE_PLANE)
+
/obj/machinery/nanite_program_hub/attackby(obj/item/I, mob/user)
if(istype(I, /obj/item/disk/nanite_program))
var/obj/item/disk/nanite_program/N = I
diff --git a/code/modules/research/nanites/nanite_programmer.dm b/code/modules/research/nanites/nanite_programmer.dm
index b6a2c8b28b..f23a44909c 100644
--- a/code/modules/research/nanites/nanite_programmer.dm
+++ b/code/modules/research/nanites/nanite_programmer.dm
@@ -11,6 +11,14 @@
flags_1 = HEAR_1
circuit = /obj/item/circuitboard/machine/nanite_programmer
+/obj/machinery/nanite_programmer/update_overlays()
+ . = ..()
+ SSvis_overlays.remove_vis_overlay(src, managed_vis_overlays)
+ if((stat & (NOPOWER|MAINT|BROKEN)) || panel_open)
+ return
+ SSvis_overlays.add_vis_overlay(src, icon, "nanite_programmer_on", layer, plane)
+ SSvis_overlays.add_vis_overlay(src, icon, "nanite_programmer_on", EMISSIVE_LAYER, EMISSIVE_PLANE)
+
/obj/machinery/nanite_programmer/attackby(obj/item/I, mob/user)
if(istype(I, /obj/item/disk/nanite_program))
var/obj/item/disk/nanite_program/N = I
diff --git a/code/modules/research/nanites/nanite_programs.dm b/code/modules/research/nanites/nanite_programs.dm
index 946dec3f49..4b6416ecb8 100644
--- a/code/modules/research/nanites/nanite_programs.dm
+++ b/code/modules/research/nanites/nanite_programs.dm
@@ -48,11 +48,12 @@
//Extra settings
///Don't ever override this or I will come to your house and stand menacingly behind a bush
- var/list/extra_settings = list()
+ 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
/// Corruptable - able to have code/configuration changed
var/corruptable = TRUE
@@ -76,6 +77,9 @@
if(nanites)
nanites.programs -= src
nanites.permanent_programs -= src
+ for(var/datum/nanite_rule/rule as anything in rules)
+ rule.remove()
+ rules.Cut()
return ..()
/**
@@ -108,6 +112,7 @@
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)
@@ -186,14 +191,17 @@
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)
@@ -203,14 +211,18 @@
if(passive_enabled)
disable_passive_effect()
-//If false, disables active and passive effects, but doesn't consume nanites
+//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(!rule.check_rule())
+ if(!all_rules_required && rule.check_rule())
+ return TRUE
+ if(all_rules_required && !rule.check_rule())
return FALSE
- return TRUE
+ return all_rules_required ? TRUE : FALSE
//Constantly procs as long as the program is active
/datum/nanite_program/proc/active_effect()
@@ -235,6 +247,8 @@
return
if(world.time < next_trigger)
return
+ if(!check_conditions())
+ return
if(!consume_nanites(trigger_cost))
return
next_trigger = world.time + trigger_cooldown
@@ -251,18 +265,22 @@
if(program_flags & NANITE_EMP_IMMUNE)
return
if(prob(severity / 2))
+ 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)
self_destruct()
/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()
@@ -273,10 +291,12 @@
type = rand(1,is_permanent()? 4 : 5)
switch(type)
if(1)
- self_destruct() //kill switch
+ host_mob.investigate_log("[src] nanite program was deleted by software error.", INVESTIGATE_NANITES)
+ qdel(src) //kill switch
return
if(2) //deprogram codes
if(corruptable)
+ host_mob.investigate_log("[src] nanite program was de-programmed by software error.", INVESTIGATE_NANITES)
activation_code = 0
deactivation_code = 0
kill_code = 0
@@ -284,15 +304,18 @@
if(3)
if(error_flicking)
toggle() //enable/disable
+ host_mob.investigate_log("[src] nanite program was toggled by software error.", INVESTIGATE_NANITES)
if(4)
- if(error_flicking && can_trigger)
+ 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
if(corruptable)
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)
- self_destruct()
+ self_destruct(src)
/datum/nanite_program/proc/receive_signal(code, source)
if(activation_code && code == activation_code && !activated)
@@ -315,9 +338,7 @@
if(is_permanent())
return
qdel(src)
-
///A nanite program containing a behaviour protocol. Only one protocol of each class can be active at once.
-//Moved to being 'normally' researched due to lack of B.E.P.I.S.
/datum/nanite_program/protocol
name = "Nanite Protocol"
var/protocol_class = NONE
@@ -337,4 +358,3 @@
if(nanites)
nanites.protocols -= src
return ..()
-
diff --git a/code/modules/research/nanites/nanite_programs/healing.dm b/code/modules/research/nanites/nanite_programs/healing.dm
index 920faae928..9274522553 100644
--- a/code/modules/research/nanites/nanite_programs/healing.dm
+++ b/code/modules/research/nanites/nanite_programs/healing.dm
@@ -2,7 +2,7 @@
/datum/nanite_program/regenerative
name = "Accelerated Regeneration"
- desc = "The nanites boost the host's natural regeneration, increasing their healing speed. Does not consume nanites if the host is unharmed."
+ 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)
@@ -31,7 +31,7 @@
/datum/nanite_program/temperature
name = "Temperature Adjustment"
- desc = "The nanites adjust the host's internal temperature to an ideal level."
+ 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)
@@ -53,10 +53,12 @@
rogue_types = list(/datum/nanite_program/suffocating, /datum/nanite_program/necrotic)
/datum/nanite_program/purging/check_conditions()
+ . = ..()
+ if(!. || !host_mob.reagents)
+ return FALSE // No trying to purge simple mobs
var/foreign_reagent = length(host_mob.reagents?.reagent_list)
if(!host_mob.getToxLoss() && !foreign_reagent)
return FALSE
- return ..()
/datum/nanite_program/purging/active_effect()
host_mob.adjustToxLoss(-1)
@@ -68,7 +70,7 @@
/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."
+ 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)
@@ -91,7 +93,7 @@
/datum/nanite_program/blood_restoring
name = "Blood Regeneration"
- desc = "The nanites stimulate and boost blood cell production in the host."
+ 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)
@@ -111,7 +113,7 @@
/datum/nanite_program/repairing
name = "Mechanical Repair"
- desc = "The nanites fix damage in the host's mechanical limbs."
+ 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 = 0.5
rogue_types = list(/datum/nanite_program/necrotic)
@@ -153,13 +155,15 @@
rogue_types = list(/datum/nanite_program/suffocating, /datum/nanite_program/necrotic)
/datum/nanite_program/purging_advanced/check_conditions()
+ . = ..()
+ if(!. || !host_mob.reagents)
+ return FALSE
var/foreign_reagent = FALSE
for(var/datum/reagent/toxin/R in host_mob.reagents.reagent_list)
foreign_reagent = TRUE
break
if(!host_mob.getToxLoss() && !foreign_reagent)
return FALSE
- return ..()
/datum/nanite_program/purging_advanced/active_effect()
host_mob.adjustToxLoss(-1, forced = TRUE)
diff --git a/code/modules/research/nanites/nanite_programs/protocols.dm b/code/modules/research/nanites/nanite_programs/protocols.dm
index 3830e7c8ba..3716bc61ac 100644
--- a/code/modules/research/nanites/nanite_programs/protocols.dm
+++ b/code/modules/research/nanites/nanite_programs/protocols.dm
@@ -1,7 +1,8 @@
//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."
+ 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
@@ -17,8 +18,9 @@
/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. \
- The factory decays if the protocol is not active, or if the nanites are disrupted by shocks or EMPs."
+ 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
@@ -46,43 +48,26 @@
factory_efficiency = min(factory_efficiency + 1, max_efficiency)
nanites.adjust_nanites(null, round(0.002 * factory_efficiency, 0.1))
-/datum/nanite_program/protocol/tinker
- name = "Tinker Protocol"
- desc = "Replication Protocol: the nanites learn to use metallic material in the host's bloodstream to speed up the replication process."
+/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 = 2
- var/list/valid_reagents = list(
- /datum/reagent/iron,
- /datum/reagent/copper,
- /datum/reagent/gold,
- /datum/reagent/silver,
- /datum/reagent/mercury,
- /datum/reagent/aluminium,
- /datum/reagent/silicon)
+ var/boost = 1.2
-/datum/nanite_program/protocol/tinker/check_conditions()
- if(!nanites.host_mob.reagents)
+/datum/nanite_program/protocol/pyramid/check_conditions()
+ if((nanites.nanite_volume / nanites.max_nanites) < 0.8)
return FALSE
- var/found_reagent = FALSE
-
- var/datum/reagents/R = nanites.host_mob.reagents
- for(var/VR in valid_reagents)
- if(R.has_reagent(VR, 0.5))
- R.remove_reagent(VR, 0.5)
- found_reagent = TRUE
- break
- if(!found_reagent)
- return FALSE
return ..()
-/datum/nanite_program/protocol/tinker/active_effect()
+/datum/nanite_program/protocol/pyramid/active_effect()
nanites.adjust_nanites(null, boost)
/datum/nanite_program/protocol/offline
- name = "Offline Production Protocol"
+ 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)
@@ -105,7 +90,6 @@
/datum/nanite_program/protocol/offline/active_effect()
nanites.adjust_nanites(null, boost)
-
/datum/nanite_program/protocol/synergy
name = "Synergy Protocol"
desc = "Replication Protocol: the nanites syncronize their tasks and processes within a host, leading to an increase in replication speed proportional to the current nanite volume."
@@ -116,3 +100,195 @@
/datum/nanite_program/protocol/synergy/active_effect()
nanites.adjust_nanites(null, round(max_boost * (nanites.nanite_volume / nanites.max_nanites), 0.1))
+
+/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/C = 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 = C.getorganslot(ORGAN_SLOT_LIVER)
+ if(liver)
+ liver.applyOrganDamage(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 = C.getorganslot(ORGAN_SLOT_STOMACH)
+ if(stomach)
+ stomach.applyOrganDamage(0.75)
+ if(prob(10))
+ var/obj/item/organ/lungs/lungs = C.getorganslot(ORGAN_SLOT_LUNGS)
+ if(lungs)
+ lungs.applyOrganDamage(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 = C.getorganslot(ORGAN_SLOT_HEART)
+ if(heart)
+ heart.applyOrganDamage(0.75)
+ if(prob(10))
+ var/obj/item/organ/brain/brain = C.getorganslot(ORGAN_SLOT_BRAIN)
+ if(brain)
+ brain.applyOrganDamage(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 = C.getorganslot(ORGAN_SLOT_EYES)
+ if(eyes)
+ eyes.applyOrganDamage(0.75)
+ if(prob(13))
+ var/obj/item/organ/ears/ears = C.getorganslot(ORGAN_SLOT_EARS)
+ if(ears)
+ ears.applyOrganDamage(0.75)
+ current_stage++
+ if(nanites.nanite_volume > 1500) //Nanites start spilling into the bloodstream, causing toxicity
+ if(prob(15))
+ C.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))
+ C.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, "[pick(main_warnings)]")
+ next_warning = world.time + rand(min_warning_cooldown, max_warning_cooldown)
+ else if(islist(extra_warnings))
+ to_chat(host_mob, "[pick(extra_warnings)]")
+ next_warning = world.time + rand(min_warning_cooldown, max_warning_cooldown)
diff --git a/code/modules/research/nanites/nanite_programs/sensor.dm b/code/modules/research/nanites/nanite_programs/sensor.dm
index 260811445b..c03a70c116 100644
--- a/code/modules/research/nanites/nanite_programs/sensor.dm
+++ b/code/modules/research/nanites/nanite_programs/sensor.dm
@@ -235,8 +235,7 @@
/datum/nanite_program/sensor/voice
name = "Voice Sensor"
- desc = "Sends a signal when the nanites hear a determined word or sentence."
- var/spent = FALSE
+ 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()
. = ..()
@@ -248,16 +247,17 @@
RegisterSignal(host_mob, COMSIG_MOVABLE_HEAR, .proc/on_hear)
/datum/nanite_program/sensor/voice/on_mob_remove()
- UnregisterSignal(host_mob, COMSIG_MOVABLE_HEAR, .proc/on_hear)
+ 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(findtextEx(hearing_args[HEARING_RAW_MESSAGE], sentence))
+ if(findtext(hearing_args[HEARING_RAW_MESSAGE], sentence.get_value()))
send_code()
else
- if(hearing_args[HEARING_RAW_MESSAGE] == sentence)
+ if(lowertext(hearing_args[HEARING_RAW_MESSAGE]) == lowertext(sentence.get_value()))
send_code()
diff --git a/code/modules/research/nanites/nanite_programs/suppression.dm b/code/modules/research/nanites/nanite_programs/suppression.dm
index d2aa243fee..4e893d2a43 100644
--- a/code/modules/research/nanites/nanite_programs/suppression.dm
+++ b/code/modules/research/nanites/nanite_programs/suppression.dm
@@ -2,7 +2,7 @@
/datum/nanite_program/sleepy
name = "Sleep Induction"
- desc = "The nanites cause rapid narcolepsy when triggered."
+ desc = "The nanites induce rapid narcolepsy when triggered."
can_trigger = TRUE
trigger_cost = 15
trigger_cooldown = 1200
@@ -116,13 +116,13 @@
//Can receive transmissions from a nanite communication remote for customized messages
/datum/nanite_program/comm
can_trigger = TRUE
- var/comm_code = 0
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)
return
if(signal_comm_code == comm_code)
@@ -138,7 +138,8 @@
rogue_types = list(/datum/nanite_program/brain_misfire, /datum/nanite_program/brain_decay)
var/static/list/blacklist = list(
"*surrender",
- "*collapse"
+ "*collapse",
+ "*faint",
)
/datum/nanite_program/comm/speech/register_extra_settings()
diff --git a/code/modules/research/nanites/nanite_programs/utility.dm b/code/modules/research/nanites/nanite_programs/utility.dm
index eea8b7f18e..feb726ded4 100644
--- a/code/modules/research/nanites/nanite_programs/utility.dm
+++ b/code/modules/research/nanites/nanite_programs/utility.dm
@@ -207,24 +207,23 @@
//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/new_access = list()
- var/obj/item/current_item
- current_item = host_mob.get_active_held_item()
- if(current_item)
- new_access += current_item.GetAccess()
- current_item = host_mob.get_inactive_held_item()
- if(current_item)
- new_access += current_item.GetAccess()
+ var/list/potential_items = list()
+
+ potential_items += host_mob.get_active_held_item()
+ potential_items += host_mob.get_inactive_held_item()
+
if(ishuman(host_mob))
var/mob/living/carbon/human/H = host_mob
- current_item = H.wear_id
- if(current_item)
- new_access += current_item.GetAccess()
+ potential_items += H.wear_id
else if(isanimal(host_mob))
+ potential_items += host_mob.pulling
var/mob/living/simple_animal/A = host_mob
- current_item = A.access_card
- if(current_item)
- new_access += current_item.GetAccess()
+ 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
@@ -253,6 +252,7 @@
//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 by [key_name(host_mob)] at [AREACOORD(infectee)].", INVESTIGATE_NANITES)
/datum/nanite_program/nanite_sting
@@ -277,13 +277,15 @@
//unlike with Infective Exo-Locomotion, this can't take over existing nanites, because Nanite Sting only targets non-hosts.
infectee.AddComponent(/datum/component/nanites, 5)
SEND_SIGNAL(infectee, COMSIG_NANITE_SYNC, nanites)
+ SEND_SIGNAL(infectee, COMSIG_NANITE_SET_CLOUD, nanites.cloud_id)
infectee.investigate_log("was infected by a nanite cluster by [key_name(host_mob)] at [AREACOORD(infectee)].", INVESTIGATE_NANITES)
to_chat(infectee, "You feel a tiny prick.")
/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.\
- The replication has also a chance to corrupt the nanite programming due to copy faults - cloud sync is highly recommended."
+ 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)
@@ -306,16 +308,14 @@
/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("one","two","three","four","five","plus","minus","power"))
- extra_settings[NES_COLOR] = new /datum/nanite_extra_setting/type("green", list("green","red","yellow","blue"))
+ 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]
- var/datum/nanite_extra_setting/bn_color = extra_settings[NES_COLOR]
if(!button)
- button = new(src, bn_name.get_value(), bn_icon.get_value(), bn_color.get_value())
+ button = new(src, bn_name.get_value(), bn_icon.get_value())
button.target = host_mob
button.Grant(host_mob)
@@ -339,14 +339,14 @@
name = "Button"
icon_icon = 'icons/mob/actions/actions_items.dmi'
check_flags = AB_CHECK_RESTRAINED|AB_CHECK_STUN|AB_CHECK_CONSCIOUS
- button_icon_state = "power_green"
+ button_icon_state = "nanite_power"
var/datum/nanite_program/dermal_button/program
-/datum/action/innate/nanite_button/New(datum/nanite_program/dermal_button/_program, _name, _icon, _color)
+/datum/action/innate/nanite_button/New(datum/nanite_program/dermal_button/_program, _name, _icon)
..()
program = _program
name = _name
- button_icon_state = "[_icon]_[_color]"
+ button_icon_state = "nanite_[_icon]"
/datum/action/innate/nanite_button/Activate()
program.press()
diff --git a/code/modules/research/nanites/nanite_programs/weapon.dm b/code/modules/research/nanites/nanite_programs/weapon.dm
index e16ce873dc..5c9754ec41 100644
--- a/code/modules/research/nanites/nanite_programs/weapon.dm
+++ b/code/modules/research/nanites/nanite_programs/weapon.dm
@@ -45,8 +45,8 @@
/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."
- use_rate = 0
+ 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."
rogue_types = list(/datum/nanite_program/necrotic)
/datum/nanite_program/aggressive_replication/active_effect()
@@ -87,11 +87,8 @@
addtimer(CALLBACK(src, .proc/boom), clamp((nanites.nanite_volume * 0.35), 25, 150))
/datum/nanite_program/explosive/proc/boom()
- var/nanite_amount = nanites.nanite_volume
- var/heavy_range = FLOOR(nanite_amount/100, 1) - 1
- var/light_range = FLOOR(nanite_amount/50, 1) - 1
- explosion(host_mob, 0, heavy_range, light_range)
- nanites.delete_nanites()
+ dyn_explosion(get_turf(host_mob), nanites.nanite_volume / 50)
+ qdel(nanites)
//TODO make it defuse if triggered again
diff --git a/icons/effects/blood.dmi b/icons/effects/blood.dmi
index 5bc30f886c..bee16d4d90 100644
Binary files a/icons/effects/blood.dmi and b/icons/effects/blood.dmi differ
diff --git a/icons/mob/actions/actions_items.dmi b/icons/mob/actions/actions_items.dmi
index 8b21c32d6b..10627cf66d 100644
Binary files a/icons/mob/actions/actions_items.dmi and b/icons/mob/actions/actions_items.dmi differ
diff --git a/icons/obj/machines/research.dmi b/icons/obj/machines/research.dmi
index 8f9594c5dc..ac6eb0eb9f 100644
Binary files a/icons/obj/machines/research.dmi and b/icons/obj/machines/research.dmi differ
diff --git a/tgui/packages/tgui/interfaces/NaniteCloudControl.js b/tgui/packages/tgui/interfaces/NaniteCloudControl.js
index e9eb566464..a24c60a20d 100644
--- a/tgui/packages/tgui/interfaces/NaniteCloudControl.js
+++ b/tgui/packages/tgui/interfaces/NaniteCloudControl.js
@@ -196,7 +196,7 @@ export const NaniteCloudBackupDetails = (props, context) => {
!!has_program && (