Skip to content

Commit

Permalink
Nanitemania: more nanite programs and stuff! (#9216)
Browse files Browse the repository at this point in the history
* Nyanitemania: more nanite programs and stuff!

* Remove self-resp nanites.

* Fix my laziness.

* Comm remotes are actually buildable now.

* Make invalid rules red in the UI

* Fix nutrition sensors.

* clean up code a bit

* More optimization.

* WHOOPS

* Sanity check in vampiric synthesis.

* Subdermal ID also takes access from pulled IDs now.

* more sanity checks

* slightly cleaner

* Update, address reviews

* That sprite doesn't exist...

* New nanite blade icon
  • Loading branch information
Absolucy authored Oct 22, 2023
1 parent 242ed22 commit 9142010
Show file tree
Hide file tree
Showing 23 changed files with 1,149 additions and 165 deletions.
1 change: 1 addition & 0 deletions beestation.dme
Original file line number Diff line number Diff line change
Expand Up @@ -3658,6 +3658,7 @@
#include "code\modules\research\nanites\extra_settings\type.dm"
#include "code\modules\research\nanites\nanite_programs\buffing.dm"
#include "code\modules\research\nanites\nanite_programs\healing.dm"
#include "code\modules\research\nanites\nanite_programs\protocol.dm"
#include "code\modules\research\nanites\nanite_programs\rogue.dm"
#include "code\modules\research\nanites\nanite_programs\sensor.dm"
#include "code\modules\research\nanites\nanite_programs\suppression.dm"
Expand Down
3 changes: 3 additions & 0 deletions code/__DEFINES/mobs.dm
Original file line number Diff line number Diff line change
Expand Up @@ -515,6 +515,9 @@
#define VOMIT_TOXIC 1
/// The mob will vomit a purple color
#define VOMIT_PURPLE 2
/// The mob will vomit up nanites
#define VOMIT_NANITE 3


/// Messages when (something) lays an egg
#define EGG_LAYING_MESSAGES list("lays an egg.","squats down and croons.","begins making a huge racket.","begins clucking raucously.")
15 changes: 15 additions & 0 deletions code/__DEFINES/nanites.dm
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,24 @@
#define NES_ICON "Icon"
#define NES_COLOR "Color"
#define NES_RULE_LOGIC "Logic"
#define NES_NUTRITION "Nutrition"
#define NES_BLOOD_PERCENT "Blood Percent"
#define NES_SIGNAL_FREQUENCY "Signal Frequency"
#define NES_SIGNAL_CODE "Signal Code"
#define NES_ACTIVATION_CODE "Sent Activation Code"
#define NES_DEACTIVATION_CODE "Sent Deactivation Code"

//Nanite Logic
#define NL_AND "AND"
#define NL_OR "OR"
#define NL_NOR "NOR"
#define NL_NAND "NAND"
#define NL_ALL list(NL_AND, NL_OR, NL_NOR, NL_NAND)

//Nanite excess thresholds
#define NANITE_EXCESS_MINOR 25
#define NANITE_EXCESS_VOMIT 100
#define NANITE_EXCESS_BURST 350

/// How many nanites/sec to add to the regen rate once nanite harmonics are researched
#define HARMONIC_REGEN_BOOST 0.1
186 changes: 115 additions & 71 deletions code/datums/components/nanites.dm
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@
var/safety_threshold = 50 //how low nanites will get before they stop processing/triggering
var/cloud_id = 0 //0 if not connected to the cloud, 1-100 to set a determined cloud backup to draw from
var/cloud_active = TRUE //if false, won't sync to the cloud
var/next_sync = 0
var/list/datum/nanite_program/programs = list()
var/max_programs = NANITE_PROGRAM_LIMIT
COOLDOWN_DECLARE(next_sync)

var/list/datum/nanite_program/protocol/protocols = list() ///Separate list of protocol programs, to avoid looping through the whole programs list when cheking for conflicts
var/list/datum/nanite_program/protocol/protocols = list() /// Separate list of protocol programs, to avoid looping through the whole programs list when cheking for conflicts
var/start_time = 0 ///Timestamp to when the nanites were first inserted in the host
var/stealth = FALSE //if TRUE, does not appear on HUDs and health scans
var/diagnostics = TRUE //if TRUE, displays program list when scanned by nanite scanners
Expand Down Expand Up @@ -103,42 +103,40 @@

/datum/component/nanites/InheritComponent(datum/component/nanites/new_nanites, i_am_original, amount, cloud)
if(new_nanites)
adjust_nanites(null, new_nanites.nanite_volume)
adjust_nanites(amount = new_nanites.nanite_volume)
else
adjust_nanites(null, amount) //just add to the nanite volume
adjust_nanites(amount = amount) //just add to the nanite volume

/datum/component/nanites/process(delta_time)
if(!IS_IN_STASIS(host_mob))
adjust_nanites(null, regen_rate * delta_time)
adjust_nanites(amount = (regen_rate + (SSresearch.science_tech.researched_nodes["nanite_harmonic"] ? HARMONIC_REGEN_BOOST : 0)) * delta_time)
add_research()
for(var/X in programs)
var/datum/nanite_program/NP = X
NP.on_process()
if(cloud_id && cloud_active && world.time > next_sync)
for(var/datum/nanite_program/program as anything in programs)
program.on_process()
if(cloud_id && cloud_active && COOLDOWN_FINISHED(src, next_sync))
cloud_sync()
next_sync = world.time + NANITE_SYNC_DELAY
COOLDOWN_START(src, next_sync, NANITE_SYNC_DELAY)
set_nanite_bar()


/// Deletes nanites!
/datum/component/nanites/proc/delete_nanites()
SIGNAL_HANDLER

qdel(src)

//Syncs the nanite component to another, making it so programs are the same with the same programming (except activation status)
/// 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_settings = TRUE, copy_activation = FALSE)
SIGNAL_HANDLER

var/list/programs_to_remove = programs.Copy()
var/list/programs_to_add = source.programs.Copy()
for(var/X in programs)
var/datum/nanite_program/NP = X
for(var/Y in programs_to_add)
var/datum/nanite_program/SNP = Y
if(NP.type == SNP.type)
programs_to_remove -= NP
programs_to_add -= SNP
SNP.copy_programming(NP, copy_activation)
for(var/datum/nanite_program/current_program as anything in programs)
for(var/datum/nanite_program/new_program as anything in programs_to_add)
if(current_program.type == new_program.type)
programs_to_remove -= current_program
programs_to_add -= new_program
new_program.copy_programming(current_program, copy_activation)
break
if(full_overwrite)
for(var/X in programs_to_remove)
Expand All @@ -148,9 +146,10 @@
cloud_id = source.cloud_id
safety_threshold = source.safety_threshold
for(var/X in programs_to_add)
var/datum/nanite_program/SNP = X
add_program(null, SNP.copy())
for(var/datum/nanite_program/program as anything in programs_to_add)
add_program(new_program = program.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)
Expand All @@ -164,13 +163,13 @@
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)
SIGNAL_HANDLER

