Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Nanitemania: more nanite programs and stuff! #9216

Merged
merged 20 commits into from
Oct 22, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading