diff --git a/code/__DEFINES/dcs/signals.dm b/code/__DEFINES/dcs/signals.dm index 7b214df421..51a5fb3ece 100644 --- a/code/__DEFINES/dcs/signals.dm +++ b/code/__DEFINES/dcs/signals.dm @@ -490,6 +490,17 @@ #define COMPONENT_PROGRAM_INSTALLED 1 //Installation successful #define COMPONENT_PROGRAM_NOT_INSTALLED 2 //Installation failed, but there are still nanites #define COMSIG_NANITE_SYNC "nanite_sync" //(datum/component/nanites, full_overwrite, copy_activation) Called to sync the target's nanites to a given nanite component +/// Checks if a nanite component is able to be controlled by console +#define COMSIG_NANITE_CHECK_CONSOLE_LOCK "is_console_locked" +/// Checks if a nanite component is able to be interfaced with by a host with innate nanite control +#define COMSIG_NANITE_CHECK_HOST_LOCK "is_host_locked" +/// Checks if a nanite component is able to be overwritten by viral replica +#define COMSIG_NANITE_CHECK_VIRAL_PREVENTION "is_virus_locked" + #define NANITE_CHANGES_LOCKED 1 +// Internal signals that programs register to and respond with to not require for loops +#define COMSIG_NANITE_INTERNAL_CONSOLE_LOCK_CHECK "naniteiconsolelocked" +#define COMSIG_NANITE_INTERNAL_HOST_LOCK_CHECK "naniteihostlocked" +#define COMSIG_NANITE_INTERNAL_VIRAL_PREVENTION_CHECK "naniteiviruslocked" // /datum/component/storage signals #define COMSIG_CONTAINS_STORAGE "is_storage" //() - returns bool. diff --git a/code/__DEFINES/mobs.dm b/code/__DEFINES/mobs.dm index e8849d04c8..9dfa6116f1 100644 --- a/code/__DEFINES/mobs.dm +++ b/code/__DEFINES/mobs.dm @@ -48,6 +48,8 @@ #define MOB_EPIC (1 << 7) // Megafauna #define MOB_REPTILE (1 << 8) #define MOB_SPIRIT (1 << 9) +/// Mobs that otherwise support nanites +#define MOB_NANITES (1 << 10) // Organ defines for carbon mobs #define ORGAN_ORGANIC 1 diff --git a/code/datums/components/nanites.dm b/code/datums/components/nanites.dm index 29add286be..2b012ab82b 100644 --- a/code/datums/components/nanites.dm +++ b/code/datums/components/nanites.dm @@ -17,6 +17,44 @@ 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 + /// Delete ourselves when we're depleted. + var/qdel_self_on_depletion = TRUE + /// Allow deletion + var/can_be_deleted = TRUE + /// Whether or not we can survive no cloud syncing without errors + var/requires_cloud_sync = TRUE + /// Permanent programs - can never be deleted. does not count towards max_programs. + var/list/datum/nanite_program/permanent_programs = list() + + // Vulnerabilities + /// EMP flat deletion upper + var/emp_flat_deletion_upper = 35 + /// EMP flat deletion lower + var/emp_Flat_deletion_lower = 20 + /// EMP percent deletion upper + var/emp_percent_deletion_upper = 0.35 + /// EMP percent deletion lower + var/emp_percent_deletion_lower = 0.25 + /// EMP severity multiplier, capping to 0 to 100 + var/emp_severity_mod = 1 + /// EMP severity div for cloudsync reset chance + var/emp_desync_mod = 0.25 + + /// Shock flat deletion upper + var/shock_flat_deletion_upper = 45 + /// Shock flat deletion lower + var/shock_flat_deletion_lower = 25 + /// Shock percent deletion upper + var/shock_percent_deletion_upper = 0.25 + /// Shock percent deletion lower + var/shock_percent_deletion_lower = 0.15 + + + /// minor shock deletion lower + var/minor_shock_deletion_lower = 5 + /// minor shock deletion upper + var/minor_shock_deletion_upper = 15 + /datum/component/nanites/Initialize(amount = 100, cloud = 0) if(!isliving(parent) && !istype(parent, /datum/nanite_cloud_backup)) return COMPONENT_INCOMPATIBLE @@ -55,6 +93,9 @@ RegisterSignal(parent, COMSIG_NANITE_ADD_PROGRAM, .proc/add_program) RegisterSignal(parent, COMSIG_NANITE_SCAN, .proc/nanite_scan) RegisterSignal(parent, COMSIG_NANITE_SYNC, .proc/sync) + RegisterSignal(parent, COMSIG_NANITE_CHECK_CONSOLE_LOCK, .proc/check_console_locking) + RegisterSignal(parent, COMSIG_NANITE_CHECK_HOST_LOCK, .proc/check_host_lockout) + RegisterSignal(parent, COMSIG_NANITE_CHECK_VIRAL_PREVENTION, .proc/check_viral_prevention) if(isliving(parent)) RegisterSignal(parent, COMSIG_ATOM_EMP_ACT, .proc/on_emp) @@ -118,13 +159,60 @@ next_sync = world.time + NANITE_SYNC_DELAY set_nanite_bar() +/** + * Called when nanites are depleted. + * Deletes ourselves by default. + */ +/datum/component/nanites/proc/nanites_depleted() + if(qdel_self_on_depletion) + delete_nantes() +/** + * Used to rid ourselves + */ /datum/component/nanites/proc/delete_nanites() - qdel(src) + if(can_be_deleted) + qdel(src) + +/** + * Adds permanent programs + */ +/datum/component/nanites/proc/add_permanent_program(list/program, immutable = FALSE) + if(!islist(program)) + program = list(program) + for(var/i in program) + if(i in permanent_programs) + continue + var/datum/nanite_program/P = i + permanent_programs += P + if(immutable) + P.immutable = TRUE + for(var/i in programs) + if(P.collision_check(i)) + qdel(i) + programs += P + +/** + * Checks if we can block out console modification + */ +/datum/component/nanites/proc/check_console_locking() + return SEND_SIGNAL(src, COMSIG_NANITE_INTERNAL_CONSOLE_LOCK_CHECK) + +/** + * Checks if we can lock out host internal conscious modification + */ +/datum/component/nanites/proc/check_host_lockout() + return SEND_SIGNAL(src, COMSIG_NANITE_INTERNAL_HOST_LOCK_CHECK) + +/** + * Checks if we can block out viral replica + */ +/datum/component/nanites/proc/check_viral_prevention() + return SEND_SIGNAL(src, COMSIG_NANITE_INTERNAL_VIRAL_LOCK_CHECK) //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() + var/list/programs_to_remove = programs.Copy() - permanent_programs var/list/programs_to_add = source.programs.Copy() for(var/X in programs) var/datum/nanite_program/NP = X @@ -151,7 +239,7 @@ sync(null, cloud_copy) return //Without cloud syncing nanites can accumulate errors and/or defects - if(prob(8) && programs.len) + if(prob(8) && programs.len && requires_cloud_sync) var/datum/nanite_program/NP = pick(programs) NP.software_error() @@ -159,8 +247,11 @@ for(var/X in programs) var/datum/nanite_program/NP = X if(NP.unique && NP.type == new_program.type) - qdel(NP) - if(programs.len >= max_programs) + if(NP in permanent_programs) + return COMPONENT_PROGRAM_NOT_INSTALLED + else + qdel(NP) + if((programs.len - length(permanent_programs)) >= max_programs) return COMPONENT_PROGRAM_NOT_INSTALLED if(source_program) source_program.copy_programming(new_program) @@ -177,7 +268,7 @@ /datum/component/nanites/proc/adjust_nanites(datum/source, amount) nanite_volume = clamp(nanite_volume + amount, 0, max_nanites) if(nanite_volume <= 0) //oops we ran out - qdel(src) + nanites_depleted() /datum/component/nanites/proc/set_nanite_bar(remove = FALSE) var/image/holder = host_mob.hud_list[DIAG_NANITE_FULL_HUD] @@ -191,28 +282,28 @@ holder.icon_state = "nanites[nanite_percent]" /datum/component/nanites/proc/on_emp(datum/source, severity) - nanite_volume *= (rand(60, 90) * 0.01) //Lose 10-40% of nanites - adjust_nanites(null, -(rand(5, 50))) //Lose 5-50 flat nanite volume - if(prob(40/severity)) + severity *= emp_severity_mod + var/loss = (severity / 100) * (rand(emp_percent_deletion_lower, emp_percent_deletion_upper) * nanite_volume) + rand(emp_flat_deletion_lower, emp_flat_deletion_upper) + adjust_nanites(null, -loss) + if(prob(severity / emp_desync_mod)) cloud_id = 0 for(var/X in programs) var/datum/nanite_program/NP = X NP.on_emp(severity) - /datum/component/nanites/proc/on_shock(datum/source, shock_damage, siemens_coeff = 1, flags = NONE) if(shock_damage < 1) return 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 + var/loss = (rand(shock_percent_deletion_lower, shock_percent_deletion_upper) * nanite_volume) + rand(shock_flat_deletion_lower, shock_flat_deletion_upper) + adjust_nanites(null, -loss) for(var/X in programs) var/datum/nanite_program/NP = X NP.on_shock(shock_damage) /datum/component/nanites/proc/on_minor_shock(datum/source) - adjust_nanites(null, -(rand(5, 15))) //Lose 5-15 flat nanite volume + adjust_nanites(null, -(rand(miner_shock_deletion_lower, miner_shock_deletion_uppper))) //Lose 5-15 flat nanite volume for(var/X in programs) var/datum/nanite_program/NP = X NP.on_minor_shock() @@ -237,7 +328,7 @@ NP.receive_comm_signal(comm_code, comm_message, comm_source) /datum/component/nanites/proc/check_viable_biotype() - if(!(host_mob.mob_biotypes & (MOB_ORGANIC|MOB_UNDEAD))) + if(!(host_mob.mob_biotypes & (MOB_ORGANIC|MOB_UNDEAD|MOB_NANITES))) qdel(src) //bodytype no longer sustains nanites /datum/component/nanites/proc/check_access(datum/source, obj/O) @@ -378,3 +469,10 @@ id++ mob_programs += list(mob_program) data["mob_programs"] = mob_programs + +/** + * Subtype that doesn't erase itself from running out + */ +/datum/component/nanites/permanent + qdel_self_on_depletion = FALSE + can_be_deleted = FALSE diff --git a/code/modules/research/nanites/nanite_programs.dm b/code/modules/research/nanites/nanite_programs.dm index faa81be0a5..d3cf755de2 100644 --- a/code/modules/research/nanites/nanite_programs.dm +++ b/code/modules/research/nanites/nanite_programs.dm @@ -54,6 +54,13 @@ //Rules that automatically manage if the program's active without requiring separate sensor programs var/list/datum/nanite_rule/rules = list() + /// Corruptable - able to have code/configuration changed + var/corruptable = TRUE + /// error flicking - able to be randomly toggled by errors + var/error_flicking = TRUE + /// immutable - cannot be overwritten by other programs + var/immutable = FALSE + /datum/nanite_program/New() . = ..() register_extra_settings() @@ -68,8 +75,15 @@ on_mob_remove() if(nanites) nanites.programs -= src + nanites.permanent_programs -= src return ..() +/** + * Checks if we're a permanent program + */ +/datum/nanite_program/proc/is_permanent() + return nanites && (src in nanites.permanent_programs) + /datum/nanite_program/proc/copy() var/datum/nanite_program/new_program = new type() copy_programming(new_program, TRUE) @@ -77,6 +91,8 @@ return new_program /datum/nanite_program/proc/copy_programming(datum/nanite_program/target, copy_activated = TRUE) + if(target.immutable) + return if(copy_activated) target.activated = activated target.timer_restart = timer_restart @@ -234,7 +250,7 @@ /datum/nanite_program/proc/on_emp(severity) if(program_flags & NANITE_EMP_IMMUNE) return - if(prob(80 / severity)) + if(prob(severity / 2)) software_error() /datum/nanite_program/proc/on_shock(shock_damage) @@ -242,7 +258,7 @@ if(prob(10)) software_error() else if(prob(33)) - qdel(src) + self_destruct() /datum/nanite_program/proc/on_minor_shock() if(!program_flags & NANITE_SHOCK_IMMUNE) @@ -254,26 +270,29 @@ /datum/nanite_program/proc/software_error(type) if(!type) - type = rand(1,5) + type = rand(1,is_permanent()? 4 : 5) switch(type) if(1) - qdel(src) //kill switch + self_destruct() //kill switch return if(2) //deprogram codes - activation_code = 0 - deactivation_code = 0 - kill_code = 0 - trigger_code = 0 + if(corruptable) + activation_code = 0 + deactivation_code = 0 + kill_code = 0 + trigger_code = 0 if(3) - toggle() //enable/disable + if(error_flicking) + toggle() //enable/disable if(4) - if(can_trigger) + if(error_flicking && can_trigger) trigger() if(5) //Program is scrambled and does something different - var/rogue_type = pick(rogue_types) - var/datum/nanite_program/rogue = new rogue_type - nanites.add_program(null, rogue, src) - qdel(src) + if(corruptable) + var/rogue_type = pick(rogue_types) + var/datum/nanite_program/rogue = new rogue_type + nanites.add_program(null, rogue, src) + self_destruct() /datum/nanite_program/proc/receive_signal(code, source) if(activation_code && code == activation_code && !activated) @@ -285,10 +304,18 @@ if(can_trigger && trigger_code && code == trigger_code) trigger() host_mob.investigate_log("'s [name] nanite program was triggered by [source] with code [code].", INVESTIGATE_NANITES) - if(kill_code && code == kill_code) + if((kill_code && code == kill_code) && !is_permanent()) host_mob.investigate_log("'s [name] nanite program was deleted by [source] with code [code].", INVESTIGATE_NANITES) qdel(src) +/** + * Attempts to destroy ourselves + */ +/datum/nanite_program/proc/self_destruct() + 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