for(var/X in programs)
var/datum/nanite_program/NP = X
if(NP.unique && NP.type == new_program.type)
qdel(NP)
for(var/datum/nanite_program/program as anything in programs)
if(program.unique && program.type == new_program.type)
qdel(program)
if(programs.len >= max_programs)
return COMPONENT_PROGRAM_NOT_INSTALLED
if(source_program)
Expand All @@ -182,16 +181,70 @@
/datum/component/nanites/proc/consume_nanites(amount, force = FALSE)
if(!force && safety_threshold && (nanite_volume - amount < safety_threshold))
return FALSE
adjust_nanites(null, -amount)
adjust_nanites(amount = -amount)
return (nanite_volume > 0)

/// Modifies the current nanite volume, then checks if the nanites are depleted or exceeding the maximum amount
/datum/component/nanites/proc/adjust_nanites(datum/source, amount)
SIGNAL_HANDLER

nanite_volume = clamp(nanite_volume + amount, 0, max_nanites)
nanite_volume += amount
if(nanite_volume > max_nanites)
reject_excess_nanites()
if(nanite_volume <= 0) //oops we ran out
qdel(src)

/**
* Handles how nanites leave the host's body if they find out that they're currently exceeding the maximum supported amount
*
* IC explanation:
* Normally nanites simply discard excess volume by slowing replication or 'sweating' it out in imperceptible amounts,
* but if there is a large excess volume, likely due to a programming change that leaves them unable to support their current volume,
* the nanites attempt to leave the host as fast as necessary to prevent nanite poisoning. This can range from minor oozing to nanites
* rapidly bursting out from every possible pathway, causing temporary inconvenience to the host.
*/
/datum/component/nanites/proc/reject_excess_nanites()
var/excess = nanite_volume - max_nanites
nanite_volume = max_nanites

