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 && (