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..496a902fac 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"
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 93735f1079..b727adef0c 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/antagonists/abductor/equipment/glands/heal.dm b/code/modules/antagonists/abductor/equipment/glands/heal.dm
index 5ea8f77d6c..6726e44f3d 100644
--- a/code/modules/antagonists/abductor/equipment/glands/heal.dm
+++ b/code/modules/antagonists/abductor/equipment/glands/heal.dm
@@ -64,7 +64,7 @@
/obj/item/organ/heart/gland/heal/proc/reject_implant(obj/item/organ/cyberimp/implant)
owner.visible_message("[owner] vomits up his [implant.name]!", "You suddenly vomit up your [implant.name]!")
- owner.vomit(0, TRUE, TRUE, 1, FALSE, FALSE, FALSE, TRUE)
+ owner.vomit(0, TRUE, TRUE, 1, FALSE, FALSE, FAL SE, TRUE)
implant.Remove()
implant.forceMove(owner.drop_location())
diff --git a/code/modules/mob/living/carbon/carbon.dm b/code/modules/mob/living/carbon/carbon.dm
index c1110af267..6110b524cd 100644
--- a/code/modules/mob/living/carbon/carbon.dm
+++ b/code/modules/mob/living/carbon/carbon.dm
@@ -502,9 +502,9 @@
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, purge_ratio = 0.1)
+ if((HAS_TRAIT(src, TRAIT_NOHUNGER) || HAS_TRAIT(src, TRAIT_TOXINLOVER)) && !force)
+ return TRUE
if(nutrition < 100 && !blood)
if(message)
@@ -533,12 +533,16 @@
if(!blood)
adjust_nutrition(-lost_nutrition)
adjustToxLoss(-3)
+
for(var/i=0 to distance)
if(blood)
if(T)
add_splatter_floor(T)
if(stun)
adjustBruteLoss(3)
+ else
+ if(T)
+ 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.
else if(src.reagents.has_reagent(/datum/reagent/consumable/ethanol/blazaam))
if(T)
T.add_vomit_floor(src, VOMIT_PURPLE)
diff --git a/code/modules/mob/living/carbon/human/human.dm b/code/modules/mob/living/carbon/human/human.dm
index 41ac05b8e2..0adb529ae4 100644
--- a/code/modules/mob/living/carbon/human/human.dm
+++ b/code/modules/mob/living/carbon/human/human.dm
@@ -840,7 +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)
+/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 && (NOBLOOD in dna.species.species_traits) && !HAS_TRAIT(src, TRAIT_TOXINLOVER))
if(blood && dna?.species && (NOBLOOD in dna.species.species_traits))
if(message)
visible_message("[src] dry heaves!", \
@@ -1095,7 +1096,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..b833bef833 100644
--- a/code/modules/research/designs/nanite_designs.dm
+++ b/code/modules/research/designs/nanite_designs.dm
@@ -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_programs/protocols.dm b/code/modules/research/nanites/nanite_programs/protocols.dm
index 3830e7c8ba..23d7a26198 100644
--- a/code/modules/research/nanites/nanite_programs/protocols.dm
+++ b/code/modules/research/nanites/nanite_programs/protocols.dm
@@ -46,43 +46,25 @@
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 more efficient as long as the saturation level is above 80%."
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)
@@ -106,13 +88,194 @@
/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."
+/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 in a host."
use_rate = 0
rogue_types = list(/datum/nanite_program/necrotic)
- protocol_class = NANITE_PROTOCOL_REPLICATION
- var/max_boost = 2 //The maximum boost this program applies to the nanite replication, multiplied with the current nanite 'saturation' percentage
+ protocol_class = NANITE_PROTOCOL_STORAGE
+ var/extra_volume = 250
-/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/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, greatly increasing the maximum volume while in a host. However, the process slows down the 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, but increases the replication rate."
+ 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 much higher saturation levels, but at the risk of causing internal damage to the host."
+ 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/weapon.dm b/code/modules/research/nanites/nanite_programs/weapon.dm
index e16ce873dc..ce619cce47 100644
--- a/code/modules/research/nanites/nanite_programs/weapon.dm
+++ b/code/modules/research/nanites/nanite_programs/weapon.dm
@@ -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