switch(excess)
if(0 to NANITE_EXCESS_MINOR) //Minor excess amount, the extra nanites are quietly expelled without visible effects
return
if((NANITE_EXCESS_MINOR + 0.1) to NANITE_EXCESS_VOMIT) //Enough nanites getting rejected at once to be visible to the naked eye
host_mob.visible_message("<span class='warning'>A grainy grey slurry starts oozing out of [host_mob].</span>", "<span class='warning'>A grainy grey slurry starts oozing out of your skin.</span>", vision_distance = 4);
if((NANITE_EXCESS_VOMIT + 0.1) to NANITE_EXCESS_BURST) //Nanites getting rejected in massive amounts, but still enough to make a semi-orderly exit through vomit
if(iscarbon(host_mob))
var/mob/living/carbon/C = host_mob
host_mob.visible_message("<span class='warning'>[host_mob] vomits a grainy grey slurry!</span>", "<span class='warning'>You suddenly vomit a metallic-tasting grainy grey slurry!</span>");
C.vomit(0, FALSE, TRUE, FLOOR(excess / 100, 1), FALSE, VOMIT_NANITE, FALSE)
else
host_mob.visible_message("<span class='warning'>A metallic grey slurry bursts out of [host_mob]'s skin!</span>", "<span class='userdanger'>A metallic grey slurry violently bursts out of your skin!</span>");
if(isturf(host_mob.drop_location()))
var/turf/T = host_mob.drop_location()
T.add_vomit_floor(host_mob, VOMIT_NANITE, FALSE)
if((NANITE_EXCESS_BURST + 0.1) to INFINITY) //Way too many nanites, they just leave through the closest exit before they harm/poison the host
host_mob.visible_message("<span class='warning'>A torrent of metallic grey slurry violently bursts out of [host_mob]'s face and floods out of [host_mob.p_their()] skin!</span>",
"<span class='userdanger'>A torrent of metallic grey slurry violently bursts out of your eyes, ears, and mouth, and floods out of your skin!</span>");

host_mob.adjust_blindness(15) //nanites coming out of your eyes
host_mob.Paralyze(12 SECONDS)
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) //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, FALSE)
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, FALSE)

/// 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)
Expand All @@ -207,7 +260,7 @@
SIGNAL_HANDLER

nanite_volume *= (rand(60, 90) * 0.01) //Lose 10-40% of nanites
adjust_nanites(null, -(rand(5, 50))) //Lose 5-50 flat nanite volume
adjust_nanites(amount = -(rand(5, 50))) //Lose 5-50 flat nanite volume
if(prob(40/severity))
cloud_id = 0
for(var/X in programs)
Expand All @@ -223,18 +276,16 @@

if(!HAS_TRAIT_NOT_FROM(host_mob, TRAIT_SHOCKIMMUNE, "nanites"))//Another shock protection must protect nanites too, but nanites protect only host
nanite_volume *= (rand(45, 80) * 0.01) //Lose 20-55% of nanites
adjust_nanites(null, -(rand(5, 50))) //Lose 5-50 flat nanite volume
for(var/X in programs)
var/datum/nanite_program/NP = X
NP.on_shock(shock_damage)
adjust_nanites(amount = -(rand(5, 50))) //Lose 5-50 flat nanite volume
for(var/datum/nanite_program/program as anything in programs)
program.on_shock(shock_damage)

/datum/component/nanites/proc/on_minor_shock(datum/source)
SIGNAL_HANDLER

adjust_nanites(null, -(rand(5, 15))) //Lose 5-15 flat nanite volume
for(var/X in programs)
var/datum/nanite_program/NP = X
NP.on_minor_shock()
adjust_nanites(amount = -(rand(5, 15))) //Lose 5-15 flat nanite volume
for(var/datum/nanite_program/program as anything in programs)
program.on_minor_shock()

/datum/component/nanites/proc/check_stealth(datum/source)
SIGNAL_HANDLER
Expand All @@ -244,24 +295,20 @@
/datum/component/nanites/proc/on_death(datum/source, gibbed)
SIGNAL_HANDLER

for(var/X in programs)
var/datum/nanite_program/NP = X
NP.on_death(gibbed)
for(var/datum/nanite_program/program as anything in programs)
program.on_death(gibbed)

/datum/component/nanites/proc/receive_signal(datum/source, code, source = "an unidentified source")
SIGNAL_HANDLER

for(var/X in programs)
var/datum/nanite_program/NP = X
NP.receive_signal(code, source)
for(var/datum/nanite_program/program as anything in programs)
program.receive_nanite_signal(code, source)

/datum/component/nanites/proc/receive_comm_signal(datum/source, comm_code, comm_message, comm_source = "an unidentified source")
SIGNAL_HANDLER

for(var/X in programs)
if(istype(X, /datum/nanite_program/comm))
var/datum/nanite_program/comm/NP = X
NP.receive_comm_signal(comm_code, comm_message, comm_source)
for(var/datum/nanite_program/comm/program in programs)
program.receive_comm_signal(comm_code, comm_message, comm_source)

/datum/component/nanites/proc/check_viable_biotype()
SIGNAL_HANDLER
Expand All @@ -287,7 +334,7 @@
/datum/component/nanites/proc/set_max_volume(datum/source, amount)
SIGNAL_HANDLER

max_nanites = max(1, max_nanites)
max_nanites = max(1, amount)

