diff --git a/bubber_archive/blueshift/Blueshift.dmm b/bubber_archive/blueshift/Blueshift.dmm index 3dd6caee06c..44c78a2faaa 100644 --- a/bubber_archive/blueshift/Blueshift.dmm +++ b/bubber_archive/blueshift/Blueshift.dmm @@ -41179,7 +41179,7 @@ pixel_y = 2 }, /obj/item/organ/external/tail/lizard, -/obj/item/organ/internal/ears/cat, +/obj/item/organ/ears/cat, /obj/effect/decal/cleanable/dirt{ icon_state = "dirt-flat-1" }, @@ -85176,7 +85176,7 @@ name = "organ storage" }, /obj/item/organ/external/tail/cat, -/obj/item/organ/internal/ears/cat, +/obj/item/organ/ears/cat, /obj/item/organ/internal/heart, /obj/effect/decal/cleanable/dirt{ icon_state = "dirt-flat-1" diff --git a/code/__DEFINES/atom_hud.dm b/code/__DEFINES/atom_hud.dm index 1682fa4f953..91862241d69 100644 --- a/code/__DEFINES/atom_hud.dm +++ b/code/__DEFINES/atom_hud.dm @@ -56,6 +56,10 @@ /// If they have the DNR trait #define DNR_HUD "27" // SKYRAT EDIT ADDITION END +// BUBBER EDIT ADDITION START +#define NANITE_HUD "28" +#define DIAG_NANITE_FULL_HUD "29" +// BUBBER EDIT ADDITION END //by default everything in the hud_list of an atom is an image //a value in hud_list with one of these will change that behavior diff --git a/code/__DEFINES/logging.dm b/code/__DEFINES/logging.dm index 93245670782..e3d71a2d9e1 100644 --- a/code/__DEFINES/logging.dm +++ b/code/__DEFINES/logging.dm @@ -26,6 +26,8 @@ #define INVESTIGATE_RECORDS "records" #define INVESTIGATE_RESEARCH "research" #define INVESTIGATE_WIRES "wires" +#define INVESTIGATE_NANITES "nanites" // BUBBER EDIT ADDITION + // Logging types for log_message() #define LOG_ATTACK (1 << 0) diff --git a/code/__DEFINES/machines.dm b/code/__DEFINES/machines.dm index e5d7ce45ac5..dc7dfac3fd0 100644 --- a/code/__DEFINES/machines.dm +++ b/code/__DEFINES/machines.dm @@ -64,6 +64,7 @@ #define BIOGENERATOR (1<<4) //Uses biomass #define LIMBGROWER (1<<5) //Uses synthetic flesh #define SMELTER (1<<6) //uses various minerals +#define NANITE_COMPILER (1<<7) //Prints nanite disks BUBBER EDIT ADDITION - NANITES /// Protolathes for offstation roles. More limited tech tree. #define AWAY_LATHE (1<<8) /// Imprinters for offstation roles. More limited tech tree. diff --git a/code/__DEFINES/research.dm b/code/__DEFINES/research.dm index cf35d553ec4..feedbd61a88 100644 --- a/code/__DEFINES/research.dm +++ b/code/__DEFINES/research.dm @@ -3,6 +3,7 @@ //! Techweb names for new point types. Can be used to define specific point values for specific types of research (science, security, engineering, etc.) #define TECHWEB_POINT_TYPE_GENERIC "General Research" +#define TECHWEB_POINT_TYPE_NANITE "Nanite Research" // BUBBER EDIT ADDITION - NANITES //! Amount of points required to unlock nodes of corresponding tiers #define TECHWEB_TIER_1_POINTS 40 diff --git a/code/__DEFINES/~~bubber_defines/nanites.dm b/code/__DEFINES/~~bubber_defines/nanites.dm new file mode 100644 index 00000000000..f99ea731b4c --- /dev/null +++ b/code/__DEFINES/~~bubber_defines/nanites.dm @@ -0,0 +1,71 @@ +#define CAN_HAVE_NANITES(host_mob) (host_mob.mob_biotypes & (MOB_ORGANIC|MOB_UNDEAD|MOB_ROBOTIC) && !issilicon(host_mob)) + +#define NANITE_SYNC_DELAY 300 + +#define NANITE_SHOCK_IMMUNE 1 +#define NANITE_EMP_IMMUNE 2 + +#define NANITE_PROGRAM_LIMIT 20 + +#define NANITE_BASE_RESEARCH_PROGRESS 25 + +#define NANITE_CLOUD_TOGGLE 1 +#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 + +#define NANITE_BIO_REGENERATION 0.5 +#define NANITE_ADV_BIO_REGENERATION NANITE_BIO_REGENERATION * 4 +#define NANITE_ROBO_REGENERATION NANITE_BIO_REGENERATION * 2 + +#define NANITE_TOX_REGENERATION 0.5 +#define NANITE_ADV_TOX_REGENERATION NANITE_TOX_REGENERATION + +#define NANITE_BRAIN_REGENERATION 0.2 +#define NANITE_ADV_BRAIN_REGENERATION NANITE_BRAIN_REGENERATION * 2 + +#define HARMFUL_PROGRAM_DAMAGE_SCALE 1.25 +#define NANITE_OXYLOSS 5 + +#define NANITE_PROTOCOL_KICKSTART_REPLICATION 3.5 +#define NANITE_PROTOCOL_FACTORY_MAX_REPLICATION 2 + +///Nanite Protocol types +#define NANITE_PROTOCOL_REPLICATION "nanite_replication" +#define NANITE_PROTOCOL_STORAGE "nanite_storage" + +///Nanite extra settings types: used to help uis know what type an extra setting is +#define NESTYPE_TEXT "text" +#define NESTYPE_NUMBER "number" +#define NESTYPE_TYPE "type" +#define NESTYPE_BOOLEAN "boolean" + +///Nanite Extra Settings - Note that these will also be the names displayed in the UI +#define NES_SENT_CODE "Sent Code" +#define NES_DELAY "Delay" +#define NES_MODE "Mode" +#define NES_COMM_CODE "Comm Code" +#define NES_RELAY_CHANNEL "Relay Channel" +#define NES_HEALTH_PERCENT "Health Percent" +#define NES_DIRECTION "Direction" +#define NES_NANITE_PERCENT "Nanite Percent" +#define NES_DAMAGE_TYPE "Damage Type" +#define NES_DAMAGE "Damage" +#define NES_SENTENCE "Sentence" +#define NES_MESSAGE "Message" +#define NES_DIRECTIVE "Directive" +#define NES_INCLUSIVE_MODE "Inclusive Mode" +#define NES_RACE "Race" +#define NES_HALLUCINATION_TYPE "Hallucination Type" +#define NES_HALLUCINATION_DETAIL "Hallucination Detail" +#define NES_MOOD_MESSAGE "Mood Message" +#define NES_PROGRAM_OVERWRITE "Program Overwrite" +#define NES_CLOUD_OVERWRITE "Cloud Overwrite" +#define NES_SCAN_TYPE "Scan Type" +#define NES_BUTTON_NAME "Button Name" +#define NES_ICON "Icon" +#define NANITE_HARMONIC_ID "Nano Harmonic ID" diff --git a/code/__DEFINES/~~bubber_defines/signals.dm b/code/__DEFINES/~~bubber_defines/signals.dm index 49876f8d3fc..cc1c21adf6e 100644 --- a/code/__DEFINES/~~bubber_defines/signals.dm +++ b/code/__DEFINES/~~bubber_defines/signals.dm @@ -8,5 +8,7 @@ #define COMSIG_ORGAN_BODYPART_REMOVED "organ_bodypart_removed" /// From /datum/controller/subsystem/economy/proc/issue_paydays() #define COMSIG_ON_BANK_ACCOUNT_PAYOUT "bank_account_payout" +/// identical to COMSIG_MOB_APPLY_DAMAGE, but always runs, even if there is no damage +#define COMSIG_MOB_ALWAYS_APPLY_DAMAGE "mob_always_apply_damage" /// From modular_zubbers/code/modules/disease/disease_transmission.dm #define COMSIG_DISEASE_COUNT_UPDATE "disease_count_update" diff --git a/code/__DEFINES/~~bubber_defines/traits/nanites.dm b/code/__DEFINES/~~bubber_defines/traits/nanites.dm new file mode 100644 index 00000000000..ea1b3b7ff12 --- /dev/null +++ b/code/__DEFINES/~~bubber_defines/traits/nanites.dm @@ -0,0 +1,40 @@ +///() returns TRUE if nanites are found +#define COMSIG_HAS_NANITES "has_nanites" +///() returns TRUE if nanites have stealth +#define COMSIG_NANITE_IS_STEALTHY "nanite_is_stealthy" +///() deletes the nanite component +#define COMSIG_NANITE_DELETE "nanite_delete" +///(list/nanite_programs) - makes the input list a copy the nanites' program list +#define COMSIG_NANITE_GET_PROGRAMS "nanite_get_programs" +///(amount) Returns nanite amount +#define COMSIG_NANITE_GET_VOLUME "nanite_get_volume" +///(amount) Sets current nanite volume to the given amount +#define COMSIG_NANITE_SET_VOLUME "nanite_set_volume" +///(amount) Adjusts nanite volume by the given amount +#define COMSIG_NANITE_ADJUST_VOLUME "nanite_adjust" +///(amount) Sets maximum nanite volume to the given amount +#define COMSIG_NANITE_SET_MAX_VOLUME "nanite_set_max_volume" +///(amount(0-100)) Sets cloud ID to the given amount +#define COMSIG_NANITE_SET_CLOUD "nanite_set_cloud" +///(method) Modify cloud sync status. Method can be toggle, enable or disable +#define COMSIG_NANITE_SET_CLOUD_SYNC "nanite_set_cloud_sync" +///(amount) Sets safety threshold to the given amount +#define COMSIG_NANITE_SET_SAFETY "nanite_set_safety" +///(amount) Sets regeneration rate to the given amount +#define COMSIG_NANITE_SET_REGEN "nanite_set_regen" +///(code(1-9999)) Called when sending a nanite signal to a mob. +#define COMSIG_NANITE_SIGNAL "nanite_signal" +///(comm_code(1-9999), comm_message) Called when sending a nanite comm signal to a mob. +#define COMSIG_NANITE_COMM_SIGNAL "nanite_comm_signal" +///(mob/user, full_scan) - sends to chat a scan of the nanites to the user, returns TRUE if nanites are detected +#define COMSIG_NANITE_SCAN "nanite_scan" +///(list/data, scan_level) - adds nanite data to the given data list - made for ui_data procs +#define COMSIG_NANITE_UI_DATA "nanite_ui_data" +///(datum/nanite_program/new_program, datum/nanite_program/source_program) Called when adding a program to a nanite component +#define COMSIG_NANITE_ADD_PROGRAM "nanite_add_program" + ///Installation successful + #define COMPONENT_PROGRAM_INSTALLED (1<<0) + ///Installation failed, but there are still nanites + #define COMPONENT_PROGRAM_NOT_INSTALLED (1<<1) +///(datum/component/nanites, full_overwrite, copy_activation) Called to sync the target's nanites to a given nanite component +#define COMSIG_NANITE_SYNC "nanite_sync" diff --git a/code/__DEFINES/~~bubber_defines/traits/sources.dm b/code/__DEFINES/~~bubber_defines/traits/sources.dm index fb073441fc2..6cc1f854a18 100644 --- a/code/__DEFINES/~~bubber_defines/traits/sources.dm +++ b/code/__DEFINES/~~bubber_defines/traits/sources.dm @@ -1,2 +1,4 @@ // Loadout trait #define TRAIT_SOURCE_LOADOUT "loadout" + +#define NANITES_TRAIT "nanites" diff --git a/code/__DEFINES/~~bubber_defines/vomit.dm b/code/__DEFINES/~~bubber_defines/vomit.dm new file mode 100644 index 00000000000..025baa04897 --- /dev/null +++ b/code/__DEFINES/~~bubber_defines/vomit.dm @@ -0,0 +1,2 @@ + +#define VOMIT_CATEGORY_NANITE MOB_VOMIT_MESSAGE diff --git a/code/_globalvars/lists/mobs.dm b/code/_globalvars/lists/mobs.dm index 73c75fec3ba..d88d08302ee 100644 --- a/code/_globalvars/lists/mobs.dm +++ b/code/_globalvars/lists/mobs.dm @@ -60,6 +60,7 @@ GLOBAL_LIST_EMPTY(spidermobs) //all sentient spider mobs GLOBAL_LIST_EMPTY(bots_list) GLOBAL_LIST_EMPTY(camera_eyes) GLOBAL_LIST_EMPTY(suit_sensors_list) //all people with suit sensors on +GLOBAL_LIST_EMPTY(nanite_sensors_list) //app people with nanite monitoring program // BUBBER EDIT ADDITION /// All alive mobs with clients. GLOBAL_LIST_EMPTY(alive_player_list) diff --git a/code/controllers/subsystem/research.dm b/code/controllers/subsystem/research.dm index 937c2e3b1d4..2beec1f1641 100644 --- a/code/controllers/subsystem/research.dm +++ b/code/controllers/subsystem/research.dm @@ -40,7 +40,7 @@ SUBSYSTEM_DEF(research) ) var/list/errored_datums = list() ///Associated list of all point types that techwebs will have and their respective 'abbreviated' name. - var/list/point_types = list(TECHWEB_POINT_TYPE_GENERIC = "Gen. Res.") + var/list/point_types = list(TECHWEB_POINT_TYPE_GENERIC = "Gen. Res.", TECHWEB_POINT_TYPE_NANITE = "Nanite Res.") // BUBBER EDIT - NANITES //---------------------------------------------- var/list/single_server_income = list( TECHWEB_POINT_TYPE_GENERIC = TECHWEB_SINGLE_SERVER_INCOME, diff --git a/code/game/data_huds.dm b/code/game/data_huds.dm index 55bafe9d152..34e47b5d654 100644 --- a/code/game/data_huds.dm +++ b/code/game/data_huds.dm @@ -18,7 +18,7 @@ /datum/atom_hud/data /datum/atom_hud/data/human/medical - hud_icons = list(STATUS_HUD, HEALTH_HUD, DNR_HUD) // SKYRAT EDIT ADDITION - DNR_HUD + hud_icons = list(STATUS_HUD, HEALTH_HUD, DNR_HUD, NANITE_HUD) // BUBBER EDIT ADDITION - DNR-HUD / NANITE_HUD /datum/atom_hud/data/human/medical/basic @@ -51,13 +51,13 @@ hud_icons = list(ID_HUD) /datum/atom_hud/data/human/security/advanced - hud_icons = list(ID_HUD, IMPSEC_FIRST_HUD, IMPLOYAL_HUD, IMPSEC_SECOND_HUD, WANTED_HUD, PERMIT_HUD, DNR_HUD) //SKYRAT EDIT ADDITION - PERMIT_HUD, DNR_HUD + hud_icons = list(ID_HUD, IMPSEC_FIRST_HUD, IMPLOYAL_HUD, IMPSEC_SECOND_HUD, WANTED_HUD, PERMIT_HUD, DNR_HUD, NANITE_HUD) // BUBBER EDIT ADDITION - DNR-HUD / NANITE_HUD /datum/atom_hud/data/human/fan_hud hud_icons = list(FAN_HUD) /datum/atom_hud/data/diagnostic - hud_icons = list(DIAG_HUD, DIAG_STAT_HUD, DIAG_BATT_HUD, DIAG_MECH_HUD, DIAG_BOT_HUD, DIAG_TRACK_HUD, DIAG_CAMERA_HUD, DIAG_AIRLOCK_HUD, DIAG_LAUNCHPAD_HUD) + hud_icons = list(DIAG_HUD, DIAG_STAT_HUD, DIAG_BATT_HUD, DIAG_MECH_HUD, DIAG_BOT_HUD, DIAG_TRACK_HUD, DIAG_CAMERA_HUD, DIAG_AIRLOCK_HUD, DIAG_LAUNCHPAD_HUD, DIAG_NANITE_FULL_HUD) // BUBBER EDIT ADDITION - NANITE_HUD /datum/atom_hud/data/bot_path hud_icons = list(DIAG_PATH_HUD) diff --git a/code/game/machinery/computer/crew.dm b/code/game/machinery/computer/crew.dm index c568c5a052a..46f6b407967 100644 --- a/code/game/machinery/computer/crew.dm +++ b/code/game/machinery/computer/crew.dm @@ -208,9 +208,9 @@ GLOBAL_DATUM_INIT(crewmonitor, /datum/crewmonitor, new) return data_by_z["[z]"] var/list/results = list() - for(var/tracked_mob in GLOB.suit_sensors_list) + for(var/tracked_mob in GLOB.suit_sensors_list | GLOB.nanite_sensors_list) // BUBBER CHANGE - NANITES if(!tracked_mob) - stack_trace("Null entry in suit sensors list.") + stack_trace("Null entry in suit sensors or nanite sensors list.") // BUBBER CHANGE - NANITES continue var/mob/living/tracked_living_mob = tracked_mob @@ -227,25 +227,33 @@ GLOBAL_DATUM_INIT(crewmonitor, /datum/crewmonitor, new) if(pos.z != z && (!is_station_level(pos.z) || !is_station_level(z)) && !HAS_TRAIT(tracked_living_mob, TRAIT_MULTIZ_SUIT_SENSORS)) continue + var/sensor_mode // BUBBER ADDITION - NANITES var/mob/living/carbon/human/tracked_human = tracked_living_mob - // Check their humanity. + // Set sensor level based on whether we're in the nanites list or the suit sensor list. + if(tracked_living_mob in GLOB.nanite_sensors_list) // BUBBER CHANGE - NANITES + sensor_mode = SENSOR_COORDS + if(!ishuman(tracked_human)) stack_trace("Non-human mob is in suit_sensors_list: [tracked_living_mob] ([tracked_living_mob.type])") continue + // BUBBER CHANGE START - NANITES // Check they have a uniform var/obj/item/clothing/under/uniform = tracked_human.w_uniform - if (!istype(uniform)) + if (!sensor_mode && istype(uniform)) + // Check if their uniform is in a compatible mode. + if((uniform.has_sensor == NO_SENSORS) || !uniform.sensor_mode) + stack_trace("Human without active suit sensors is in suit_sensors_list: [tracked_human] ([tracked_human.type]) ([uniform.type])") + continue + + sensor_mode = uniform.sensor_mode + // BUBBER CHANGE END - NANITES + else stack_trace("Human without a suit sensors compatible uniform is in suit_sensors_list: [tracked_human] ([tracked_human.type]) ([uniform?.type])") continue - // Check if their uniform is in a compatible mode. - if((uniform.has_sensor == NO_SENSORS) || !uniform.sensor_mode) - stack_trace("Human without active suit sensors is in suit_sensors_list: [tracked_human] ([tracked_human.type]) ([uniform.type])") - continue - var/sensor_mode = uniform.sensor_mode // The entry for this human var/list/entry = list( @@ -262,7 +270,7 @@ GLOBAL_DATUM_INIT(crewmonitor, /datum/crewmonitor, new) var/trim_assignment = id_card.get_trim_assignment() if (jobs[trim_assignment] != null) entry["ijob"] = jobs[trim_assignment] - + // SKYRAT EDIT BEGIN: Checking for robotic race if (issynthetic(tracked_human)) entry["is_robot"] = TRUE @@ -285,10 +293,9 @@ GLOBAL_DATUM_INIT(crewmonitor, /datum/crewmonitor, new) // If sensors are above living tracking, set DNR state if (sensor_mode >= SENSOR_LIVING) entry["is_dnr"] = tracked_human.get_dnr() - // BUBBERSTATION EDIT END // Broken sensors show garbage data - if (uniform.has_sensor == BROKEN_SENSORS) + if (uniform?.has_sensor == BROKEN_SENSORS) entry["life_status"] = rand(0,1) entry["area"] = pick_list (ION_FILE, "ionarea") entry["oxydam"] = rand(0,175) diff --git a/code/game/objects/items/devices/scanners/health_analyzer.dm b/code/game/objects/items/devices/scanners/health_analyzer.dm index 69a58223899..a3eea0e3523 100644 --- a/code/game/objects/items/devices/scanners/health_analyzer.dm +++ b/code/game/objects/items/devices/scanners/health_analyzer.dm @@ -436,6 +436,7 @@ . = jointext(render_list, "") if(tochat) to_chat(user, custom_boxed_message("blue_box", .), trailing_newline = FALSE, type = MESSAGE_TYPE_INFO) + SEND_SIGNAL(target, COMSIG_NANITE_SCAN, user, FALSE) // BUBBER EDIT ADDITION - NANITES return . /obj/item/healthanalyzer/click_ctrl_shift(mob/user) diff --git a/code/game/objects/items/pinpointer.dm b/code/game/objects/items/pinpointer.dm index 878fd0ad3bb..3b877393031 100644 --- a/code/game/objects/items/pinpointer.dm +++ b/code/game/objects/items/pinpointer.dm @@ -105,6 +105,8 @@ var/turf/here = get_turf(src) var/turf/there = get_turf(H) if(here && there && (there.z == here.z || (is_station_level(here.z) && is_station_level(there.z)))) // Device and target should be on the same level or different levels of the same station + if (H in GLOB.nanite_sensors_list) // BUBBER ADDITION START - NANITES + return TRUE if (istype(H.w_uniform, /obj/item/clothing/under)) var/obj/item/clothing/under/U = H.w_uniform if(U.has_sensor && (U.sensor_mode >= SENSOR_COORDS || ignore_suit_sensor_level)) // Suit sensors must be on maximum or a contractor pinpointer diff --git a/code/modules/admin/admin_investigate.dm b/code/modules/admin/admin_investigate.dm index e3840198665..75c5176c844 100644 --- a/code/modules/admin/admin_investigate.dm +++ b/code/modules/admin/admin_investigate.dm @@ -31,6 +31,7 @@ ADMIN_VERB(investigate_show, R_NONE, "Investigate", "Browse various detailed log INVESTIGATE_RECORDS, INVESTIGATE_RESEARCH, INVESTIGATE_WIRES, + INVESTIGATE_NANITES, // BUBBER ADDITION - NANITES ) var/list/logs_present = list("notes, memos, watchlist") diff --git a/code/modules/mob/living/carbon/alien/alien.dm b/code/modules/mob/living/carbon/alien/alien.dm index 821c8167368..c0234006bb1 100644 --- a/code/modules/mob/living/carbon/alien/alien.dm +++ b/code/modules/mob/living/carbon/alien/alien.dm @@ -177,6 +177,13 @@ Des: Removes all infected images from the alien. if(istype(melting_pot) && istype(frying_pan)) for (var/atom/movable/poor_sod as anything in melting_pot.stomach_contents) frying_pan.consume_thing(poor_sod) + // BUBBER ADDITION START - NANITES + var/datum/component/nanites/nanites = GetComponent(/datum/component/nanites) + if(nanites) + new_xeno.AddComponent(/datum/component/nanites, nanites.nanite_volume) + SEND_SIGNAL(new_xeno, COMSIG_NANITE_SYNC, nanites) + // BUBBER ADDITION END - NANITES + qdel(src) /// Changes the name of the xeno we are evolving into in order to keep the same numerical identifier the old xeno had. diff --git a/code/modules/mob/living/carbon/carbon_defines.dm b/code/modules/mob/living/carbon/carbon_defines.dm index ca6fea58393..30b37dea0ab 100644 --- a/code/modules/mob/living/carbon/carbon_defines.dm +++ b/code/modules/mob/living/carbon/carbon_defines.dm @@ -2,7 +2,7 @@ blood_volume = BLOOD_VOLUME_NORMAL gender = MALE pressure_resistance = 15 - hud_possible = list(HEALTH_HUD,STATUS_HUD,ANTAG_HUD,GLAND_HUD, DNR_HUD) // SKYRAT EDIT ADDITION - DNR ICON + hud_possible = list(HEALTH_HUD,STATUS_HUD,ANTAG_HUD,GLAND_HUD,DNR_HUD,NANITE_HUD,DIAG_NANITE_FULL_HUD) // BUBBER EDIT ADDITION - DNR_HUD / NANITE_HUD has_limbs = TRUE held_items = list(null, null) num_legs = 0 //Populated on init through list/bodyparts diff --git a/code/modules/mob/living/carbon/human/human_defines.dm b/code/modules/mob/living/carbon/human/human_defines.dm index 3346a07513c..023ecb8801f 100644 --- a/code/modules/mob/living/carbon/human/human_defines.dm +++ b/code/modules/mob/living/carbon/human/human_defines.dm @@ -5,7 +5,7 @@ icon = 'icons/mob/human/human.dmi' icon_state = "human_basic" appearance_flags = KEEP_TOGETHER|TILE_BOUND|PIXEL_SCALE|LONG_GLIDE - hud_possible = list(HEALTH_HUD,STATUS_HUD,ID_HUD,WANTED_HUD,IMPLOYAL_HUD,IMPSEC_FIRST_HUD,IMPSEC_SECOND_HUD,ANTAG_HUD,GLAND_HUD,FAN_HUD,PERMIT_HUD, DNR_HUD) //SKYRAT EDIT ADDITION - PERMIT_HUD, DNR_HUD + hud_possible = list(HEALTH_HUD,STATUS_HUD,ID_HUD,WANTED_HUD,IMPLOYAL_HUD,IMPSEC_FIRST_HUD,IMPSEC_SECOND_HUD,ANTAG_HUD,GLAND_HUD,FAN_HUD,PERMIT_HUD,DNR_HUD,NANITE_HUD,DIAG_NANITE_FULL_HUD) //BUBBER EDIT ADDITION - PERMIT_HUD, DNR_HUD hud_type = /datum/hud/human pressure_resistance = 25 buckle_lying = 0 diff --git a/code/modules/mob/living/carbon/human/species_types/jellypeople.dm b/code/modules/mob/living/carbon/human/species_types/jellypeople.dm index 23514ef63ff..6ce75f45737 100644 --- a/code/modules/mob/living/carbon/human/species_types/jellypeople.dm +++ b/code/modules/mob/living/carbon/human/species_types/jellypeople.dm @@ -320,6 +320,14 @@ spare.domutcheck() spare.Move(get_step(H.loc, pick(NORTH,SOUTH,EAST,WEST))) + // BUBBER ADDITION START - NANITES + var/datum/component/nanites/owner_nanites = H.GetComponent(/datum/component/nanites) + if(owner_nanites) + //copying over nanite programs/cloud sync with 50% saturation in host and spare + owner_nanites.nanite_volume *= 0.5 + spare.AddComponent(/datum/component/nanites, owner_nanites.nanite_volume) + SEND_SIGNAL(spare, COMSIG_NANITE_SYNC, owner_nanites, TRUE, TRUE) //The trues are to copy activation as well + // BUBBER ADDITION END - NANITES H.blood_volume *= 0.45 REMOVE_TRAIT(H, TRAIT_NO_TRANSFORM, REF(src)) diff --git a/code/modules/mob/living/damage_procs.dm b/code/modules/mob/living/damage_procs.dm index 7f87af20d32..302dacb8bb0 100644 --- a/code/modules/mob/living/damage_procs.dm +++ b/code/modules/mob/living/damage_procs.dm @@ -39,6 +39,9 @@ if(!forced) damage_amount *= ((100 - blocked) / 100) damage_amount *= get_incoming_damage_modifier(damage_amount, damagetype, def_zone, sharpness, attack_direction, attacking_item) + + SEND_SIGNAL(src, COMSIG_MOB_ALWAYS_APPLY_DAMAGE, damage_amount, damagetype, def_zone, blocked, wound_bonus, bare_wound_bonus, sharpness, attack_direction, attacking_item, wound_clothing) // BUBBER ADDITION: FLAT DAMAGE MODIFIERS + if(damage_amount <= 0) return 0 diff --git a/code/modules/mob/living/living_defines.dm b/code/modules/mob/living/living_defines.dm index 811c86c082a..25cf6e26dbe 100644 --- a/code/modules/mob/living/living_defines.dm +++ b/code/modules/mob/living/living_defines.dm @@ -1,6 +1,6 @@ /mob/living see_invisible = SEE_INVISIBLE_LIVING - hud_possible = list(HEALTH_HUD,STATUS_HUD,ANTAG_HUD, DNR_HUD) // SKYRAT EDIT ADDITION - DNR_HUD + hud_possible = list(HEALTH_HUD,STATUS_HUD,ANTAG_HUD,DNR_HUD,NANITE_HUD,DIAG_NANITE_FULL_HUD) // BUBBER EDIT ADDITION - DNR_HUD / NANITE_HUD pressure_resistance = 10 hud_type = /datum/hud/living interaction_flags_click = ALLOW_RESTING diff --git a/code/modules/modular_computers/file_system/programs/radar.dm b/code/modules/modular_computers/file_system/programs/radar.dm index 33dcd95e86b..77b376ba900 100644 --- a/code/modules/modular_computers/file_system/programs/radar.dm +++ b/code/modules/modular_computers/file_system/programs/radar.dm @@ -269,6 +269,8 @@ return . if(!istype(humanoid)) return RADAR_NOT_TRACKABLE + if (humanoid in GLOB.nanite_sensors_list) // BUBBER ADDITION - NANITES + return TRUE if(!istype(humanoid.w_uniform, /obj/item/clothing/under)) return RADAR_NOT_TRACKABLE var/obj/item/clothing/under/uniform = humanoid.w_uniform diff --git a/icons/obj/machines/computer.dmi b/icons/obj/machines/computer.dmi index 34f632044c1..7dad1157cbb 100644 Binary files a/icons/obj/machines/computer.dmi and b/icons/obj/machines/computer.dmi differ diff --git a/modular_zubbers/code/__DEFINES/traits.dm b/modular_zubbers/code/__DEFINES/traits.dm index 0de6ca840a2..44d23be3f85 100644 --- a/modular_zubbers/code/__DEFINES/traits.dm +++ b/modular_zubbers/code/__DEFINES/traits.dm @@ -1,3 +1,6 @@ #define TRAIT_MONOPHOBIA "monophobia" #define TRAIT_COFFIN_ENLARGED "coffin_enlarged" + +/// The mob's nanites are sending a monitoring signal visible on diag HUD +#define TRAIT_NANITE_MONITORING "nanite_monitoring" diff --git a/modular_zubbers/code/controllers/subsystem/processing/nanites.dm b/modular_zubbers/code/controllers/subsystem/processing/nanites.dm new file mode 100644 index 00000000000..b8109a904af --- /dev/null +++ b/modular_zubbers/code/controllers/subsystem/processing/nanites.dm @@ -0,0 +1,22 @@ +PROCESSING_SUBSYSTEM_DEF(nanites) + name = "Nanites" + flags = SS_BACKGROUND|SS_POST_FIRE_TIMING|SS_NO_INIT + wait = 1 SECONDS + + var/list/datum/nanite_cloud_backup/cloud_backups = list() + var/list/mob/living/nanite_monitored_mobs = list() + var/list/datum/nanite_program/relay/nanite_relays = list() + var/neural_network_count = 0 + +/datum/controller/subsystem/processing/nanites/proc/check_hardware(datum/nanite_cloud_backup/backup) + if(QDELETED(backup.storage) || (backup.storage.machine_stat & (NOPOWER|BROKEN))) + return FALSE + return TRUE + +/datum/controller/subsystem/processing/nanites/proc/get_cloud_backup(cloud_id, force = FALSE) + for(var/I in cloud_backups) + var/datum/nanite_cloud_backup/backup = I + if(!force && !check_hardware(backup)) + return + if(backup.cloud_id == cloud_id) + return backup diff --git a/modular_zubbers/code/datums/components/nanites.dm b/modular_zubbers/code/datums/components/nanites.dm new file mode 100644 index 00000000000..98758530dad --- /dev/null +++ b/modular_zubbers/code/datums/components/nanites.dm @@ -0,0 +1,531 @@ +#define HARMONIC_REGEN_BOOST 0.1 + +/datum/component/nanites + dupe_mode = COMPONENT_DUPE_UNIQUE_PASSARGS + + var/mob/living/host_mob + var/nanite_volume = 100 //amount of nanites in the system, used as fuel for nanite programs + var/max_nanites = 500 //maximum amount of nanites in the system + var/regen_rate = 0.5 //nanites generated per second + 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 + + 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 = FALSE //if TRUE, displays program list when scanned by nanite scanners + var/harmonic_regenerating = FALSE //if TRUE, the host is regenerating nanites at a boosted rate + var/datum/techweb/techweb + // 0 - 100 + var/research_progress = 0 + +/datum/component/nanites/Initialize(amount = 100, cloud = 0) + if(!isliving(parent) && !istype(parent, /datum/nanite_cloud_backup)) + return COMPONENT_INCOMPATIBLE + + nanite_volume = amount + cloud_id = cloud + + //Nanites without hosts are non-interactive through normal means + if(isliving(parent)) + host_mob = parent + + if(!CAN_HAVE_NANITES(host_mob)) //Shouldn't happen, but this avoids HUD runtimes in case a silicon gets them somehow. + return COMPONENT_INCOMPATIBLE + + start_time = world.time + + host_mob.hud_set_nanite_indicator() + START_PROCESSING(SSnanites, src) + + if(cloud_id && cloud_active) + cloud_sync() + +/datum/component/nanites/RegisterWithParent() + RegisterSignal(parent, COMSIG_HAS_NANITES, .proc/confirm_nanites) + RegisterSignal(parent, COMSIG_NANITE_IS_STEALTHY, .proc/check_stealth) + RegisterSignal(parent, COMSIG_NANITE_DELETE, .proc/delete_nanites) + RegisterSignal(parent, COMSIG_NANITE_UI_DATA, .proc/nanite_ui_data) + RegisterSignal(parent, COMSIG_NANITE_GET_PROGRAMS, .proc/get_programs) + RegisterSignal(parent, COMSIG_NANITE_SET_VOLUME, .proc/set_volume) + RegisterSignal(parent, COMSIG_NANITE_ADJUST_VOLUME, .proc/adjust_nanites) + RegisterSignal(parent, COMSIG_NANITE_SET_MAX_VOLUME, .proc/set_max_volume) + RegisterSignal(parent, COMSIG_NANITE_SET_CLOUD, .proc/set_cloud) + RegisterSignal(parent, COMSIG_NANITE_SET_CLOUD_SYNC, .proc/set_cloud_sync) + RegisterSignal(parent, COMSIG_NANITE_SET_SAFETY, .proc/set_safety) + RegisterSignal(parent, COMSIG_NANITE_SET_REGEN, .proc/set_regen) + RegisterSignal(parent, COMSIG_NANITE_ADD_PROGRAM, .proc/add_program) + RegisterSignal(parent, COMSIG_NANITE_SCAN, .proc/nanite_scan) + RegisterSignal(parent, COMSIG_NANITE_SYNC, .proc/sync) + + if(isliving(parent)) + RegisterSignal(parent, COMSIG_ATOM_EMP_ACT, .proc/on_emp) + RegisterSignal(parent, COMSIG_LIVING_DEATH, .proc/on_death) + RegisterSignal(parent, COMSIG_MOB_TRIED_ACCESS, .proc/check_access) + RegisterSignal(parent, COMSIG_LIVING_ELECTROCUTE_ACT, .proc/on_shock) + RegisterSignal(parent, COMSIG_LIVING_MINOR_SHOCK, .proc/on_minor_shock) + RegisterSignal(parent, COMSIG_SPECIES_GAIN, .proc/check_viable_biotype) + RegisterSignal(parent, COMSIG_NANITE_SIGNAL, .proc/receive_signal) + RegisterSignal(parent, COMSIG_NANITE_COMM_SIGNAL, .proc/receive_comm_signal) + +/datum/component/nanites/UnregisterFromParent() + UnregisterSignal( + parent, + list( + COMSIG_HAS_NANITES, + COMSIG_NANITE_IS_STEALTHY, + COMSIG_NANITE_DELETE, + COMSIG_NANITE_UI_DATA, + COMSIG_NANITE_GET_PROGRAMS, + COMSIG_NANITE_SET_VOLUME, + COMSIG_NANITE_ADJUST_VOLUME, + COMSIG_NANITE_SET_MAX_VOLUME, + COMSIG_NANITE_SET_CLOUD, + COMSIG_NANITE_SET_CLOUD_SYNC, + COMSIG_NANITE_SET_SAFETY, + COMSIG_NANITE_SET_REGEN, + COMSIG_NANITE_ADD_PROGRAM, + COMSIG_NANITE_SCAN, + COMSIG_NANITE_SYNC, + COMSIG_ATOM_EMP_ACT, + COMSIG_LIVING_DEATH, + COMSIG_MOB_TRIED_ACCESS, + COMSIG_LIVING_ELECTROCUTE_ACT, + COMSIG_LIVING_MINOR_SHOCK, + COMSIG_MOVABLE_HEAR, + COMSIG_SPECIES_GAIN, + COMSIG_NANITE_SIGNAL, + COMSIG_NANITE_COMM_SIGNAL, + ) + ) + disconnect_techweb() + +/datum/component/nanites/Destroy() + STOP_PROCESSING(SSnanites, src) + QDEL_LIST(programs) + if(host_mob) + set_nanite_bar(TRUE) + host_mob.hud_set_nanite_indicator() + host_mob = null + return ..() + +/datum/component/nanites/InheritComponent(datum/component/nanites/new_nanites, i_am_original, amount, cloud) + if(new_nanites) + adjust_nanites(null, new_nanites.nanite_volume) + else + adjust_nanites(null, amount) //just add to the nanite volume + +/datum/component/nanites/process(delta_time) + if(!host_mob.has_status_effect(/datum/status_effect/grouped/stasis)) + adjust_nanites(null, get_regen_rate() * 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) + cloud_sync() + next_sync = world.time + 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) +/datum/component/nanites/proc/sync(datum/signal_source, datum/component/nanites/source, full_overwrite = 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) + break + if(full_overwrite) + for(var/X in programs_to_remove) + qdel(X) + for(var/X in programs_to_add) + 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) + if(backup) + connect_techweb(backup.techweb) + var/datum/component/nanites/cloud_copy = backup.nanites + if(cloud_copy) + sync(null, cloud_copy) + return + //Without cloud syncing nanites can accumulate errors and/or defects + if(prob(8) && programs.len) + var/datum/nanite_program/NP = pick(programs) + NP.software_error() +// todo test this +/datum/component/nanites/proc/add_design(datum/techweb/web, datum/design/design, custom = FALSE) + SIGNAL_HANDLER + if(design.id == "nanite_harmonic") + harmonic_regenerating = TRUE + +/datum/component/nanites/proc/remove_design(datum/techweb/web, datum/design/design, custom = FALSE) + SIGNAL_HANDLER + if(design.id == "nanite_harmonic") + harmonic_regenerating = FALSE + +/datum/component/nanites/proc/connect_techweb(datum/techweb/new_web) + if(!new_web || new_web == techweb) + return + if(techweb) + disconnect_techweb() + techweb = new_web + var/list/all_nodes = techweb.get_researched_nodes() + harmonic_regenerating = !!all_nodes["nanite_harmonic"] + RegisterSignal(techweb, COMSIG_TECHWEB_ADD_DESIGN, .proc/add_design) + RegisterSignal(techweb, COMSIG_TECHWEB_REMOVE_DESIGN, .proc/remove_design) + +/datum/component/nanites/proc/disconnect_techweb() + if(!techweb) + return + UnregisterSignal(techweb, COMSIG_TECHWEB_ADD_DESIGN) + UnregisterSignal(techweb, COMSIG_TECHWEB_REMOVE_DESIGN) + techweb = null + +///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) + if(programs.len >= max_programs) + return COMPONENT_PROGRAM_NOT_INSTALLED + if(source_program) + source_program.copy_programming(new_program) + programs += new_program + new_program.on_add(src) + return COMPONENT_PROGRAM_INSTALLED + +/datum/component/nanites/proc/consume_nanites(amount, force = FALSE) + if(!force && safety_threshold && (nanite_volume - amount < safety_threshold)) + return FALSE + 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) + SIGNAL_HANDLER + + 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_warning("A grainy grey slurry starts oozing out of [host_mob]."), span_warning("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(span_warning("[host_mob] vomits a grainy grey slurry!"), span_warning("You suddenly vomit a metallic-tasting grainy grey slurry!"), null); + C.vomit(VOMIT_CATEGORY_NANITE, /obj/effect/decal/cleanable/vomit/nanites, FLOOR(excess / 100, 1)) + else + host_mob.visible_message(span_warning("A metallic grey slurry bursts out of [host_mob]'s skin!"), span_userdanger("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, /obj/effect/decal/cleanable/vomit/nanites, VOMIT_CATEGORY_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(span_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_userdanger("A torrent of metallic grey slurry violently bursts out of your eyes, ears, and mouth, and floods out of your skin!")); + host_mob.adjust_eye_blur_up_to(15, 1 MINUTES) + host_mob.Paralyze(120) + if(iscarbon(host_mob)) + var/mob/living/carbon/carbon = host_mob + var/obj/item/organ/ears/ears = carbon.get_organ_slot(ORGAN_SLOT_EARS) + if(ears) + ears.adjustEarDamage(0, 30) //nanites coming out of your ears + carbon.vomit(VOMIT_CATEGORY_NANITE, /obj/effect/decal/cleanable/vomit/nanites) //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_CATEGORY_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, /obj/effect/decal/cleanable/vomit/nanites, VOMIT_CATEGORY_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) + holder.pixel_y = I.Height() - world.icon_size + holder.icon_state = null + if(remove || stealth) + return //bye icon + var/nanite_percent = (nanite_volume / max_nanites) * 100 + nanite_percent = clamp(CEILING(nanite_percent, 10), 10, 100) + holder.icon_state = "nanites[nanite_percent]" + +/datum/component/nanites/proc/on_emp(datum/source, severity) + 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 + if(prob(40/severity)) + 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) + SIGNAL_HANDLER + + if(flags & SHOCK_ILLUSION || 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 + 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) + 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() + +/datum/component/nanites/proc/check_stealth(datum/source) + SIGNAL_HANDLER + + return stealth + +/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) + +/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) + +/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) + +/datum/component/nanites/proc/check_viable_biotype() + SIGNAL_HANDLER + + if(!CAN_HAVE_NANITES(host_mob)) + qdel(src) //bodytype no longer sustains nanites + +/datum/component/nanites/proc/check_access(datum/source, atom/locked_thing) + SIGNAL_HANDLER + var/obj/locked_object = locked_thing + if(!istype(locked_thing)) + return FALSE + for(var/datum/nanite_program/access/access_program in programs) + if(access_program.activated) + return locked_object.check_access_list(access_program.access) + else + return FALSE + return FALSE + +/datum/component/nanites/proc/set_volume(datum/source, amount) + SIGNAL_HANDLER + + nanite_volume = clamp(amount, 0, max_nanites) + +/datum/component/nanites/proc/set_max_volume(datum/source, amount) + SIGNAL_HANDLER + + max_nanites = max(1, amount) + +/datum/component/nanites/proc/set_cloud(datum/source, amount) + SIGNAL_HANDLER + + cloud_id = clamp(amount, 0, 100) + +/datum/component/nanites/proc/set_cloud_sync(datum/source, method) + SIGNAL_HANDLER + + switch(method) + if(NANITE_CLOUD_TOGGLE) + cloud_active = !cloud_active + if(NANITE_CLOUD_DISABLE) + cloud_active = FALSE + if(NANITE_CLOUD_ENABLE) + cloud_active = TRUE + +/datum/component/nanites/proc/set_safety(datum/source, amount) + SIGNAL_HANDLER + + safety_threshold = clamp(amount, 0, max_nanites) + +/datum/component/nanites/proc/set_regen(datum/source, amount) + SIGNAL_HANDLER + + regen_rate = amount + +/datum/component/nanites/proc/confirm_nanites() + SIGNAL_HANDLER + + return TRUE //yup i exist + +/datum/component/nanites/proc/get_programs(datum/source, list/nanite_programs) + SIGNAL_HANDLER + + nanite_programs |= programs + +/datum/component/nanites/proc/add_research() + var/progress_addition = NANITE_BASE_RESEARCH_PROGRESS + if(!techweb) + return + if(!ishuman(host_mob)) + if(!iscarbon(host_mob)) + progress_addition *= 0.4 + else + progress_addition *= 0.8 + if(!host_mob.client) + progress_addition *= 0.5 + if(host_mob.stat == DEAD) + progress_addition *= 0.75 + research_progress += progress_addition + if(research_progress >= 100) + research_progress -= 100 + techweb.add_point_list( + list( + TECHWEB_POINT_TYPE_GENERIC = TECHWEB_SINGLE_SERVER_INCOME, + TECHWEB_POINT_TYPE_NANITE = TECHWEB_SINGLE_SERVER_INCOME + ) + ) + +/datum/component/nanites/proc/nanite_scan(datum/source, mob/user, full_scan) + SIGNAL_HANDLER + + if(!full_scan) + if(!stealth) + to_chat(user, span_notice("Nanites Detected")) + to_chat(user, span_notice("Saturation: [nanite_volume]/[max_nanites]")) + return TRUE + else + to_chat(user, span_info("NANITES DETECTED")) + to_chat(user, span_info("================")) + to_chat(user, span_info("Saturation: [nanite_volume]/[max_nanites]")) + to_chat(user, span_info("Safety Threshold: [safety_threshold]")) + to_chat(user, span_info("Cloud ID: [cloud_id ? cloud_id : "None"]")) + to_chat(user, span_info("Cloud Sync: [cloud_active ? "Active" : "Disabled"]")) + to_chat(user, span_info("================")) + to_chat(user, span_info("Program List:")) + if(!diagnostics) + to_chat(user, span_alert("Nanite debugging disabled.")) + else + for(var/X in programs) + var/datum/nanite_program/NP = X + to_chat(user, span_info("[NP.name] | [NP.activated ? "Active" : "Inactive"]")) + return TRUE + +/datum/component/nanites/proc/get_regen_rate() + return regen_rate + (harmonic_regenerating ? HARMONIC_REGEN_BOOST : 0) + +/datum/component/nanites/proc/nanite_ui_data(datum/source, list/data, scan_level) + SIGNAL_HANDLER + + data["has_nanites"] = TRUE + data["nanite_volume"] = nanite_volume + data["regen_rate"] = get_regen_rate() + 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 + var/list/mob_program = list() + mob_program["name"] = P.name + mob_program["desc"] = P.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 + + 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["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 + var/list/rules = list() + var/rule_id = 1 + for(var/Z in P.rules) + var/datum/nanite_rule/nanite_rule = Z + var/list/rule = list() + rule["display"] = nanite_rule.display() + rule["program_id"] = id + rule["id"] = rule_id + rules += list(rule) + rule_id++ + mob_program["rules"] = rules + if(LAZYLEN(rules)) + mob_program["has_rules"] = TRUE + id++ + mob_programs += list(mob_program) + data["mob_programs"] = mob_programs + +#undef HARMONIC_REGEN_BOOST diff --git a/modular_zubbers/code/datums/mood_events/nanite_events.dm b/modular_zubbers/code/datums/mood_events/nanite_events.dm new file mode 100644 index 00000000000..ae21bd84f4c --- /dev/null +++ b/modular_zubbers/code/datums/mood_events/nanite_events.dm @@ -0,0 +1,13 @@ +/datum/mood_event/nanite_happiness + description = "+++++++HAPPINESS ENHANCEMENT+++++++\n" + mood_change = 7 + +/datum/mood_event/nanite_happiness/add_effects(message) + description = "+++++++[message]+++++++\n" + +/datum/mood_event/nanite_sadness + description = "+++++++HAPPINESS SUPPRESSION+++++++\n" + mood_change = -7 + +/datum/mood_event/nanite_sadness/add_effects(message) + description = "+++++++[message]+++++++\n" diff --git a/modular_zubbers/code/game/area/nanite.dm b/modular_zubbers/code/game/area/nanite.dm new file mode 100644 index 00000000000..a5ad3943be2 --- /dev/null +++ b/modular_zubbers/code/game/area/nanite.dm @@ -0,0 +1,3 @@ +/area/science/nanite + name = "Nanite Lab" + icon_state = "nanite" diff --git a/modular_zubbers/code/modules/mob/living/carbon/human/physiology.dm b/modular_zubbers/code/modules/mob/living/carbon/human/physiology.dm new file mode 100644 index 00000000000..49497003629 --- /dev/null +++ b/modular_zubbers/code/modules/mob/living/carbon/human/physiology.dm @@ -0,0 +1,28 @@ +/datum/physiology + var/flat_brute_mod = 0 + var/flat_burn_mod = 0 + +/mob/living/carbon/human/apply_damage( + damage, + damagetype, + def_zone, + blocked, + forced, + spread_damage, + wound_bonus, + bare_wound_bonus, + sharpness, + attack_direction, + attacking_item, + wound_clothing + ) + if(!physiology) + return ..() + + switch(damagetype) + if(BRUTE) + damage += physiology.flat_brute_mod + if(BURN) + damage += physiology.flat_burn_mod + + . = ..() diff --git a/modular_zubbers/code/modules/nanites/designs.dm b/modular_zubbers/code/modules/nanites/designs.dm new file mode 100644 index 00000000000..72d46247ef9 --- /dev/null +++ b/modular_zubbers/code/modules/nanites/designs.dm @@ -0,0 +1,91 @@ +/datum/design/nanite_disk + name = "Nanite Program Disk" + desc = "Stores nanite programs." + id = "nanite_disk" + build_type = PROTOLATHE | AWAY_LATHE + materials = list(/datum/material/iron = 300, /datum/material/glass = 100) + build_path = /obj/item/disk/nanite_program + category = list("Electronics") + departmental_flags = DEPARTMENT_BITFLAG_SCIENCE + +/datum/design/board/nanite_chamber_control + name = "Computer Design (Nanite Chamber Control)" + desc = "Allows for the construction of circuit boards used to build a new nanite chamber control console." + id = "nanite_chamber_control" + build_path = /obj/item/circuitboard/computer/nanite_chamber_control + category = list("Computer Boards") + departmental_flags = DEPARTMENT_BITFLAG_SCIENCE + +/datum/design/board/nanite_cloud_control + name = "Computer Design (Nanite Cloud Control)" + desc = "Allows for the construction of circuit boards used to build a new nanite cloud control console." + id = "nanite_cloud_control" + build_path = /obj/item/circuitboard/computer/nanite_cloud_controller + category = list("Computer Boards") + departmental_flags = DEPARTMENT_BITFLAG_SCIENCE + +/datum/design/board/nanite_chamber + name = "Machine Design (Nanite Chamber Board)" + desc = "The circuit board for a Nanite Chamber." + id = "nanite_chamber" + build_path = /obj/item/circuitboard/machine/nanite_chamber + category = list("Research Machinery") + departmental_flags = DEPARTMENT_BITFLAG_SCIENCE + +/datum/design/board/public_nanite_chamber + name = "Machine Design (Public Nanite Chamber Board)" + desc = "The circuit board for a Public Nanite Chamber." + id = "public_nanite_chamber" + build_path = /obj/item/circuitboard/machine/public_nanite_chamber + category = list("Research Machinery") + departmental_flags = DEPARTMENT_BITFLAG_SCIENCE + +/datum/design/board/nanite_programmer + name = "Machine Design (Nanite Programmer Board)" + desc = "The circuit board for a Nanite Programmer." + id = "nanite_programmer" + build_path = /obj/item/circuitboard/machine/nanite_programmer + category = list("Research Machinery") + departmental_flags = DEPARTMENT_BITFLAG_SCIENCE + +/datum/design/board/nanite_program_hub + name = "Machine Design (Nanite Program Hub Board)" + desc = "The circuit board for a Nanite Program Hub." + id = "nanite_program_hub" + build_path = /obj/item/circuitboard/machine/nanite_program_hub + category = list("Research Machinery") + departmental_flags = DEPARTMENT_BITFLAG_SCIENCE + +/////////////////////////////////// +//////////Nanite Devices/////////// +/////////////////////////////////// +/datum/design/nanite_remote + name = "Nanite Remote" + desc = "Allows for the construction of a nanite remote." + id = "nanite_remote" + build_type = PROTOLATHE | AWAY_LATHE + materials = list(/datum/material/glass = 500, /datum/material/iron = 500) + build_path = /obj/item/nanite_remote + category = list("Electronics") + departmental_flags = DEPARTMENT_BITFLAG_SCIENCE + +/datum/design/nanite_comm_remote + name = "Nanite Communication Remote" + desc = "Allows for the construction of a nanite communication remote." + id = "nanite_comm_remote" + build_type = PROTOLATHE | AWAY_LATHE + materials = list(/datum/material/glass = 500, /datum/material/iron = 500) + build_path = /obj/item/nanite_remote/comm + category = list("Electronics") + departmental_flags = DEPARTMENT_BITFLAG_SCIENCE + +/datum/design/nanite_scanner + name = "Nanite Scanner" + desc = "Allows for the construction of a nanite scanner." + id = "nanite_scanner" + build_type = PROTOLATHE | AWAY_LATHE + materials = list(/datum/material/glass = 500, /datum/material/iron = 500) + build_path = /obj/item/nanite_scanner + category = list("Electronics") + departmental_flags = DEPARTMENT_BITFLAG_SCIENCE + diff --git a/modular_zubbers/code/modules/nanites/extra_settings/_extra_setting.dm b/modular_zubbers/code/modules/nanites/extra_settings/_extra_setting.dm new file mode 100644 index 00000000000..b3bef77eb40 --- /dev/null +++ b/modular_zubbers/code/modules/nanites/extra_settings/_extra_setting.dm @@ -0,0 +1,20 @@ +/datum/nanite_extra_setting + var/setting_type + var/value + +/datum/nanite_extra_setting/proc/get_value() + return value + +/datum/nanite_extra_setting/proc/set_value(value) + src.value = value + +/datum/nanite_extra_setting/proc/get_copy() + return + +//I made the choice to send the name as part of the parameter instead of storing it directly on +//this datum as a way of avoiding duplication of data between the containing assoc list +//and this datum. +//Also make sure to double wrap the list when implementing this as +//+= is interpreted as a combine on lists, so the outer list gets unwrapped +/datum/nanite_extra_setting/proc/get_frontend_list(name) + return diff --git a/modular_zubbers/code/modules/nanites/extra_settings/boolean.dm b/modular_zubbers/code/modules/nanites/extra_settings/boolean.dm new file mode 100644 index 00000000000..7d66eb988ea --- /dev/null +++ b/modular_zubbers/code/modules/nanites/extra_settings/boolean.dm @@ -0,0 +1,27 @@ +/datum/nanite_extra_setting/boolean + setting_type = NESTYPE_BOOLEAN + var/true_text + var/false_text + +/datum/nanite_extra_setting/boolean/New(initial, true_text, false_text) + value = initial + src.true_text = true_text + src.false_text = false_text + +/datum/nanite_extra_setting/boolean/set_value(value) + if(isnull(value)) + src.value = !src.value + return + . = ..() + +/datum/nanite_extra_setting/boolean/get_copy() + return new /datum/nanite_extra_setting/boolean(value, true_text, false_text) + +/datum/nanite_extra_setting/boolean/get_frontend_list(name) + return list(list( + "name" = name, + "type" = setting_type, + "value" = value, + "true_text" = true_text, + "false_text" = false_text + )) diff --git a/modular_zubbers/code/modules/nanites/extra_settings/number.dm b/modular_zubbers/code/modules/nanites/extra_settings/number.dm new file mode 100644 index 00000000000..75489635f55 --- /dev/null +++ b/modular_zubbers/code/modules/nanites/extra_settings/number.dm @@ -0,0 +1,32 @@ +/datum/nanite_extra_setting/number + setting_type = NESTYPE_NUMBER + var/min + var/max + var/unit = "" + +/datum/nanite_extra_setting/number/New(initial, min, max, unit) + value = initial + src.min = min + src.max = max + if(unit) + src.unit = unit + +/datum/nanite_extra_setting/number/set_value(value) + if(istext(value)) + value = text2num(value) + if(!value || !isnum(value)) + return + src.value = clamp(value, min, max) + +/datum/nanite_extra_setting/number/get_copy() + return new /datum/nanite_extra_setting/number(value, min, max, unit) + +/datum/nanite_extra_setting/number/get_frontend_list(name) + return list(list( + "name" = name, + "type" = setting_type, + "value" = value, + "min" = min, + "max" = max, + "unit" = unit + )) diff --git a/modular_zubbers/code/modules/nanites/extra_settings/text.dm b/modular_zubbers/code/modules/nanites/extra_settings/text.dm new file mode 100644 index 00000000000..d3cad27bcf5 --- /dev/null +++ b/modular_zubbers/code/modules/nanites/extra_settings/text.dm @@ -0,0 +1,21 @@ +/datum/nanite_extra_setting/text + setting_type = NESTYPE_TEXT + +/datum/nanite_extra_setting/text/New(initial) + value = initial + +/datum/nanite_extra_setting/text/set_value(value) + src.value = trim(value) + +/datum/nanite_extra_setting/text/get_copy() + return new /datum/nanite_extra_setting/text(value) + +/datum/nanite_extra_setting/text/get_value() + return html_encode(value) + +/datum/nanite_extra_setting/text/get_frontend_list(name) + return list(list( + "name" = name, + "type" = setting_type, + "value" = value + )) diff --git a/modular_zubbers/code/modules/nanites/extra_settings/type.dm b/modular_zubbers/code/modules/nanites/extra_settings/type.dm new file mode 100644 index 00000000000..2085ba510c4 --- /dev/null +++ b/modular_zubbers/code/modules/nanites/extra_settings/type.dm @@ -0,0 +1,18 @@ +/datum/nanite_extra_setting/type + setting_type = NESTYPE_TYPE + var/list/types + +/datum/nanite_extra_setting/type/New(initial, types) + value = initial + src.types = types + +/datum/nanite_extra_setting/type/get_copy() + return new /datum/nanite_extra_setting/type(value, types) + +/datum/nanite_extra_setting/type/get_frontend_list(name) + return list(list( + "name" = name, + "type" = setting_type, + "value" = value, + "types" = types + )) diff --git a/modular_zubbers/code/modules/nanites/items/boards.dm b/modular_zubbers/code/modules/nanites/items/boards.dm new file mode 100644 index 00000000000..154dec071c3 --- /dev/null +++ b/modular_zubbers/code/modules/nanites/items/boards.dm @@ -0,0 +1,58 @@ +/obj/item/circuitboard/machine/nanite_chamber + name = "Nanite Chamber (Machine Board)" + greyscale_colors = CIRCUIT_COLOR_SCIENCE + build_path = /obj/machinery/nanite_chamber + req_components = list( + /obj/item/stock_parts/scanning_module = 2, + /obj/item/stock_parts/micro_laser = 2, + /obj/item/stock_parts/servo = 1 + ) + +/obj/item/circuitboard/machine/nanite_program_hub + name = "Nanite Program Hub (Machine Board)" + greyscale_colors = CIRCUIT_COLOR_SCIENCE + build_path = /obj/machinery/nanite_program_hub + req_components = list( + /obj/item/stock_parts/matter_bin = 1, + /obj/item/stock_parts/servo = 1) + +/obj/item/circuitboard/machine/nanite_programmer + name = "Nanite Programmer (Machine Board)" + greyscale_colors = CIRCUIT_COLOR_SCIENCE + build_path = /obj/machinery/nanite_programmer + req_components = list( + /obj/item/stock_parts/servo = 2, + /obj/item/stock_parts/micro_laser = 2, + /obj/item/stock_parts/scanning_module = 1 + ) + +/obj/item/circuitboard/machine/public_nanite_chamber + name = "Public Nanite Chamber (Machine Board)" + greyscale_colors = CIRCUIT_COLOR_SCIENCE + build_path = /obj/machinery/public_nanite_chamber + req_components = list( + /obj/item/stock_parts/micro_laser = 2, + /obj/item/stock_parts/servo = 1 + ) + var/cloud_id = 1 + +/obj/item/circuitboard/machine/public_nanite_chamber/multitool_act(mob/living/user) + . = ..() + var/new_cloud = tgui_input_number(user, "Set the public nanite chamber's Cloud ID (1-100).", "Cloud ID", cloud_id, 100, 1, 0,) + if(!isnum(new_cloud)) + return + cloud_id = clamp(round(new_cloud, 1), 1, 100) + +/obj/item/circuitboard/machine/public_nanite_chamber/examine(mob/user) + . = ..() + . += "Cloud ID is currently set to [cloud_id]." + +/obj/item/circuitboard/computer/nanite_chamber_control + name = "Nanite Chamber Control (Computer Board)" + greyscale_colors = CIRCUIT_COLOR_SCIENCE + build_path = /obj/machinery/computer/nanite_chamber_control + +/obj/item/circuitboard/computer/nanite_cloud_controller + name = "Nanite Cloud Control (Computer Board)" + greyscale_colors = CIRCUIT_COLOR_SCIENCE + build_path = /obj/machinery/computer/nanite_cloud_controller diff --git a/modular_zubbers/code/modules/nanites/items/items.dm b/modular_zubbers/code/modules/nanites/items/items.dm new file mode 100644 index 00000000000..d70fa243725 --- /dev/null +++ b/modular_zubbers/code/modules/nanites/items/items.dm @@ -0,0 +1,47 @@ +/obj/item/nanite_scanner + name = "nanite scanner" + icon = 'modular_zubbers/icons/obj/devices/nanite_devices.dmi' + icon_state = "nanite_scanner" + inhand_icon_state = "electronic" + worn_icon_state = "electronic" + lefthand_file = 'icons/mob/inhands/items/devices_lefthand.dmi' + righthand_file = 'icons/mob/inhands/items/devices_righthand.dmi' + desc = "A hand-held body scanner able to detect nanites and their programming." + obj_flags = CONDUCTS_ELECTRICITY + item_flags = NOBLUDGEON + slot_flags = ITEM_SLOT_BELT + throwforce = 3 + w_class = WEIGHT_CLASS_TINY + throw_speed = 3 + throw_range = 7 + custom_materials = list(/datum/material/iron=200) + +/obj/item/nanite_scanner/attack(mob/living/M, mob/living/carbon/human/user) + user.visible_message( + span_notice("[user] analyzes [M]'s nanites."), + span_notice("You analyze [M]'s nanites.") + ) + + add_fingerprint(user) + + var/response = SEND_SIGNAL(M, COMSIG_NANITE_SCAN, user, TRUE) + if(!response) + to_chat(user, span_info("No nanites detected in the subject.")) + +/obj/item/storage/box/disks_nanite + name = "nanite program disks box" + illustration = "disk_kit" + +/obj/item/storage/box/disks_nanite/PopulateContents() + for(var/i in 1 to 7) + new /obj/item/disk/nanite_program(src) + +/obj/item/nanite_injector + name = "nanite injector" + desc = "Injects nanites into the user." + w_class = WEIGHT_CLASS_SMALL + icon = 'modular_zubbers/icons/obj/devices/nanite_devices.dmi' + icon_state = "nanite_remote" + +/obj/item/nanite_injector/attack_self(mob/user) + user.AddComponent(/datum/component/nanites, 150) diff --git a/modular_zubbers/code/modules/nanites/items/program_disks.dm b/modular_zubbers/code/modules/nanites/items/program_disks.dm new file mode 100644 index 00000000000..efa6e6ad15f --- /dev/null +++ b/modular_zubbers/code/modules/nanites/items/program_disks.dm @@ -0,0 +1,147 @@ + +/obj/item/disk/nanite_program + name = "nanite program disk" + desc = "A disk capable of storing nanite programs. Can be customized using a Nanite Programming Console." + var/program_type + var/datum/nanite_program/program + +/obj/item/disk/nanite_program/Initialize(mapload) + . = ..() + if(program_type) + program = new program_type + update_name() + +/obj/item/disk/nanite_program/update_name() + . = ..() + if(!program) + return + name = "[initial(name)] \[[program.name]\]" + +/obj/item/disk/nanite_program/debugging + program_type = /datum/nanite_program/nanite_debugging + +/obj/item/disk/nanite_program/aggressive_replication + program_type = /datum/nanite_program/aggressive_replication + +/obj/item/disk/nanite_program/metabolic_synthesis + program_type = /datum/nanite_program/metabolic_synthesis + +/obj/item/disk/nanite_program/viral + program_type = /datum/nanite_program/viral + +/obj/item/disk/nanite_program/meltdown + program_type = /datum/nanite_program/meltdown + +/obj/item/disk/nanite_program/monitoring + program_type = /datum/nanite_program/monitoring + +/obj/item/disk/nanite_program/relay + program_type = /datum/nanite_program/relay + +/obj/item/disk/nanite_program/emp + program_type = /datum/nanite_program/emp + +/obj/item/disk/nanite_program/spreading + program_type = /datum/nanite_program/spreading + +/obj/item/disk/nanite_program/regenerative + program_type = /datum/nanite_program/regenerative + +/obj/item/disk/nanite_program/regenerative_advanced + program_type = /datum/nanite_program/regenerative/advanced + +/obj/item/disk/nanite_program/repairing + program_type = /datum/nanite_program/regenerative/robotic + +/obj/item/disk/nanite_program/temperature + program_type = /datum/nanite_program/temperature + +/obj/item/disk/nanite_program/purging + program_type = /datum/nanite_program/purging + +/obj/item/disk/nanite_program/purging_advanced + program_type = /datum/nanite_program/purging/advanced + +/obj/item/disk/nanite_program/brain_heal + program_type = /datum/nanite_program/brain_heal + +/obj/item/disk/nanite_program/brain_heal_advanced + program_type = /datum/nanite_program/brain_heal_advanced + +/obj/item/disk/nanite_program/blood_restoring + program_type = /datum/nanite_program/blood_restoring + +/obj/item/disk/nanite_program/defib + program_type = /datum/nanite_program/defib + +/obj/item/disk/nanite_program/nervous + program_type = /datum/nanite_program/nervous + +/obj/item/disk/nanite_program/hardening + program_type = /datum/nanite_program/dermal_armor + +/obj/item/disk/nanite_program/refractive + program_type = /datum/nanite_program/dermal_armor/refractive + +/obj/item/disk/nanite_program/coagulating + program_type = /datum/nanite_program/coagulating + +/obj/item/disk/nanite_program/necrotic + program_type = /datum/nanite_program/necrotic + +/obj/item/disk/nanite_program/brain_decay + program_type = /datum/nanite_program/brain_decay + +/obj/item/disk/nanite_program/pyro + program_type = /datum/nanite_program/pyro + +/obj/item/disk/nanite_program/cryo + program_type = /datum/nanite_program/cryo + +/obj/item/disk/nanite_program/toxic + program_type = /datum/nanite_program/toxic + +/obj/item/disk/nanite_program/suffocating + program_type = /datum/nanite_program/suffocating + +/obj/item/disk/nanite_program/heart_stop + program_type = /datum/nanite_program/heart_stop + +/obj/item/disk/nanite_program/explosive + program_type = /datum/nanite_program/explosive + +/obj/item/disk/nanite_program/shock + program_type = /datum/nanite_program/shocking + +/obj/item/disk/nanite_program/sleepy + program_type = /datum/nanite_program/sleepy + +/obj/item/disk/nanite_program/paralyzing + program_type = /datum/nanite_program/paralyzing + +/obj/item/disk/nanite_program/fake_death + program_type = /datum/nanite_program/fake_death + +/obj/item/disk/nanite_program/pacifying + program_type = /datum/nanite_program/pacifying + +/obj/item/disk/nanite_program/glitch + program_type = /datum/nanite_program/glitch + +/obj/item/disk/nanite_program/brain_misfire + program_type = /datum/nanite_program/pacifying + +/obj/item/disk/nanite_program/skin_decay + program_type = /datum/nanite_program/pacifying + +/obj/item/disk/nanite_program/nerve_decay + program_type = /datum/nanite_program/pacifying + +/obj/item/disk/nanite_program/conductive + program_type = /datum/nanite_program/pacifying + +/obj/item/disk/nanite_program/stun + program_type = /datum/nanite_program/stun + +/obj/item/disk/nanite_program/sting + program_type = /datum/nanite_program/nanite_sting diff --git a/modular_zubbers/code/modules/nanites/items/remote.dm b/modular_zubbers/code/modules/nanites/items/remote.dm new file mode 100644 index 00000000000..8ed634cb7cf --- /dev/null +++ b/modular_zubbers/code/modules/nanites/items/remote.dm @@ -0,0 +1,247 @@ +#define REMOTE_MODE_OFF "Off" +#define REMOTE_MODE_SELF "Local" +#define REMOTE_MODE_TARGET "Targeted" +#define REMOTE_MODE_AOE "Area" +#define REMOTE_MODE_RELAY "Relay" + +/obj/item/nanite_remote + name = "nanite remote control" + desc = "A device that can remotely control active nanites through wireless signals." + w_class = WEIGHT_CLASS_SMALL + req_access = list(ACCESS_ROBOTICS) + icon = 'modular_zubbers/icons/obj/devices/nanite_devices.dmi' + icon_state = "nanite_remote" + item_flags = NOBLUDGEON + var/locked = FALSE //Can be locked, so it can be given to users with a set code and mode + var/mode = REMOTE_MODE_OFF + var/list/saved_settings = list() + var/last_id = 0 + var/code = 0 + var/relay_code = 0 + var/current_program_name = "Program" + +/obj/item/nanite_remote/examine(mob/user) + . = ..() + if(locked) + . += span_notice("Alt-click to unlock.") + +/obj/item/nanite_remote/click_alt(mob/user) + . = ..() + if(!user.can_perform_action(src, FORBID_TELEKINESIS_REACH)) + return + if(locked) + if(allowed(user)) + to_chat(user, span_notice("You unlock [src].")) + locked = FALSE + update_appearance() + else + to_chat(user, span_warning("Access denied.")) + +/obj/item/nanite_remote/emag_act(mob/user) + if(obj_flags & EMAGGED) + return + to_chat(user, span_warning("You override [src]'s ID lock.")) + obj_flags |= EMAGGED + if(locked) + locked = FALSE + update_appearance() + +/obj/item/nanite_remote/update_overlays() + . = ..() + if(obj_flags & EMAGGED) + . += "nanite_remote_emagged" + if(locked) + . += "nanite_remote_locked" + +/obj/item/nanite_remote/interact_with_atom(atom/interacting_with, mob/living/user, list/modifiers) + return trigger_nanites(interacting_with, user, modifiers) + +/obj/item/nanite_remote/ranged_interact_with_atom(atom/interacting_with, mob/living/user, list/modifiers) + return trigger_nanites(interacting_with, user, modifiers) + +/obj/item/nanite_remote/proc/trigger_nanites(atom/interacting_with, mob/living/user, list/modifiers) + switch(mode) + if(REMOTE_MODE_SELF) + to_chat(user, span_notice("You activate [src], signaling the nanites in your bloodstream.")) + signal_mob(user, code, key_name(user)) + return ITEM_INTERACT_SUCCESS + if(REMOTE_MODE_TARGET) + if(!istype(interacting_with, /mob/living)) + return NONE + if(isliving(interacting_with) && (get_dist(interacting_with, get_turf(src)) <= 7)) + to_chat(user, span_notice("You activate [src], signaling the nanites inside [interacting_with].")) + signal_mob(interacting_with, code, key_name(user)) + return ITEM_INTERACT_SUCCESS + if(REMOTE_MODE_AOE) + to_chat(user, span_notice("You activate [src], signaling the nanites inside every host around you.")) + for(var/mob/living/L in view(user, 7)) + signal_mob(L, code, key_name(user)) + return ITEM_INTERACT_SUCCESS + if(REMOTE_MODE_RELAY) + to_chat(user, span_notice("You activate [src], signaling all connected relay nanites.")) + signal_relay(code, relay_code, key_name(user)) + return ITEM_INTERACT_SUCCESS + return NONE + +/obj/item/nanite_remote/proc/signal_mob(mob/living/M, code, source) + SEND_SIGNAL(M, COMSIG_NANITE_SIGNAL, code, source) + +/obj/item/nanite_remote/proc/signal_relay(code, relay_code, source) + for(var/X in SSnanites.nanite_relays) + var/datum/nanite_program/relay/N = X + N.relay_signal(code, relay_code, source) + +/obj/item/nanite_remote/ui_state(mob/user) + return GLOB.hands_state + +/obj/item/nanite_remote/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "NaniteRemote", name) + ui.open() + +/obj/item/nanite_remote/ui_data() + var/list/data = list() + data["code"] = code + data["relay_code"] = relay_code + data["mode"] = mode + data["locked"] = locked + data["saved_settings"] = saved_settings + data["program_name"] = current_program_name + return data + +/obj/item/nanite_remote/ui_act(action, params) + . = ..() + if(.) + return + switch(action) + if("set_code") + if(locked) + return + var/new_code = text2num(params["code"]) + if(!isnull(new_code)) + new_code = clamp(round(new_code, 1),0,9999) + code = new_code + . = TRUE + if("set_relay_code") + if(locked) + return + var/new_code = text2num(params["code"]) + if(!isnull(new_code)) + new_code = clamp(round(new_code, 1),0,9999) + relay_code = new_code + . = TRUE + if("update_name") + current_program_name = params["name"] + . = TRUE + if("save") + if(locked) + return + var/new_save = list() + new_save["id"] = last_id + 1 + last_id++ + new_save["name"] = current_program_name + new_save["code"] = code + new_save["mode"] = mode + new_save["relay_code"] = relay_code + + saved_settings += list(new_save) + . = TRUE + if("load") + var/code_id = params["save_id"] + var/list/setting + for(var/list/X in saved_settings) + if(X["id"] == text2num(code_id)) + setting = X + break + if(setting) + code = setting["code"] + mode = setting["mode"] + relay_code = setting["relay_code"] + . = TRUE + if("remove_save") + if(locked) + return + var/code_id = params["save_id"] + for(var/list/setting in saved_settings) + if(setting["id"] == text2num(code_id)) + saved_settings -= list(setting) + break + . = TRUE + if("select_mode") + if(locked) + return + mode = params["mode"] + . = TRUE + if("lock") + if(!(obj_flags & EMAGGED)) + locked = TRUE + update_appearance() + . = TRUE + + +/obj/item/nanite_remote/comm + name = "nanite communication remote" + desc = "A device that can send text messages to specific programs." + icon_state = "nanite_comm_remote" + var/comm_message = "" + +/obj/item/nanite_remote/comm/afterattack(atom/target, mob/user, etc) + switch(mode) + if(REMOTE_MODE_OFF) + return + if(REMOTE_MODE_SELF) + to_chat(user, span_notice("You activate [src], signaling the nanites in your bloodstream.")) + signal_mob(user, code, key_name(user)) + if(REMOTE_MODE_TARGET) + if(isliving(target) && (get_dist(target, get_turf(src)) <= 7)) + to_chat(user, span_notice("You activate [src], signaling the nanites inside [target].")) + signal_mob(target, code, key_name(user)) + if(REMOTE_MODE_AOE) + to_chat(user, span_notice("You activate [src], signaling the nanites inside every host around you.")) + for(var/mob/living/L in view(user, 7)) + signal_mob(L, code, key_name(user)) + if(REMOTE_MODE_RELAY) + to_chat(user, span_notice("You activate [src], signaling all connected relay nanites.")) + signal_relay(code, relay_code, key_name(user)) + +/obj/item/nanite_remote/comm/signal_mob(mob/living/M, code, source) + SEND_SIGNAL(M, COMSIG_NANITE_COMM_SIGNAL, code, comm_message, source) + +/obj/item/nanite_remote/comm/signal_relay(code, relay_code, source) + for(var/X in SSnanites.nanite_relays) + var/datum/nanite_program/relay/N = X + N.relay_comm_signal(code, relay_code, comm_message) + +/obj/item/nanite_remote/comm/ui_data() + var/list/data = list() + data["comms"] = TRUE + data["code"] = code + data["relay_code"] = relay_code + data["message"] = comm_message + data["mode"] = mode + data["locked"] = locked + data["saved_settings"] = saved_settings + data["program_name"] = current_program_name + + return data + +/obj/item/nanite_remote/comm/ui_act(action, params) + . = ..() + if(.) + return + switch(action) + if("set_message") + if(locked) + return + var/new_message = html_encode(params["value"]) + if(!new_message) + return + comm_message = new_message + . = TRUE + +#undef REMOTE_MODE_OFF +#undef REMOTE_MODE_SELF +#undef REMOTE_MODE_TARGET +#undef REMOTE_MODE_AOE +#undef REMOTE_MODE_RELAY diff --git a/modular_zubbers/code/modules/nanites/living.dm b/modular_zubbers/code/modules/nanites/living.dm new file mode 100644 index 00000000000..3e16ae76ed6 --- /dev/null +++ b/modular_zubbers/code/modules/nanites/living.dm @@ -0,0 +1,7 @@ +/mob/living/proc/hud_set_nanite_indicator() + var/image/holder = hud_list[NANITE_HUD] + var/icon/I = icon(icon, icon_state, dir) + holder.pixel_y = I.Height() - world.icon_size + holder.icon_state = null + if(HAS_TRAIT(src, TRAIT_NANITE_MONITORING)) + holder.icon_state = "nanite_ping" diff --git a/modular_zubbers/code/modules/nanites/machines/nanite_chamber.dm b/modular_zubbers/code/modules/nanites/machines/nanite_chamber.dm new file mode 100644 index 00000000000..63e425dbcc1 --- /dev/null +++ b/modular_zubbers/code/modules/nanites/machines/nanite_chamber.dm @@ -0,0 +1,213 @@ +/obj/machinery/nanite_chamber + name = "nanite chamber" + desc = "A device that can scan, reprogram, and inject nanites." + circuit = /obj/item/circuitboard/machine/nanite_chamber + icon = 'icons/obj/machines/bci_implanter.dmi' + icon_state = "bci_implanter" + base_icon_state = "bci_implanter" + layer = ABOVE_WINDOW_LAYER + use_power = IDLE_POWER_USE + anchored = TRUE + density = TRUE + idle_power_usage = 50 + active_power_usage = 300 + + var/locked = FALSE + var/breakout_time = 1200 + var/scan_level + var/busy = FALSE + var/busy_icon_state + var/busy_message + var/message_cooldown = 0 + +/obj/machinery/nanite_chamber/Initialize(mapload, obj/item/circuitboard/C) + . = ..() + occupant_typecache = GLOB.typecache_living + +/obj/machinery/nanite_chamber/RefreshParts() + . = ..() + scan_level = 0 + for(var/datum/stock_part/P in component_parts) + scan_level += P.tier + +/obj/machinery/nanite_chamber/examine(mob/user) + . = ..() + if(in_range(user, src) || isobserver(user)) + . += span_notice("The status display reads: Scanning module has been upgraded to level [scan_level].") + +/obj/machinery/nanite_chamber/proc/set_busy(status, message, working_icon) + busy = status + busy_message = message + busy_icon_state = working_icon + update_appearance() + +/obj/machinery/nanite_chamber/proc/set_safety(threshold) + if(!occupant) + return + SEND_SIGNAL(occupant, COMSIG_NANITE_SET_SAFETY, threshold) + +/obj/machinery/nanite_chamber/proc/set_cloud(cloud_id) + if(!occupant) + return + SEND_SIGNAL(occupant, COMSIG_NANITE_SET_CLOUD, cloud_id) + +/obj/machinery/nanite_chamber/proc/inject_nanites() + if(machine_stat & (NOPOWER|BROKEN)) + return + if((machine_stat & MAINT) || panel_open) + return + if(!occupant || busy) + return + + var/locked_state = locked + locked = TRUE + + //TODO OMINOUS MACHINE SOUNDS + set_busy(TRUE, "Initializing injection protocol...", "[initial(icon_state)]_raising") + addtimer(CALLBACK(src, .proc/set_busy, TRUE, "Analyzing host bio-structure...", "[initial(icon_state)]_active"),20) + addtimer(CALLBACK(src, .proc/set_busy, TRUE, "Priming nanites...", "[initial(icon_state)]_active"),40) + addtimer(CALLBACK(src, .proc/set_busy, TRUE, "Injecting...", "[initial(icon_state)]_active"),70) + addtimer(CALLBACK(src, .proc/set_busy, TRUE, "Activating nanites...", "[initial(icon_state)]_falling"),110) + addtimer(CALLBACK(src, .proc/complete_injection, locked_state), 130) + +/obj/machinery/nanite_chamber/proc/complete_injection(locked_state) + //TODO MACHINE DING + locked = locked_state + set_busy(FALSE) + if(!occupant) + return + occupant.AddComponent(/datum/component/nanites, 100) + +/obj/machinery/nanite_chamber/proc/remove_nanites(datum/nanite_program/NP) + if(machine_stat & (NOPOWER|BROKEN)) + return + if((machine_stat & MAINT) || panel_open) + return + if(!occupant || busy) + return + + var/locked_state = locked + locked = TRUE + + //TODO OMINOUS MACHINE SOUNDS + set_busy(TRUE, "Initializing cleanup protocol...", "[initial(icon_state)]_raising") + addtimer(CALLBACK(src, .proc/set_busy, TRUE, "Analyzing host bio-structure...", "[initial(icon_state)]_active"),20) + addtimer(CALLBACK(src, .proc/set_busy, TRUE, "Pinging nanites...", "[initial(icon_state)]_active"),40) + addtimer(CALLBACK(src, .proc/set_busy, TRUE, "Initiating graceful self-destruct sequence...", "[initial(icon_state)]_active"),70) + addtimer(CALLBACK(src, .proc/set_busy, TRUE, "Removing debris...", "[initial(icon_state)]_falling"),110) + addtimer(CALLBACK(src, .proc/complete_removal, locked_state),130) + +/obj/machinery/nanite_chamber/proc/complete_removal(locked_state) + //TODO MACHINE DING + locked = locked_state + set_busy(FALSE) + if(!occupant) + return + SEND_SIGNAL(occupant, COMSIG_NANITE_DELETE) + +/obj/machinery/nanite_chamber/update_icon_state() + //running and someone in there + if(occupant) + icon_state = busy ? busy_icon_state : "[base_icon_state]_occupied" + return ..() + //running + icon_state = "[base_icon_state][state_open ? "_open" : null]" + return ..() + +/obj/machinery/nanite_chamber/update_overlays() + . = ..() + + if((machine_stat & MAINT) || panel_open) + . += "maint" + return + + if(machine_stat & (NOPOWER|BROKEN)) + return + + if(busy || locked) + . += "red" + if(locked) + . += "bolted" + return + + . += "green" + +/obj/machinery/nanite_chamber/proc/toggle_open(mob/user) + if(panel_open) + to_chat(user, span_notice("Close the maintenance panel first.")) + return + + if(state_open) + close_machine() + return + + else if(locked) + to_chat(user, span_notice("The bolts are locked down, securing the door shut.")) + return + + open_machine() + +/obj/machinery/nanite_chamber/container_resist_act(mob/living/user) + if(!locked) + open_machine() + return + if(busy) + return + user.changeNext_move(CLICK_CD_BREAKOUT) + user.last_special = world.time + CLICK_CD_BREAKOUT + user.visible_message(span_notice("You see [user] kicking against the door of [src]!"), \ + span_notice("You lean on the back of [src] and start pushing the door open... (this will take about [DisplayTimeText(breakout_time)].)"), \ + span_hear("You hear a metallic creaking from [src].")) + if(do_after(user,(breakout_time), target = src)) + if(!user || user.stat != CONSCIOUS || user.loc != src || state_open || !locked || busy) + return + locked = FALSE + user.visible_message(span_warning("[user] successfully broke out of [src]!"), \ + span_notice("You successfully break out of [src]!")) + open_machine() + +/obj/machinery/nanite_chamber/close_machine(mob/living/carbon/user, density_to_set = TRUE) + if(!state_open) + return FALSE + + ..(user) + return TRUE + +/obj/machinery/nanite_chamber/open_machine(drop = TRUE, density_to_set = FALSE) + if(state_open) + return FALSE + + ..() + + return TRUE + +/obj/machinery/nanite_chamber/relaymove(mob/living/user, direction) + if(user.stat || locked) + if(message_cooldown <= world.time) + message_cooldown = world.time + 50 + to_chat(user, span_warning("[src]'s door won't budge!")) + return + open_machine() + +/obj/machinery/nanite_chamber/attackby(obj/item/I, mob/user, params) + if(!occupant && default_deconstruction_screwdriver(user, icon_state, icon_state, I))//sent icon_state is irrelevant... + update_appearance()//..since we're updating the icon here, since the scanner can be unpowered when opened/closed + return + + if(default_pry_open(I)) + return + + if(default_deconstruction_crowbar(I)) + return + + return ..() + +/obj/machinery/nanite_chamber/interact(mob/user) + toggle_open(user) + +/obj/machinery/nanite_chamber/mouse_drop_receive(atom/target, mob/user, params) + if(!user.can_perform_action(src, FORBID_TELEKINESIS_REACH|SILENT_ADJACENCY) || !iscarbon(target)) + return + if(close_machine(target)) + log_combat(user, target, "inserted", null, "into [src].") + add_fingerprint(user) diff --git a/modular_zubbers/code/modules/nanites/machines/nanite_chamber_computer.dm b/modular_zubbers/code/modules/nanites/machines/nanite_chamber_computer.dm new file mode 100644 index 00000000000..edec4df1511 --- /dev/null +++ b/modular_zubbers/code/modules/nanites/machines/nanite_chamber_computer.dm @@ -0,0 +1,109 @@ +/obj/machinery/computer/nanite_chamber_control + name = "nanite chamber control console" + desc = "Controls a connected nanite chamber. Can inoculate nanites, load programs, and analyze existing nanite swarms." + icon_screen = "nanite_chamber_control" + circuit = /obj/item/circuitboard/computer/nanite_chamber_control + var/obj/machinery/nanite_chamber/chamber + +/obj/machinery/computer/nanite_chamber_control/Initialize(mapload, obj/item/circuitboard/C) + . = ..() + find_chamber() + +/obj/machinery/computer/nanite_chamber_control/proc/find_chamber() + for(var/direction in GLOB.cardinals) + var/C = locate(/obj/machinery/nanite_chamber, get_step(src, direction)) + if(C) + var/obj/machinery/nanite_chamber/NC = C + set_connected_chamber(NC) + +/obj/machinery/computer/nanite_chamber_control/interact() + if(!chamber) + find_chamber() + ..() + +/obj/machinery/computer/nanite_chamber_control/ui_interact(mob/user, datum/tgui/ui) + . = ..() + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "NaniteChamberControl", name) + ui.open() + +/obj/machinery/computer/nanite_chamber_control/ui_data() + var/list/data = list() + + if(!chamber) + data["status_msg"] = "No chamber detected." + return data + + if(!chamber.occupant) + data["status_msg"] = "No occupant detected." + return data + + var/mob/living/L = chamber.occupant + + if(!CAN_HAVE_NANITES(L)) + data["status_msg"] = "Occupant not compatible with nanites." + return data + + if(chamber.busy) + data["status_msg"] = chamber.busy_message + return data + + data["status_msg"] = null + data["scan_level"] = chamber.scan_level + data["locked"] = chamber.locked + data["occupant_name"] = chamber.occupant.name + + SEND_SIGNAL(L, COMSIG_NANITE_UI_DATA, data, chamber.scan_level) + + return data + +/obj/machinery/computer/nanite_chamber_control/ui_act(action, params) + . = ..() + if(.) + return + switch(action) + if("toggle_lock") + chamber.locked = !chamber.locked + chamber.update_appearance() + . = TRUE + if("set_safety") + var/threshold = text2num(params["value"]) + if(!isnull(threshold)) + chamber.set_safety(clamp(round(threshold, 1),0,500)) + playsound(src, "terminal_type", 25, FALSE) + chamber.occupant.investigate_log("'s nanites' safety threshold was set to [threshold] by [key_name(usr)] via [src] at [AREACOORD(src)].", INVESTIGATE_NANITES) + . = TRUE + if("set_cloud") + var/cloud_id = text2num(params["value"]) + if(!isnull(cloud_id)) + chamber.set_cloud(clamp(round(cloud_id, 1),0,100)) + playsound(src, "terminal_type", 25, FALSE) + chamber.occupant.investigate_log("'s nanites' cloud id was set to [cloud_id] by [key_name(usr)] via [src] at [AREACOORD(src)].", INVESTIGATE_NANITES) + . = TRUE + if("connect_chamber") + find_chamber() + . = TRUE + if("remove_nanites") + playsound(src, 'sound/machines/terminal/terminal_prompt.ogg', 25, FALSE) + chamber.remove_nanites() + log_combat(usr, chamber.occupant, "cleared nanites from", null, "via [src]") + chamber.occupant.investigate_log("'s nanites were cleared by [key_name(usr)] via [src] at [AREACOORD(src)].", INVESTIGATE_NANITES) + . = TRUE + if("nanite_injection") + playsound(src, 'sound/machines/terminal/terminal_prompt.ogg', 25, FALSE) + chamber.inject_nanites() + log_combat(usr, chamber.occupant, "injected", null, "with nanites via [src]") + chamber.occupant.investigate_log("was injected with nanites by [key_name(usr)] via [src] at [AREACOORD(src)].", INVESTIGATE_NANITES) + . = TRUE + +/obj/machinery/computer/nanite_chamber_control/proc/set_connected_chamber(new_chamber) + if(chamber) + UnregisterSignal(chamber, COMSIG_PREQDELETED) + chamber = new_chamber + if(chamber) + RegisterSignal(chamber, COMSIG_PREQDELETED, .proc/react_to_chamber_del) + +/obj/machinery/computer/nanite_chamber_control/proc/react_to_chamber_del(datum/source) + SIGNAL_HANDLER + set_connected_chamber(null) diff --git a/modular_zubbers/code/modules/nanites/machines/nanite_cloud_controller.dm b/modular_zubbers/code/modules/nanites/machines/nanite_cloud_controller.dm new file mode 100644 index 00000000000..a4ea1e07de4 --- /dev/null +++ b/modular_zubbers/code/modules/nanites/machines/nanite_cloud_controller.dm @@ -0,0 +1,306 @@ +/obj/machinery/computer/nanite_cloud_controller + name = "nanite cloud controller" + desc = "Stores and controls nanite cloud backups." + icon = 'modular_zubbers/icons/obj/machines/nanite_machines.dmi' + icon_state = "nanite_cloud_controller" + circuit = /obj/item/circuitboard/computer/nanite_cloud_controller + icon_screen = "nanite_cloud_controller_screen" + icon_keyboard = null + + var/obj/item/disk/nanite_program/disk + var/list/datum/nanite_cloud_backup/cloud_backups = list() + var/datum/techweb/linked_techweb + var/current_view = 0 //0 is the main menu, any other number is the page of the backup with that ID + var/new_backup_id = 1 + +/obj/machinery/computer/nanite_cloud_controller/Initialize(mapload, obj/item/circuitboard/C) + . = ..() + if(!CONFIG_GET(flag/no_default_techweb_link) && !linked_techweb) + CONNECT_TO_RND_SERVER_ROUNDSTART(linked_techweb, src) + register_context() + +/obj/machinery/computer/nanite_cloud_controller/Destroy() + QDEL_LIST(cloud_backups) //rip backups + eject() + return ..() + +/obj/machinery/computer/nanite_cloud_controller/examine(mob/user) + . = ..() + . += span_notice("Use with a linked multitool to link to a techweb server.") + +/obj/machinery/computer/nanite_cloud_controller/add_context(atom/source, list/context, obj/item/held_item, mob/user) + . = ..() + if(isnull(held_item)) + return + if(held_item.tool_behaviour == TOOL_SCREWDRIVER) + context[SCREENTIP_CONTEXT_LMB] = "Open Panel" + return CONTEXTUAL_SCREENTIP_SET + if(held_item.tool_behaviour == TOOL_MULTITOOL) + context[SCREENTIP_CONTEXT_LMB] = "Upload Techweb" + return CONTEXTUAL_SCREENTIP_SET + +/obj/machinery/computer/nanite_cloud_controller/item_interaction(mob/living/user, obj/item/tool, list/modifiers) + . = ..() + if(!istype(tool, /obj/item/disk/nanite_program)) + return NONE + var/obj/item/disk/nanite_program/new_disk = tool + if(!user.transferItemToLoc(new_disk, src)) + return NONE + to_chat(user, span_notice("You insert [new_disk] into [src]")) + playsound(src, 'sound/machines/terminal/terminal_insert_disc.ogg', 50, FALSE) + if(disk) + eject(user) + disk = new_disk + return ITEM_INTERACT_SUCCESS + +/obj/machinery/computer/nanite_cloud_controller/multitool_act(mob/living/user, obj/item/multitool/tool) + if(!QDELETED(tool.buffer) && istype(tool.buffer, /datum/techweb)) + linked_techweb = tool.buffer + balloon_alert(user, "linked!") + update_static_data_for_all_viewers() + return TRUE + +/obj/machinery/computer/nanite_cloud_controller/attack_hand_secondary(mob/user, list/modifiers) + . = ..() + if(disk && user.can_perform_action(src, !issilicon(user))) + to_chat(user, span_notice("You take out [disk] from [src].")) + eject(user) + return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN + +/obj/machinery/computer/nanite_cloud_controller/proc/eject(mob/living/user) + if(!disk) + return + if(!istype(user) || !Adjacent(user) ||!user.put_in_active_hand(disk)) + disk.forceMove(drop_location()) + disk = null + +/obj/machinery/computer/nanite_cloud_controller/proc/get_backup(cloud_id) + for(var/I in cloud_backups) + var/datum/nanite_cloud_backup/backup = I + if(backup.cloud_id == cloud_id) + return backup + +/obj/machinery/computer/nanite_cloud_controller/proc/generate_backup(cloud_id, mob/user) + if(SSnanites.get_cloud_backup(cloud_id, TRUE)) + to_chat(user, span_warning("Cloud ID already registered.")) + return + + var/datum/nanite_cloud_backup/backup = new(src) + var/datum/component/nanites/cloud_copy = backup.AddComponent(/datum/component/nanites) + backup.cloud_id = cloud_id + backup.nanites = cloud_copy + if(linked_techweb) + backup.techweb = linked_techweb + investigate_log("[key_name(user)] created a new nanite cloud backup with id #[cloud_id]", INVESTIGATE_NANITES) + +/obj/machinery/computer/nanite_cloud_controller/ui_interact(mob/user, datum/tgui/ui) + . = ..() + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "NaniteCloudControl", name) + ui.open() + +/obj/machinery/computer/nanite_cloud_controller/ui_static_data(mob/user) + . = ..() + var/list/data = . + if(linked_techweb) + data["techweb"] = list( + "name" = linked_techweb.id, + "organization" = linked_techweb.organization, + ) + +// a lot of this should be in static data +/obj/machinery/computer/nanite_cloud_controller/ui_data() + var/list/data = list() + if(disk) + data["has_disk"] = TRUE + var/list/disk_data = list() + var/datum/nanite_program/P = disk.program + data["has_program"] = P ? TRUE : FALSE + if(P) + disk_data["name"] = P.name + disk_data["desc"] = P.desc + disk_data["use_rate"] = P.use_rate + disk_data["can_trigger"] = P.can_trigger + disk_data["trigger_cost"] = P.trigger_cost + disk_data["trigger_cooldown"] = P.trigger_cooldown / 10 + + disk_data["activated"] = P.activated + disk_data["activation_code"] = P.activation_code + disk_data["deactivation_code"] = P.deactivation_code + disk_data["kill_code"] = P.kill_code + disk_data["trigger_code"] = P.trigger_code + disk_data["timer_restart"] = P.timer_restart / 10 + disk_data["timer_shutdown"] = P.timer_shutdown / 10 + disk_data["timer_trigger"] = P.timer_trigger / 10 + disk_data["timer_trigger_delay"] = P.timer_trigger_delay / 10 + + var/list/extra_settings = P.get_extra_settings_frontend() + disk_data["extra_settings"] = extra_settings + if(LAZYLEN(extra_settings)) + disk_data["has_extra_settings"] = TRUE + if(istype(P, /datum/nanite_program/sensor)) + var/datum/nanite_program/sensor/sensor = P + if(sensor.can_rule) + disk_data["can_rule"] = TRUE + data["disk"] = disk_data + else + data["has_disk"] = FALSE + + data["new_backup_id"] = new_backup_id + + data["current_view"] = current_view + if(current_view) + var/datum/nanite_cloud_backup/backup = get_backup(current_view) + if(backup) + var/datum/component/nanites/nanites = backup.nanites + data["cloud_backup"] = TRUE + var/list/cloud_programs = list() + var/id = 1 + for(var/datum/nanite_program/P in nanites.programs) + var/list/cloud_program = list() + cloud_program["name"] = P.name + cloud_program["desc"] = P.desc + cloud_program["id"] = id + cloud_program["use_rate"] = P.use_rate + cloud_program["can_trigger"] = P.can_trigger + cloud_program["trigger_cost"] = P.trigger_cost + cloud_program["trigger_cooldown"] = P.trigger_cooldown / 10 + cloud_program["activated"] = P.activated + cloud_program["timer_restart"] = P.timer_restart / 10 + cloud_program["timer_shutdown"] = P.timer_shutdown / 10 + cloud_program["timer_trigger"] = P.timer_trigger / 10 + cloud_program["timer_trigger_delay"] = P.timer_trigger_delay / 10 + + cloud_program["activation_code"] = P.activation_code + cloud_program["deactivation_code"] = P.deactivation_code + cloud_program["kill_code"] = P.kill_code + cloud_program["trigger_code"] = P.trigger_code + var/list/rules = list() + var/rule_id = 1 + for(var/X in P.rules) + var/datum/nanite_rule/nanite_rule = X + var/list/rule = list() + rule["display"] = nanite_rule.display() + rule["program_id"] = id + rule["id"] = rule_id + rules += list(rule) + rule_id++ + 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 + if(LAZYLEN(extra_settings)) + cloud_program["has_extra_settings"] = TRUE + id++ + cloud_programs += list(cloud_program) + data["cloud_programs"] = cloud_programs + else + var/list/backup_list = list() + for(var/X in cloud_backups) + var/datum/nanite_cloud_backup/backup = X + var/list/cloud_backup = list() + cloud_backup["cloud_id"] = backup.cloud_id + backup_list += list(cloud_backup) + data["cloud_backups"] = backup_list + return data + +/obj/machinery/computer/nanite_cloud_controller/ui_act(action, params) + . = ..() + if(.) + return + switch(action) + if("eject") + eject(usr) + . = TRUE + if("set_view") + current_view = text2num(params["view"]) + . = TRUE + if("update_new_backup_value") + var/backup_value = text2num(params["value"]) + new_backup_id = backup_value + if("create_backup") + var/cloud_id = new_backup_id + if(!isnull(cloud_id)) + playsound(src, 'sound/machines/terminal/terminal_prompt.ogg', 50, FALSE) + cloud_id = clamp(round(cloud_id, 1),1,100) + generate_backup(cloud_id, usr) + . = TRUE + if("delete_backup") + var/datum/nanite_cloud_backup/backup = get_backup(current_view) + if(backup) + playsound(src, 'sound/machines/terminal/terminal_prompt.ogg', 50, FALSE) + qdel(backup) + investigate_log("[key_name(usr)] deleted the nanite cloud backup #[current_view]", INVESTIGATE_NANITES) + . = TRUE + if("upload_program") + if(disk?.program) + var/datum/nanite_cloud_backup/backup = get_backup(current_view) + if(backup) + playsound(src, 'sound/machines/terminal/terminal_prompt.ogg', 50, FALSE) + var/datum/component/nanites/nanites = backup.nanites + nanites.add_program(null, disk.program.copy()) + investigate_log("[key_name(usr)] uploaded program [disk.program.name] to cloud #[current_view]", INVESTIGATE_NANITES) + . = TRUE + if("remove_program") + var/datum/nanite_cloud_backup/backup = get_backup(current_view) + if(backup) + playsound(src, 'sound/machines/terminal/terminal_prompt.ogg', 50, FALSE) + var/datum/component/nanites/nanites = backup.nanites + var/datum/nanite_program/P = nanites.programs[text2num(params["program_id"])] + investigate_log("[key_name(usr)] deleted program [P.name] from cloud #[current_view]", INVESTIGATE_NANITES) + qdel(P) + . = TRUE + if("add_rule") + if(disk && disk.program && istype(disk.program, /datum/nanite_program/sensor)) + var/datum/nanite_program/sensor/rule_template = disk.program + if(!rule_template.can_rule) + return + var/datum/nanite_cloud_backup/backup = get_backup(current_view) + if(backup) + playsound(src, 'sound/machines/terminal/terminal_prompt.ogg', 50, 0) + var/datum/component/nanites/nanites = backup.nanites + var/datum/nanite_program/P = nanites.programs[text2num(params["program_id"])] + var/datum/nanite_rule/rule = rule_template.make_rule(P) + + investigate_log("[key_name(usr)] added rule [rule.display()] to program [P.name] in cloud #[current_view]", INVESTIGATE_NANITES) + . = TRUE + if("remove_rule") + var/datum/nanite_cloud_backup/backup = get_backup(current_view) + if(backup) + playsound(src, 'sound/machines/terminal/terminal_prompt.ogg', 50, 0) + var/datum/component/nanites/nanites = backup.nanites + var/datum/nanite_program/P = nanites.programs[text2num(params["program_id"])] + var/datum/nanite_rule/rule = P.rules[text2num(params["rule_id"])] + rule.remove() + + 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/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 + var/datum/component/nanites/nanites + var/obj/machinery/computer/nanite_cloud_controller/storage + var/datum/techweb/techweb + +/datum/nanite_cloud_backup/New(obj/machinery/computer/nanite_cloud_controller/_storage) + storage = _storage + storage.cloud_backups += src + SSnanites.cloud_backups += src + +/datum/nanite_cloud_backup/Destroy() + storage.cloud_backups -= src + SSnanites.cloud_backups -= src + return ..() diff --git a/modular_zubbers/code/modules/nanites/machines/nanite_program_hub.dm b/modular_zubbers/code/modules/nanites/machines/nanite_program_hub.dm new file mode 100644 index 00000000000..4b3ec8d3a72 --- /dev/null +++ b/modular_zubbers/code/modules/nanites/machines/nanite_program_hub.dm @@ -0,0 +1,181 @@ +/obj/machinery/nanite_program_hub + name = "nanite program hub" + desc = "Compiles nanite programs from the techweb servers and downloads them into disks." + icon = 'modular_zubbers/icons/obj/machines/nanite_machines.dmi' + icon_state = "nanite_program_hub" + use_power = IDLE_POWER_USE + anchored = TRUE + density = TRUE + circuit = /obj/item/circuitboard/machine/nanite_program_hub + + var/obj/item/disk/nanite_program/disk + var/datum/techweb/linked_techweb + var/current_category = "Main" + var/detail_view = TRUE + var/categories = list( + list(name = "Utility Nanites"), + list(name = "Medical Nanites"), + list(name = "Sensor Nanites"), + list(name = "Augmentation Nanites"), + list(name = "Suppression Nanites"), + list(name = "Weaponized Nanites"), + list(name = "Protocols"), + ) + +/obj/machinery/nanite_program_hub/Initialize(mapload, obj/item/circuitboard/C) + . = ..() + if(!CONFIG_GET(flag/no_default_techweb_link) && !linked_techweb) + CONNECT_TO_RND_SERVER_ROUNDSTART(linked_techweb, src) + register_context() + +/obj/machinery/nanite_program_hub/examine(mob/user) + . = ..() + . += span_notice("Use with a linked multitool to link to a techweb server.") + +/obj/machinery/nanite_program_hub/add_context(atom/source, list/context, obj/item/held_item, mob/user) + . = ..() + if(isnull(held_item)) + return + if(held_item.tool_behaviour == TOOL_SCREWDRIVER) + context[SCREENTIP_CONTEXT_LMB] = "Open Panel" + return CONTEXTUAL_SCREENTIP_SET + if(held_item.tool_behaviour == TOOL_MULTITOOL) + context[SCREENTIP_CONTEXT_LMB] = "Upload Techweb" + return CONTEXTUAL_SCREENTIP_SET + +/obj/machinery/nanite_program_hub/update_overlays() + . = ..() + if((machine_stat & (NOPOWER|MAINT|BROKEN)) || panel_open) + return + . += "nanite_program_hub_on" + . += emissive_appearance(icon, "nanite_program_hub_on", src, alpha = src.alpha) + +/obj/machinery/nanite_program_hub/item_interaction(mob/living/user, obj/item/tool, list/modifiers) + . = ..() + if(!istype(tool, /obj/item/disk/nanite_program)) + return NONE + var/obj/item/disk/nanite_program/new_disk = tool + if(!user.transferItemToLoc(new_disk, src)) + return NONE + to_chat(user, span_notice("You insert [new_disk] into [src]")) + playsound(src, 'sound/machines/terminal/terminal_insert_disc.ogg', 50, FALSE) + if(disk) + eject(user) + disk = new_disk + return ITEM_INTERACT_SUCCESS + +/obj/machinery/nanite_program_hub/multitool_act(mob/living/user, obj/item/multitool/tool) + if(!QDELETED(tool.buffer) && istype(tool.buffer, /datum/techweb)) + linked_techweb = tool.buffer + balloon_alert(user, "linked!") + update_static_data_for_all_viewers() + return TRUE + +/obj/machinery/nanite_program_hub/screwdriver_act(mob/living/user, obj/item/I) + if(..()) + return TRUE + + return default_deconstruction_screwdriver(user, "nanite_program_hub_t", "nanite_program_hub", I) + +/obj/machinery/nanite_program_hub/crowbar_act(mob/living/user, obj/item/I) + if(..()) + return TRUE + + return default_deconstruction_crowbar(I) + +/obj/machinery/nanite_program_hub/proc/eject(mob/living/user) + if(!disk) + return + if(!istype(user) || !Adjacent(user) || !user.put_in_active_hand(disk)) + disk.forceMove(drop_location()) + disk = null + +/obj/machinery/nanite_program_hub/attack_hand_secondary(mob/user, list/modifiers) + . = ..() + if(disk && user.can_perform_action(src, !issilicon(user))) + to_chat(user, span_notice("You take out [disk] from [src].")) + eject(user) + return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN + +/obj/machinery/nanite_program_hub/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "NaniteProgramHub", name) + ui.open() + +/obj/machinery/nanite_program_hub/ui_data() + var/list/data = list() + if(disk) + data["has_disk"] = TRUE + var/list/disk_data = list() + var/datum/nanite_program/P = disk.program + if(P) + data["has_program"] = TRUE + disk_data["name"] = P.name + disk_data["desc"] = P.desc + data["disk"] = disk_data + else + data["has_disk"] = FALSE + + data["detail_view"] = detail_view + + return data + +/obj/machinery/nanite_program_hub/ui_static_data(mob/user) + var/list/data = list() + if(linked_techweb) + data["techweb"] = list( + "name" = linked_techweb.id, + "organization" = linked_techweb.organization, + ) + data["programs"] = list() + for(var/i in linked_techweb.researched_designs) + var/datum/design/nanites/D = SSresearch.techweb_design_by_id(i) + if(!istype(D)) + continue + var/cat_name = D.category[1] //just put them in the first category fuck it + if(isnull(data["programs"][cat_name])) + data["programs"][cat_name] = list() + var/list/program_design = list() + program_design["id"] = D.id + program_design["name"] = D.name + program_design["desc"] = D.desc + data["programs"][cat_name] += list(program_design) + + if(!length(data["programs"])) + data["programs"] = null + + return data + +/obj/machinery/nanite_program_hub/ui_act(action, params) + . = ..() + if(.) + return + switch(action) + if("eject") + eject(usr) + . = TRUE + if("download") + if(!disk) + return + var/datum/design/nanites/downloaded = linked_techweb.isDesignResearchedID(params["program_id"]) //check if it's a valid design + if(!istype(downloaded)) + return + if(disk.program) + qdel(disk.program) + disk.program = new downloaded.program_type + disk.update_name() + playsound(src, 'sound/machines/terminal/terminal_prompt.ogg', 25, FALSE) + . = TRUE + if("refresh") + update_static_data(usr) + . = TRUE + if("toggle_details") + detail_view = !detail_view + . = TRUE + if("clear") + if(disk?.program) + qdel(disk.program) + disk.program = null + disk.name = initial(disk.name) + . = TRUE diff --git a/modular_zubbers/code/modules/nanites/machines/nanite_programmer.dm b/modular_zubbers/code/modules/nanites/machines/nanite_programmer.dm new file mode 100644 index 00000000000..979a89f7988 --- /dev/null +++ b/modular_zubbers/code/modules/nanites/machines/nanite_programmer.dm @@ -0,0 +1,178 @@ +/obj/machinery/nanite_programmer + name = "nanite programmer" + desc = "A device that can edit nanite program disks to adjust their functionality." + icon = 'modular_zubbers/icons/obj/machines/nanite_machines.dmi' + icon_state = "nanite_programmer" + use_power = IDLE_POWER_USE + anchored = TRUE + density = TRUE + circuit = /obj/item/circuitboard/machine/nanite_programmer + var/obj/item/disk/nanite_program/disk + var/datum/nanite_program/program + +/obj/machinery/nanite_programmer/Initialize(mapload, obj/item/circuitboard/C) + . = ..() + become_hearing_sensitive(trait_source = ROUNDSTART_TRAIT) + +/obj/machinery/nanite_programmer/update_overlays() + . = ..() + if((machine_stat & (NOPOWER|MAINT|BROKEN)) || panel_open) + return + . += "nanite_programmer_on" + . += emissive_appearance(icon, "nanite_programmer_on", src, alpha = src.alpha) + +/obj/machinery/nanite_programmer/item_interaction(mob/living/user, obj/item/tool, list/modifiers) + . = ..() + if(!istype(tool, /obj/item/disk/nanite_program)) + return NONE + var/obj/item/disk/nanite_program/new_disk = tool + if(!user.transferItemToLoc(new_disk, src)) + return NONE + to_chat(user, span_notice("You insert [new_disk] into [src]")) + playsound(src, 'sound/machines/terminal/terminal_insert_disc.ogg', 50, FALSE) + if(disk) + eject(user) + disk = new_disk + program = new_disk.program + return ITEM_INTERACT_SUCCESS + +/obj/machinery/nanite_programmer/screwdriver_act(mob/living/user, obj/item/I) + if(..()) + return TRUE + + return default_deconstruction_screwdriver(user, "nanite_programmer_t", "nanite_programmer", I) + +/obj/machinery/nanite_programmer/crowbar_act(mob/living/user, obj/item/I) + if(..()) + return TRUE + + return default_deconstruction_crowbar(I) + +/obj/machinery/nanite_programmer/proc/eject(mob/living/user) + if(!disk) + return + if(!istype(user) || !Adjacent(user) || !user.put_in_active_hand(disk)) + disk.forceMove(drop_location()) + disk = null + program = null + +/obj/machinery/nanite_programmer/attack_hand_secondary(mob/user, list/modifiers) + . = ..() + if(disk && user.can_perform_action(src, !issilicon(user))) + to_chat(user, span_notice("You take out [disk] from [src].")) + eject(user) + return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN + +/obj/machinery/nanite_programmer/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "NaniteProgrammer", name) + ui.open() + +/obj/machinery/nanite_programmer/ui_data() + var/list/data = list() + data["has_disk"] = istype(disk) + data["has_program"] = istype(program) + if(program) + data["name"] = program.name + data["desc"] = program.desc + data["use_rate"] = program.use_rate + data["can_trigger"] = program.can_trigger + data["trigger_cost"] = program.trigger_cost + data["trigger_cooldown"] = program.trigger_cooldown / 10 + + data["activated"] = program.activated + data["activation_code"] = program.activation_code + data["deactivation_code"] = program.deactivation_code + data["kill_code"] = program.kill_code + data["trigger_code"] = program.trigger_code + data["timer_restart"] = program.timer_restart / 10 + data["timer_shutdown"] = program.timer_shutdown / 10 + data["timer_trigger"] = program.timer_trigger / 10 + data["timer_trigger_delay"] = program.timer_trigger_delay / 10 + + var/list/extra_settings = program.get_extra_settings_frontend() + data["extra_settings"] = extra_settings + if(LAZYLEN(extra_settings)) + data["has_extra_settings"] = TRUE + + return data + +/obj/machinery/nanite_programmer/ui_act(action, params) + . = ..() + if(.) + return + switch(action) + if("eject") + eject(usr) + . = TRUE + if("toggle_active") + playsound(src, "terminal_type", 25, FALSE) + program.activated = !program.activated //we don't use the activation procs since we aren't in a mob + investigate_log("[key_name(usr)] edited [program.name]'s initial activation status into [program.activated ? "Activated" : "Deactivated"]", INVESTIGATE_NANITES) + . = TRUE + if("set_code") + var/new_code = text2num(params["code"]) + playsound(src, "terminal_type", 25, FALSE) + var/target_code = params["target_code"] + switch(target_code) + if("activation") + program.activation_code = clamp(round(new_code, 1),0,9999) + investigate_log("[key_name(usr)] edited [program.name]'s activation code into [program.activation_code]", INVESTIGATE_NANITES) + if("deactivation") + program.deactivation_code = clamp(round(new_code, 1),0,9999) + investigate_log("[key_name(usr)] edited [program.name]'s deactivation code into [program.deactivation_code]", INVESTIGATE_NANITES) + if("kill") + program.kill_code = clamp(round(new_code, 1),0,9999) + investigate_log("[key_name(usr)] edited [program.name]'s kill code into [program.kill_code]", INVESTIGATE_NANITES) + if("trigger") + program.trigger_code = clamp(round(new_code, 1),0,9999) + investigate_log("[key_name(usr)] edited [program.name]'s trigger code into [program.trigger_code]", INVESTIGATE_NANITES) + . = TRUE + if("set_extra_setting") + program.set_extra_setting(params["target_setting"], params["value"]) + investigate_log("[key_name(usr)] edited [program.name]'s extra setting '[params["target_setting"]]' into [params["value"]]", INVESTIGATE_NANITES) + playsound(src, "terminal_type", 25, FALSE) + . = TRUE + if("set_restart_timer") + var/timer = text2num(params["delay"]) + if(!isnull(timer)) + playsound(src, "terminal_type", 25, FALSE) + timer = clamp(round(timer, 1), 0, 3600) + timer *= 10 //convert to deciseconds + program.timer_restart = timer + investigate_log("[key_name(usr)] edited [program.name]'s restart timer into [timer/10] s", INVESTIGATE_NANITES) + . = TRUE + if("set_shutdown_timer") + var/timer = text2num(params["delay"]) + if(!isnull(timer)) + playsound(src, "terminal_type", 25, FALSE) + timer = clamp(round(timer, 1), 0, 3600) + timer *= 10 //convert to deciseconds + program.timer_shutdown = timer + investigate_log("[key_name(usr)] edited [program.name]'s shutdown timer into [timer/10] s", INVESTIGATE_NANITES) + . = TRUE + if("set_trigger_timer") + var/timer = text2num(params["delay"]) + if(!isnull(timer)) + playsound(src, "terminal_type", 25, FALSE) + timer = clamp(round(timer, 1), 0, 3600) + timer *= 10 //convert to deciseconds + program.timer_trigger = timer + investigate_log("[key_name(usr)] edited [program.name]'s trigger timer into [timer/10] s", INVESTIGATE_NANITES) + . = TRUE + if("set_timer_trigger_delay") + var/timer = text2num(params["delay"]) + if(!isnull(timer)) + playsound(src, "terminal_type", 25, FALSE) + timer = clamp(round(timer, 1), 0, 3600) + timer *= 10 //convert to deciseconds + program.timer_trigger_delay = timer + investigate_log("[key_name(usr)] edited [program.name]'s trigger delay timer into [timer/10] s", INVESTIGATE_NANITES) + . = TRUE + +/obj/machinery/nanite_programmer/Hear(message, atom/movable/speaker, message_language, raw_message, radio_freq, list/spans, list/message_mods = list(), message_range=0) + . = ..() + var/static/regex/when = regex("(?:^\\W*when|when\\W*$)", "i") //starts or ends with when + if(findtext(raw_message, when) && !istype(speaker, /obj/machinery/nanite_programmer)) + say("When you code it!!") diff --git a/modular_zubbers/code/modules/nanites/machines/public_chamber.dm b/modular_zubbers/code/modules/nanites/machines/public_chamber.dm new file mode 100644 index 00000000000..09904de4246 --- /dev/null +++ b/modular_zubbers/code/modules/nanites/machines/public_chamber.dm @@ -0,0 +1,207 @@ +/obj/machinery/public_nanite_chamber + name = "public nanite chamber" + desc = "A device that can rapidly implant cloud-synced nanites without an external operator." + circuit = /obj/item/circuitboard/machine/public_nanite_chamber + icon = 'icons/obj/machines/bci_implanter.dmi' + icon_state = "bci_implanter" + base_icon_state = "bci_implanter" + layer = ABOVE_WINDOW_LAYER + use_power = IDLE_POWER_USE + anchored = TRUE + density = TRUE + idle_power_usage = 50 + active_power_usage = 300 + + var/cloud_id = 1 + var/locked = FALSE + var/breakout_time = 1200 + var/busy = FALSE + var/busy_icon_state + var/message_cooldown = 0 + +/obj/machinery/public_nanite_chamber/Initialize(mapload, obj/item/circuitboard/C) + . = ..() + occupant_typecache = GLOB.typecache_living + +/obj/machinery/public_nanite_chamber/RefreshParts() + . = ..() + var/obj/item/circuitboard/machine/public_nanite_chamber/board = circuit + if(board) + cloud_id = board.cloud_id + +/obj/machinery/public_nanite_chamber/proc/set_busy(status, working_icon) + busy = status + busy_icon_state = working_icon + update_appearance() + +/obj/machinery/public_nanite_chamber/proc/inject_nanites(mob/living/attacker) + if(machine_stat & (NOPOWER|BROKEN)) + return + if((machine_stat & MAINT) || panel_open) + return + if(!occupant || busy) + return + + var/locked_state = locked + locked = TRUE + + //TODO OMINOUS MACHINE SOUNDS + set_busy(TRUE, "[initial(icon_state)]_raising") + addtimer(CALLBACK(src, .proc/set_busy, TRUE, "[initial(icon_state)]_active"),20) + addtimer(CALLBACK(src, .proc/set_busy, TRUE, "[initial(icon_state)]_falling"),60) + addtimer(CALLBACK(src, .proc/complete_injection, locked_state, attacker),80) + +/obj/machinery/public_nanite_chamber/proc/complete_injection(locked_state, mob/living/attacker) + //TODO MACHINE DING + locked = locked_state + set_busy(FALSE) + if(!occupant) + return + if(attacker) + occupant.investigate_log("was injected with nanites with cloud ID [cloud_id] by [key_name(attacker)] using [src] at [AREACOORD(src)].", INVESTIGATE_NANITES) + log_combat(attacker, occupant, "injected", null, "with nanites via [src]") + occupant.AddComponent(/datum/component/nanites, 75, cloud_id) + +/obj/machinery/public_nanite_chamber/proc/change_cloud(mob/living/attacker) + if(machine_stat & (NOPOWER|BROKEN)) + return + if((machine_stat & MAINT) || panel_open) + return + if(!occupant || busy) + return + + var/locked_state = locked + locked = TRUE + + set_busy(TRUE, "[initial(icon_state)]_raising") + addtimer(CALLBACK(src, .proc/set_busy, TRUE, "[initial(icon_state)]_active"),20) + addtimer(CALLBACK(src, .proc/set_busy, TRUE, "[initial(icon_state)]_falling"),40) + addtimer(CALLBACK(src, .proc/complete_cloud_change, locked_state, attacker),60) + +/obj/machinery/public_nanite_chamber/proc/complete_cloud_change(locked_state, mob/living/attacker) + locked = locked_state + set_busy(FALSE) + if(!occupant) + return + if(attacker) + occupant.investigate_log("had their nanite cloud ID changed into [cloud_id] by [key_name(attacker)] using [src] at [AREACOORD(src)].", INVESTIGATE_NANITES) + SEND_SIGNAL(occupant, COMSIG_NANITE_SET_CLOUD, cloud_id) + +/obj/machinery/public_nanite_chamber/update_icon_state() + //running and someone in there + if(occupant) + icon_state = busy ? busy_icon_state : "[base_icon_state]_occupied" + return ..() + //running + icon_state = "[base_icon_state][state_open ? "_open" : null]" + return ..() + +/obj/machinery/public_nanite_chamber/update_overlays() + . = ..() + if((machine_stat & MAINT) || panel_open) + . += "maint" + return + if(machine_stat & (NOPOWER|BROKEN)) + return + + if(busy || locked) + . += "red" + if(locked) + . += "bolted" + return + . += "green" + +/obj/machinery/public_nanite_chamber/proc/toggle_open(mob/user) + if(panel_open) + to_chat(user, span_notice("Close the maintenance panel first.")) + return + + if(state_open) + close_machine(null, user) + return + + else if(locked) + to_chat(user, span_notice("The bolts are locked down, securing the door shut.")) + return + + open_machine() + +/obj/machinery/public_nanite_chamber/container_resist_act(mob/living/user) + if(!locked) + open_machine() + return + if(busy) + return + user.changeNext_move(CLICK_CD_BREAKOUT) + user.last_special = world.time + CLICK_CD_BREAKOUT + user.visible_message(span_notice("You see [user] kicking against the door of [src]!"), \ + span_notice("You lean on the back of [src] and start pushing the door open... (this will take about [DisplayTimeText(breakout_time)].)"), \ + span_hear("You hear a metallic creaking from [src].")) + if(do_after(user,(breakout_time), target = src)) + if(!user || user.stat != CONSCIOUS || user.loc != src || state_open || !locked || busy) + return + locked = FALSE + user.visible_message(span_warning("[user] successfully broke out of [src]!"), \ + span_notice("You successfully break out of [src]!")) + open_machine() + +/obj/machinery/public_nanite_chamber/close_machine(mob/living/carbon/user, density_to_set = TRUE) + if(!state_open) + return FALSE + + ..() + + . = TRUE + + addtimer(CALLBACK(src, .proc/try_inject_nanites, user), 3 SECONDS) //If someone is shoved in give them a chance to get out before the injection starts + +/obj/machinery/public_nanite_chamber/proc/try_inject_nanites(mob/living/attacker) + if(occupant) + var/mob/living/L = occupant + if(SEND_SIGNAL(L, COMSIG_HAS_NANITES)) + var/datum/component/nanites/nanites = L.GetComponent(/datum/component/nanites) + if(nanites && nanites.cloud_id != cloud_id) + change_cloud(attacker) + return + if(CAN_HAVE_NANITES(L)) + inject_nanites(attacker) + +/obj/machinery/public_nanite_chamber/open_machine(drop = TRUE, density_to_set = FALSE) + . = ..() + if(state_open) + return FALSE + + ..() + + return TRUE + +/obj/machinery/public_nanite_chamber/relaymove(mob/living/user, direction) + if(user.stat || locked) + if(message_cooldown <= world.time) + message_cooldown = world.time + 50 + to_chat(user, span_warning("[src]'s door won't budge!")) + return + open_machine() + +/obj/machinery/public_nanite_chamber/attackby(obj/item/I, mob/user, params) + if(!occupant && default_deconstruction_screwdriver(user, icon_state, icon_state, I))//sent icon_state is irrelevant... + update_appearance()//..since we're updating the icon here, since the scanner can be unpowered when opened/closed + return + + if(default_pry_open(I)) + return + + if(default_deconstruction_crowbar(I)) + return + + return ..() + +/obj/machinery/public_nanite_chamber/interact(mob/user) + toggle_open(user) + +/obj/machinery/public_nanite_chamber/mouse_drop_receive(mob/living/M, mob/user, params) + if(!user.can_perform_action(src, FORBID_TELEKINESIS_REACH | SILENT_ADJACENCY) || !iscarbon(M)) + return + if(close_machine(M, user)) + log_combat(user, M, "inserted", null, "into [src].") + add_fingerprint(user) diff --git a/modular_zubbers/code/modules/nanites/nanite_programs.dm b/modular_zubbers/code/modules/nanites/nanite_programs.dm new file mode 100644 index 00000000000..0b26b4e79f0 --- /dev/null +++ b/modular_zubbers/code/modules/nanites/nanite_programs.dm @@ -0,0 +1,335 @@ +/datum/nanite_program + var/name = "Generic Nanite Program" + var/desc = "Warn a coder if you can read this." + + var/datum/component/nanites/nanites + var/mob/living/host_mob + + var/use_rate = 0 //Amount of nanites used while active + var/unique = TRUE //If there can be more than one copy in the same nanites + var/can_trigger = FALSE //If the nanites have a trigger function (used for the programming UI) + var/trigger_cost = 0 //Amount of nanites required to trigger + var/trigger_cooldown = 50 //Deciseconds required between each trigger activation + var/next_trigger = 0 //World time required for the next trigger activation + + var/program_flags = NONE + var/passive_enabled = FALSE //If the nanites have an on/off-style effect, it's tracked by this var + + var/list/rogue_types = list(/datum/nanite_program/glitch) //What this can turn into if it glitches. + //As a rule of thumb, these should be: + //A: simpler + //B: negative + //C: affecting the same parts of the body, roughly + //B is mostly a consequence of A: it's always going to be simpler to cause damage than to repair it, so a software bug will not randomly make the flesh eating + //nanites learn how to repair cells. + //Given enough glitch-swapping you'll end up with stuff like necrotic or toxic nanites, which are very simple as they just try to eat what's in front of them + //or just lie around polluting the blood + + + //The following vars are customizable + var/activated = TRUE //If FALSE, the program won't process, disables passive effects, can't trigger and doesn't consume nanites + + var/timer_restart = 0 //When deactivated, the program will wait X deciseconds before self-reactivating. Also works if the program begins deactivated. + var/timer_shutdown = 0 //When activated, the program will wait X deciseconds before self-deactivating. Also works if the program begins activated. + var/timer_trigger = 0 //[Trigger only] While active, the program will attempt to trigger once every x deciseconds. + var/timer_trigger_delay = 0 //[Trigger only] While active, the program will delay trigger signals by X deciseconds. + + //Indicates the next world.time tick where these timers will act + var/timer_restart_next = 0 + var/timer_shutdown_next = 0 + var/timer_trigger_next = 0 + var/timer_trigger_delay_next = 0 + + //Signal codes, these handle remote input to the nanites. If set to 0 they'll ignore signals. + var/activation_code = 0 //Code that activates the program [1-9999] + var/deactivation_code = 0 //Code that deactivates the program [1-9999] + var/kill_code = 0 //Code that permanently removes the program [1-9999] + var/trigger_code = 0 //Code that triggers the program (if available) [1-9999] + + //Extra settings + ///Don't ever override this or I will come to your house and stand menacingly behind a bush + 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 + +/datum/nanite_program/New() + . = ..() + register_extra_settings() + +/datum/nanite_program/Destroy() + extra_settings = null + if(host_mob) + if(activated) + deactivate() + if(passive_enabled) + disable_passive_effect() + on_mob_remove() + if(nanites) + nanites.programs -= src + for(var/datum/nanite_rule/rule as anything in rules) + rule.remove() + rules.Cut() + return ..() + +/datum/nanite_program/proc/copy() + var/datum/nanite_program/new_program = new type() + copy_programming(new_program, TRUE) + + return new_program + +/datum/nanite_program/proc/copy_programming(datum/nanite_program/target, copy_activated = TRUE) + if(copy_activated) + target.activated = activated + target.timer_restart = timer_restart + target.timer_shutdown = timer_shutdown + target.timer_trigger = timer_trigger + target.timer_trigger_delay = timer_trigger_delay + target.activation_code = activation_code + target.deactivation_code = deactivation_code + target.kill_code = kill_code + target.trigger_code = trigger_code + + target.rules = list() + 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) + +/// Register extra settings by overriding this. +/// +/// `extra_settings[name] = new typepath()` for each extra setting. +/datum/nanite_program/proc/register_extra_settings() + return + +///You can override this if you need to have special behavior after setting certain settings. +/datum/nanite_program/proc/set_extra_setting(setting, value) + var/datum/nanite_extra_setting/ES = extra_settings[setting] + return ES.set_value(value) + +///You probably shouldn't be overriding this one, but I'm not a cop. +/datum/nanite_program/proc/get_extra_setting_value(setting) + var/datum/nanite_extra_setting/ES = extra_settings[setting] + return ES.get_value() + +///Used for getting information about the extra settings to the frontend +/datum/nanite_program/proc/get_extra_settings_frontend() + var/list/out = list() + for(var/name in extra_settings) + var/datum/nanite_extra_setting/ES = extra_settings[name] + out += ES.get_frontend_list(name) + return out + +///Copy of the list instead of direct reference for obvious reasons +/datum/nanite_program/proc/copy_extra_settings_to(datum/nanite_program/target) + var/list/copy_list = list() + for(var/ns_name in extra_settings) + var/datum/nanite_extra_setting/extra_setting = extra_settings[ns_name] + copy_list[ns_name] = extra_setting.get_copy() + target.extra_settings = copy_list + +/datum/nanite_program/proc/on_add(datum/component/nanites/_nanites) + nanites = _nanites + if(nanites.host_mob) + on_mob_add() + +/datum/nanite_program/proc/on_mob_add() + host_mob = nanites.host_mob + if(activated) //apply activation effects depending on initial status; starts the restart and shutdown timers + activate() + else + deactivate() + +/datum/nanite_program/proc/on_mob_remove() + return + +/datum/nanite_program/proc/toggle() + if(!activated) + activate() + else + deactivate() + +/datum/nanite_program/proc/activate() + activated = TRUE + if(timer_shutdown) + timer_shutdown_next = world.time + timer_shutdown + +/datum/nanite_program/proc/deactivate() + if(passive_enabled) + disable_passive_effect() + activated = FALSE + if(timer_restart) + timer_restart_next = world.time + timer_restart + +/datum/nanite_program/proc/on_process() + if(!activated) + if(timer_restart_next && world.time > timer_restart_next) + activate() + timer_restart_next = 0 + return + + 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) + enable_passive_effect() + active_effect() + else + if(passive_enabled) + disable_passive_effect() + +//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(!all_rules_required && rule.check_rule()) + return TRUE + if(all_rules_required && !rule.check_rule()) + return FALSE + return all_rules_required ? TRUE : FALSE + +//Constantly procs as long as the program is active +/datum/nanite_program/proc/active_effect() + return + +//Procs once when the program activates +/datum/nanite_program/proc/enable_passive_effect() + passive_enabled = TRUE + +//Procs once when the program deactivates +/datum/nanite_program/proc/disable_passive_effect() + passive_enabled = FALSE + +//Checks conditions then fires the nanite trigger effect +/datum/nanite_program/proc/trigger(delayed = FALSE, comm_message) + if(!can_trigger) + return + if(!activated) + return + if(timer_trigger_delay && !delayed) + timer_trigger_delay_next = world.time + timer_trigger_delay + return + if(world.time < next_trigger) + return + if(!check_conditions()) + return + if(!consume_nanites(trigger_cost)) + return + next_trigger = world.time + trigger_cooldown + on_trigger(comm_message) + +//Nanite trigger effect, requires can_trigger to be used +/datum/nanite_program/proc/on_trigger(comm_message) + return + +/datum/nanite_program/proc/consume_nanites(amount, force = FALSE) + return nanites.consume_nanites(amount, force) + +/datum/nanite_program/proc/on_emp(severity) + if(program_flags & NANITE_EMP_IMMUNE) + return + if(prob(80 / severity)) + 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) + qdel(src) + +/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() + return + +/datum/nanite_program/proc/software_error(type) + if(!type) + type = rand(1,5) + switch(type) + if(1) + host_mob.investigate_log("[src] nanite program was deleted by software error.", INVESTIGATE_NANITES) + qdel(src) //kill switch + return + if(2) //deprogram codes + host_mob.investigate_log("[src] nanite program was de-programmed by software error.", INVESTIGATE_NANITES) + activation_code = 0 + deactivation_code = 0 + kill_code = 0 + trigger_code = 0 + if(3) + host_mob.investigate_log("[src] nanite program was toggled by software error.", INVESTIGATE_NANITES) + toggle() //enable/disable + if(4) + 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 + 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) + qdel(src) + +/datum/nanite_program/proc/receive_signal(code, source) + if(activation_code && code == activation_code && !activated) + activate() + host_mob.investigate_log("'s [name] nanite program was activated by [source] with code [code].", INVESTIGATE_NANITES) + else if(deactivation_code && code == deactivation_code && activated) + deactivate() + host_mob.investigate_log("'s [name] nanite program was deactivated by [source] with code [code].", INVESTIGATE_NANITES) + 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) + host_mob.investigate_log("'s [name] nanite program was deleted by [source] with code [code].", INVESTIGATE_NANITES) + qdel(src) + +///A nanite program containing a behaviour protocol. Only one protocol of each class can be active at once. +/datum/nanite_program/protocol + name = "Nanite Protocol" + var/protocol_class = NONE + +/datum/nanite_program/protocol/check_conditions() + . = ..() + for(var/protocol in nanites.protocols) + var/datum/nanite_program/protocol/P = protocol + if(P != src && P.activated && P.protocol_class == protocol_class) + return FALSE + +/datum/nanite_program/protocol/on_add(datum/component/nanites/_nanites) + ..() + nanites.protocols += src + +/datum/nanite_program/protocol/Destroy() + if(nanites) + nanites.protocols -= src + return ..() diff --git a/modular_zubbers/code/modules/nanites/nanite_programs/buffing.dm b/modular_zubbers/code/modules/nanites/nanite_programs/buffing.dm new file mode 100644 index 00000000000..05e2ba311cf --- /dev/null +++ b/modular_zubbers/code/modules/nanites/nanite_programs/buffing.dm @@ -0,0 +1,231 @@ +//Programs that buff the host in generally passive ways. + +/datum/nanite_program/nervous + name = "Nerve Support" + desc = "The nanites act as a secondary nervous system, reducing the amount of time the host is stunned." + use_rate = 1.5 + rogue_types = list(/datum/nanite_program/nerve_decay) + +/datum/nanite_program/nervous/enable_passive_effect() + . = ..() + if(ishuman(host_mob)) + var/mob/living/carbon/human/H = host_mob + H.physiology.stun_mod *= 0.5 + +/datum/nanite_program/nervous/disable_passive_effect() + . = ..() + if(ishuman(host_mob)) + var/mob/living/carbon/human/H = host_mob + H.physiology.stun_mod *= 2 + +/datum/nanite_program/dermal_armor + name = "Dermal Hardening" + desc = "The nanites form a mesh under the host's skin, protecting them from melee and bullet impacts. \ + Each hit costs nanites two times to damage, but the more nanites you have, the more protection you get. \ + 50 nanites = 1 flat armor, with each hit costing two times it's damage in nanites. \ + Will not turn on with other dermal armor programs." + use_rate = 2 + rogue_types = list(/datum/nanite_program/skin_decay) + var/damage_type = BRUTE + var/signal = COMSIG_MOB_ALWAYS_APPLY_DAMAGE + var/max_damage_resist = 30 + var/current_flat_mod = 0 + var/rgb_color = list(255, 0, 0) + var/filter_name = "nanite_armor_outline" + var/hide_timer + var/cleanup_timer + /// how much to multiply the nanite cost on the damage received + var/cost_modifier = 2 + +/datum/nanite_program/dermal_armor/activate() + if(check_duplicate_programs()) + return + if(!ishuman(host_mob)) + return + . = ..() + +/datum/nanite_program/dermal_armor/enable_passive_effect() + . = ..() + RegisterSignal(host_mob, signal, PROC_REF(pre_damage)) + +/datum/nanite_program/dermal_armor/proc/check_duplicate_programs() + for(var/program in nanites.programs) + if(istype(program, type) && program != src) + return TRUE + return FALSE + +/datum/nanite_program/dermal_armor/disable_passive_effect() + . = ..() + UnregisterSignal(host_mob, signal) + + delete_hide_timer() + delete_cleanup_timer() + + update_damage_modifier(0) + +/datum/nanite_program/dermal_armor/active_effect() + // 1 for every 50 nanites, up to 30 flat armor, max of 10 at 500 nanites + var/nanite_armor = ceil(nanites.nanite_volume * 0.02) + var/clamped_armor = clamp(nanite_armor, 1, max_damage_resist) + update_damage_modifier(clamped_armor) + +// update and handle the damage modifier before taking damage, thus only when needed +/datum/nanite_program/dermal_armor/proc/pre_damage( + mob/living/damaged_mob, + damage_amount, + damagetype, + def_zone, + blocked, + wound_bonus, + bare_wound_bonus, + sharpness, + attack_direction, + attacking_item, + wound_clothing + ) + SIGNAL_HANDLER + if(damagetype != damage_type) + return + var/mob/living/carbon/human/humie = host_mob + if(!istype(humie) || !humie.physiology || !nanites) + return + // since we are likely to already have a damage modifier, we need to calculate the actual damage received + // as the damage_amount received here is already modified by the flat_brute_mod + var/actual_damage = damage_amount + current_flat_mod + if(actual_damage >= nanites.nanite_volume) + playsound(host_mob, SFX_SHATTER) + host_mob.Knockdown(1 SECONDS) + to_chat(host_mob, span_warning("Something shatters under your skin, and you feel a sharp pain!")) + disable_passive_effect() + return + + // after this we don't need to prevent the rest of the code from running + INVOKE_ASYNC(src, PROC_REF(on_pre_damage), damagetype, actual_damage) + +/datum/nanite_program/dermal_armor/proc/on_pre_damage(damagetype, amount) + // 2 nanites for every 1 damage blocked, ensure that we don't spend extra nanites when armor is higher than damage + if(amount >= current_flat_mod) + consume_nanites(current_flat_mod * cost_modifier) + else + consume_nanites(amount * cost_modifier) + + var/damage_duration = (amount / current_flat_mod) * 2 SECONDS + var/timeleft = timeleft(hide_timer) + var/leftover_time = !isnull(timeleft) ? timeleft : 0 + // if we're already showing the outline, reset the timer + + if(hide_timer) + delete_hide_timer() + else + // how long to keep the color effect, while also changing color intensity based on damage + // only add the filter if we're not cleaning up, as that means we're already showing the outline which is animating to hide itself + if(cleanup_timer) + delete_cleanup_timer() + else + host_mob.add_filter(filter_name, 2, list("type" = "outline", size = 0)) + // control the intensity of the color based on how much damage we're taking vs the armor we have + var/rgb_color = damage_color(amount, current_flat_mod) + host_mob.transition_filter(filter_name, list("type" = "outline", color = rgb(rgb_color[1], rgb_color[2], rgb_color[3]), size = 0.5), 0.5 SECONDS) + + var/time_to_hide = clamp((damage_duration + leftover_time * 0.5) * 0.8, 0.5 SECONDS, 2 SECONDS) + var/difference = max(damage_duration - time_to_hide, 0.5 SECONDS) // spend 20% of the time to hide the outline + hide_timer = addtimer(CALLBACK(src, PROC_REF(hide_outline), difference), time_to_hide, TIMER_STOPPABLE) + +/datum/nanite_program/dermal_armor/proc/delete_hide_timer() + deltimer(hide_timer) + hide_timer = null + +/datum/nanite_program/dermal_armor/proc/delete_cleanup_timer() + deltimer(cleanup_timer) + cleanup_timer = null + +// modifies the RGB color based on the damage amount, making it more or less intense based on the damage +/datum/nanite_program/dermal_armor/proc/damage_color(amount, armor) + var/color_strength = clamp(amount / armor, 0.2, 1) + var/colors = list() + for(var/i in 1 to 3) + var/color = rgb_color[i] + colors += clamp(color * color_strength, 0, 255) + return colors + +// hiding the outline is a 2 step process as we have a delay before we start hiding it +// this is to prevent the outline from flickering when we're taking damage in quick succession +/datum/nanite_program/dermal_armor/proc/hide_outline(hide_time = 0.5 SECONDS) + hide_timer = null + host_mob.transition_filter(filter_name, list("type" = "outline", size = 0), hide_time) + cleanup_timer = addtimer(CALLBACK(src, PROC_REF(cleanup_outline)), hide_time, TIMER_STOPPABLE) + +/datum/nanite_program/dermal_armor/proc/cleanup_outline() + host_mob.remove_filter(filter_name) + cleanup_timer = null + +/datum/nanite_program/dermal_armor/proc/update_damage_modifier(amount) + var/mob/living/carbon/human/humie = host_mob + var/valid_change = clamp(amount, 1, 30) + humie.physiology.flat_brute_mod += current_flat_mod + current_flat_mod = valid_change + humie.physiology.flat_brute_mod -= valid_change + +/datum/nanite_program/dermal_armor/refractive + name = "Dermal Refractive Surface" + desc = "The nanites form a membrane above the host's skin, reducing the effect of laser and energy impacts. \ + Each hit costs nanites equilavent to damage, but the more nanites you have, the more protection you get. \ + 50 nanites = 1 flat armor, with each hit costing two times it's damage in nanites. \ + Will not turn on with other dermal armor programs." + damage_type = BURN + rgb_color = list(255, 255, 0) + +/datum/nanite_program/dermal_armor/refractive/update_damage_modifier(mob/living/carbon/human/humie, amount) + humie.physiology.flat_burn_mod -= current_flat_mod + current_flat_mod = amount + humie.physiology.flat_burn_mod += amount + +/datum/nanite_program/coagulating + name = "Vein Repressurization" + desc = "The nanites re-route circulating blood away from open wounds, dramatically reducing bleeding rate." + use_rate = 0.20 + rogue_types = list(/datum/nanite_program/suffocating) + +/datum/nanite_program/coagulating/enable_passive_effect() + . = ..() + if(ishuman(host_mob)) + var/mob/living/carbon/human/H = host_mob + H.physiology.bleed_mod *= 0.5 + +/datum/nanite_program/coagulating/disable_passive_effect() + . = ..() + if(ishuman(host_mob)) + var/mob/living/carbon/human/H = host_mob + H.physiology.bleed_mod *= 2 + +/datum/nanite_program/conductive + name = "Electric Conduction" + desc = "The nanites act as a grounding rod for electric shocks, protecting the host. Shocks can still damage the nanites themselves." + use_rate = 0.20 + program_flags = NANITE_SHOCK_IMMUNE + rogue_types = list(/datum/nanite_program/nerve_decay) + +/datum/nanite_program/conductive/enable_passive_effect() + . = ..() + ADD_TRAIT(host_mob, TRAIT_SHOCKIMMUNE, NANITES_TRAIT) + +/datum/nanite_program/conductive/disable_passive_effect() + . = ..() + REMOVE_TRAIT(host_mob, TRAIT_SHOCKIMMUNE, NANITES_TRAIT) + +/datum/nanite_program/mindshield + name = "Mental Barrier" + desc = "The nanites form a protective membrane around the host's brain, shielding them from abnormal influences while they're active." + use_rate = 0.40 + rogue_types = list(/datum/nanite_program/brain_decay, /datum/nanite_program/brain_misfire) + +/datum/nanite_program/mindshield/enable_passive_effect() + . = ..() + if(!host_mob.mind.has_antag_datum(/datum/antagonist/rev, TRUE)) //won't work if on a rev, to avoid having implanted revs. + ADD_TRAIT(host_mob, TRAIT_MINDSHIELD, NANITES_TRAIT) + host_mob.sec_hud_set_implants() + +/datum/nanite_program/mindshield/disable_passive_effect() + . = ..() + REMOVE_TRAIT(host_mob, TRAIT_MINDSHIELD, NANITES_TRAIT) + host_mob.sec_hud_set_implants() diff --git a/modular_zubbers/code/modules/nanites/nanite_programs/healing.dm b/modular_zubbers/code/modules/nanites/nanite_programs/healing.dm new file mode 100644 index 00000000000..b5c972ca43d --- /dev/null +++ b/modular_zubbers/code/modules/nanites/nanite_programs/healing.dm @@ -0,0 +1,190 @@ +//Programs that heal the host in some way. + +/datum/nanite_program/regenerative + name = "Accelerated Regeneration" + 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) + var/valid_bodytype = BODYTYPE_ORGANIC + var/valid_biotype = MOB_ORGANIC + var/healing_rate = NANITE_BIO_REGENERATION + var/always_active = FALSE + +/datum/nanite_program/regenerative/check_conditions() + if(always_active) + return TRUE + if(!host_mob.getBruteLoss() && !host_mob.getFireLoss()) + return FALSE + if(iscarbon(host_mob)) + var/mob/living/carbon/carbon = host_mob + var/list/parts = carbon.get_damaged_bodyparts(TRUE, TRUE, valid_bodytype) + if(!parts.len) + return FALSE + else if(!(host_mob.mob_biotypes & valid_biotype)) + return FALSE + return ..() + +/datum/nanite_program/regenerative/active_effect() + host_mob.heal_overall_damage(healing_rate, healing_rate, required_bodytype = valid_bodytype) + +/datum/nanite_program/regenerative/advanced + name = "Bio-Reconstruction" + desc = "The nanites manually repair and replace organic cells, acting much faster than normal regeneration. \ + However, this program cannot detect the difference between harmed and unharmed, causing it to consume nanites even if it has no effect." + use_rate = 5.5 + rogue_types = list(/datum/nanite_program/suffocating, /datum/nanite_program/necrotic) + healing_rate = NANITE_ADV_BIO_REGENERATION + always_active = TRUE + +/datum/nanite_program/regenerative/robotic + name = "Mechanical Repair" + 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 = 3 + rogue_types = list(/datum/nanite_program/necrotic) + valid_bodytype = BODYTYPE_ROBOTIC + valid_biotype = MOB_ROBOTIC + healing_rate = NANITE_ROBO_REGENERATION + +/datum/nanite_program/temperature + name = "Temperature Adjustment" + 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) + +/datum/nanite_program/temperature/check_conditions() + if(host_mob.bodytemperature > (host_mob.get_body_temp_normal(apply_change=FALSE) - 30) && host_mob.bodytemperature < (host_mob.get_body_temp_normal(apply_change=FALSE) + 30)) + return FALSE + return ..() + +/datum/nanite_program/temperature/active_effect() + var/target_temp = host_mob.get_body_temp_normal(apply_change=FALSE) + if(host_mob.bodytemperature > target_temp) + host_mob.adjust_bodytemperature(-40 * TEMPERATURE_DAMAGE_COEFFICIENT, target_temp) + else if(host_mob.bodytemperature < (target_temp + 1)) + host_mob.adjust_bodytemperature(40 * TEMPERATURE_DAMAGE_COEFFICIENT, 0, target_temp) + +/datum/nanite_program/purging + name = "Blood Purification" + desc = "The nanites purge toxins and chemicals from the host's bloodstream. Consumes nanites even if it has no effect." + use_rate = 1 + rogue_types = list(/datum/nanite_program/suffocating, /datum/nanite_program/necrotic) + var/healing_rate = NANITE_TOX_REGENERATION + var/purge_path = /datum/reagent + var/force_heal = FALSE + +/datum/nanite_program/purging/check_conditions() + . = ..() + if(!. || !host_mob.reagents) + return FALSE // No trying to purge simple mobs + +/datum/nanite_program/purging/active_effect() + host_mob.adjustToxLoss(-healing_rate, forced = force_heal) + for(var/datum/reagent/R as anything in host_mob.reagents.reagent_list) + if(!istype(R, purge_path)) + continue + host_mob.reagents.remove_reagent(R.type, 1) + +/datum/nanite_program/purging/advanced + name = "Selective Blood Purification" + desc = "The nanites purge toxins and dangerous chemicals from the host's bloodstream, while ignoring beneficial chemicals. \ + The added processing power required to analyze the chemicals severely increases the nanite consumption rate. Consumes nanites even if it has no effect." + use_rate = 2 + rogue_types = list(/datum/nanite_program/suffocating, /datum/nanite_program/necrotic) + healing_rate = NANITE_ADV_TOX_REGENERATION + purge_path = /datum/reagent/toxin + force_heal = TRUE + +/datum/nanite_program/blood_restoring + name = "Blood Regeneration" + 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) + +/datum/nanite_program/blood_restoring/check_conditions() + if(iscarbon(host_mob)) + var/mob/living/carbon/C = host_mob + if(C.blood_volume >= BLOOD_VOLUME_SAFE) + return FALSE + else + return FALSE + return ..() + +/datum/nanite_program/blood_restoring/active_effect() + if(iscarbon(host_mob)) + var/mob/living/carbon/C = host_mob + C.blood_volume += 2 + +/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. Will not consume nanites while it would not have an effect." + use_rate = 1.5 + rogue_types = list(/datum/nanite_program/brain_decay) + +/datum/nanite_program/brain_heal/check_conditions() + if(host_mob.get_organ_loss(ORGAN_SLOT_BRAIN) > 0) + return ..() + if(iscarbon(host_mob)) + var/mob/living/carbon/carbon = host_mob + if (carbon.has_trauma_type(resilience = TRAUMA_RESILIENCE_BASIC)) + return ..() + return FALSE + +/datum/nanite_program/brain_heal/active_effect() + host_mob.adjustOrganLoss(ORGAN_SLOT_BRAIN, -NANITE_BRAIN_REGENERATION) + if(iscarbon(host_mob) && prob(1)) + var/mob/living/carbon/C = host_mob + C.cure_trauma_type(resilience = TRAUMA_RESILIENCE_BASIC) + +/datum/nanite_program/brain_heal_advanced + name = "Neural Reimaging" + desc = "The nanites are able to backup and restore the host's neural connections, potentially replacing entire chunks of missing or damaged brain matter. Consumes nanites even if it has no effect." + use_rate = 3 + rogue_types = list(/datum/nanite_program/brain_decay, /datum/nanite_program/brain_misfire) + +/datum/nanite_program/brain_heal_advanced/active_effect() + host_mob.adjustOrganLoss(ORGAN_SLOT_BRAIN, -NANITE_ADV_BRAIN_REGENERATION) + if(iscarbon(host_mob) && prob(1)) + var/mob/living/carbon/C = host_mob + C.cure_trauma_type(resilience = TRAUMA_RESILIENCE_LOBOTOMY) + +/datum/nanite_program/defib + name = "Defibrillation" + desc = "The nanites shock the host's heart when triggered, bringing them back to life if the body can sustain it." + can_trigger = TRUE + trigger_cost = 25 + trigger_cooldown = 120 + rogue_types = list(/datum/nanite_program/shocking) + +/datum/nanite_program/defib/on_trigger(comm_message) + host_mob.notify_revival("Your heart is being defibrillated by nanites. Re-enter your corpse if you want to be revived!") + host_mob.grab_ghost() + addtimer(CALLBACK(src, .proc/zap), 50) + +/datum/nanite_program/defib/proc/check_revivable() + if(!iscarbon(host_mob)) //nonstandard biology + return FALSE + var/mob/living/carbon/C = host_mob + if(C.get_ghost()) + return FALSE + return C.can_defib() == DEFIB_POSSIBLE + +/datum/nanite_program/defib/proc/zap() + var/mob/living/carbon/C = host_mob + playsound(C, 'sound/machines/defib/defib_charge.ogg', 50, FALSE) + sleep(30) + playsound(C, 'sound/machines/defib/defib_zap.ogg', 50, FALSE) + if(check_revivable()) + if(C.stat == DEAD) + var/original_oxyloss = C.getOxyLoss() + C.setOxyLoss(OXYLOSS_PASSOUT_THRESHOLD - 5) + if(C.revive()) + playsound(C, 'sound/machines/defib/defib_success.ogg', 50, FALSE) + C.emote("gasp") + C.adjust_jitter_up_to(10 SECONDS, 1 MINUTES) + SEND_SIGNAL(C, COMSIG_LIVING_MINOR_SHOCK) + log_game("[C] has been successfully defibrillated by nanites.") + return + else + C.setOxyLoss(original_oxyloss) + playsound(C, 'sound/machines/defib/defib_failed.ogg', 50, FALSE) + diff --git a/modular_zubbers/code/modules/nanites/nanite_programs/protocols.dm b/modular_zubbers/code/modules/nanites/nanite_programs/protocols.dm new file mode 100644 index 00000000000..1b40e6d7f61 --- /dev/null +++ b/modular_zubbers/code/modules/nanites/nanite_programs/protocols.dm @@ -0,0 +1,277 @@ +//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, \ + 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 + var/boost_duration = 1200 + +/datum/nanite_program/protocol/kickstart/check_conditions() + if(!(world.time < nanites.start_time + boost_duration)) + return FALSE + return ..() + +/datum/nanite_program/protocol/kickstart/active_effect() + nanites.adjust_nanites(null, NANITE_PROTOCOL_KICKSTART_REPLICATION) + +/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, \ + 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 + var/factory_efficiency = 0 + var/max_efficiency = 1000 //Goes up to 2 bonus regen per tick after 16 minutes and 40 seconds + +/datum/nanite_program/protocol/factory/on_process() + if(!activated || !check_conditions()) + factory_efficiency = max(0, factory_efficiency - 5) + ..() + +/datum/nanite_program/protocol/factory/on_emp(severity) + ..() + factory_efficiency = max(0, factory_efficiency - 300) + +/datum/nanite_program/protocol/factory/on_shock(shock_damage) + ..() + factory_efficiency = max(0, factory_efficiency - 200) + +/datum/nanite_program/protocol/factory/on_minor_shock() + ..() + factory_efficiency = max(0, factory_efficiency - 100) + +/datum/nanite_program/protocol/factory/active_effect() + factory_efficiency = min(factory_efficiency + 1, max_efficiency) + nanites.adjust_nanites(null, round(0.002 * factory_efficiency, 0.1)) + +/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 = 1.2 + +/datum/nanite_program/protocol/pyramid/check_conditions() + if((nanites.nanite_volume / nanites.max_nanites) < 0.8) + return FALSE + + return ..() + +/datum/nanite_program/protocol/pyramid/active_effect() + nanites.adjust_nanites(null, boost) + +/datum/nanite_program/protocol/offline + 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) + protocol_class = NANITE_PROTOCOL_REPLICATION + var/boost = 3 + + +/datum/nanite_program/protocol/offline/check_conditions() + if(nanites.host_mob.stat == CONSCIOUS) + return FALSE + return ..() + + +/datum/nanite_program/protocol/offline/active_effect() + nanites.adjust_nanites(null, boost) + +/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/carbon = 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 = carbon.get_organ_slot(ORGAN_SLOT_LIVER) + if(liver) + liver.apply_organ_damage(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 = carbon.get_organ_slot(ORGAN_SLOT_STOMACH) + if(stomach) + stomach.apply_organ_damage(0.75) + if(prob(10)) + var/obj/item/organ/lungs/lungs = carbon.get_organ_slot(ORGAN_SLOT_LUNGS) + if(lungs) + lungs.apply_organ_damage(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 = carbon.get_organ_slot(ORGAN_SLOT_HEART) + if(heart) + heart.apply_organ_damage(0.75) + if(prob(10)) + var/obj/item/organ/brain/brain = carbon.get_organ_slot(ORGAN_SLOT_BRAIN) + if(brain) + brain.apply_organ_damage(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 = carbon.get_organ_slot(ORGAN_SLOT_EYES) + if(eyes) + eyes.apply_organ_damage(0.75) + if(prob(13)) + var/obj/item/organ/ears/ears = carbon.get_organ_slot(ORGAN_SLOT_EARS) + if(ears) + ears.apply_organ_damage(0.75) + current_stage++ + if(nanites.nanite_volume > 1500) //Nanites start spilling into the bloodstream, causing toxicity + if(prob(15)) + carbon.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)) + carbon.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, span_warning("[pick(main_warnings)]")) + next_warning = world.time + rand(min_warning_cooldown, max_warning_cooldown) + else if(islist(extra_warnings)) + to_chat(host_mob, span_warning("[pick(extra_warnings)]")) + next_warning = world.time + rand(min_warning_cooldown, max_warning_cooldown) diff --git a/modular_zubbers/code/modules/nanites/nanite_programs/rogue.dm b/modular_zubbers/code/modules/nanites/nanite_programs/rogue.dm new file mode 100644 index 00000000000..affe66e1b51 --- /dev/null +++ b/modular_zubbers/code/modules/nanites/nanite_programs/rogue.dm @@ -0,0 +1,119 @@ +//Programs generated through degradation of other complex programs. +//They generally cause minor damage or annoyance. + +//Last stop of the error train +/datum/nanite_program/glitch + name = "Glitch" + desc = "A heavy software corruption that causes nanites to gradually break down." + use_rate = 1.5 + unique = FALSE + rogue_types = list() + +//Generic body-affecting programs will decay into this +/datum/nanite_program/necrotic + name = "Necrosis" + desc = "The nanites attack internal tissues indiscriminately, causing widespread damage." + use_rate = 0.75 + unique = FALSE + rogue_types = list(/datum/nanite_program/glitch) + +/datum/nanite_program/necrotic/active_effect() + host_mob.adjustBruteLoss(0.75, TRUE) + if(prob(1)) + to_chat(host_mob, span_warning("You feel a mild ache from somewhere inside you.")) + +//Programs that don't directly interact with the body will decay into this +/datum/nanite_program/toxic + name = "Toxin Buildup" + desc = "The nanites cause a slow but constant toxin buildup inside the host." + use_rate = 0.25 + unique = FALSE + rogue_types = list(/datum/nanite_program/glitch) + +/datum/nanite_program/toxic/active_effect() + host_mob.adjustToxLoss(0.5) + if(prob(1)) + to_chat(host_mob, span_warning("You feel a bit sick.")) + +//Generic blood-affecting programs will decay into this +/datum/nanite_program/suffocating + name = "Hypoxemia" + desc = "The nanites prevent the host's blood from absorbing oxygen efficiently." + use_rate = 0.75 + unique = FALSE + rogue_types = list(/datum/nanite_program/glitch) + +/datum/nanite_program/suffocating/active_effect() + host_mob.adjustOxyLoss(3, 0) + if(prob(1)) + to_chat(host_mob, span_warning("You feel short of breath.")) + +//Generic brain-affecting programs will decay into this +/datum/nanite_program/brain_decay + name = "Neuro-Necrosis" + desc = "The nanites seek and attack brain cells, causing extensive neural damage to the host." + use_rate = 0.75 + unique = FALSE + rogue_types = list(/datum/nanite_program/necrotic) + +/datum/nanite_program/brain_decay/active_effect() + if(prob(4)) + host_mob.set_hallucinations_if_lower(15) + host_mob.adjustOrganLoss(ORGAN_SLOT_BRAIN, 1) + +//Generic brain-affecting programs can also decay into this +/datum/nanite_program/brain_misfire + name = "Brain Misfire" + desc = "The nanites interfere with neural pathways, causing minor psychological disturbances." + use_rate = 0.50 + unique = FALSE + rogue_types = list(/datum/nanite_program/brain_decay) + +/datum/nanite_program/brain_misfire/active_effect() + if(prob(10)) + switch(rand(1,4)) + if(1) + host_mob.adjust_hallucinations(15) + if(2) + host_mob.adjust_confusion(10) + if(3) + host_mob.adjust_drowsiness(10) + if(4) + host_mob.adjust_slurring(10) + +//Generic skin-affecting programs will decay into this +/datum/nanite_program/skin_decay + name = "Dermalysis" + desc = "The nanites attack skin cells, causing irritation, rashes, and minor damage." + use_rate = 0.25 + unique = FALSE + rogue_types = list(/datum/nanite_program/necrotic) + +/datum/nanite_program/skin_decay/active_effect() + host_mob.adjustBruteLoss(0.25) + if(prob(5)) //itching + var/picked_bodypart = pick(BODY_ZONE_HEAD, BODY_ZONE_CHEST, BODY_ZONE_R_ARM, BODY_ZONE_L_ARM, BODY_ZONE_R_LEG, BODY_ZONE_L_LEG) + var/obj/item/bodypart/bodypart = host_mob.get_bodypart(picked_bodypart) + var/can_scratch = !host_mob.incapacitated && get_location_accessible(host_mob, picked_bodypart) + + host_mob.visible_message("[can_scratch ? span_warning("[host_mob] scratches [host_mob.p_their()] [bodypart.name].") : ""]",\ + span_warning("Your [bodypart.name] itches. [can_scratch ? " You scratch it." : ""]")) + +//Generic nerve-affecting programs will decay into this +/datum/nanite_program/nerve_decay + name = "Nerve Decay" + desc = "The nanites attack the host's nerves, causing lack of coordination and short bursts of paralysis." + use_rate = 1 + unique = FALSE + rogue_types = list(/datum/nanite_program/necrotic) + +/datum/nanite_program/nerve_decay/active_effect() + if(prob(5)) + to_chat(host_mob, span_warning("You feel unbalanced!")) + host_mob.adjust_confusion(10) + else if(prob(4)) + to_chat(host_mob, span_warning("You can't feel your hands!")) + host_mob.drop_all_held_items() + else if(prob(4)) + to_chat(host_mob, span_warning("You can't feel your legs!")) + host_mob.Paralyze(30) diff --git a/modular_zubbers/code/modules/nanites/nanite_programs/sensor.dm b/modular_zubbers/code/modules/nanites/nanite_programs/sensor.dm new file mode 100644 index 00000000000..916c2aa989a --- /dev/null +++ b/modular_zubbers/code/modules/nanites/nanite_programs/sensor.dm @@ -0,0 +1,312 @@ +/datum/nanite_program/sensor + name = "Sensor Nanites" + desc = "These nanites send a signal code when a certain condition is met." + unique = FALSE + var/can_rule = FALSE + +/datum/nanite_program/sensor/register_extra_settings() + extra_settings[NES_SENT_CODE] = new /datum/nanite_extra_setting/number(0, 1, 9999) + +/datum/nanite_program/sensor/proc/check_event() + return FALSE + +/datum/nanite_program/sensor/proc/send_code() + if(activated) + var/datum/nanite_extra_setting/ES = extra_settings[NES_SENT_CODE] + SEND_SIGNAL(host_mob, COMSIG_NANITE_SIGNAL, ES.value, "a [name] program") + +/datum/nanite_program/sensor/active_effect() + if(check_event()) + send_code() + +/datum/nanite_program/sensor/proc/make_rule(datum/nanite_program/target) + return + +/datum/nanite_program/sensor/repeat + name = "Signal Repeater" + desc = "When triggered, sends another signal to the nanites, optionally with a delay." + can_trigger = TRUE + trigger_cost = 0 + trigger_cooldown = 10 + var/spent = FALSE + +/datum/nanite_program/sensor/repeat/register_extra_settings() + . = ..() + extra_settings[NES_DELAY] = new /datum/nanite_extra_setting/number(0, 0, 3600, "s") + +/datum/nanite_program/sensor/repeat/on_trigger(comm_message) + var/datum/nanite_extra_setting/ES = extra_settings[NES_DELAY] + addtimer(CALLBACK(src, .proc/send_code), ES.get_value() * 10) + +/datum/nanite_program/sensor/relay_repeat + name = "Relay Signal Repeater" + desc = "When triggered, sends another signal to a relay channel, optionally with a delay." + can_trigger = TRUE + trigger_cost = 0 + trigger_cooldown = 10 + var/spent = FALSE + +/datum/nanite_program/sensor/relay_repeat/register_extra_settings() + . = ..() + extra_settings[NES_RELAY_CHANNEL] = new /datum/nanite_extra_setting/number(1, 1, 9999) + extra_settings[NES_DELAY] = new /datum/nanite_extra_setting/number(0, 0, 3600, "s") + +/datum/nanite_program/sensor/relay_repeat/on_trigger(comm_message) + var/datum/nanite_extra_setting/ES = extra_settings[NES_DELAY] + addtimer(CALLBACK(src, .proc/send_code), ES.get_value() * 10) + +/datum/nanite_program/sensor/relay_repeat/send_code() + var/datum/nanite_extra_setting/relay = extra_settings[NES_RELAY_CHANNEL] + if(activated && relay.get_value()) + for(var/X in SSnanites.nanite_relays) + var/datum/nanite_program/relay/N = X + var/datum/nanite_extra_setting/code = extra_settings[NES_SENT_CODE] + N.relay_signal(code.get_value(), relay.get_value(), "a [name] program") + +/datum/nanite_program/sensor/health + name = "Health Sensor" + desc = "The nanites receive a signal when the host's health is above/below a target percentage." + can_rule = TRUE + var/spent = FALSE + +/datum/nanite_program/sensor/health/register_extra_settings() + . = ..() + extra_settings[NES_HEALTH_PERCENT] = new /datum/nanite_extra_setting/number(50, -99, 100, "%") + extra_settings[NES_DIRECTION] = new /datum/nanite_extra_setting/boolean(TRUE, "Above", "Below") + +/datum/nanite_program/sensor/health/check_event() + var/health_percent = host_mob.health / host_mob.maxHealth * 100 + var/datum/nanite_extra_setting/percent = extra_settings[NES_HEALTH_PERCENT] + var/datum/nanite_extra_setting/direction = extra_settings[NES_DIRECTION] + var/detected = FALSE + if(direction.get_value()) + if(health_percent >= percent.get_value()) + detected = TRUE + else + if(health_percent < percent.get_value()) + detected = TRUE + + if(detected) + if(!spent) + spent = TRUE + return TRUE + return FALSE + else + spent = FALSE + return FALSE + +/datum/nanite_program/sensor/health/make_rule(datum/nanite_program/target) + var/datum/nanite_rule/health/rule = new(target) + var/datum/nanite_extra_setting/direction = extra_settings[NES_DIRECTION] + var/datum/nanite_extra_setting/percent = extra_settings[NES_HEALTH_PERCENT] + rule.above = direction.get_value() + rule.threshold = percent.get_value() + return rule + +/datum/nanite_program/sensor/crit + name = "Critical Health Sensor" + desc = "The nanites receive a signal when the host first reaches critical health." + can_rule = TRUE + var/spent = FALSE + +/datum/nanite_program/sensor/crit/check_event() + if(HAS_TRAIT(host_mob, TRAIT_CRITICAL_CONDITION)) + if(spent) + return FALSE + spent = TRUE + return TRUE + spent = FALSE + return FALSE + + +/datum/nanite_program/sensor/crit/make_rule(datum/nanite_program/target) + var/datum/nanite_rule/crit/rule = new(target) + return rule + +/datum/nanite_program/sensor/death + name = "Death Sensor" + desc = "The nanites receive a signal when they detect the host is dead." + can_rule = TRUE + var/spent = FALSE + +/datum/nanite_program/sensor/death/on_death() + send_code() + +/datum/nanite_program/sensor/death/make_rule(datum/nanite_program/target) + var/datum/nanite_rule/death/rule = new(target) + return rule + +/datum/nanite_program/sensor/nanite_volume + name = "Nanite Volume Sensor" + desc = "The nanites receive a signal when the nanite supply is above/below a certain percentage." + can_rule = TRUE + var/spent = FALSE + +/datum/nanite_program/sensor/nanite_volume/register_extra_settings() + . = ..() + extra_settings[NES_NANITE_PERCENT] = new /datum/nanite_extra_setting/number(50, -99, 100, "%") + extra_settings[NES_DIRECTION] = new /datum/nanite_extra_setting/boolean(TRUE, "Above", "Below") + +/datum/nanite_program/sensor/nanite_volume/check_event() + var/nanite_percent = (nanites.nanite_volume - nanites.safety_threshold)/(nanites.max_nanites - nanites.safety_threshold)*100 + var/datum/nanite_extra_setting/percent = extra_settings[NES_NANITE_PERCENT] + var/datum/nanite_extra_setting/direction = extra_settings[NES_DIRECTION] + var/detected = FALSE + if(direction.get_value()) + if(nanite_percent >= percent.get_value()) + detected = TRUE + else + if(nanite_percent < percent.get_value()) + detected = TRUE + + if(detected) + if(!spent) + spent = TRUE + return TRUE + return FALSE + else + spent = FALSE + return FALSE + +/datum/nanite_program/sensor/nanite_volume/make_rule(datum/nanite_program/target) + var/datum/nanite_rule/nanites/rule = new(target) + var/datum/nanite_extra_setting/direction = extra_settings[NES_DIRECTION] + var/datum/nanite_extra_setting/percent = extra_settings[NES_NANITE_PERCENT] + rule.above = direction.get_value() + rule.threshold = percent.get_value() + return rule + +/datum/nanite_program/sensor/damage + name = "Damage Sensor" + desc = "The nanites receive a signal when a host's specific damage type is above/below a target value." + can_rule = TRUE + var/spent = FALSE + +/datum/nanite_program/sensor/damage/register_extra_settings() + . = ..() + extra_settings[NES_DAMAGE_TYPE] = new /datum/nanite_extra_setting/type(BRUTE, list(BRUTE, BURN, TOX, OXY)) + extra_settings[NES_DAMAGE] = new /datum/nanite_extra_setting/number(50, 0, 500) + extra_settings[NES_DIRECTION] = new /datum/nanite_extra_setting/boolean(TRUE, "Above", "Below") + +/datum/nanite_program/sensor/damage/check_event() + var/reached_threshold = FALSE + var/datum/nanite_extra_setting/type = extra_settings[NES_DAMAGE_TYPE] + var/datum/nanite_extra_setting/damage = extra_settings[NES_DAMAGE] + var/datum/nanite_extra_setting/direction = extra_settings[NES_DIRECTION] + var/check_above = direction.get_value() + var/damage_amt = 0 + switch(type.get_value()) + if(BRUTE) + damage_amt = host_mob.getBruteLoss() + if(BURN) + damage_amt = host_mob.getFireLoss() + if(TOX) + damage_amt = host_mob.getToxLoss() + if(OXY) + damage_amt = host_mob.getOxyLoss() + + if(check_above) + if(damage_amt >= damage.get_value()) + reached_threshold = TRUE + else + if(damage_amt < damage.get_value()) + reached_threshold = TRUE + + if(reached_threshold) + if(!spent) + spent = TRUE + return TRUE + return FALSE + else + spent = FALSE + return FALSE + +/datum/nanite_program/sensor/damage/make_rule(datum/nanite_program/target) + var/datum/nanite_rule/damage/rule = new(target) + var/datum/nanite_extra_setting/direction = extra_settings[NES_DIRECTION] + var/datum/nanite_extra_setting/damage_type = extra_settings[NES_DAMAGE_TYPE] + var/datum/nanite_extra_setting/damage = extra_settings[NES_DAMAGE] + rule.above = direction.get_value() + rule.threshold = damage.get_value() + rule.damage_type = damage_type.get_value() + return rule + +/datum/nanite_program/sensor/voice + name = "Voice Sensor" + 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() + . = ..() + extra_settings[NES_SENTENCE] = new /datum/nanite_extra_setting/text("") + extra_settings[NES_INCLUSIVE_MODE] = new /datum/nanite_extra_setting/boolean(TRUE, "Inclusive", "Exclusive") + +/datum/nanite_program/sensor/voice/on_mob_add() + . = ..() + RegisterSignal(host_mob, COMSIG_MOVABLE_HEAR, .proc/on_hear) + +/datum/nanite_program/sensor/voice/on_mob_remove() + 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(findtext(hearing_args[HEARING_RAW_MESSAGE], sentence.get_value())) + send_code() + else + if(LOWER_TEXT(hearing_args[HEARING_RAW_MESSAGE]) == LOWER_TEXT(sentence.get_value())) + send_code() + +/datum/nanite_program/sensor/species + name = "Species Sensor" + desc = "When triggered, the nanites scan the host to determine their species and receive a signal depending on the conditions set in the settings." + can_trigger = TRUE + trigger_cost = 0 + trigger_cooldown = 5 + + var/static/list/allowed_species = list( + "Human" = /datum/species/human, + "Lizard" = /datum/species/lizard, + "Moth" = /datum/species/moth, + "Ethereal" = /datum/species/ethereal, + "Pod" = /datum/species/pod, + "Fly" = /datum/species/fly, + "Felinid" = /datum/species/human/felinid, + "Jelly" = /datum/species/jelly, + ) + +/datum/nanite_program/sensor/species/register_extra_settings() + . = ..() + var/list/species_types = list() + for(var/name in allowed_species) + species_types += name + species_types += "Other" + extra_settings[NES_RACE] = new /datum/nanite_extra_setting/type("Human", species_types) + extra_settings[NES_MODE] = new /datum/nanite_extra_setting/boolean(TRUE, "Is", "Is Not") + +/datum/nanite_program/sensor/species/on_trigger(comm_message) + var/datum/nanite_extra_setting/species_type = extra_settings[NES_RACE] + var/species = allowed_species[species_type.get_value()] + var/species_match = FALSE + + if(species) + if(is_species(host_mob, species)) + species_match = TRUE + else //this is the check for the "Other" option + species_match = TRUE + for(var/name in allowed_species) + var/species_other = allowed_species[name] + if(is_species(host_mob, species_other)) + species_match = FALSE + break + + var/datum/nanite_extra_setting/mode = extra_settings[NES_MODE] + if(mode.get_value()) + if(species_match) + send_code() + else + if(!species_match) + send_code() diff --git a/modular_zubbers/code/modules/nanites/nanite_programs/suppression.dm b/modular_zubbers/code/modules/nanites/nanite_programs/suppression.dm new file mode 100644 index 00000000000..ddd38bc9790 --- /dev/null +++ b/modular_zubbers/code/modules/nanites/nanite_programs/suppression.dm @@ -0,0 +1,292 @@ +//Programs that are generally useful for population control and non-harmful suppression. + +/datum/nanite_program/sleepy + name = "Sleep Induction" + desc = "The nanites induce rapid narcolepsy when triggered." + can_trigger = TRUE + trigger_cost = 15 + trigger_cooldown = 1200 + rogue_types = list(/datum/nanite_program/brain_misfire, /datum/nanite_program/brain_decay) + +/datum/nanite_program/sleepy/on_trigger(comm_message) + to_chat(host_mob, span_warning("You start to feel very sleepy...")) + host_mob.adjust_drowsiness(20) + addtimer(CALLBACK(host_mob, /mob/living.proc/Sleeping, 200), rand(60,200)) + +/datum/nanite_program/paralyzing + name = "Paralysis" + desc = "The nanites force muscle contraction, effectively paralyzing the host." + use_rate = 3 + rogue_types = list(/datum/nanite_program/nerve_decay) + +/datum/nanite_program/paralyzing/active_effect() + host_mob.Stun(40) + +/datum/nanite_program/paralyzing/enable_passive_effect() + . = ..() + to_chat(host_mob, span_warning("Your muscles seize! You can't move!")) + +/datum/nanite_program/paralyzing/disable_passive_effect() + . = ..() + to_chat(host_mob, span_notice("Your muscles relax, and you can move again.")) + +/datum/nanite_program/shocking + name = "Electric Shock" + desc = "The nanites shock the host when triggered. Destroys a large amount of nanites!" + can_trigger = TRUE + trigger_cost = 10 + trigger_cooldown = 300 + program_flags = NANITE_SHOCK_IMMUNE + rogue_types = list(/datum/nanite_program/toxic) + +/datum/nanite_program/shocking/on_trigger(comm_message) + host_mob.electrocute_act(rand(5,10), "shock nanites", 1, SHOCK_NOGLOVES) + +/datum/nanite_program/stun + name = "Neural Shock" + desc = "The nanites pulse the host's nerves when triggered, inapacitating them for a short period." + can_trigger = TRUE + trigger_cost = 4 + trigger_cooldown = 300 + rogue_types = list(/datum/nanite_program/shocking, /datum/nanite_program/nerve_decay) + +/datum/nanite_program/stun/on_trigger(comm_message) + playsound(host_mob, "sparks", 75, TRUE, -1, SHORT_RANGE_SOUND_EXTRARANGE) + host_mob.Paralyze(80) + +/datum/nanite_program/pacifying + name = "Pacification" + desc = "The nanites suppress the aggression center of the brain, preventing the host from causing direct harm to others." + use_rate = 1 + rogue_types = list(/datum/nanite_program/brain_misfire, /datum/nanite_program/brain_decay) + +/datum/nanite_program/pacifying/enable_passive_effect() + . = ..() + ADD_TRAIT(host_mob, TRAIT_PACIFISM, NANITES_TRAIT) + +/datum/nanite_program/pacifying/disable_passive_effect() + . = ..() + REMOVE_TRAIT(host_mob, TRAIT_PACIFISM, NANITES_TRAIT) + +/datum/nanite_program/blinding + name = "Blindness" + desc = "The nanites suppress the host's ocular nerves, blinding them while they're active." + use_rate = 1.5 + rogue_types = list(/datum/nanite_program/nerve_decay) + +/datum/nanite_program/blinding/enable_passive_effect() + . = ..() + host_mob.become_blind(NANITES_TRAIT) + +/datum/nanite_program/blinding/disable_passive_effect() + . = ..() + host_mob.cure_blind(NANITES_TRAIT) + +/datum/nanite_program/mute + name = "Mute" + desc = "The nanites suppress the host's speech, making them mute while they're active." + use_rate = 0.75 + rogue_types = list(/datum/nanite_program/brain_decay, /datum/nanite_program/brain_misfire) + +/datum/nanite_program/mute/enable_passive_effect() + . = ..() + ADD_TRAIT(host_mob, TRAIT_MUTE, NANITES_TRAIT) + +/datum/nanite_program/mute/disable_passive_effect() + . = ..() + REMOVE_TRAIT(host_mob, TRAIT_MUTE, NANITES_TRAIT) + +/datum/nanite_program/fake_death + name = "Death Simulation" + desc = "The nanites induce a death-like coma into the host, able to fool most medical scans." + use_rate = 3.5 + rogue_types = list(/datum/nanite_program/nerve_decay, /datum/nanite_program/necrotic, /datum/nanite_program/brain_decay) + +/datum/nanite_program/fake_death/enable_passive_effect() + . = ..() + host_mob.emote("deathgasp") + host_mob.fakedeath("nanites") + +/datum/nanite_program/fake_death/disable_passive_effect() + . = ..() + host_mob.cure_fakedeath("nanites") + +//Can receive transmissions from a nanite communication remote for customized messages +/datum/nanite_program/comm + can_trigger = TRUE + 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.get_value()) + return + if(signal_comm_code == comm_code.get_value()) + host_mob.investigate_log("'s [name] nanite program was messaged by [comm_source] with comm code [signal_comm_code] and message '[comm_message]'.", INVESTIGATE_NANITES) + trigger(FALSE, comm_message) + +/datum/nanite_program/comm/speech + name = "Forced Speech" + desc = "The nanites force the host to say a pre-programmed sentence when triggered." + unique = FALSE + trigger_cost = 3 + trigger_cooldown = 20 + rogue_types = list(/datum/nanite_program/brain_misfire, /datum/nanite_program/brain_decay) + var/static/list/blacklist = list( + "*surrender", + "*collapse", + "*faint", + ) + +/datum/nanite_program/comm/speech/register_extra_settings() + . = ..() + extra_settings[NES_SENTENCE] = new /datum/nanite_extra_setting/text("") + +/datum/nanite_program/comm/speech/on_trigger(comm_message) + var/sent_message = comm_message + if(!comm_message) + var/datum/nanite_extra_setting/sentence = extra_settings[NES_SENTENCE] + sent_message = sentence.get_value() + if(sent_message in blacklist) + return + if(host_mob.stat == DEAD) + return + to_chat(host_mob, span_warning("You feel compelled to speak...")) + host_mob.say(sent_message, forced = "nanite speech") + +/datum/nanite_program/comm/voice + name = "Skull Echo" + desc = "The nanites echo a synthesized message inside the host's skull." + unique = FALSE + trigger_cost = 1 + trigger_cooldown = 20 + rogue_types = list(/datum/nanite_program/brain_misfire, /datum/nanite_program/brain_decay) + +/datum/nanite_program/comm/voice/register_extra_settings() + . = ..() + extra_settings[NES_MESSAGE] = new /datum/nanite_extra_setting/text("") + +/datum/nanite_program/comm/voice/on_trigger(comm_message) + var/sent_message = comm_message + if(!comm_message) + var/datum/nanite_extra_setting/message_setting = extra_settings[NES_MESSAGE] + sent_message = message_setting.get_value() + if(host_mob.stat == DEAD) + return + to_chat(host_mob, "You hear a strange, robotic voice in your head... \"[span_robot("[sent_message]")]\"") + +/datum/nanite_program/comm/hallucination + name = "Hallucination" + desc = "The nanites make the host hallucinate something when triggered." + trigger_cost = 4 + trigger_cooldown = 80 + unique = FALSE + rogue_types = list(/datum/nanite_program/brain_misfire) + var/list/hallucination_options = list( + "Message" = /datum/hallucination/chat, + "Battle" = /datum/hallucination/battle, + "Sound" = /datum/hallucination/fake_sound, + "Weird Sound" = /datum/hallucination/fake_sound/weird, + "Station Message" = /datum/hallucination/station_message, + "Health" = null, + "Alert" = /datum/hallucination/fake_alert, + "Fire" = /datum/hallucination/fire, + "Shock" = /datum/hallucination/shock, + "Plasma Flood" = /datum/hallucination/fake_flood, + "Random" = null + ) + +/datum/nanite_program/comm/hallucination/register_extra_settings() + . = ..() + var/list/options = assoc_to_keys(hallucination_options) + extra_settings[NES_HALLUCINATION_TYPE] = new /datum/nanite_extra_setting/type("Message", options) + extra_settings[NES_HALLUCINATION_DETAIL] = new /datum/nanite_extra_setting/text("") + +/datum/nanite_program/comm/hallucination/on_trigger(comm_message) + var/datum/nanite_extra_setting/hal_setting = extra_settings[NES_HALLUCINATION_TYPE] + var/hal_type = hal_setting.get_value() + var/datum/nanite_extra_setting/hal_detail_setting = extra_settings[NES_HALLUCINATION_DETAIL] + var/hal_details = hal_detail_setting.get_value() + if(comm_message && (hal_type != "Message")) //Triggered via comm remote, but not set to a message hallucination + return + var/sent_message = comm_message //Comm remotes can send custom hallucination messages for the chat hallucination + if(!sent_message) + sent_message = hal_details + + if(!iscarbon(host_mob)) + return + var/mob/living/carbon/carbon = host_mob + if(hal_details == "random") + hal_details = null + switch(hal_type) + if("Random") + carbon.adjust_hallucinations(15) + if("Health") + switch(hal_details) + if("critical") + carbon.adjust_timed_status_effect(3 MINUTES, /datum/status_effect/grouped/screwy_hud/fake_crit) + if("dead") + carbon.adjust_timed_status_effect(3 MINUTES, /datum/status_effect/grouped/screwy_hud/fake_dead) + if("healthy") + carbon.adjust_timed_status_effect(3 MINUTES, /datum/status_effect/grouped/screwy_hud/fake_healthy) + else if(hallucination_options[hal_type]) + carbon.cause_hallucination(hallucination_options[hal_type], TRUE, hal_details) + +/datum/nanite_program/comm/hallucination/set_extra_setting(setting, value) + . = ..() + if(setting == NES_HALLUCINATION_TYPE) + switch(value) + if("Message") + extra_settings[NES_HALLUCINATION_DETAIL] = new /datum/nanite_extra_setting/text("") + if("Battle") + extra_settings[NES_HALLUCINATION_DETAIL] = new /datum/nanite_extra_setting/type("random", list("random","laser","disabler","esword","gun","stunprod","harmbaton","bomb")) + if("Sound") + extra_settings[NES_HALLUCINATION_DETAIL] = new /datum/nanite_extra_setting/type("random", list("random","airlock","airlock pry","console","explosion","far explosion","mech","glass","alarm","beepsky","mech","wall decon","door hack")) + if("Weird Sound") + extra_settings[NES_HALLUCINATION_DETAIL] = new /datum/nanite_extra_setting/type("random", list("random","phone","hallelujah","highlander","laughter","hyperspace","game over","creepy","tesla")) + if("Station Message") + extra_settings[NES_HALLUCINATION_DETAIL] = new /datum/nanite_extra_setting/type("random", list("random","ratvar","shuttle dock","blob alert","malf ai","meteors","supermatter")) + if("Health") + extra_settings[NES_HALLUCINATION_DETAIL] = new /datum/nanite_extra_setting/type("random", list("random","critical","dead","healthy")) + if("Alert") + extra_settings[NES_HALLUCINATION_DETAIL] = new /datum/nanite_extra_setting/type("random", list("random","not_enough_oxy","not_enough_tox","not_enough_co2","too_much_oxy","too_much_co2","too_much_tox","newlaw","nutrition","charge","gravity","fire","locked","hacked","temphot","tempcold","pressure")) + else + extra_settings.Remove(NES_HALLUCINATION_DETAIL) + +/datum/nanite_program/good_mood + name = "Happiness Enhancer" + desc = "The nanites synthesize serotonin inside the host's brain, creating an artificial sense of happiness." + use_rate = 0.1 + rogue_types = list(/datum/nanite_program/brain_decay) + +/datum/nanite_program/good_mood/register_extra_settings() + . = ..() + extra_settings[NES_MOOD_MESSAGE] = new /datum/nanite_extra_setting/text("HAPPINESS ENHANCEMENT") + +/datum/nanite_program/good_mood/enable_passive_effect() + . = ..() + host_mob.add_mood_event("nanite_happy", /datum/mood_event/nanite_happiness, get_extra_setting_value(NES_MOOD_MESSAGE)) + +/datum/nanite_program/good_mood/disable_passive_effect() + . = ..() + host_mob.clear_mood_event("nanite_happy") + +/datum/nanite_program/bad_mood + name = "Happiness Suppressor" + desc = "The nanites suppress the production of serotonin inside the host's brain, creating an artificial state of depression." + use_rate = 0.1 + rogue_types = list(/datum/nanite_program/brain_decay) + +/datum/nanite_program/bad_mood/register_extra_settings() + . = ..() + extra_settings[NES_MOOD_MESSAGE] = new /datum/nanite_extra_setting/text("HAPPINESS SUPPRESSION") + +/datum/nanite_program/bad_mood/enable_passive_effect() + . = ..() + host_mob.add_mood_event("nanite_unhappy", /datum/mood_event/nanite_sadness, get_extra_setting_value(NES_MOOD_MESSAGE)) + +/datum/nanite_program/bad_mood/disable_passive_effect() + . = ..() + host_mob.add_mood_event("nanite_unhappy") diff --git a/modular_zubbers/code/modules/nanites/nanite_programs/utility.dm b/modular_zubbers/code/modules/nanites/nanite_programs/utility.dm new file mode 100644 index 00000000000..77567fd5e1e --- /dev/null +++ b/modular_zubbers/code/modules/nanites/nanite_programs/utility.dm @@ -0,0 +1,331 @@ +//Programs that interact with other programs or nanites directly, or have other special purposes. +/datum/nanite_program/viral + name = "Viral Replica" + desc = "The nanites constantly send encrypted signals attempting to forcefully copy their own programming into other nanite clusters, also overriding or disabling their cloud sync." + use_rate = 0.5 + rogue_types = list(/datum/nanite_program/toxic) + var/pulse_cooldown = 0 + +/datum/nanite_program/viral/register_extra_settings() + extra_settings[NES_PROGRAM_OVERWRITE] = new /datum/nanite_extra_setting/type("Add To", list("Overwrite", "Add To", "Ignore")) + extra_settings[NES_CLOUD_OVERWRITE] = new /datum/nanite_extra_setting/number(0, 0, 100) + +/datum/nanite_program/viral/active_effect() + if(world.time < pulse_cooldown) + return + var/datum/nanite_extra_setting/program = extra_settings[NES_PROGRAM_OVERWRITE] + var/datum/nanite_extra_setting/cloud = extra_settings[NES_CLOUD_OVERWRITE] + for(var/mob/M in orange(host_mob, 5)) + if(SEND_SIGNAL(M, COMSIG_NANITE_IS_STEALTHY)) + continue + switch(program.get_value()) + if("Overwrite") + SEND_SIGNAL(M, COMSIG_NANITE_SYNC, nanites, TRUE) + if("Add To") + SEND_SIGNAL(M, COMSIG_NANITE_SYNC, nanites, FALSE) + SEND_SIGNAL(M, COMSIG_NANITE_SET_CLOUD, cloud.get_value()) + pulse_cooldown = world.time + 75 + +/datum/nanite_program/monitoring + name = "Monitoring" + desc = "The nanites monitor the host's vitals and location, sending them to the suit sensor network." + rogue_types = list(/datum/nanite_program/toxic) + +/datum/nanite_program/monitoring/enable_passive_effect() + . = ..() + ADD_TRAIT(host_mob, TRAIT_NANITE_MONITORING, NANITES_TRAIT) //Shows up in diagnostic and medical HUDs as a small blinking icon + if(ishuman(host_mob)) + GLOB.nanite_sensors_list |= host_mob + host_mob.hud_set_nanite_indicator() + +/datum/nanite_program/monitoring/disable_passive_effect() + . = ..() + REMOVE_TRAIT(host_mob, TRAIT_NANITE_MONITORING, "nanites") + if(ishuman(host_mob)) + GLOB.nanite_sensors_list -= host_mob + + host_mob.hud_set_nanite_indicator() + +/datum/nanite_program/self_scan + name = "Host Scan" + desc = "The nanites display a detailed readout of a body scan to the host." + unique = FALSE + can_trigger = TRUE + trigger_cost = 3 + trigger_cooldown = 50 + rogue_types = list(/datum/nanite_program/toxic) + +/datum/nanite_program/self_scan/register_extra_settings() + extra_settings[NES_SCAN_TYPE] = new /datum/nanite_extra_setting/type("Medical", list("Medical", "Chemical", "Wound", "Nanite")) + +/datum/nanite_program/self_scan/on_trigger(comm_message) + if(host_mob.stat == DEAD) + return + var/datum/nanite_extra_setting/NS = extra_settings[NES_SCAN_TYPE] + switch(NS.get_value()) + if("Medical") + healthscan(host_mob, host_mob) + if("Chemical") + chemscan(host_mob, host_mob) + if("Wound") + woundscan(host_mob, host_mob) + if("Nanite") + SEND_SIGNAL(host_mob, COMSIG_NANITE_SCAN, host_mob, TRUE) + +/datum/nanite_program/stealth + name = "Stealth" + desc = "The nanites mask their activity from superficial scans, becoming undetectable by HUDs and non-specialized scanners." + rogue_types = list(/datum/nanite_program/toxic) + use_rate = 0.2 + +/datum/nanite_program/stealth/enable_passive_effect() + . = ..() + nanites.stealth = TRUE + +/datum/nanite_program/stealth/disable_passive_effect() + . = ..() + nanites.stealth = FALSE + +/datum/nanite_program/nanite_debugging + name = "Nanite Debugging" + desc = "Enables various high-cost diagnostics in the nanites, making them able to communicate their program list to portable scanners. \ + Doing so uses some power, slightly decreasing their replication speed." + rogue_types = list(/datum/nanite_program/toxic) + use_rate = 0.1 + +/datum/nanite_program/nanite_debugging/enable_passive_effect() + . = ..() + nanites.diagnostics = TRUE + +/datum/nanite_program/nanite_debugging/disable_passive_effect() + . = ..() + nanites.diagnostics = FALSE + +/datum/nanite_program/relay + name = "Relay" + desc = "The nanites receive and relay long-range nanite signals." + rogue_types = list(/datum/nanite_program/toxic) + +/datum/nanite_program/relay/register_extra_settings() + extra_settings[NES_RELAY_CHANNEL] = new /datum/nanite_extra_setting/number(1, 1, 9999) + +/datum/nanite_program/relay/enable_passive_effect() + . = ..() + SSnanites.nanite_relays |= src + +/datum/nanite_program/relay/disable_passive_effect() + . = ..() + SSnanites.nanite_relays -= src + +/datum/nanite_program/relay/proc/relay_signal(code, relay_code, source) + if(!activated) + return + if(!host_mob) + return + var/datum/nanite_extra_setting/NS = extra_settings[NES_RELAY_CHANNEL] + if(relay_code != NS.get_value()) + return + SEND_SIGNAL(host_mob, COMSIG_NANITE_SIGNAL, code, source) + +/datum/nanite_program/relay/proc/relay_comm_signal(comm_code, relay_code, comm_message) + if(!activated) + return + if(!host_mob) + return + var/datum/nanite_extra_setting/NS = extra_settings[NES_RELAY_CHANNEL] + if(relay_code != NS.get_value()) + return + SEND_SIGNAL(host_mob, COMSIG_NANITE_COMM_SIGNAL, comm_code, comm_message) + +/datum/nanite_program/metabolic_synthesis + name = "Metabolic Synthesis" + desc = "The nanites use the metabolic cycle of the host to speed up their replication rate, using their extra nutrition as fuel." + use_rate = -0.5 //generates nanites + rogue_types = list(/datum/nanite_program/toxic) + +/datum/nanite_program/metabolic_synthesis/check_conditions() + if(!iscarbon(host_mob)) + return FALSE + var/mob/living/carbon/C = host_mob + if(C.nutrition <= NUTRITION_LEVEL_WELL_FED) + return FALSE + return ..() + +/datum/nanite_program/metabolic_synthesis/active_effect() + host_mob.adjust_nutrition(-0.5) + +/datum/nanite_program/access + name = "Subdermal ID" + desc = "The nanites store the host's ID access rights in a subdermal magnetic strip. Updates when triggered, copying the host's current access." + can_trigger = TRUE + trigger_cost = 3 + trigger_cooldown = 30 + rogue_types = list(/datum/nanite_program/skin_decay) + var/access = list() + +//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/potential_items = list() + + potential_items += host_mob.get_active_held_item() + potential_items += host_mob.get_inactive_held_item() + potential_items += host_mob.pulling + + if(ishuman(host_mob)) + var/mob/living/carbon/human/H = host_mob + potential_items += H.wear_id + else if(isanimal(host_mob)) + var/mob/living/simple_animal/A = host_mob + 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 + name = "Infective Exo-Locomotion" + desc = "The nanites gain the ability to survive for brief periods outside of the human body, as well as the ability to start new colonies without an integration process; \ + resulting in an extremely infective strain of nanites." + use_rate = 1.50 + rogue_types = list(/datum/nanite_program/aggressive_replication, /datum/nanite_program/necrotic) + var/spread_cooldown = 0 + +/datum/nanite_program/spreading/active_effect() + if(world.time < spread_cooldown) + return + spread_cooldown = world.time + 50 + var/list/mob/living/target_hosts = list() + for(var/mob/living/L in oview(5, host_mob)) + if(!prob(25)) + continue + if(!CAN_HAVE_NANITES(L)) + continue + target_hosts += L + if(!target_hosts.len) + return + var/mob/living/infectee = pick(target_hosts) + if(prob(100 - infectee.getarmor(null, BIO))) + //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 with cloud ID [nanites.cloud_id] by [key_name(host_mob)] at [AREACOORD(infectee)].", INVESTIGATE_NANITES) + +/datum/nanite_program/nanite_sting + name = "Nanite Sting" + desc = "When triggered, builds a invisible spikes of nanites on the host's skin that \ + can infect a nearby non-host with a copy of the host's nanites cluster \ + that is unlinked from the cloud. \ + Will not work on hosts or those already infected." + can_trigger = TRUE + trigger_cost = 5 + trigger_cooldown = 100 + rogue_types = list(/datum/nanite_program/glitch, /datum/nanite_program/toxic) + var/decay_timer + +/datum/nanite_program/nanite_sting/Destroy() + . = ..() + if(!decay_timer) + return + decay_sting() + +/datum/nanite_program/nanite_sting/on_trigger(comm_message) + consume_nanites(-15) + to_chat(host_mob, span_warning("Your hands becomes sharp and prickly.")) + RegisterSignal(host_mob, COMSIG_LIVING_EARLY_UNARMED_ATTACK, PROC_REF(on_attack_hand)) + decay_timer = addtimer(CALLBACK(src, PROC_REF(decay_sting)), 30 SECONDS, TIMER_STOPPABLE) + +/datum/nanite_program/nanite_sting/proc/on_attack_hand(atom/source, mob/user, modifiers) + SIGNAL_HANDLER + var/mob/living/living = source + if(!istype(living)) return + if(!CAN_HAVE_NANITES(living) || SEND_SIGNAL(living, COMSIG_HAS_NANITES) || !living.Adjacent(host_mob)) return + + if(prob(100 - living.getarmor(null, BIO))) + //unlike with Infective Exo-Locomotion, this can't take over existing nanites, because Nanite Sting only targets non-hosts. + living.AddComponent(/datum/component/nanites, 5) + SEND_SIGNAL(living, COMSIG_NANITE_SYNC, nanites) + // SEND_SIGNAL(living, COMSIG_NANITE_SET_CLOUD, nanites.cloud_id) won't set the cloud + // SEND_SIGNAL(living, COMSIG_NANITE_SET_CLOUD_SYNC, NANITE_CLOUD_DISABLE) + living.investigate_log("was infected by a nanite cluster with cloud ID [nanites.cloud_id] by [key_name(host_mob)] at [AREACOORD(living)].", INVESTIGATE_NANITES) + to_chat(living, span_warning("You feel a tiny prick.")) + decay_sting() + +/datum/nanite_program/nanite_sting/proc/decay_sting() + to_chat(host_mob, span_warning("Your hands no longer feel like they're covered in spines.")) + deltimer(decay_timer) + decay_timer = null + UnregisterSignal(host_mob, COMSIG_LIVING_EARLY_UNARMED_ATTACK) + +/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; \ + 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) + +/datum/nanite_program/mitosis/active_effect() + var/rep_rate = round(nanites.nanite_volume / 50, 1) //0.5 per 50 nanite volume + rep_rate *= 0.5 + nanites.adjust_nanites(null, rep_rate) + if(prob(rep_rate)) + var/datum/nanite_program/fault = pick(nanites.programs) + if(fault == src) + return + fault.software_error() + host_mob.investigate_log("[fault] nanite program received a software error due to Mitosis program.", INVESTIGATE_NANITES) + +/datum/nanite_program/dermal_button + name = "Dermal Button" + desc = "Displays a button on the host's skin, which can be used to send a signal to the nanites." + unique = FALSE + var/datum/action/innate/nanite_button/button + +/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("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] + if(!button) + button = new(src, bn_name.get_value(), bn_icon.get_value()) + button.target = host_mob + button.Grant(host_mob) + +/datum/nanite_program/dermal_button/disable_passive_effect() + . = ..() + if(button) + button.Remove(host_mob) + +/datum/nanite_program/dermal_button/on_mob_remove() + . = ..() + QDEL_NULL(button) + +/datum/nanite_program/dermal_button/proc/press() + if(activated) + host_mob.visible_message(span_notice("[host_mob] presses a button on [host_mob.p_their()] forearm."), + span_notice("You press the nanite button on your forearm."), null, 2) + var/datum/nanite_extra_setting/sent_code = extra_settings[NES_SENT_CODE] + SEND_SIGNAL(host_mob, COMSIG_NANITE_SIGNAL, sent_code.get_value(), "a [name] program") + +/datum/action/innate/nanite_button + name = "Button" + button_icon = 'icons/mob/actions/actions_items.dmi' + check_flags = AB_CHECK_HANDS_BLOCKED|AB_CHECK_IMMOBILE|AB_CHECK_CONSCIOUS + button_icon_state = "bci_power" + var/datum/nanite_program/dermal_button/program + +/datum/action/innate/nanite_button/New(datum/nanite_program/dermal_button/_program, _name, _icon) + ..() + program = _program + name = _name + button_icon_state = "bci_[_icon]" + +/datum/action/innate/nanite_button/Activate() + program.press() diff --git a/modular_zubbers/code/modules/nanites/nanite_programs/weapon.dm b/modular_zubbers/code/modules/nanites/nanite_programs/weapon.dm new file mode 100644 index 00000000000..f71a101ad97 --- /dev/null +++ b/modular_zubbers/code/modules/nanites/nanite_programs/weapon.dm @@ -0,0 +1,186 @@ +//Programs specifically engineered to cause harm to either the user or its surroundings (as opposed to ones that only do it due to broken programming) +//Very dangerous! + +/datum/nanite_program/flesh_eating + name = "Cellular Breakdown" + desc = "The nanites destroy cellular structures in the host's body, causing brute damage." + use_rate = 1.5 + rogue_types = list(/datum/nanite_program/necrotic) + +/datum/nanite_program/flesh_eating/active_effect() + if(iscarbon(host_mob)) + var/mob/living/carbon/C = host_mob + C.take_bodypart_damage(1, 0, TRUE) + else + host_mob.adjustBruteLoss(1, TRUE) + if(prob(3)) + to_chat(host_mob, span_warning("You feel a stab of pain from somewhere inside you.")) + +/datum/nanite_program/poison + name = "Poisoning" + desc = "The nanites deliver poisonous chemicals to the host's internal organs, causing toxin damage and vomiting." + use_rate = 1.5 + rogue_types = list(/datum/nanite_program/toxic) + +/datum/nanite_program/poison/active_effect() + host_mob.adjustToxLoss(1) + if(prob(2)) + to_chat(host_mob, span_warning("You feel nauseous.")) + if(iscarbon(host_mob)) + var/mob/living/carbon/C = host_mob + C.vomit(20) + +/datum/nanite_program/memory_leak + name = "Memory Leak" + desc = "This program invades the memory space used by other programs, causing frequent corruptions and errors." + use_rate = 0 + rogue_types = list(/datum/nanite_program/toxic) + +/datum/nanite_program/memory_leak/active_effect() + if(prob(6)) + var/datum/nanite_program/target = pick(nanites.programs) + if(target == src) + return + target.software_error() + host_mob.investigate_log("[target] nanite program received a software error due to Memory Leak program.", INVESTIGATE_NANITES) + +/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, \ + and scaling linearly for a net positive of 0.1 production rate per 20 nanite volume beyond that." + use_rate = 1 + rogue_types = list(/datum/nanite_program/necrotic) + +/datum/nanite_program/aggressive_replication/active_effect() + var/extra_regen = round(nanites.nanite_volume / 200, 0.1) + nanites.adjust_nanites(null, extra_regen) + host_mob.adjustBruteLoss(extra_regen / 2, TRUE) + +/datum/nanite_program/meltdown + name = "Meltdown" + desc = "Causes an internal meltdown inside the nanites, causing internal burns inside the host as well as rapidly destroying the nanite population.\ + Sets the nanites' safety threshold to 0 when activated." + use_rate = 10 + rogue_types = list(/datum/nanite_program/glitch) + +/datum/nanite_program/meltdown/active_effect() + host_mob.adjustFireLoss(3.5) + +/datum/nanite_program/meltdown/enable_passive_effect() + . = ..() + to_chat(host_mob, span_userdanger("Your blood is burning!")) + nanites.safety_threshold = 0 + +/datum/nanite_program/meltdown/disable_passive_effect() + . = ..() + to_chat(host_mob, span_warning("Your blood cools down, and the pain gradually fades.")) + +/datum/nanite_program/explosive + name = "Chain Detonation" + desc = "Detonates all the nanites inside the host in a chain reaction when triggered." + can_trigger = TRUE + trigger_cost = 25 //plus every idle nanite left afterwards + trigger_cooldown = 100 //Just to avoid double-triggering + rogue_types = list(/datum/nanite_program/toxic) + +/datum/nanite_program/explosive/on_trigger(comm_message) + host_mob.visible_message(span_warning("[host_mob] starts emitting a high-pitched buzzing, and [host_mob.p_their()] skin begins to glow..."),\ + span_userdanger("You start emitting a high-pitched buzzing, and your skin begins to glow...")) + addtimer(CALLBACK(src, .proc/boom), clamp((nanites.nanite_volume * 0.35), 25, 150)) + +/datum/nanite_program/explosive/proc/boom() + dyn_explosion(host_mob, nanites.nanite_volume / 50) + qdel(nanites) + +//TODO make it defuse if triggered again + +/datum/nanite_program/heart_stop + name = "Heart-Stopper" + desc = "Stops the host's heart when triggered; restarts it if triggered again." + can_trigger = TRUE + trigger_cost = 12 + trigger_cooldown = 10 + rogue_types = list(/datum/nanite_program/nerve_decay) + +/datum/nanite_program/heart_stop/on_trigger(comm_message) + if(iscarbon(host_mob)) + var/mob/living/carbon/carbon = host_mob + var/obj/item/organ/heart/heart = carbon.get_organ_slot(ORGAN_SLOT_HEART) + if(heart) + if(heart.is_beating()) + heart.Stop() + else + heart.Restart() + +/datum/nanite_program/emp + name = "Electromagnetic Resonance" + desc = "The nanites cause an electromagnetic pulse around the host when triggered. Will corrupt other nanite programs!" + can_trigger = TRUE + trigger_cost = 10 + trigger_cooldown = 50 + program_flags = NANITE_EMP_IMMUNE + rogue_types = list(/datum/nanite_program/toxic) + +/datum/nanite_program/emp/on_trigger(comm_message) + empulse(host_mob, 1, 2) + +/datum/nanite_program/pyro + name = "Sub-Dermal Combustion" + desc = "The nanites cause buildup of flammable fluids under the host's skin, then ignites them." + use_rate = 4 + rogue_types = list(/datum/nanite_program/skin_decay, /datum/nanite_program/cryo) + +/datum/nanite_program/pyro/check_conditions() + if(host_mob.fire_stacks >= 10 && host_mob.on_fire) + return FALSE + return ..() + +/datum/nanite_program/pyro/active_effect() + host_mob.adjust_fire_stacks(1) + host_mob.ignite_mob() + +/datum/nanite_program/cryo + name = "Cryogenic Treatment" + desc = "The nanites rapidly sink heat through the host's skin, lowering their temperature." + use_rate = 1 + rogue_types = list(/datum/nanite_program/skin_decay, /datum/nanite_program/pyro) + +/datum/nanite_program/cryo/check_conditions() + if(host_mob.bodytemperature <= 70) + return FALSE + return ..() + +/datum/nanite_program/cryo/active_effect() + host_mob.adjust_bodytemperature(-rand(15,25), 50) + +/datum/nanite_program/comm/mind_control + name = "Mind Control" + desc = "The nanites imprint an absolute directive onto the host's brain for one minute when triggered." + trigger_cost = 30 + trigger_cooldown = 1800 + rogue_types = list(/datum/nanite_program/brain_decay, /datum/nanite_program/brain_misfire) + +/datum/nanite_program/comm/mind_control/register_extra_settings() + . = ..() + extra_settings[NES_DIRECTIVE] = new /datum/nanite_extra_setting/text("...") + +/datum/nanite_program/comm/mind_control/on_trigger(comm_message) + if(host_mob.stat == DEAD) + return + var/sent_directive = comm_message + if(!comm_message) + var/datum/nanite_extra_setting/ES = extra_settings[NES_DIRECTIVE] + sent_directive = ES.get_value() + brainwash(host_mob, sent_directive) + log_game("A mind control nanite program brainwashed [key_name(host_mob)] with the objective '[sent_directive]'.") + host_mob.log_message("has been brainwashed with the objective '[sent_directive]' triggered by a mind control nanite program.", LOG_ATTACK) + addtimer(CALLBACK(src, .proc/end_brainwashing), 600) + +/datum/nanite_program/comm/mind_control/proc/end_brainwashing() + if(host_mob.mind && host_mob.mind.has_antag_datum(/datum/antagonist/brainwashed)) + host_mob.mind.remove_antag_datum(/datum/antagonist/brainwashed) + log_game("[key_name(host_mob)] is no longer brainwashed by nanites.") + +/datum/nanite_program/comm/mind_control/disable_passive_effect() + . = ..() + end_brainwashing() diff --git a/modular_zubbers/code/modules/nanites/nanite_sign.dm b/modular_zubbers/code/modules/nanites/nanite_sign.dm new file mode 100644 index 00000000000..f849aca4e1c --- /dev/null +++ b/modular_zubbers/code/modules/nanites/nanite_sign.dm @@ -0,0 +1,9 @@ +/obj/structure/sign/departments/nanites + name = "\improper Nanite Lab sign" + icon = 'modular_zubbers/icons/obj/signs/nanite_sign.dmi' + icon_state = "nanites" + sign_change_name = "Department - Science: Nanites" + desc = "A sign labelling an area where testing and development of nanites is performed." + is_editable = TRUE + +MAPPING_DIRECTIONAL_HELPERS(/obj/structure/sign/departments/nanites, 32) diff --git a/modular_zubbers/code/modules/nanites/nanite_symptom.dm b/modular_zubbers/code/modules/nanites/nanite_symptom.dm new file mode 100644 index 00000000000..1ca11b28e27 --- /dev/null +++ b/modular_zubbers/code/modules/nanites/nanite_symptom.dm @@ -0,0 +1,71 @@ +/datum/symptom/nano_boost + name = "Nano-symbiosis" + desc = "The virus reacts to nanites in the host's bloodstream by enhancing their replication cycle." + stealth = 0 + resistance = 2 + stage_speed = 2 + transmittable = -1 + level = 7 + severity = 0 + symptom_delay_min = 1 + symptom_delay_max = 1 + var/reverse_boost = FALSE + threshold_descs = list( + "Transmission 5" = "Increases the virus' growth rate while nanites are present.", + "Stage Speed 7" = "Increases the replication boost." + ) + +/datum/symptom/nano_boost/Start(datum/disease/advance/A) + . = ..() + if(!.) + return + if(A.totalTransmittable() >= 5) //reverse boost + reverse_boost = TRUE + if(A.totalStageSpeed() >= 7) //more nanites + power = 2 + +/datum/symptom/nano_boost/Activate(datum/disease/advance/A) + . = ..() + if(!.) + return + var/mob/living/carbon/M = A.affected_mob + SEND_SIGNAL(M, COMSIG_NANITE_ADJUST_VOLUME, 0.5 * power) + if(reverse_boost && SEND_SIGNAL(M, COMSIG_HAS_NANITES)) + if(prob(A.stage_prob)) + A.stage = min(A.stage + 1,A.max_stages) + +/datum/symptom/nano_destroy + name = "Silicolysis" + desc = "The virus reacts to nanites in the host's bloodstream by attacking and consuming them." + stealth = 0 + resistance = 4 + stage_speed = -1 + transmittable = 1 + level = 7 + severity = 0 + symptom_delay_min = 1 + symptom_delay_max = 1 + var/reverse_boost = FALSE + threshold_descs = list( + "Stage Speed 5" = "Increases the virus' growth rate while nanites are present.", + "Resistance 7" = "Severely increases the rate at which the nanites are destroyed." + ) + +/datum/symptom/nano_destroy/Start(datum/disease/advance/A) + . = ..() + if(!.) + return + if(A.totalStageSpeed() >= 5) //reverse boost + reverse_boost = TRUE + if(A.totalResistance() >= 7) //more nanites + power = 3 + +/datum/symptom/nano_destroy/Activate(datum/disease/advance/A) + . = ..() + if(!.) + return + var/mob/living/carbon/M = A.affected_mob + SEND_SIGNAL(M, COMSIG_NANITE_ADJUST_VOLUME, -0.4 * power) + if(reverse_boost && SEND_SIGNAL(M, COMSIG_HAS_NANITES)) + if(prob(A.stage_prob)) + A.stage = min(A.stage + 1,A.max_stages) diff --git a/modular_zubbers/code/modules/nanites/program_designs.dm b/modular_zubbers/code/modules/nanites/program_designs.dm new file mode 100644 index 00000000000..6dbfb469664 --- /dev/null +++ b/modular_zubbers/code/modules/nanites/program_designs.dm @@ -0,0 +1,573 @@ +/datum/design/nanites + name = "None" + desc = "Warn a coder if you see this." + id = "default_nanites" + build_type = NANITE_COMPILER + construction_time = 50 + category = list() + research_icon = 'modular_zubbers/icons/obj/devices/nanite_devices.dmi' + research_icon_state = "nanite_program" + var/program_type = /datum/nanite_program + +////////////////////UTILITY NANITES////////////////////////////////////// + +/datum/design/nanites/metabolic_synthesis + name = "Metabolic Synthesis" + desc = "The nanites use the metabolic cycle of the host to speed up their replication rate, using their extra nutrition as fuel." + id = "metabolic_nanites" + program_type = /datum/nanite_program/metabolic_synthesis + category = list("Utility Nanites") + +/datum/design/nanites/viral + name = "Viral Replica" + desc = "The nanites constantly send encrypted signals attempting to forcefully copy their own programming into other nanite clusters." + id = "viral_nanites" + program_type = /datum/nanite_program/viral + category = list("Utility Nanites") + +/datum/design/nanites/monitoring + name = "Monitoring" + desc = "The nanites monitor the host's vitals and location, sending them to the suit sensor network." + id = "monitoring_nanites" + program_type = /datum/nanite_program/monitoring + category = list("Utility Nanites") + +/datum/design/nanites/self_scan + name = "Host Scan" + desc = "The nanites display a detailed readout of a body scan to the host." + id = "selfscan_nanites" + program_type = /datum/nanite_program/self_scan + category = list("Utility Nanites") + +/datum/design/nanites/dermal_button + name = "Dermal Button" + desc = "Displays a button on the host's skin, which can be used to send a signal to the nanites." + id = "dermal_button_nanites" + program_type = /datum/nanite_program/dermal_button + category = list("Utility Nanites") + +/datum/design/nanites/stealth + name = "Stealth" + desc = "The nanites hide their activity and programming from superficial scans." + id = "stealth_nanites" + program_type = /datum/nanite_program/stealth + category = list("Utility Nanites") + +/datum/design/nanites/nanite_debugging + name = "Nanite Debugging" + desc = "Enables various high-cost diagnostics in the nanites, making them able to communicate their program list to portable scanners. \ + Doing so uses some power, slightly decreasing their replication speed." + id = "debugging_nanites" + program_type = /datum/nanite_program/nanite_debugging + category = list("Utility Nanites") + +/datum/design/nanites/access + name = "Subdermal ID" + desc = "The nanites store the host's ID access rights in a subdermal magnetic strip. Updates when triggered, copying the host's current access." + id = "access_nanites" + program_type = /datum/nanite_program/access + category = list("Utility Nanites") + +/datum/design/nanites/relay + name = "Relay" + desc = "The nanites receive and relay long-range nanite signals." + id = "relay_nanites" + program_type = /datum/nanite_program/relay + category = list("Utility Nanites") + +/datum/design/nanites/repeater + name = "Signal Repeater" + desc = "When triggered, sends another signal to the nanites, optionally with a delay." + id = "repeater_nanites" + program_type = /datum/nanite_program/sensor/repeat + category = list("Utility Nanites") + +/datum/design/nanites/relay_repeater + name = "Relay Signal Repeater" + desc = "When triggered, sends another signal to a relay channel, optionally with a delay." + id = "relay_repeater_nanites" + program_type = /datum/nanite_program/sensor/relay_repeat + category = list("Utility Nanites") + +/datum/design/nanites/emp + name = "Electromagnetic Resonance" + desc = "The nanites cause an electromagnetic pulse around the host when triggered. Will corrupt other nanite programs!" + id = "emp_nanites" + program_type = /datum/nanite_program/emp + category = list("Utility Nanites") + +/datum/design/nanites/spreading + name = "Infective Exo-Locomotion" + desc = "The nanites gain the ability to survive for brief periods outside of the human body, as well as the ability to start new colonies without an integration process; \ + resulting in an extremely infective strain of nanites." + id = "spreading_nanites" + program_type = /datum/nanite_program/spreading + category = list("Utility Nanites") + +/datum/design/nanites/nanite_sting + name = "Nanite Sting" + desc = "When triggered, projects a nearly invisible spike of nanites that attempts to infect a nearby non-host with a copy of the host's nanites cluster." + id = "nanite_sting_nanites" + program_type = /datum/nanite_program/nanite_sting + category = list("Utility Nanites") + +/datum/design/nanites/mitosis + name = "Mitosis" + desc = "The nanites gain the ability to self-replicate, using bluespace to power the process, instead of drawing from a template. This rapidly speeds up the replication rate,\ + but it causes occasional software errors due to faulty copies." + id = "mitosis_nanites" + program_type = /datum/nanite_program/mitosis + category = list("Utility Nanites") + +////////////////////MEDICAL NANITES////////////////////////////////////// +/datum/design/nanites/regenerative + name = "Accelerated Regeneration" + desc = "The nanites boost the host's natural regeneration, increasing their healing speed." + id = "regenerative_nanites" + program_type = /datum/nanite_program/regenerative + category = list("Medical Nanites") + +/datum/design/nanites/regenerative_advanced + name = "Bio-Reconstruction" + desc = "The nanites manually repair and replace organic cells, acting much faster than normal regeneration. \ + However, this program cannot detect the difference between harmed and unharmed, causing it to consume nanites even if it has no effect." + id = "regenerative_plus_nanites" + program_type = /datum/nanite_program/regenerative/advanced + category = list("Medical Nanites") + +/datum/design/nanites/temperature + name = "Temperature Adjustment" + desc = "The nanites adjust the host's internal temperature to an ideal level." + id = "temperature_nanites" + program_type = /datum/nanite_program/temperature + category = list("Medical Nanites") + +/datum/design/nanites/purging + name = "Blood Purification" + desc = "The nanites purge toxins and chemicals from the host's bloodstream." + id = "purging_nanites" + program_type = /datum/nanite_program/purging + category = list("Medical Nanites") + +/datum/design/nanites/purging_advanced + name = "Selective Blood Purification" + desc = "The nanites purge toxins and dangerous chemicals from the host's bloodstream, while ignoring beneficial chemicals. \ + The added processing power required to analyze the chemicals severely increases the nanite consumption rate." + id = "purging_plus_nanites" + program_type = /datum/nanite_program/purging/advanced + category = list("Medical Nanites") + +/datum/design/nanites/brain_heal + name = "Neural Regeneration" + desc = "The nanites fix neural connections in the host's brain, reversing brain damage and minor traumas." + id = "brainheal_nanites" + program_type = /datum/nanite_program/brain_heal + category = list("Medical Nanites") + +/datum/design/nanites/brain_heal_advanced + name = "Neural Reimaging" + desc = "The nanites are able to backup and restore the host's neural connections, potentially replacing entire chunks of missing or damaged brain matter." + id = "brainheal_plus_nanites" + program_type = /datum/nanite_program/brain_heal_advanced + category = list("Medical Nanites") + +/datum/design/nanites/blood_restoring + name = "Blood Regeneration" + desc = "The nanites stimulate and boost blood cell production in the host." + id = "bloodheal_nanites" + program_type = /datum/nanite_program/blood_restoring + category = list("Medical Nanites") + +/datum/design/nanites/repairing + name = "Mechanical Repair" + desc = "The nanites fix damage in the host's mechanical limbs." + id = "repairing_nanites" + program_type = /datum/nanite_program/regenerative/robotic + category = list("Medical Nanites") + +/datum/design/nanites/defib + name = "Defibrillation" + desc = "The nanites, when triggered, send a defibrillating shock to the host's heart." + id = "defib_nanites" + program_type = /datum/nanite_program/defib + category = list("Medical Nanites") + + +////////////////////AUGMENTATION NANITES////////////////////////////////////// + +/datum/design/nanites/nervous + name = "Nerve Support" + desc = "The nanites act as a secondary nervous system, reducing the amount of time the host is stunned." + id = "nervous_nanites" + program_type = /datum/nanite_program/nervous + category = list("Augmentation Nanites") + +/datum/design/nanites/hardening + name = "Dermal Hardening" + desc = "The nanites form a mesh under the host's skin, protecting them from melee and bullet impacts." + id = "hardening_nanites" + program_type = /datum/nanite_program/dermal_armor + category = list("Augmentation Nanites") + +/datum/design/nanites/refractive + name = "Dermal Refractive Surface" + desc = "The nanites form a membrane above the host's skin, reducing the effect of laser and energy impacts." + id = "refractive_nanites" + program_type = /datum/nanite_program/dermal_armor/refractive + category = list("Augmentation Nanites") + +/datum/design/nanites/coagulating + 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") + +/datum/design/nanites/conductive + name = "Electric Conduction" + desc = "The nanites act as a grounding rod for electric shocks, protecting the host. Shocks can still damage the nanites themselves." + id = "conductive_nanites" + program_type = /datum/nanite_program/conductive + category = list("Augmentation Nanites") + +/datum/design/nanites/mindshield + name = "Mental Barrier" + desc = "The nanites form a protective membrane around the host's brain, shielding them from abnormal influences while they're active." + id = "mindshield_nanites" + program_type = /datum/nanite_program/mindshield + category = list("Augmentation Nanites") + +////////////////////DEFECTIVE NANITES////////////////////////////////////// + +/datum/design/nanites/glitch + name = "Glitch" + desc = "A heavy software corruption that causes nanites to gradually break down." + id = "glitch_nanites" + program_type = /datum/nanite_program/glitch + category = list("Defective Nanites") + +/datum/design/nanites/necrotic + name = "Necrosis" + desc = "The nanites attack internal tissues indiscriminately, causing widespread damage." + id = "necrotic_nanites" + program_type = /datum/nanite_program/necrotic + category = list("Defective Nanites") + +/datum/design/nanites/toxic + name = "Toxin Buildup" + desc = "The nanites cause a slow but constant toxin buildup inside the host." + id = "toxic_nanites" + program_type = /datum/nanite_program/toxic + category = list("Defective Nanites") + +/datum/design/nanites/suffocating + name = "Hypoxemia" + desc = "The nanites prevent the host's blood from absorbing oxygen efficiently." + id = "suffocating_nanites" + program_type = /datum/nanite_program/suffocating + category = list("Defective Nanites") + +/datum/design/nanites/brain_misfire + name = "Brain Misfire" + desc = "The nanites interfere with neural pathways, causing minor psychological disturbances." + id = "brainmisfire_nanites" + program_type = /datum/nanite_program/brain_misfire + category = list("Defective Nanites") + +/datum/design/nanites/skin_decay + name = "Dermalysis" + desc = "The nanites attack skin cells, causing irritation, rashes, and minor damage." + id = "skindecay_nanites" + program_type = /datum/nanite_program/skin_decay + category = list("Defective Nanites") + +/datum/design/nanites/nerve_decay + name = "Nerve Decay" + desc = "The nanites attack the host's nerves, causing lack of coordination and short bursts of paralysis." + id = "nervedecay_nanites" + program_type = /datum/nanite_program/nerve_decay + category = list("Defective Nanites") + +/datum/design/nanites/brain_decay + name = "Brain-Eating Nanites" + desc = "Damages brain cells, gradually decreasing the host's cognitive functions." + id = "braindecay_nanites" + program_type = /datum/nanite_program/brain_decay + category = list("Defective Nanites") + +////////////////////WEAPONIZED NANITES///////////////////////////////////// + +/datum/design/nanites/flesh_eating + name = "Cellular Breakdown" + desc = "The nanites destroy cellular structures in the host's body, causing brute damage." + id = "flesheating_nanites" + program_type = /datum/nanite_program/flesh_eating + category = list("Weaponized Nanites") + +/datum/design/nanites/poison + name = "Poisoning" + desc = "The nanites deliver poisonous chemicals to the host's internal organs, causing toxin damage and vomiting." + id = "poison_nanites" + program_type = /datum/nanite_program/poison + category = list("Weaponized Nanites") + +/datum/design/nanites/memory_leak + name = "Memory Leak" + desc = "This program invades the memory space used by other programs, causing frequent corruptions and errors." + id = "memleak_nanites" + program_type = /datum/nanite_program/memory_leak + category = list("Weaponized Nanites") + +/datum/design/nanites/aggressive_replication + name = "Aggressive Replication" + desc = "Nanites will consume organic matter to improve their replication rate, damaging the host." + id = "aggressive_nanites" + program_type = /datum/nanite_program/aggressive_replication + category = list("Weaponized Nanites") + +/datum/design/nanites/meltdown + name = "Meltdown" + desc = "Causes an internal meltdown inside the nanites, causing internal burns inside the host as well as rapidly destroying the nanite population.\ + Sets the nanites' safety threshold to 0 when activated." + id = "meltdown_nanites" + program_type = /datum/nanite_program/meltdown + category = list("Weaponized Nanites") + +/datum/design/nanites/cryo + name = "Cryogenic Treatment" + desc = "The nanites rapidly skin heat through the host's skin, lowering their temperature." + id = "cryo_nanites" + program_type = /datum/nanite_program/cryo + category = list("Weaponized Nanites") + +/datum/design/nanites/pyro + name = "Sub-Dermal Combustion" + desc = "The nanites cause buildup of flammable fluids under the host's skin, then ignites them." + id = "pyro_nanites" + program_type = /datum/nanite_program/pyro + category = list("Weaponized Nanites") + +/datum/design/nanites/heart_stop + name = "Heart-Stopper" + desc = "Stops the host's heart when triggered; restarts it if triggered again." + id = "heartstop_nanites" + program_type = /datum/nanite_program/heart_stop + category = list("Weaponized Nanites") + +/datum/design/nanites/explosive + name = "Chain Detonation" + desc = "Blows up all the nanites inside the host in a chain reaction when triggered." + id = "explosive_nanites" + program_type = /datum/nanite_program/explosive + category = list("Weaponized Nanites") + +/datum/design/nanites/mind_control + name = "Mind Control" + desc = "The nanites imprint an absolute directive onto the host's brain while they're active." + id = "mindcontrol_nanites" + program_type = /datum/nanite_program/comm/mind_control + category = list("Weaponized Nanites") + +////////////////////SUPPRESSION NANITES////////////////////////////////////// + +/datum/design/nanites/shock + name = "Electric Shock" + desc = "The nanites shock the host when triggered. Destroys a large amount of nanites!" + id = "shock_nanites" + program_type = /datum/nanite_program/shocking + category = list("Suppression Nanites") + +/datum/design/nanites/stun + name = "Neural Shock" + desc = "The nanites pulse the host's nerves when triggered, inapacitating them for a short period." + id = "stun_nanites" + program_type = /datum/nanite_program/stun + category = list("Suppression Nanites") + +/datum/design/nanites/sleepy + name = "Sleep Induction" + desc = "The nanites cause rapid narcolepsy when triggered." + id = "sleep_nanites" + program_type = /datum/nanite_program/sleepy + category = list("Suppression Nanites") + +/datum/design/nanites/paralyzing + name = "Paralysis" + desc = "The nanites actively suppress nervous pulses, effectively paralyzing the host." + id = "paralyzing_nanites" + program_type = /datum/nanite_program/paralyzing + category = list("Suppression Nanites") + +/datum/design/nanites/fake_death + name = "Death Simulation" + desc = "The nanites induce a death-like coma into the host, able to fool most medical scans." + id = "fakedeath_nanites" + program_type = /datum/nanite_program/fake_death + category = list("Suppression Nanites") + +/datum/design/nanites/pacifying + name = "Pacification" + desc = "The nanites suppress the aggression center of the brain, preventing the host from causing direct harm to others." + id = "pacifying_nanites" + program_type = /datum/nanite_program/pacifying + category = list("Suppression Nanites") + +/datum/design/nanites/blinding + name = "Blindness" + desc = "The nanites suppress the host's ocular nerves, blinding them while they're active." + id = "blinding_nanites" + program_type = /datum/nanite_program/blinding + category = list("Suppression Nanites") + +/datum/design/nanites/mute + name = "Mute" + desc = "The nanites suppress the host's speech, making them mute while they're active." + id = "mute_nanites" + program_type = /datum/nanite_program/mute + category = list("Suppression Nanites") + +/datum/design/nanites/voice + name = "Skull Echo" + desc = "The nanites echo a synthesized message inside the host's skull." + id = "voice_nanites" + program_type = /datum/nanite_program/comm/voice + category = list("Suppression Nanites") + +/datum/design/nanites/speech + name = "Forced Speech" + desc = "The nanites force the host to say a pre-programmed sentence when triggered." + id = "speech_nanites" + program_type = /datum/nanite_program/comm/speech + category = list("Suppression Nanites") + +/datum/design/nanites/hallucination + name = "Hallucination" + desc = "The nanites make the host see and hear things that aren't real." + id = "hallucination_nanites" + program_type = /datum/nanite_program/comm/hallucination + category = list("Suppression Nanites") + +/datum/design/nanites/good_mood + name = "Happiness Enhancer" + desc = "The nanites synthesize serotonin inside the host's brain, creating an artificial sense of happiness." + id = "good_mood_nanites" + program_type = /datum/nanite_program/good_mood + category = list("Suppression Nanites") + +/datum/design/nanites/bad_mood + name = "Happiness Suppressor" + desc = "The nanites suppress the production of serotonin inside the host's brain, creating an artificial state of depression." + id = "bad_mood_nanites" + program_type = /datum/nanite_program/bad_mood + category = list("Suppression Nanites") + +////////////////////SENSOR NANITES////////////////////////////////////// + +/datum/design/nanites/sensor_health + name = "Health Sensor" + desc = "The nanites receive a signal when the host's health is above/below a certain percentage." + id = "sensor_health_nanites" + program_type = /datum/nanite_program/sensor/health + category = list("Sensor Nanites") + +/datum/design/nanites/sensor_damage + name = "Damage Sensor" + desc = "The nanites receive a signal when a host's specific damage type is above/below a target value." + id = "sensor_damage_nanites" + program_type = /datum/nanite_program/sensor/damage + category = list("Sensor Nanites") + +/datum/design/nanites/sensor_crit + name = "Critical Health Sensor" + desc = "The nanites receive a signal when the host first reaches critical health." + id = "sensor_crit_nanites" + program_type = /datum/nanite_program/sensor/crit + category = list("Sensor Nanites") + +/datum/design/nanites/sensor_death + name = "Death Sensor" + desc = "The nanites receive a signal when they detect the host is dead." + id = "sensor_death_nanites" + program_type = /datum/nanite_program/sensor/death + category = list("Sensor Nanites") + +/datum/design/nanites/sensor_voice + name = "Voice Sensor" + desc = "Sends a signal when the nanites hear a determined word or sentence." + id = "sensor_voice_nanites" + program_type = /datum/nanite_program/sensor/voice + category = list("Sensor Nanites") + +/datum/design/nanites/sensor_nanite_volume + name = "Nanite Volume Sensor" + desc = "The nanites receive a signal when the nanite supply is above/below a certain percentage." + id = "sensor_nanite_volume" + program_type = /datum/nanite_program/sensor/nanite_volume + category = list("Sensor Nanites") + +/datum/design/nanites/sensor_species + name = "Species Sensor" + desc = "When triggered, the nanites scan the host to determine their species and output a signal depending on the conditions set in the settings." + id = "sensor_species_nanites" + program_type = /datum/nanite_program/sensor/species + category = list("Sensor Nanites") + +////////////////////NANITE PROTOCOLS////////////////////////////////////// +//Note about the category name: The UI cuts the last 8 characters from the category name to remove the " Nanites" in the other categories +//Because of this, Protocols was getting cut down to "P", so i had to add some padding +/datum/design/nanites/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." + id = "kickstart_nanites" + program_type = /datum/nanite_program/protocol/kickstart + category = list("Protocols_Nanites") + +/datum/design/nanites/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." + id = "factory_nanites" + program_type = /datum/nanite_program/protocol/factory + category = list("Protocols_Nanites") + +/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 = "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 + 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/modular_zubbers/code/modules/nanites/rules.dm b/modular_zubbers/code/modules/nanites/rules.dm new file mode 100644 index 00000000000..79723ca5b03 --- /dev/null +++ b/modular_zubbers/code/modules/nanites/rules.dm @@ -0,0 +1,148 @@ +/datum/nanite_rule + var/name = "Generic Condition" + var/desc = "When triggered, the program is active" + var/datum/nanite_program/program + +/datum/nanite_rule/New(datum/nanite_program/new_program) + program = new_program + if(LAZYLEN(new_program.rules) <= 5) //Avoid infinite stacking rules + new_program.rules += src + else + qdel(src) + +/datum/nanite_rule/proc/remove() + program.rules -= src + program = null + qdel(src) + +/datum/nanite_rule/proc/check_rule() + return TRUE + +/datum/nanite_rule/proc/display() + return name + +/datum/nanite_rule/proc/copy_to(datum/nanite_program/new_program) + new type(new_program) + +/datum/nanite_rule/health + name = "Health" + desc = "Checks the host's health status." + + var/threshold = 50 + var/above = TRUE + +/datum/nanite_rule/health/check_rule() + var/health_percent = program.host_mob.health / program.host_mob.maxHealth * 100 + if(above) + if(health_percent >= threshold) + return TRUE + else + if(health_percent < threshold) + return TRUE + return FALSE + +/datum/nanite_rule/health/display() + return "[name] [above ? ">" : "<"] [threshold]%" + +/datum/nanite_rule/health/copy_to(datum/nanite_program/new_program) + var/datum/nanite_rule/health/rule = new(new_program) + rule.above = above + rule.threshold = threshold + +//TODO allow inversion +/datum/nanite_rule/crit + name = "Crit" + desc = "Checks if the host is in critical condition." + +/datum/nanite_rule/crit/check_rule() + return HAS_TRAIT(program.host_mob, TRAIT_CRITICAL_CONDITION) + + +/datum/nanite_rule/death + name = "Death" + desc = "Checks if the host is dead." + +/datum/nanite_rule/death/check_rule() + if(program.host_mob.stat == DEAD || HAS_TRAIT(program.host_mob, TRAIT_FAKEDEATH)) + return TRUE + return FALSE + +/datum/nanite_rule/cloud_sync + name = "Cloud Sync" + desc = "Checks if the nanites have cloud sync enabled or disabled." + var/check_type = "Enabled" + +/datum/nanite_rule/cloud_sync/check_rule() + if(check_type == "Enabled") + return program.nanites.cloud_active + else + return !program.nanites.cloud_active + +/datum/nanite_rule/cloud_sync/copy_to(datum/nanite_program/new_program) + var/datum/nanite_rule/cloud_sync/rule = new(new_program) + rule.check_type = check_type + +/datum/nanite_rule/cloud_sync/display() + return "[name]:[check_type]" + +/datum/nanite_rule/nanites + name = "Nanite Volume" + desc = "Checks the host's nanite volume." + + var/threshold = 50 + var/above = TRUE + +/datum/nanite_rule/nanites/check_rule() + var/nanite_percent = (program.nanites.nanite_volume - program.nanites.safety_threshold)/(program.nanites.max_nanites - program.nanites.safety_threshold)*100 + if(above) + if(nanite_percent >= threshold) + return TRUE + else + if(nanite_percent < threshold) + return TRUE + return FALSE + +/datum/nanite_rule/nanites/copy_to(datum/nanite_program/new_program) + var/datum/nanite_rule/nanites/rule = new(new_program) + rule.above = above + rule.threshold = threshold + +/datum/nanite_rule/nanites/display() + return "[name] [above ? ">" : "<"] [threshold]%" + +/datum/nanite_rule/damage + name = "Damage" + desc = "Checks the host's damage." + + var/threshold = 50 + var/above = TRUE + var/damage_type = BRUTE + +/datum/nanite_rule/damage/check_rule() + var/damage_amt = 0 + switch(damage_type) + if(BRUTE) + damage_amt = program.host_mob.getBruteLoss() + if(BURN) + damage_amt = program.host_mob.getFireLoss() + if(TOX) + damage_amt = program.host_mob.getToxLoss() + if(OXY) + damage_amt = program.host_mob.getOxyLoss() + + if(above) + if(damage_amt >= threshold) + return TRUE + else + if(damage_amt < threshold) + return TRUE + return FALSE + +/datum/nanite_rule/damage/copy_to(datum/nanite_program/new_program) + var/datum/nanite_rule/damage/rule = new(new_program) + rule.above = above + rule.threshold = threshold + rule.damage_type = damage_type + +/datum/nanite_rule/damage/display() + return "[damage_type] [above ? ">" : "<"] [threshold]" diff --git a/modular_zubbers/code/modules/nanites/spawners.dm b/modular_zubbers/code/modules/nanites/spawners.dm new file mode 100644 index 00000000000..d0a775cfa6a --- /dev/null +++ b/modular_zubbers/code/modules/nanites/spawners.dm @@ -0,0 +1,11 @@ + +/obj/effect/spawner/random/techstorage/rnd_all/Initialize(mapload) + loot += list( + /obj/item/circuitboard/computer/nanite_chamber_control, + /obj/item/circuitboard/computer/nanite_cloud_controller, + /obj/item/circuitboard/machine/nanite_chamber, + /obj/item/circuitboard/machine/nanite_programmer, + /obj/item/circuitboard/machine/nanite_program_hub, + /obj/item/circuitboard/computer/scan_consolenew, + ) + . = ..() diff --git a/modular_zubbers/code/modules/nanites/techweb_nodes.dm b/modular_zubbers/code/modules/nanites/techweb_nodes.dm new file mode 100644 index 00000000000..8aebe364c94 --- /dev/null +++ b/modular_zubbers/code/modules/nanites/techweb_nodes.dm @@ -0,0 +1,180 @@ +/datum/techweb_node/nanite_base + id = "nanite_base" + display_name = "Basic Nanite Programming" + description = "The basics of nanite construction and programming. Nanites will passively generate science points based on how many are hosting them." + prereq_ids = list(TECHWEB_NODE_BCI) + design_ids = list( + // "access_nanites", + "debugging_nanites", + "monitoring_nanites", + "nanite_chamber", + "nanite_chamber_control", + "nanite_cloud_control", + "nanite_comm_remote", + "nanite_disk", + "nanite_program_hub", + "nanite_programmer", + "nanite_remote", + "nanite_scanner", + "public_nanite_chamber", + "relay_nanites", + "relay_repeater_nanites", + "repairing_nanites", + "repeater_nanites", + "sensor_nanite_volume", + ) + research_costs = list(TECHWEB_POINT_TYPE_GENERIC = TECHWEB_TIER_2_POINTS) + +/datum/techweb_node/nanite_smart + id = "nanite_smart" + display_name = "Smart Nanite Programming" + description = "Nanite programs that require nanites to perform complex actions, act independently, roam or seek targets." + prereq_ids = list("nanite_base", TECHWEB_NODE_PROGRAMMING) + design_ids = list( + "memleak_nanites", + "metabolic_nanites", + "purging_nanites", + "sensor_voice_nanites", + "stealth_nanites", + "voice_nanites", + ) + research_costs = list(TECHWEB_POINT_TYPE_GENERIC = TECHWEB_TIER_3_POINTS, TECHWEB_POINT_TYPE_NANITE = TECHWEB_TIER_1_POINTS) + +/datum/techweb_node/nanite_mesh + id = "nanite_mesh" + display_name = "Mesh Nanite Programming" + description = "Nanite programs that require static structures and membranes." + prereq_ids = list("nanite_base", TECHWEB_NODE_MOD_ENGI_ADV) + design_ids = list( + "conductive_nanites", + "cryo_nanites", + "dermal_button_nanites", + "emp_nanites", + "hardening_nanites", + "refractive_nanites", + "shock_nanites", + "temperature_nanites", + ) + research_costs = list(TECHWEB_POINT_TYPE_GENERIC = TECHWEB_TIER_4_POINTS, TECHWEB_POINT_TYPE_NANITE = TECHWEB_TIER_2_POINTS) + +/datum/techweb_node/nanite_bio + id = "nanite_bio" + display_name = "Biological Nanite Programming" + description = "Nanite programs that require complex biological interaction." + prereq_ids = list("nanite_base", TECHWEB_NODE_MOD_MEDICAL_ADV) + design_ids = list( + "bloodheal_nanites", + "coagulating_nanites", + "flesheating_nanites", + "poison_nanites", + "sensor_crit_nanites", + "sensor_damage_nanites", + "sensor_death_nanites", + "sensor_health_nanites", + "sensor_species_nanites", + ) + research_costs = list(TECHWEB_POINT_TYPE_GENERIC = TECHWEB_TIER_3_POINTS, TECHWEB_POINT_TYPE_NANITE = TECHWEB_TIER_3_POINTS) + +/datum/techweb_node/nanite_neural + id = "nanite_neural" + display_name = "Neural Nanite Programming" + description = "Nanite programs affecting nerves and brain matter." + prereq_ids = list("nanite_bio") + design_ids = list( + "bad_mood_nanites", + "brainheal_nanites", + "good_mood_nanites", + "nervous_nanites", + "paralyzing_nanites", + "selfscan_nanites", + "stun_nanites", + ) + research_costs = list(TECHWEB_POINT_TYPE_GENERIC = TECHWEB_TIER_3_POINTS, TECHWEB_POINT_TYPE_NANITE = TECHWEB_TIER_1_POINTS) + +/datum/techweb_node/nanite_synaptic + id = "nanite_synaptic" + display_name = "Synaptic Nanite Programming" + description = "Nanite programs affecting mind and thoughts." + prereq_ids = list("nanite_neural", TECHWEB_NODE_SURGERY_EXP) + design_ids = list( + "blinding_nanites", + "hallucination_nanites", + "mindshield_nanites", + "mute_nanites", + "pacifying_nanites", + "sleep_nanites", + // "speech_nanites", + ) + research_costs = list(TECHWEB_POINT_TYPE_GENERIC = TECHWEB_TIER_3_POINTS, TECHWEB_POINT_TYPE_NANITE = TECHWEB_TIER_1_POINTS) + +/datum/techweb_node/nanite_harmonic + id = "nanite_harmonic" + display_name = "Harmonic Nanite Programming" + description = "Nanite programs that require seamless integration between nanites and biology. Passively increases nanite regeneration rate for all clouds upon researching." + prereq_ids = list("nanite_bio","nanite_smart","nanite_mesh") + design_ids = list( + "regenerative_nanites", + "aggressive_nanites", + "brainheal_plus_nanites", + "defib_nanites", + "fakedeath_nanites", + "purging_plus_nanites", + // "regenerative_plus_nanites", + ) + research_costs = list(TECHWEB_POINT_TYPE_GENERIC = TECHWEB_TIER_4_POINTS, TECHWEB_POINT_TYPE_NANITE = TECHWEB_TIER_2_POINTS) + +/datum/techweb_node/nanite_combat + id = "nanite_military" + display_name = "Military Nanite Programming" + description = "Nanite programs that perform military-grade functions." + prereq_ids = list("nanite_harmonic", TECHWEB_NODE_SYNDICATE_BASIC) + design_ids = list( + // "explosive_nanites", + "meltdown_nanites", + "nanite_sting_nanites", + "pyro_nanites", + "viral_nanites", + ) + research_costs = list(TECHWEB_POINT_TYPE_GENERIC = TECHWEB_TIER_5_POINTS, TECHWEB_POINT_TYPE_NANITE = TECHWEB_TIER_3_POINTS) + +/datum/techweb_node/nanite_hazard + id = "nanite_hazard" + display_name = "Hazard Nanite Programs" + description = "Extremely advanced Nanite programs with the potential of being extremely dangerous." + prereq_ids = list("nanite_harmonic", TECHWEB_NODE_ALIENTECH) + design_ids = list( + "mindcontrol_nanites", + "mitosis_nanites", + // "spreading_nanites", + ) + research_costs = list(TECHWEB_POINT_TYPE_GENERIC = TECHWEB_TIER_4_POINTS, TECHWEB_POINT_TYPE_NANITE = TECHWEB_TIER_2_POINTS) + +/datum/techweb_node/nanite_replication_protocols + id = "nanite_replication_protocols" + display_name = "Nanite Replication Protocols" + description = "Protocols that overwrite the default nanite replication routine to achieve more efficiency in certain circumstances." + prereq_ids = list("nanite_smart") + design_ids = list( + "factory_nanites", + "kickstart_nanites", + "offline_nanites", + "pyramid_nanites", + ) + research_costs = list(TECHWEB_POINT_TYPE_GENERIC = TECHWEB_TIER_5_POINTS, TECHWEB_POINT_TYPE_NANITE = TECHWEB_TIER_3_POINTS) + hidden = TRUE + experimental = TRUE + +/datum/techweb_node/nanite_storage_protocols + id = "nanite_storage_protocols" + display_name = "Nanite Storage Protocols" + description = "Protocols that overwrite the default nanite storage routine to achieve more efficiency or greater capacity." + prereq_ids = list("nanite_smart") + design_ids = list( + "free_range_nanites", + "hive_nanites", + "unsafe_storage_nanites", + "zip_nanites", + ) + research_costs = list(TECHWEB_POINT_TYPE_GENERIC = TECHWEB_TIER_5_POINTS, TECHWEB_POINT_TYPE_NANITE = TECHWEB_TIER_3_POINTS) + hidden = TRUE + experimental = TRUE diff --git a/modular_zubbers/code/modules/nanites/unit_test.dm b/modular_zubbers/code/modules/nanites/unit_test.dm new file mode 100644 index 00000000000..1b725f6ac91 --- /dev/null +++ b/modular_zubbers/code/modules/nanites/unit_test.dm @@ -0,0 +1,9 @@ + +/datum/unit_test/nanite_designs/Run() +//Can't use allocate because of bug with certain datums + var/datum/design/nanites/default_design_nanites = new /datum/design/nanites() + + for(var/path in subtypesof(/datum/design)) + var/datum/design/nanites/current_design = new path //Create an instance of each design + if (isnull(current_design.program_type) || current_design.program_type == default_design_nanites.program_type) //Check if the Nanite design provides a program + Fail("Nanite Design [current_design.type] does not have have any program_type set") diff --git a/modular_zubbers/icons/areas/nanite_area.dmi b/modular_zubbers/icons/areas/nanite_area.dmi new file mode 100644 index 00000000000..132b954307e Binary files /dev/null and b/modular_zubbers/icons/areas/nanite_area.dmi differ diff --git a/modular_zubbers/icons/effects/decals/nanite_decal.dmi b/modular_zubbers/icons/effects/decals/nanite_decal.dmi new file mode 100644 index 00000000000..a4384c24bd0 Binary files /dev/null and b/modular_zubbers/icons/effects/decals/nanite_decal.dmi differ diff --git a/modular_zubbers/icons/mob/huds/hud.dmi b/modular_zubbers/icons/mob/huds/hud.dmi index e123a7b9966..3c31a58122d 100644 Binary files a/modular_zubbers/icons/mob/huds/hud.dmi and b/modular_zubbers/icons/mob/huds/hud.dmi differ diff --git a/modular_zubbers/icons/obj/devices/nanite_devices.dmi b/modular_zubbers/icons/obj/devices/nanite_devices.dmi new file mode 100644 index 00000000000..e9cec8222bf Binary files /dev/null and b/modular_zubbers/icons/obj/devices/nanite_devices.dmi differ diff --git a/modular_zubbers/icons/obj/machines/nanite_machines.dmi b/modular_zubbers/icons/obj/machines/nanite_machines.dmi new file mode 100644 index 00000000000..815d3924c60 Binary files /dev/null and b/modular_zubbers/icons/obj/machines/nanite_machines.dmi differ diff --git a/modular_zubbers/icons/obj/signs/nanite_sign.dmi b/modular_zubbers/icons/obj/signs/nanite_sign.dmi new file mode 100644 index 00000000000..a4384c24bd0 Binary files /dev/null and b/modular_zubbers/icons/obj/signs/nanite_sign.dmi differ diff --git a/strings/cas_white.txt b/strings/cas_white.txt index f8f324272df..58c5b96e931 100644 --- a/strings/cas_white.txt +++ b/strings/cas_white.txt @@ -92,6 +92,7 @@ Bluespace artillery. Brain cakes. Breaking spacetime with thousands of bluespace tomatoes. Buttbots. +The primal, nerve-slapping sex that nanites are having inside you. Button spam. BWOINK! Calling a shuttle because I broke a toenail. diff --git a/strings/locations.json b/strings/locations.json index 00befdaee7c..867060cda2a 100644 --- a/strings/locations.json +++ b/strings/locations.json @@ -92,6 +92,7 @@ "Virology", "Warehouse", "Waste Disposal", - "Xenobiology Lab" + "Xenobiology Lab", + "Nanite Lab" ] } diff --git a/tgstation.dme b/tgstation.dme index a0bfd6a121b..7cf552d9c2a 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -521,6 +521,7 @@ #include "code\__DEFINES\~~bubber_defines\loadout.dm" #include "code\__DEFINES\~~bubber_defines\misc.dm" #include "code\__DEFINES\~~bubber_defines\mood.dm" +#include "code\__DEFINES\~~bubber_defines\nanites.dm" #include "code\__DEFINES\~~bubber_defines\research_categories.dm" #include "code\__DEFINES\~~bubber_defines\say.dm" #include "code\__DEFINES\~~bubber_defines\signals.dm" @@ -531,9 +532,11 @@ #include "code\__DEFINES\~~bubber_defines\surgery.dm" #include "code\__DEFINES\~~bubber_defines\transport.dm" #include "code\__DEFINES\~~bubber_defines\vehicles.dm" +#include "code\__DEFINES\~~bubber_defines\vomit.dm" #include "code\__DEFINES\~~bubber_defines\___HELPERS\global_lists.dm" #include "code\__DEFINES\~~bubber_defines\research\techweb_nodes.dm" #include "code\__DEFINES\~~bubber_defines\traits\declarations.dm" +#include "code\__DEFINES\~~bubber_defines\traits\nanites.dm" #include "code\__DEFINES\~~bubber_defines\traits\sources.dm" #include "code\__HELPERS\_auxtools_api.dm" #include "code\__HELPERS\_dreamluau.dm" @@ -8783,6 +8786,7 @@ #include "modular_zubbers\code\controllers\subsystem\mapping.dm" #include "modular_zubbers\code\controllers\subsystem\research.dm" #include "modular_zubbers\code\controllers\subsystem\vote.dm" +#include "modular_zubbers\code\controllers\subsystem\processing\nanites.dm" #include "modular_zubbers\code\controllers\subsystem\processing\sol_subsystem.dm" #include "modular_zubbers\code\datums\action.dm" #include "modular_zubbers\code\datums\akula_overrides.dm" @@ -8793,6 +8797,7 @@ #include "modular_zubbers\code\datums\ai_laws\laws_neutral.dm" #include "modular_zubbers\code\datums\ai_laws\laws_station_sided.dm" #include "modular_zubbers\code\datums\brain_damage\magic.dm" +#include "modular_zubbers\code\datums\components\nanites.dm" #include "modular_zubbers\code\datums\components\sign_language.dm" #include "modular_zubbers\code\datums\components\squeak.dm" #include "modular_zubbers\code\datums\components\surgery_initiator.dm" @@ -8823,6 +8828,7 @@ #include "modular_zubbers\code\datums\mood_events\dominant_mood.dm" #include "modular_zubbers\code\datums\mood_events\food_events.dm" #include "modular_zubbers\code\datums\mood_events\miasma_events.dm" +#include "modular_zubbers\code\datums\mood_events\nanite_events.dm" #include "modular_zubbers\code\datums\mutations\visuals_override.dm" #include "modular_zubbers\code\datums\quirks\_quirk.dm" #include "modular_zubbers\code\datums\quirks\negative_quirks\all_nighter.dm" @@ -8869,6 +8875,7 @@ #include "modular_zubbers\code\datums\weather\weather_types\sand_storm.dm" #include "modular_zubbers\code\datums\wounds\permanent_limp.dm" #include "modular_zubbers\code\game\area\areas.dm" +#include "modular_zubbers\code\game\area\nanite.dm" #include "modular_zubbers\code\game\area\areas\centcom.dm" #include "modular_zubbers\code\game\area\areas\moonstation.dm" #include "modular_zubbers\code\game\area\areas\station.dm" @@ -9294,6 +9301,7 @@ #include "modular_zubbers\code\modules\mob\living\carbon\alien\adult\adult.dm" #include "modular_zubbers\code\modules\mob\living\carbon\human\_species.dm" #include "modular_zubbers\code\modules\mob\living\carbon\human\human.dm" +#include "modular_zubbers\code\modules\mob\living\carbon\human\physiology.dm" #include "modular_zubbers\code\modules\mob\living\carbon\human\species\akula.dm" #include "modular_zubbers\code\modules\mob\living\carbon\human\species\tajaran.dm" #include "modular_zubbers\code\modules\mob\living\carbon\human\species\xeno.dm" @@ -9357,6 +9365,38 @@ #include "modular_zubbers\code\modules\more_renaming\renaming_overrides.dm" #include "modular_zubbers\code\modules\movespeed\_movespeed_modifier.dm" #include "modular_zubbers\code\modules\movespeed\modifiers\status_effects.dm" +#include "modular_zubbers\code\modules\nanites\designs.dm" +#include "modular_zubbers\code\modules\nanites\living.dm" +#include "modular_zubbers\code\modules\nanites\nanite_programs.dm" +#include "modular_zubbers\code\modules\nanites\nanite_sign.dm" +#include "modular_zubbers\code\modules\nanites\nanite_symptom.dm" +#include "modular_zubbers\code\modules\nanites\program_designs.dm" +#include "modular_zubbers\code\modules\nanites\rules.dm" +#include "modular_zubbers\code\modules\nanites\spawners.dm" +#include "modular_zubbers\code\modules\nanites\techweb_nodes.dm" +#include "modular_zubbers\code\modules\nanites\extra_settings\_extra_setting.dm" +#include "modular_zubbers\code\modules\nanites\extra_settings\boolean.dm" +#include "modular_zubbers\code\modules\nanites\extra_settings\number.dm" +#include "modular_zubbers\code\modules\nanites\extra_settings\text.dm" +#include "modular_zubbers\code\modules\nanites\extra_settings\type.dm" +#include "modular_zubbers\code\modules\nanites\items\boards.dm" +#include "modular_zubbers\code\modules\nanites\items\items.dm" +#include "modular_zubbers\code\modules\nanites\items\program_disks.dm" +#include "modular_zubbers\code\modules\nanites\items\remote.dm" +#include "modular_zubbers\code\modules\nanites\machines\nanite_chamber.dm" +#include "modular_zubbers\code\modules\nanites\machines\nanite_chamber_computer.dm" +#include "modular_zubbers\code\modules\nanites\machines\nanite_cloud_controller.dm" +#include "modular_zubbers\code\modules\nanites\machines\nanite_program_hub.dm" +#include "modular_zubbers\code\modules\nanites\machines\nanite_programmer.dm" +#include "modular_zubbers\code\modules\nanites\machines\public_chamber.dm" +#include "modular_zubbers\code\modules\nanites\nanite_programs\buffing.dm" +#include "modular_zubbers\code\modules\nanites\nanite_programs\healing.dm" +#include "modular_zubbers\code\modules\nanites\nanite_programs\protocols.dm" +#include "modular_zubbers\code\modules\nanites\nanite_programs\rogue.dm" +#include "modular_zubbers\code\modules\nanites\nanite_programs\sensor.dm" +#include "modular_zubbers\code\modules\nanites\nanite_programs\suppression.dm" +#include "modular_zubbers\code\modules\nanites\nanite_programs\utility.dm" +#include "modular_zubbers\code\modules\nanites\nanite_programs\weapon.dm" #include "modular_zubbers\code\modules\nyamagotchi\nyamagotchi.dm" #include "modular_zubbers\code\modules\opposing_force\code\items.dm" #include "modular_zubbers\code\modules\opposing_force\code\equipment\antagonist_powers.dm" diff --git a/tgui/packages/tgui/interfaces/NaniteChamberControl.tsx b/tgui/packages/tgui/interfaces/NaniteChamberControl.tsx new file mode 100644 index 00000000000..31d36eaaf53 --- /dev/null +++ b/tgui/packages/tgui/interfaces/NaniteChamberControl.tsx @@ -0,0 +1,286 @@ +import { + Box, + Button, + Collapsible, + LabeledList, + NoticeBox, + NumberInput, + Section, + Stack, +} from 'tgui-core/components'; + +import { useBackend } from '../backend'; +import { Window } from '../layouts'; +import { NaniteProgram } from './Nanites/types'; + +export const NaniteChamberControl = (props, context) => { + return ( + + + + + + ); +}; + +interface NaniteChamberProps { + status_msg: string; + locked: boolean; + occupant_name: string; + has_nanites: boolean; + nanite_volume: number; + regen_rate: number; + safety_threshold: number; + cloud_id: number; + scan_level: number; + mob_programs: NaniteProgram[]; +} + +export const NaniteChamberControlContent = () => { + const { act, data } = useBackend(); + const { + status_msg, + locked, + occupant_name, + has_nanites, + nanite_volume, + regen_rate, + safety_threshold, + cloud_id, + scan_level, + mob_programs = [], + } = data; + + if (status_msg) { + return {status_msg}; + } + + return ( +
act('toggle_lock')} + > + {locked ? 'Locked' : 'Unlocked'} + + } + > + {!has_nanites ? ( + <> + + No Nanites Detected + + + + ) : ( + <> +
act('remove_nanites')} + > + Destroy Nanites + + } + > + + + + + {nanite_volume} + + + {regen_rate} + + + + + + + + act('set_safety', { + value: value, + }) + } + /> + + + + act('set_cloud', { + value: value, + }) + } + /> + + + + +
+
+ {mob_programs?.map((program) => { + const extra_settings = program.extra_settings || []; + const rules = program.rules || []; + return ( + +
+ + {scan_level >= 2 && ( + + + + + {program.activated ? 'Active' : 'Inactive'} + + + + {program.use_rate}/s + + + + )} + {program.desc} + + {scan_level >= 2 && ( + + {!!program.can_trigger && ( + +
+ + + {program.trigger_cost} + + + {program.trigger_cooldown} + + {!!program.timer_trigger_delay && ( + + {program.timer_trigger_delay} s + + )} + {!!program.timer_trigger && ( + + {program.timer_trigger} s + + )} + +
+
+ )} + {!!( + program.timer_restart || program.timer_shutdown + ) && ( + +
+ + {/* I mean, bruh, this indentation level + is ABSOLUTELY INSANE!!! */} + {program.timer_restart && ( + + {program.timer_restart} s + + )} + {program.timer_shutdown && ( + + {program.timer_shutdown} s + + )} + +
+
+ )} +
+ )} + {scan_level >= 3 && !!program.has_extra_settings && ( +
+ + {extra_settings.map((extra_setting) => ( + + {extra_setting.value} + + ))} + +
+ )} + {scan_level >= 4 && ( + + +
+ + {!!program.activation_code && ( + + {program.activation_code} + + )} + {!!program.deactivation_code && ( + + {program.deactivation_code} + + )} + {!!program.kill_code && ( + + {program.kill_code} + + )} + {!!program.can_trigger && + !!program.trigger_code && ( + + {program.trigger_code} + + )} + +
+
+ {program.has_rules && ( + +
+ {rules.map((rule) => ( + {rule.display} + ))} +
+
+ )} +
+ )} +
+
+ ); + })} +
+ + )} +
+ ); +}; diff --git a/tgui/packages/tgui/interfaces/NaniteCloudControl.tsx b/tgui/packages/tgui/interfaces/NaniteCloudControl.tsx new file mode 100644 index 00000000000..ab3095c4ffc --- /dev/null +++ b/tgui/packages/tgui/interfaces/NaniteCloudControl.tsx @@ -0,0 +1,366 @@ +import { + Box, + Button, + Collapsible, + LabeledList, + NoticeBox, + NumberInput, + Section, + Stack, +} from 'tgui-core/components'; + +import { useBackend } from '../backend'; +import { Window } from '../layouts'; +import { TechwebWarning } from './Nanites/NoTechwebWarning'; +import { NaniteProgram, Techweb } from './Nanites/types'; + +interface NaniteInfoBoxProps { + has_disk: boolean; + has_program: boolean; + disk: NaniteProgram; + current_view: number; + backup_id: number; + new_backup_id: number; + techweb: Techweb; +} + +export const NaniteDiskBox = (props, context) => { + const { data } = useBackend(); + const { has_disk, has_program, disk } = data; + if (!has_disk) { + return No disk inserted; + } + if (!has_program) { + return Inserted disk has no program; + } + return ; +}; + +export const NaniteInfoBox = (props) => { + const { program } = props; + const { + name, + desc, + activated, + use_rate, + can_trigger, + trigger_cost, + trigger_cooldown, + activation_code, + deactivation_code, + kill_code, + trigger_code, + timer_restart, + timer_shutdown, + timer_trigger, + timer_trigger_delay, + extra_settings, + }: NaniteProgram = program; + return ( +
+ {activated ? 'Activated' : 'Deactivated'} + + } + > + + {desc} + + + + {use_rate} + {!!can_trigger && ( + <> + + {trigger_cost} + + + {trigger_cooldown} + + + )} + +
+
+
+ + +
+ + + {activation_code} + + + {deactivation_code} + + {kill_code} + {!!can_trigger && ( + + {trigger_code} + + )} + +
+
+ +
+ + + {timer_restart} s + + + {timer_shutdown} s + + {!!can_trigger && ( + <> + + {timer_trigger} s + + + {timer_trigger_delay} s + + + )} + +
+
+
+
+ + {extra_settings?.map((setting) => { + const naniteTypesDisplayMap = { + number: ( + <> + {setting.value} + {setting.unit} + + ), + text: setting.value, + type: setting.value, + boolean: setting.value ? setting.true_text : setting.false_text, + }; + return ( + + {naniteTypesDisplayMap[setting.type]} + + ); + })} + +
+
+ ); +}; + +interface NaniteCloudBackupProps { + cloud_id: number; + cloud_programs: NaniteProgram[]; + cloud_backups: CloudBackups[]; +} + +interface CloudBackups { + cloud_id: number; + cloud_programs: NaniteProgram[]; +} + +export const NaniteCloudBackupList = () => { + const { act, data } = useBackend(); + const { cloud_backups = [] } = data; + + return cloud_backups.map((backup) => ( + + )); +}; + +interface NaniteCloudBackupDetailsProps { + cloud_programs: NaniteProgram[]; + cloud_backup: CloudBackups; + current_view: number; + disk: NaniteProgram; + has_program: boolean; +} + +export const NaniteCloudBackupDetails = () => { + const { act, data } = useBackend(); + const { current_view, disk, has_program, cloud_backup } = data; + const can_rule = (disk && disk.can_rule) || false; + if (!cloud_backup) { + return ERROR: Backup not found; + } + const cloud_programs = data.cloud_programs || []; + return ( +
act('upload_program')} + > + Upload Program from Disk + + ) + } + > + {cloud_programs.map((program) => { + const rules = program.rules || []; + return ( + + act('remove_program', { + program_id: program.id, + }) + } + /> + } + > +
+ + {(!!can_rule || !!program.has_rules) && ( +
+ {!!can_rule && ( + + )} + + + } + > + {program.has_rules ? ( + rules.map((rule) => ( + +
+ )} +
+
+ ); + })} +
+ ); +}; + +export const NaniteCloudControl = () => { + const { act, data } = useBackend(); + const { has_disk, current_view, new_backup_id, techweb } = data; + return ( + + + +
act('eject')} + > + Eject + + } + > + +
+
+ act('set_view', { + view: 0, + }) + } + > + Return + + ) : ( + <> + {'New Backup: '} + + act('update_new_backup_value', { + value: value, + }) + } + /> +
+
+
+ ); +}; diff --git a/tgui/packages/tgui/interfaces/NaniteProgramHub.tsx b/tgui/packages/tgui/interfaces/NaniteProgramHub.tsx new file mode 100644 index 00000000000..e57cfa71ebb --- /dev/null +++ b/tgui/packages/tgui/interfaces/NaniteProgramHub.tsx @@ -0,0 +1,177 @@ +import { map } from 'common/collections'; +import React from 'react'; +import { + Button, + Flex, + LabeledList, + NoticeBox, + Section, + Tabs, +} from 'tgui-core/components'; + +import { useBackend, useSharedState } from '../backend'; +import { Window } from '../layouts'; +import { TechwebWarning } from './Nanites/NoTechwebWarning'; +import { NaniteProgram, Techweb } from './Nanites/types'; + +interface NaniteProgramHubProps { + detail_view: boolean; + disk: { + name: string; + desc: string; + }; + techweb: Techweb; + has_disk: boolean; + has_program: boolean; + programs: Record>; +} + +export const NaniteProgramHub = (props, context) => { + const { act, data } = useBackend(); + const { + detail_view, + disk, + has_disk, + has_program, + programs = {}, + techweb, + } = data; + const [selectedCategory, setSelectedCategory] = useSharedState( + context, + 'category', + ); + const programsInCategory = (programs && programs[selectedCategory]) || []; + return ( + + + +
+ + + + } + > + {has_disk ? ( + has_program ? ( + + + {disk.name} + + + {disk.desc} + + + ) : ( + No Program Installed + ) + ) : ( + Insert Disk + )} +
+
+ + + + } + > + {programs !== null ? ( + + + + {map(programs, (cat_contents, category) => { + // Backend was sending stupid data that would have been + // annoying to fix + const tabLabel = category.substring(0, category.length - 8); + return ( + setSelectedCategory(category)} + > + {tabLabel} + + ); + })} + + + + {detail_view ? ( + programsInCategory.map((program) => ( +
+ act('download', { + program_id: program.id, + }) + } + > + Download + + } + > + {program.desc} +
+ )) + ) : ( + + {programsInCategory.map((program) => ( + + act('download', { + program_id: program.id, + }) + } + > + Download + + } + /> + ))} + + )} +
+
+ ) : ( + No nanite programs are currently researched. + )} +
+
+
+ ); +}; diff --git a/tgui/packages/tgui/interfaces/NaniteProgrammer.tsx b/tgui/packages/tgui/interfaces/NaniteProgrammer.tsx new file mode 100644 index 00000000000..a4dbc80aef3 --- /dev/null +++ b/tgui/packages/tgui/interfaces/NaniteProgrammer.tsx @@ -0,0 +1,402 @@ +import { + Button, + Dropdown, + Input, + LabeledList, + NoticeBox, + NumberInput, + Section, + Stack, +} from 'tgui-core/components'; + +import { useBackend } from '../backend'; +import { Window } from '../layouts'; +import { NaniteProgram } from './Nanites/types'; + +interface ExtraSetting { + name: string; + value: string; + types: string[]; + unit: string; + true_text: string; + false_text: string; + type: string; +} + +export const NaniteCodes = (props, context) => { + const { act, data } = useBackend(); + const { activation_code, deactivation_code, kill_code, trigger_code } = data; + return ( +
+ + + + act('set_code', { + target_code: 'activation', + code: value, + }) + } + /> + + + + act('set_code', { + target_code: 'deactivation', + code: value, + }) + } + /> + + + + act('set_code', { + target_code: 'kill', + code: value, + }) + } + /> + + {!!data.can_trigger && ( + + + act('set_code', { + target_code: 'trigger', + code: value, + }) + } + /> + + )} + +
+ ); +}; + +export const NaniteDelays = (props, context) => { + const { act, data } = useBackend(); + + const { timer_restart, timer_shutdown, timer_trigger, timer_trigger_delay } = + data; + + return ( +
+ + + + act('set_restart_timer', { + delay: value, + }) + } + /> + + + + act('set_shutdown_timer', { + delay: value, + }) + } + /> + + {!!data.can_trigger && ( + <> + + + act('set_trigger_timer', { + delay: value, + }) + } + /> + + + + act('set_timer_trigger_delay', { + delay: value, + }) + } + /> + + + )} + +
+ ); +}; + +export const NaniteExtraEntry = (props, context) => { + const { extra_setting } = props; + const { name, type } = extra_setting; + const typeComponentMap = { + number: , + text: , + type: , + boolean: , + }; + return ( + {typeComponentMap[type]} + ); +}; + +export const NaniteExtraNumber = (props, context) => { + const { extra_setting } = props; + const { act } = useBackend(); + const { name, value, min, max, unit } = extra_setting; + return ( + + act('set_extra_setting', { + target_setting: name, + value: val, + }) + } + /> + ); +}; + +export const NaniteExtraText = (props, context) => { + const { extra_setting } = props; + const { act } = useBackend(); + const { name, value }: ExtraSetting = extra_setting; + return ( + + act('set_extra_setting', { + target_setting: name, + value: val, + }) + } + /> + ); +}; + +export const NaniteExtraType = (props, context) => { + const { extra_setting } = props; + const { act } = useBackend(); + const { name, value, types }: ExtraSetting = extra_setting; + return ( + + act('set_extra_setting', { + target_setting: name, + value: val, + }) + } + /> + ); +}; + +interface ExtraSettingBoolean { + name: string; + true_text: string; + false_text: string; + value: boolean; +} + +export const NaniteExtraBoolean = (props, context) => { + const { extra_setting } = props; + const { act } = useBackend(); + const { name, value, true_text, false_text }: ExtraSettingBoolean = + extra_setting; + return ( + + act('set_extra_setting', { + target_setting: name, + }) + } + > + {value ? true_text : false_text} + + ); +}; + +export const NaniteProgrammer = (props, context) => { + return ( + + + + + + ); +}; + +interface NaniteProgrammerContentProps { + has_disk: boolean; + has_program: boolean; + name: string; + desc: string; + use_rate: string; + can_trigger: boolean; + trigger_cost: number; + trigger_cooldown: number; + activated: boolean; + has_extra_settings: boolean; + extra_settings: ExtraSetting[]; +} + +export const NaniteProgrammerContent = (props, context) => { + const { act, data } = useBackend(); + const { + has_disk, + has_program, + name, + desc, + use_rate, + can_trigger, + trigger_cost, + trigger_cooldown, + activated, + has_extra_settings, + extra_settings, + } = data; + if (!has_disk) { + return ( + Insert a nanite program disk + ); + } + if (!has_program) { + return ( +
act('eject')} + > + Eject + + } + /> + ); + } + return ( +
act('eject')}> + Eject + + } + > +
+ + {desc} + + + + {use_rate} + {!!can_trigger && ( + <> + + {trigger_cost} + + + {trigger_cooldown} + + + )} + +
+
+
+
+
act('toggle_active')} + > + {activated ? 'Active' : 'Inactive'} + + } + > + + + + + + + + + {!!has_extra_settings && ( +
+ + {extra_settings.map((setting) => ( + + ))} + +
+ )} +
+
+ ); +}; diff --git a/tgui/packages/tgui/interfaces/NaniteRemote.tsx b/tgui/packages/tgui/interfaces/NaniteRemote.tsx new file mode 100644 index 00000000000..fbba3c1f56f --- /dev/null +++ b/tgui/packages/tgui/interfaces/NaniteRemote.tsx @@ -0,0 +1,198 @@ +import { + Button, + Input, + LabeledList, + NoticeBox, + NumberInput, + Section, + Table, +} from 'tgui-core/components'; + +import { useBackend } from '../backend'; +import { Window } from '../layouts'; + +export const NaniteRemote = (props, context) => { + return ( + + + + + + ); +}; + +interface NaniteProgramProps { + code: number; + locked: boolean; + mode: string; + program_name: string; + relay_code: number; + comms: boolean; + message: string; + saved_settings: Array; +} + +interface SavedSetting { + id: string; + name: string; + mode: string; + code: number; + relay_code: number; +} + +export const NaniteRemoteContent = () => { + const { act, data } = useBackend(); + const { + code, + locked, + mode, + program_name, + relay_code, + comms, + message, + saved_settings = [], + } = data; + + const modes = ['Off', 'Local', 'Targeted', 'Area', 'Relay']; + + if (locked) { + return This interface is locked.; + } + + return ( + <> +
act('lock')}> + Lock Interface + + } + > + + + + act('update_name', { + name: value, + }) + } + /> + + + + + act('set_code', { + code: value, + }) + } + /> + + {!!comms && ( + + + act('set_message', { + value: value, + }) + } + /> + + )} + {mode === 'Relay' && ( + + + act('set_relay_code', { + code: value, + }) + } + /> + + )} + + {modes.map((key) => ( + + ))} + + +
+
+ {saved_settings.length > 0 ? ( + + + Name + Mode + Code + Relay + + {saved_settings.map((setting) => ( + + + {setting.name}: + + {setting.mode} + {setting.code} + + {setting.mode === 'Relay' && setting.relay_code} + + +
+ ) : ( + No settings currently saved + )} +
+ + ); +}; diff --git a/tgui/packages/tgui/interfaces/Nanites/NoTechwebWarning.tsx b/tgui/packages/tgui/interfaces/Nanites/NoTechwebWarning.tsx new file mode 100644 index 00000000000..d84c5336d18 --- /dev/null +++ b/tgui/packages/tgui/interfaces/Nanites/NoTechwebWarning.tsx @@ -0,0 +1,17 @@ +import { NoticeBox } from 'tgui-core/components'; + +interface TechwebWarningProps { + display: boolean; +} + +export const TechwebWarning = (props: TechwebWarningProps) => { + const { display } = props; + if (!display) { + return null; + } + return ( + + Warning: No techweb connected! + + ); +}; diff --git a/tgui/packages/tgui/interfaces/Nanites/types.d.ts b/tgui/packages/tgui/interfaces/Nanites/types.d.ts new file mode 100644 index 00000000000..6105f8acea9 --- /dev/null +++ b/tgui/packages/tgui/interfaces/Nanites/types.d.ts @@ -0,0 +1,34 @@ +export interface NaniteProgram { + name: string; + id: string; + desc: string; + activated: boolean; + activation_code: number; + use_rate: number; + deactivation_code: number; + kill_code: number; + trigger_cost: number; + trigger_cooldown: number; + trigger_code: number; + can_trigger: boolean; + timer_restart: number; + timer_shutdown: number; + timer_trigger: number; + timer_trigger_delay: number; + has_extra_settings: boolean; + extra_settings: ExtraSetting[]; + rules: NaniteRules[]; + can_rule?: boolean; + has_rules?: boolean; + all_rules_required?: boolean; +} + +export interface NaniteRules { + display: string; + id: string; +} + +export interface Techweb { + name: string; + organization: string; +} diff --git a/tgui/packages/tgui/interfaces/ScannerGate.jsx b/tgui/packages/tgui/interfaces/ScannerGate.jsx index ca2c4f02968..a9a9f246283 100644 --- a/tgui/packages/tgui/interfaces/ScannerGate.jsx +++ b/tgui/packages/tgui/interfaces/ScannerGate.jsx @@ -1,4 +1,10 @@ -import { Box, Button, LabeledList, Section } from 'tgui-core/components'; +import { + Box, + Button, + LabeledList, + NumberInput, + Section, +} from 'tgui-core/components'; import { useBackend } from '../backend'; import { Window } from '../layouts'; @@ -87,6 +93,16 @@ const SCANNER_GATE_ROUTES = { component: () => ScannerGateGender, }, // SKYRAT EDIT END - MORE SCANNER GATE OPTIONS + Contraband: { + title: 'Scanner Mode: Contraband', + component: () => ScannerGateContraband, + }, + // BUBBER EDIT START - NANITES + Nanites: { + title: 'Scanner Mode: Nanites', + component: () => ScannerGateNanites, + }, + // BUBBER EDIT END - NANITES }; const ScannerGateControl = (props) => { @@ -146,6 +162,18 @@ const ScannerGateOff = (props) => { content="Nutrition" onClick={() => act('set_mode', { new_mode: 'Nutrition' })} /> + {/* BUBBER EDIT START - NANITES */} +