/datum/component/nanites/proc/set_cloud(datum/source, amount)
SIGNAL_HANDLER
Expand Down Expand Up @@ -368,10 +415,9 @@
if(!diagnostics)
message += "<span class='alert'>Diagnostics Disabled</span>"
else
for(var/X in programs)
var/datum/nanite_program/program = X
for(var/datum/nanite_program/program as anything in programs)
message += "<span class='info'><b>[program.name]</b> | [program.activated ? "<span class='green'>Active</span>" : "<span class='red'>Inactive</span>"]</span>"
for(var/datum/nanite_rule/rule in program.rules)
for(var/datum/nanite_rule/rule as anything in program.rules)
message += "<span class='[rule.check_rule() ? "green" : "red"]'>[GLOB.TAB][rule.display()]</span>"
. = TRUE
if(length(message))
Expand All @@ -382,47 +428,45 @@

data["has_nanites"] = TRUE
data["nanite_volume"] = nanite_volume
data["regen_rate"] = regen_rate
data["regen_rate"] = regen_rate + (SSresearch.science_tech.researched_nodes["nanite_harmonic"] ? HARMONIC_REGEN_BOOST : 0)
data["safety_threshold"] = safety_threshold
data["cloud_id"] = cloud_id
data["cloud_active"] = cloud_active
var/list/mob_programs = list()
var/id = 1
for(var/X in programs)
var/datum/nanite_program/P = X
for(var/datum/nanite_program/program as anything in programs)
var/list/mob_program = list()
mob_program["name"] = P.name
mob_program["desc"] = P.desc
mob_program["name"] = program.name
mob_program["desc"] = program.desc
mob_program["id"] = id

if(scan_level >= 2)
mob_program["activated"] = P.activated
mob_program["use_rate"] = P.use_rate
mob_program["can_trigger"] = P.can_trigger
mob_program["trigger_cost"] = P.trigger_cost
mob_program["trigger_cooldown"] = P.trigger_cooldown / 10
mob_program["activated"] = program.activated
mob_program["use_rate"] = program.use_rate
mob_program["can_trigger"] = program.can_trigger
mob_program["trigger_cost"] = program.trigger_cost
mob_program["trigger_cooldown"] = program.trigger_cooldown / 10

if(scan_level >= 3)
mob_program["timer_restart"] = P.timer_restart / 10
mob_program["timer_shutdown"] = P.timer_shutdown / 10
mob_program["timer_trigger"] = P.timer_trigger / 10
mob_program["timer_trigger_delay"] = P.timer_trigger_delay / 10
var/list/extra_settings = P.get_extra_settings_frontend()
mob_program["timer_restart"] = program.timer_restart / 10
mob_program["timer_shutdown"] = program.timer_shutdown / 10
mob_program["timer_trigger"] = program.timer_trigger / 10
mob_program["timer_trigger_delay"] = program.timer_trigger_delay / 10
var/list/extra_settings = program.get_extra_settings_frontend()
mob_program["extra_settings"] = extra_settings
if(LAZYLEN(extra_settings))
mob_program["has_extra_settings"] = TRUE
else
mob_program["has_extra_settings"] = FALSE

if(scan_level >= 4)
mob_program["activation_code"] = P.activation_code
mob_program["deactivation_code"] = P.deactivation_code
mob_program["kill_code"] = P.kill_code
mob_program["trigger_code"] = P.trigger_code
mob_program["activation_code"] = program.activation_code
mob_program["deactivation_code"] = program.deactivation_code
mob_program["kill_code"] = program.kill_code
mob_program["trigger_code"] = program.trigger_code
var/list/rules = list()
var/rule_id = 1
for(var/Z in P.rules)
var/datum/nanite_rule/nanite_rule = Z
for(var/datum/nanite_rule/nanite_rule as anything in program.rules)
var/list/rule = list()
rule["display"] = nanite_rule.display()
rule["program_id"] = id
Expand Down
8 changes: 6 additions & 2 deletions code/game/turfs/turf.dm
Original file line number Diff line number Diff line change
Expand Up @@ -525,7 +525,7 @@ GLOBAL_LIST_EMPTY(created_baseturf_lists)
/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 = TRUE)

var/obj/effect/decal/cleanable/vomit/V = new /obj/effect/decal/cleanable/vomit(src, M.get_static_viruses())

Expand All @@ -539,7 +539,11 @@ GLOBAL_LIST_EMPTY(created_baseturf_lists)
V.icon_state = "vomitpurp_[pick(1,4)]"
else if (toxvomit == VOMIT_TOXIC)
V.icon_state = "vomittox_[pick(1,4)]"
if (iscarbon(M))
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 && iscarbon(M))
var/mob/living/carbon/C = M
if(C.reagents)
clear_reagents_to_vomit_pool(C,V)
Expand Down
Loading

0 comments on commit 9142010

Please sign in to comment.