diff --git a/massmeta/code/modules/antags/wizard/equipment/spellbook_entries/offensive.dm b/massmeta/code/modules/antags/wizard/equipment/spellbook_entries/offensive.dm index 979434aa47539..fe97eaf69bc14 100644 --- a/massmeta/code/modules/antags/wizard/equipment/spellbook_entries/offensive.dm +++ b/massmeta/code/modules/antags/wizard/equipment/spellbook_entries/offensive.dm @@ -1,7 +1,7 @@ #define SPELLBOOK_CATEGORY_OFFENSIVE "Offensive" // Offensive wizard spells -datum/spellbook_entry/testicular_torsion +/datum/spellbook_entry/testicular_torsion name = "Testicular Torsion" desc = "A dark spell capable of exploding victim's balls." spell_type = /datum/action/cooldown/spell/touch/testicular_torsion diff --git a/massmeta/features/nanites/code/Z_edits/hud_edits.dm b/massmeta/features/nanites/code/Z_edits/hud_edits.dm new file mode 100644 index 0000000000000..d58c5d4cf5368 --- /dev/null +++ b/massmeta/features/nanites/code/Z_edits/hud_edits.dm @@ -0,0 +1,20 @@ +/mob/New() + // add our nanite huds to everyone so they can (if possible) actually see nanites. + hud_possible += list(NANITE_HUD, DIAG_NANITE_FULL_HUD) + return ..() + +/datum/atom_hud/data/human/medical/New() + . = ..() + hud_icons += list(NANITE_HUD) + +/datum/atom_hud/data/human/security/advanced/New() + . = ..() + hud_icons += list(NANITE_HUD) + +/datum/atom_hud/data/diagnostic/basic/New() + . = ..() + hud_icons += list(DIAG_NANITE_FULL_HUD) + +/datum/atom_hud/data/diagnostic/advanced/New() + . = ..() + hud_icons += list(DIAG_NANITE_FULL_HUD) diff --git a/massmeta/features/nanites/code/Z_edits/research_edits.dm b/massmeta/features/nanites/code/Z_edits/research_edits.dm new file mode 100644 index 0000000000000..bc7996132bd77 --- /dev/null +++ b/massmeta/features/nanites/code/Z_edits/research_edits.dm @@ -0,0 +1,3 @@ +/datum/controller/subsystem/research/Initialize() + . = ..() + point_types += list(TECHWEB_POINT_TYPE_NANITES = "Nanite Res.") diff --git a/massmeta/features/nanites/code/areas.dm b/massmeta/features/nanites/code/areas.dm new file mode 100644 index 0000000000000..f8d6a4db06fdd --- /dev/null +++ b/massmeta/features/nanites/code/areas.dm @@ -0,0 +1,4 @@ +/area/station/science/nanite + name = "Nanite Lab" + icon = 'massmeta/features/nanites/icons/areas.dmi' + icon_state = "nanite_lab" diff --git a/massmeta/features/nanites/code/circuitboards.dm b/massmeta/features/nanites/code/circuitboards.dm new file mode 100644 index 0000000000000..d404e9743e813 --- /dev/null +++ b/massmeta/features/nanites/code/circuitboards.dm @@ -0,0 +1,38 @@ +/obj/item/circuitboard/computer/nanite_chamber_control + name = "Nanite Chamber Control" + greyscale_colors = CIRCUIT_COLOR_SCIENCE + build_path = /obj/machinery/computer/nanite_chamber_control + +/obj/item/circuitboard/computer/nanite_cloud_controller + name = "Nanite Cloud Control" + greyscale_colors = CIRCUIT_COLOR_SCIENCE + build_path = /obj/machinery/computer/nanite_cloud_controller + +/obj/item/circuitboard/machine/nanite_chamber + name = "Nanite Chamber" + greyscale_colors = CIRCUIT_COLOR_SCIENCE + build_path = /obj/machinery/nanite_chamber + req_components = list( + /obj/item/stock_parts/micro_laser = 2, + /obj/item/stock_parts/scanning_module = 1, + /obj/item/stock_parts/servo = 1, + ) + +/obj/item/circuitboard/machine/nanite_program_hub + name = "Nanite Program Hub" + 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" + 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, + ) diff --git a/massmeta/features/nanites/code/designs/_nanite.dm b/massmeta/features/nanites/code/designs/_nanite.dm new file mode 100644 index 0000000000000..b878ffc9ffb5b --- /dev/null +++ b/massmeta/features/nanites/code/designs/_nanite.dm @@ -0,0 +1,11 @@ +/datum/design/nanites + name = "None" + desc = "Warn a coder if you see this." + id = "default_nanites" + build_type = NONE + construction_time = 50 + category = list() + research_icon = 'massmeta/features/nanites/icons/nanite_device.dmi' + research_icon_state = "nanite_program" + ///The nanite program researching this will unlock. + var/program_type = /datum/nanite_program diff --git a/massmeta/features/nanites/code/designs/_tools.dm b/massmeta/features/nanites/code/designs/_tools.dm new file mode 100644 index 0000000000000..42765533b8ee5 --- /dev/null +++ b/massmeta/features/nanites/code/designs/_tools.dm @@ -0,0 +1,63 @@ +/datum/design/nanite_remote + name = "Nanite Remote" + desc = "Allows for the construction of a nanite remote." + id = "nanite_remote" + build_type = PROTOLATHE + materials = list(/datum/material/glass = 500, /datum/material/iron = 500) + build_path = /obj/item/nanite_remote + category = list(RND_CATEGORY_CONSTRUCTION + RND_SUBCATEGORY_CONSTRUCTION_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 + materials = list(/datum/material/glass = 500, /datum/material/iron = 500) + build_path = /obj/item/nanite_remote/comm + category = list(RND_CATEGORY_CONSTRUCTION + RND_SUBCATEGORY_CONSTRUCTION_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 + materials = list(/datum/material/glass = 500, /datum/material/iron = 500) + build_path = /obj/item/nanite_scanner + category = list(RND_CATEGORY_CONSTRUCTION + RND_SUBCATEGORY_CONSTRUCTION_ELECTRONICS) + departmental_flags = DEPARTMENT_BITFLAG_SCIENCE + +/datum/design/nanite_disk + name = "Nanite Program Disk" + desc = "Stores nanite programs." + id = "nanite_disk" + build_type = PROTOLATHE + materials = list(/datum/material/iron = 300, /datum/material/glass = 100) + build_path = /obj/item/disk/nanite_program + category = list(RND_CATEGORY_CONSTRUCTION + RND_SUBCATEGORY_CONSTRUCTION_ELECTRONICS) + 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(RND_CATEGORY_MACHINE + RND_SUBCATEGORY_MACHINE_RESEARCH) + 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(RND_CATEGORY_MACHINE + RND_SUBCATEGORY_MACHINE_RESEARCH) + 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(RND_CATEGORY_MACHINE + RND_SUBCATEGORY_MACHINE_RESEARCH) + departmental_flags = DEPARTMENT_BITFLAG_SCIENCE diff --git a/massmeta/features/nanites/code/designs/nanite_augmentation.dm b/massmeta/features/nanites/code/designs/nanite_augmentation.dm new file mode 100644 index 0000000000000..4642695a95e22 --- /dev/null +++ b/massmeta/features/nanites/code/designs/nanite_augmentation.dm @@ -0,0 +1,48 @@ +/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" + category = list(NANITES_CATEGORY_AUGMENTATION) + program_type = /datum/nanite_program/nervous + +/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" + category = list(NANITES_CATEGORY_AUGMENTATION) + program_type = /datum/nanite_program/hardening + +/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" + category = list(NANITES_CATEGORY_AUGMENTATION) + program_type = /datum/nanite_program/refractive + +/datum/design/nanites/coagulating + name = "Rapid Coagulation" + desc = "The nanites induce rapid coagulation when the host is wounded, dramatically reducing bleeding rate." + id = "coagulating_nanites" + category = list(NANITES_CATEGORY_AUGMENTATION) + program_type = /datum/nanite_program/coagulating + +/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" + category = list(NANITES_CATEGORY_AUGMENTATION) + program_type = /datum/nanite_program/conductive + +/datum/design/nanites/adrenaline + name = "Adrenaline Burst" + desc = "The nanites cause a burst of adrenaline when triggered, waking the host from stuns and temporarily increasing their speed." + id = "adrenaline_nanites" + category = list(NANITES_CATEGORY_AUGMENTATION) + program_type = /datum/nanite_program/adrenaline + +/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" + category = list(NANITES_CATEGORY_AUGMENTATION) + program_type = /datum/nanite_program/mindshield diff --git a/massmeta/features/nanites/code/designs/nanite_defective.dm b/massmeta/features/nanites/code/designs/nanite_defective.dm new file mode 100644 index 0000000000000..04571380bd1fa --- /dev/null +++ b/massmeta/features/nanites/code/designs/nanite_defective.dm @@ -0,0 +1,55 @@ +/datum/design/nanites/glitch + name = "Glitch" + desc = "A heavy software corruption that causes nanites to gradually break down." + id = "glitch_nanites" + category = list(NANITES_CATEGORY_DEFECTIVE) + program_type = /datum/nanite_program/glitch + +/datum/design/nanites/necrotic + name = "Necrosis" + desc = "The nanites attack internal tissues indiscriminately, causing widespread damage." + id = "necrotic_nanites" + category = list(NANITES_CATEGORY_DEFECTIVE) + program_type = /datum/nanite_program/necrotic + +/datum/design/nanites/toxic + name = "Toxin Buildup" + desc = "The nanites cause a slow but constant toxin buildup inside the host." + id = "toxic_nanites" + category = list(NANITES_CATEGORY_DEFECTIVE) + program_type = /datum/nanite_program/toxic + +/datum/design/nanites/suffocating + name = "Hypoxemia" + desc = "The nanites prevent the host's blood from absorbing oxygen efficiently." + id = "suffocating_nanites" + category = list(NANITES_CATEGORY_DEFECTIVE) + program_type = /datum/nanite_program/suffocating + +/datum/design/nanites/brain_misfire + name = "Brain Misfire" + desc = "The nanites interfere with neural pathways, causing minor psychological disturbances." + id = "brainmisfire_nanites" + category = list(NANITES_CATEGORY_DEFECTIVE) + program_type = /datum/nanite_program/brain_misfire + +/datum/design/nanites/skin_decay + name = "Dermalysis" + desc = "The nanites attack skin cells, causing irritation, rashes, and minor damage." + id = "skindecay_nanites" + category = list(NANITES_CATEGORY_DEFECTIVE) + program_type = /datum/nanite_program/skin_decay + +/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" + category = list(NANITES_CATEGORY_DEFECTIVE) + program_type = /datum/nanite_program/nerve_decay + +/datum/design/nanites/brain_decay + name = "Brain-Eating Nanites" + desc = "Damages brain cells, gradually decreasing the host's cognitive functions." + id = "braindecay_nanites" + category = list(NANITES_CATEGORY_DEFECTIVE) + program_type = /datum/nanite_program/brain_decay diff --git a/massmeta/features/nanites/code/designs/nanite_medical.dm b/massmeta/features/nanites/code/designs/nanite_medical.dm new file mode 100644 index 0000000000000..ff55c2527b374 --- /dev/null +++ b/massmeta/features/nanites/code/designs/nanite_medical.dm @@ -0,0 +1,71 @@ +/datum/design/nanites/regenerative + name = "Accelerated Regeneration" + desc = "The nanites boost the host's natural regeneration, increasing their healing speed." + id = "regenerative_nanites" + category = list(NANITE_CATEGORY_MEDICAL) + program_type = /datum/nanite_program/regenerative + +/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" + category = list(NANITE_CATEGORY_MEDICAL) + program_type = /datum/nanite_program/regenerative_advanced + +/datum/design/nanites/temperature + name = "Temperature Adjustment" + desc = "The nanites adjust the host's internal temperature to an ideal level." + id = "temperature_nanites" + category = list(NANITE_CATEGORY_MEDICAL) + program_type = /datum/nanite_program/temperature + +/datum/design/nanites/purging + name = "Blood Purification" + desc = "The nanites purge toxins and chemicals from the host's bloodstream." + id = "purging_nanites" + category = list(NANITE_CATEGORY_MEDICAL) + program_type = /datum/nanite_program/purging + +/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" + category = list(NANITE_CATEGORY_MEDICAL) + program_type = /datum/nanite_program/purging_advanced + +/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" + category = list(NANITE_CATEGORY_MEDICAL) + program_type = /datum/nanite_program/brain_heal + +/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" + category = list(NANITE_CATEGORY_MEDICAL) + program_type = /datum/nanite_program/brain_heal_advanced + +/datum/design/nanites/blood_restoring + name = "Blood Regeneration" + desc = "The nanites stimulate and boost blood cell production in the host." + id = "bloodheal_nanites" + category = list(NANITE_CATEGORY_MEDICAL) + program_type = /datum/nanite_program/blood_restoring + +/datum/design/nanites/repairing + name = "Mechanical Repair" + desc = "The nanites fix damage in the host's mechanical limbs." + id = "repairing_nanites" + category = list(NANITE_CATEGORY_MEDICAL) + program_type = /datum/nanite_program/repairing + +/datum/design/nanites/defib + name = "Defibrillation" + desc = "The nanites, when triggered, send a defibrillating shock to the host's heart." + id = "defib_nanites" + category = list(NANITE_CATEGORY_MEDICAL) + program_type = /datum/nanite_program/defib diff --git a/massmeta/features/nanites/code/designs/nanite_protocols.dm b/massmeta/features/nanites/code/designs/nanite_protocols.dm new file mode 100644 index 0000000000000..b5fe64f902611 --- /dev/null +++ b/massmeta/features/nanites/code/designs/nanite_protocols.dm @@ -0,0 +1,56 @@ +/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" + category = list(NANITES_CATEGORY_PROTOCOLS) + program_type = /datum/nanite_program/protocol/kickstart + +/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" + category = list(NANITES_CATEGORY_PROTOCOLS) + program_type = /datum/nanite_program/protocol/factory + +/datum/design/nanites/tinker + name = "Tinker Protocol" + desc = "Replication Protocol: the nanites learn to use metallic material in the host's bloodstream to speed up the replication process." + id = "tinker_nanites" + category = list(NANITES_CATEGORY_PROTOCOLS) + program_type = /datum/nanite_program/protocol/tinker + +/datum/design/nanites/offline + name = "Offline Production 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" + category = list(NANITES_CATEGORY_PROTOCOLS) + program_type = /datum/nanite_program/protocol/offline + +/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" + category = list(NANITES_CATEGORY_PROTOCOLS) + program_type = /datum/nanite_program/protocol/hive + +/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" + category = list(NANITES_CATEGORY_PROTOCOLS) + program_type = /datum/nanite_program/protocol/zip + +/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" + category = list(NANITES_CATEGORY_PROTOCOLS) + program_type = /datum/nanite_program/protocol/free_range + +/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" + category = list(NANITES_CATEGORY_PROTOCOLS) + program_type = /datum/nanite_program/protocol/unsafe_storage diff --git a/massmeta/features/nanites/code/designs/nanite_sensor.dm b/massmeta/features/nanites/code/designs/nanite_sensor.dm new file mode 100644 index 0000000000000..02daadb61fc1e --- /dev/null +++ b/massmeta/features/nanites/code/designs/nanite_sensor.dm @@ -0,0 +1,48 @@ +/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" + category = list(NANITES_CATEGORY_SENSOR) + program_type = /datum/nanite_program/sensor/health + +/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" + category = list(NANITES_CATEGORY_SENSOR) + program_type = /datum/nanite_program/sensor/damage + +/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" + category = list(NANITES_CATEGORY_SENSOR) + program_type = /datum/nanite_program/sensor/crit + +/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" + category = list(NANITES_CATEGORY_SENSOR) + program_type = /datum/nanite_program/sensor/death + +/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" + category = list(NANITES_CATEGORY_SENSOR) + program_type = /datum/nanite_program/sensor/voice + +/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" + category = list(NANITES_CATEGORY_SENSOR) + program_type = /datum/nanite_program/sensor/nanite_volume + +/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" + category = list(NANITES_CATEGORY_SENSOR) + program_type = /datum/nanite_program/sensor/species diff --git a/massmeta/features/nanites/code/designs/nanite_suppression.dm b/massmeta/features/nanites/code/designs/nanite_suppression.dm new file mode 100644 index 0000000000000..4ef27f62d2a25 --- /dev/null +++ b/massmeta/features/nanites/code/designs/nanite_suppression.dm @@ -0,0 +1,83 @@ +/datum/design/nanites/shock + name = "Electric Shock" + desc = "The nanites shock the host when triggered. Destroys a large amount of nanites!" + id = "shock_nanites" + category = list(NANITES_CATEGORY_SUPPRESSION) + program_type = /datum/nanite_program/shocking + +/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" + category = list(NANITES_CATEGORY_SUPPRESSION) + program_type = /datum/nanite_program/stun + +/datum/design/nanites/sleepy + name = "Sleep Induction" + desc = "The nanites cause rapid narcolepsy when triggered." + id = "sleep_nanites" + category = list(NANITES_CATEGORY_SUPPRESSION) + program_type = /datum/nanite_program/sleepy + +/datum/design/nanites/paralyzing + name = "Paralysis" + desc = "The nanites actively suppress nervous pulses, effectively paralyzing the host." + id = "paralyzing_nanites" + category = list(NANITES_CATEGORY_SUPPRESSION) + program_type = /datum/nanite_program/paralyzing + +/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" + category = list(NANITES_CATEGORY_SUPPRESSION) + program_type = /datum/nanite_program/fake_death + +/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" + category = list(NANITES_CATEGORY_SUPPRESSION) + program_type = /datum/nanite_program/pacifying + +/datum/design/nanites/blinding + name = "Blindness" + desc = "The nanites suppress the host's ocular nerves, blinding them while they're active." + id = "blinding_nanites" + category = list(NANITES_CATEGORY_SUPPRESSION) + program_type = /datum/nanite_program/blinding + +/datum/design/nanites/mute + name = "Mute" + desc = "The nanites suppress the host's speech, making them mute while they're active." + id = "mute_nanites" + category = list(NANITES_CATEGORY_SUPPRESSION) + program_type = /datum/nanite_program/mute + +/datum/design/nanites/voice + name = "Skull Echo" + desc = "The nanites echo a synthesized message inside the host's skull." + id = "voice_nanites" + category = list(NANITES_CATEGORY_SUPPRESSION) + program_type = /datum/nanite_program/comm/voice + +/datum/design/nanites/speech + name = "Forced Speech" + desc = "The nanites force the host to say a pre-programmed sentence when triggered." + id = "speech_nanites" + category = list(NANITES_CATEGORY_SUPPRESSION) + program_type = /datum/nanite_program/comm/speech + +/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" + category = list(NANITES_CATEGORY_SUPPRESSION) + program_type = /datum/nanite_program/good_mood + +/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" + category = list(NANITES_CATEGORY_SUPPRESSION) + program_type = /datum/nanite_program/bad_mood diff --git a/massmeta/features/nanites/code/designs/nanite_utilities.dm b/massmeta/features/nanites/code/designs/nanite_utilities.dm new file mode 100644 index 0000000000000..431cba93b306c --- /dev/null +++ b/massmeta/features/nanites/code/designs/nanite_utilities.dm @@ -0,0 +1,100 @@ +/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" + category = list(NANITE_CATEGORY_UTILITIES) + program_type = /datum/nanite_program/metabolic_synthesis + +/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" + category = list(NANITE_CATEGORY_UTILITIES) + program_type = /datum/nanite_program/viral + +/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" + category = list(NANITE_CATEGORY_UTILITIES) + program_type = /datum/nanite_program/self_scan + +/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" + category = list(NANITE_CATEGORY_UTILITIES) + program_type = /datum/nanite_program/dermal_button + +/datum/design/nanites/stealth + name = "Stealth" + desc = "The nanites hide their activity and programming from superficial scans." + id = "stealth_nanites" + category = list(NANITE_CATEGORY_UTILITIES) + program_type = /datum/nanite_program/stealth + +/datum/design/nanites/reduced_diagnostics + name = "Reduced Diagnostics" + desc = "Disables some high-cost diagnostics in the nanites, making them unable to communicate their program list to portable scanners. \ + Doing so saves some power, slightly increasing their replication speed." + id = "red_diag_nanites" + category = list(NANITE_CATEGORY_UTILITIES) + program_type = /datum/nanite_program/reduced_diagnostics + +/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" + category = list(NANITE_CATEGORY_UTILITIES) + program_type = /datum/nanite_program/access + +/datum/design/nanites/relay + name = "Relay" + desc = "The nanites receive and relay long-range nanite signals." + id = "relay_nanites" + category = list(NANITE_CATEGORY_UTILITIES) + program_type = /datum/nanite_program/relay + +/datum/design/nanites/repeater + name = "Signal Repeater" + desc = "When triggered, sends another signal to the nanites, optionally with a delay." + id = "repeater_nanites" + category = list(NANITE_CATEGORY_UTILITIES) + program_type = /datum/nanite_program/sensor/repeat + +/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" + category = list(NANITE_CATEGORY_UTILITIES) + program_type = /datum/nanite_program/sensor/relay_repeat + +/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" + category = list(NANITE_CATEGORY_UTILITIES) + program_type = /datum/nanite_program/emp + +/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" + category = list(NANITE_CATEGORY_UTILITIES) + program_type = /datum/nanite_program/spreading + +/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" + category = list(NANITE_CATEGORY_UTILITIES) + program_type = /datum/nanite_program/nanite_sting + +/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. Not compatible with cloud sync." + id = "mitosis_nanites" + category = list(NANITE_CATEGORY_UTILITIES) + program_type = /datum/nanite_program/mitosis diff --git a/massmeta/features/nanites/code/designs/nanite_weaponized.dm b/massmeta/features/nanites/code/designs/nanite_weaponized.dm new file mode 100644 index 0000000000000..ec3858f3df57e --- /dev/null +++ b/massmeta/features/nanites/code/designs/nanite_weaponized.dm @@ -0,0 +1,70 @@ +/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" + category = list(NANITES_CATEGORY_WEAPONIZED) + program_type = /datum/nanite_program/flesh_eating + +/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" + category = list(NANITES_CATEGORY_WEAPONIZED) + program_type = /datum/nanite_program/poison + +/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" + category = list(NANITES_CATEGORY_WEAPONIZED) + program_type = /datum/nanite_program/memory_leak + +/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" + category = list(NANITES_CATEGORY_WEAPONIZED) + program_type = /datum/nanite_program/aggressive_replication + +/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" + category = list(NANITES_CATEGORY_WEAPONIZED) + program_type = /datum/nanite_program/meltdown + +/datum/design/nanites/cryo + name = "Cryogenic Treatment" + desc = "The nanites rapidly skin heat through the host's skin, lowering their temperature." + id = "cryo_nanites" + category = list(NANITES_CATEGORY_WEAPONIZED) + program_type = /datum/nanite_program/cryo + +/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" + category = list(NANITES_CATEGORY_WEAPONIZED) + program_type = /datum/nanite_program/pyro + +/datum/design/nanites/heart_stop + name = "Heart-Stopper" + desc = "Stops the host's heart when triggered; restarts it if triggered again." + id = "heartstop_nanites" + category = list(NANITES_CATEGORY_WEAPONIZED) + program_type = /datum/nanite_program/heart_stop + +/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" + category = list(NANITES_CATEGORY_WEAPONIZED) + program_type = /datum/nanite_program/explosive + +/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" + category = list(NANITES_CATEGORY_WEAPONIZED) + program_type = /datum/nanite_program/comm/mind_control diff --git a/massmeta/features/nanites/code/items.dm b/massmeta/features/nanites/code/items.dm new file mode 100644 index 0000000000000..37a29d713339f --- /dev/null +++ b/massmeta/features/nanites/code/items.dm @@ -0,0 +1,149 @@ +/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) + +//Names are intentionally all the same - track your nanites, or use a hand labeler +//This also means that you can give flesh melting nanites to your victims if you feel like it + +/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." + icon = 'massmeta/features/nanites/icons/disk.dmi' + icon_state = "nanite" + ///The program currently uploaded into the disk. If set to something, the disk will spawn with that program on mapload. + var/datum/nanite_program/program + +/obj/item/disk/nanite_program/Initialize(mapload) + . = ..() + if(program) + program = new program + +/obj/item/disk/nanite_program/aggressive_replication + program = /datum/nanite_program/aggressive_replication + +/obj/item/disk/nanite_program/metabolic_synthesis + program = /datum/nanite_program/metabolic_synthesis + +/obj/item/disk/nanite_program/viral + program = /datum/nanite_program/viral + +/obj/item/disk/nanite_program/meltdown + program = /datum/nanite_program/meltdown + +/obj/item/disk/nanite_program/relay + program = /datum/nanite_program/relay + +/obj/item/disk/nanite_program/emp + program = /datum/nanite_program/emp + +/obj/item/disk/nanite_program/spreading + program = /datum/nanite_program/spreading + +/obj/item/disk/nanite_program/regenerative + program = /datum/nanite_program/regenerative + +/obj/item/disk/nanite_program/regenerative_advanced + program = /datum/nanite_program/regenerative_advanced + +/obj/item/disk/nanite_program/temperature + program = /datum/nanite_program/temperature + +/obj/item/disk/nanite_program/purging + program = /datum/nanite_program/purging + +/obj/item/disk/nanite_program/purging_advanced + program = /datum/nanite_program/purging_advanced + +/obj/item/disk/nanite_program/brain_heal + program = /datum/nanite_program/brain_heal + +/obj/item/disk/nanite_program/brain_heal_advanced + program = /datum/nanite_program/brain_heal_advanced + +/obj/item/disk/nanite_program/blood_restoring + program = /datum/nanite_program/blood_restoring + +/obj/item/disk/nanite_program/repairing + program = /datum/nanite_program/repairing + +/obj/item/disk/nanite_program/nervous + program = /datum/nanite_program/nervous + +/obj/item/disk/nanite_program/hardening + program = /datum/nanite_program/hardening + +/obj/item/disk/nanite_program/coagulating + program = /datum/nanite_program/coagulating + +/obj/item/disk/nanite_program/necrotic + program = /datum/nanite_program/necrotic + +/obj/item/disk/nanite_program/brain_decay + program = /datum/nanite_program/brain_decay + +/obj/item/disk/nanite_program/pyro + program = /datum/nanite_program/pyro + +/obj/item/disk/nanite_program/cryo + program = /datum/nanite_program/cryo + +/obj/item/disk/nanite_program/toxic + program = /datum/nanite_program/toxic + +/obj/item/disk/nanite_program/suffocating + program = /datum/nanite_program/suffocating + +/obj/item/disk/nanite_program/heart_stop + program = /datum/nanite_program/heart_stop + +/obj/item/disk/nanite_program/explosive + program = /datum/nanite_program/explosive + +/obj/item/disk/nanite_program/shock + program = /datum/nanite_program/shocking + +/obj/item/disk/nanite_program/sleepy + program = /datum/nanite_program/sleepy + +/obj/item/disk/nanite_program/paralyzing + program = /datum/nanite_program/paralyzing + +/obj/item/disk/nanite_program/fake_death + program = /datum/nanite_program/fake_death + +/obj/item/disk/nanite_program/pacifying + program = /datum/nanite_program/pacifying + +/obj/item/disk/nanite_program/glitch + program = /datum/nanite_program/glitch + +/obj/item/disk/nanite_program/brain_misfire + program = /datum/nanite_program/brain_misfire + +/obj/item/disk/nanite_program/skin_decay + program = /datum/nanite_program/skin_decay + +/obj/item/disk/nanite_program/nerve_decay + program = /datum/nanite_program/nerve_decay + +/obj/item/disk/nanite_program/refractive + program = /datum/nanite_program/refractive + +/obj/item/disk/nanite_program/conductive + program = /datum/nanite_program/conductive + +/obj/item/disk/nanite_program/stun + program = /datum/nanite_program/stun + +/obj/item/disk/nanite_program/speech + program = /datum/nanite_program/comm/speech + +/obj/item/disk/nanite_program/voice + program = /datum/nanite_program/comm/voice + +/obj/item/disk/nanite_program/mind_control + program = /datum/nanite_program/comm/mind_control diff --git a/massmeta/features/nanites/code/machines.dm b/massmeta/features/nanites/code/machines.dm new file mode 100644 index 0000000000000..f0e4df998e88e --- /dev/null +++ b/massmeta/features/nanites/code/machines.dm @@ -0,0 +1,15 @@ +/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(RND_CATEGORY_COMPUTER + RND_SUBCATEGORY_COMPUTER_RESEARCH) + 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(RND_CATEGORY_COMPUTER + RND_SUBCATEGORY_COMPUTER_RESEARCH) + departmental_flags = DEPARTMENT_BITFLAG_SCIENCE diff --git a/massmeta/features/nanites/code/machines/nanite_chamber.dm b/massmeta/features/nanites/code/machines/nanite_chamber.dm new file mode 100644 index 0000000000000..095703dc5ddc5 --- /dev/null +++ b/massmeta/features/nanites/code/machines/nanite_chamber.dm @@ -0,0 +1,221 @@ +#define NANITE_CHAMBER_BREAKOUT_TIME (2 MINUTES) + +/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 = 'massmeta/features/nanites/icons/nanite_machines.dmi' + icon_state = "nanite_chamber" + base_icon_state = "nanite_chamber" + layer = ABOVE_WINDOW_LAYER + use_power = IDLE_POWER_USE + anchored = TRUE + density = TRUE + idle_power_usage = 50 + active_power_usage = 300 + + ///The icon file used post-initialize, the default icon is used solely so it shows up in the R&D console. + var/chamber_icon = 'massmeta/features/nanites/icons/nanite_chamber.dmi' + ///The nanite chamber control machine we're synced to. + var/obj/machinery/computer/nanite_chamber_control/linked_console + ///The level of the scanning module installed in the nanite chamber. + var/scan_level + ///Boolean on whether we're currently locked, preventing the machine from being opened/closed. + var/locked = FALSE + ///Boolean on whether the machine is currently busy. + var/busy = FALSE + ///An icon state we're gonna set ourselves to, given to us by set_busy. + var/busy_icon_state + ///The message that will be displayed to the nanite controller, given by set_busy. + var/busy_message + ///The cooldown between messages telling a resisting player that they can't leave. + COOLDOWN_DECLARE(message_cooldown) + +/obj/machinery/nanite_chamber/Initialize(mapload) + occupant_typecache = GLOB.typecache_living + return ..() + +/obj/machinery/nanite_chamber/RefreshParts() + . = ..() + scan_level = 0 + for(var/obj/item/stock_parts/scanning_module/scanning_mod in component_parts) + scan_level = scanning_mod.rating + +/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(UPDATE_ICON) + +/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 + + playsound(src, 'massmeta/features/nanites/sound/nanite_install.wav', 50) + set_busy(TRUE, "Initializing injection protocol...", "[initial(icon_state)]_raising") + addtimer(CALLBACK(src, PROC_REF(set_busy), TRUE, "Analyzing host bio-structure...", "[initial(icon_state)]_active"), 2 SECONDS) + addtimer(CALLBACK(src, PROC_REF(set_busy), TRUE, "Priming nanites...", "[initial(icon_state)]_active"), 4 SECONDS) + addtimer(CALLBACK(src, PROC_REF(set_busy), TRUE, "Injecting...", "[initial(icon_state)]_active"), 7 SECONDS) + addtimer(CALLBACK(src, PROC_REF(set_busy), TRUE, "Activating nanites...", "[initial(icon_state)]_falling"), 9 SECONDS) + addtimer(CALLBACK(src, PROC_REF(complete_injection), locked_state), 11 SECONDS) + +/obj/machinery/nanite_chamber/proc/complete_injection(locked_state) + //TODO MACHINE DING + locked = locked_state + set_busy(FALSE) + if(!occupant || !linked_console.linked_techweb) + return + occupant.AddComponent(/datum/component/nanites, linked_console.linked_techweb) + +/obj/machinery/nanite_chamber/proc/remove_nanites() + if(machine_stat & (NOPOWER|BROKEN)) + return + if((machine_stat & MAINT) || panel_open) + return + if(!occupant || busy) + return + + var/locked_state = locked + locked = TRUE + + playsound(src, 'massmeta/features/nanites/sound/nanite_install_short.mp3', 50) + set_busy(TRUE, "Initializing cleanup protocol...", "[initial(icon_state)]_raising") + addtimer(CALLBACK(src, PROC_REF(set_busy), TRUE, "Analyzing host bio-structure...", "[initial(icon_state)]_active"), 2 SECONDS) + addtimer(CALLBACK(src, PROC_REF(set_busy), TRUE, "Pinging nanites...", "[initial(icon_state)]_active"), 3 SECONDS) + addtimer(CALLBACK(src, PROC_REF(set_busy), TRUE, "Initiating graceful self-destruct sequence...", "[initial(icon_state)]_active"), 5 SECONDS) + addtimer(CALLBACK(src, PROC_REF(set_busy), TRUE, "Removing debris...", "[initial(icon_state)]_falling"), 7 SECONDS) + addtimer(CALLBACK(src, PROC_REF(complete_removal), locked_state), 9 SECONDS) + +/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(updates=ALL) + icon = chamber_icon + return ..() + +/obj/machinery/nanite_chamber/update_icon_state() + . = ..() + if(!occupant) + icon_state = "[base_icon_state][state_open ? "_open" : ""]" + return + if(busy) + icon_state = busy_icon_state + else + icon_state = "[base_icon_state]_occupied" + +/obj/machinery/nanite_chamber/update_overlays() + . = ..() + if((machine_stat & MAINT) || panel_open) + . += "maint" + return . + if(machine_stat & (NOPOWER|BROKEN)) + return . + if(!busy && !locked) + . += "green" + return . + . += "red" + if(locked) + . += "bolted" + +/obj/machinery/nanite_chamber/proc/toggle_open(mob/user) + if(panel_open) + balloon_alert(user, "panel open!") + return + if(state_open) + close_machine() + return + if(locked) + balloon_alert(user, "bolts locked down!") + 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(NANITE_CHAMBER_BREAKOUT_TIME)].)"), + span_hear("You hear a metallic creaking from [src]."), + ) + if(!do_after(user, NANITE_CHAMBER_BREAKOUT_TIME, target = src)) + return + 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(atom/movable/target, density_to_set = TRUE) + if(!state_open) + return FALSE + playsound(src, 'massmeta/features/nanites/sound/nanite_chamber.wav', 40) + return ..() + +/obj/machinery/nanite_chamber/open_machine(drop = TRUE, density_to_set = FALSE) + if(state_open) + return FALSE + playsound(src, 'massmeta/features/nanites/sound/nanite_chamber.wav', 40) + return ..() + +/obj/machinery/nanite_chamber/relaymove(mob/living/user, direction) + if((user.stat < HARD_CRIT) && !locked) + open_machine() + return + if(COOLDOWN_FINISHED(src, message_cooldown)) + COOLDOWN_START(src, message_cooldown, 5 SECONDS) + balloon_alert(user, "door won't budge!") + +/obj/machinery/nanite_chamber/crowbar_act(mob/living/user, obj/item/tool) + if(default_pry_open(tool) || default_deconstruction_crowbar(tool)) + return ITEM_INTERACT_SUCCESS + return ..() + +/obj/machinery/nanite_chamber/screwdriver_act(mob/living/user, obj/item/tool) + if(!occupant && default_deconstruction_screwdriver(user, icon_state, icon_state, tool)) + update_appearance() + return ITEM_INTERACT_SUCCESS + return ..() + +/obj/machinery/nanite_chamber/interact(mob/user) + toggle_open(user) + +/obj/machinery/nanite_chamber/mouse_drop_receive(mob/target, mob/user, params) + if(!user.can_perform_action(src, FORBID_TELEKINESIS_REACH) || !Adjacent(target) || !user.Adjacent(target) || !(can_be_occupant(target))) + return + if(close_machine(target)) + log_combat(user, target, "inserted", null, "into [src].") + add_fingerprint(user) diff --git a/massmeta/features/nanites/code/machines/nanite_chamber_control.dm b/massmeta/features/nanites/code/machines/nanite_chamber_control.dm new file mode 100644 index 0000000000000..c1d8a7c3f7023 --- /dev/null +++ b/massmeta/features/nanites/code/machines/nanite_chamber_control.dm @@ -0,0 +1,123 @@ +/obj/machinery/computer/nanite_chamber_control + name = "nanite chamber control console" + desc = "Controls a connected nanite chamber. Can inoculate and destroy nanites or analyze existing nanite swarms within patients." + icon = 'massmeta/features/nanites/icons/computer.dmi' + icon_screen = "nanite_chamber_control" + icon_keyboard = null + circuit = /obj/item/circuitboard/computer/nanite_chamber_control + + ///The nanite chamber we're connected to, that we use to scan people and modify nanites. + var/obj/machinery/nanite_chamber/chamber + ///The techweb that hosts the nanites we're injecting into people. + var/datum/techweb/linked_techweb + +/obj/machinery/computer/nanite_chamber_control/post_machine_initialize() + . = ..() + find_chamber() + if(!CONFIG_GET(flag/no_default_techweb_link) && !linked_techweb) + CONNECT_TO_RND_SERVER_ROUNDSTART(linked_techweb, src) + +/obj/machinery/computer/nanite_chamber_control/Destroy() + linked_techweb = null + return ..() + +/obj/machinery/computer/nanite_chamber_control/interact() + if(!chamber) + find_chamber() + return ..() + +/obj/machinery/computer/nanite_chamber_control/multitool_act(mob/living/user, obj/item/multitool/tool) + if(!QDELETED(tool.buffer) && istype(tool.buffer, /datum/techweb)) + linked_techweb = tool.buffer + return TRUE + +/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(!linked_techweb) + data["status_msg"] = "No techweb detected." + return data + + if(!chamber) + data["status_msg"] = "No chamber detected." + return data + + if(!chamber.occupant) + data["status_msg"] = "No occupant detected." + return data + + var/mob/living/person_inside = chamber.occupant + + if(!(person_inside.mob_biotypes & (MOB_ORGANIC|MOB_UNDEAD))) + data["status_msg"] = "Occupant not compatible with nanites." + return data + + if(chamber.busy) + data["status_msg"] = chamber.busy_message + return data + + data["has_nanites"] = FALSE + data["status_msg"] = null + data["scan_level"] = chamber.scan_level + data["locked"] = chamber.locked + data["occupant_name"] = chamber.occupant.name + + SEND_SIGNAL(person_inside, 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(UPDATE_ICON) + return 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) + log_game("[chamber.occupant]'s nanites' safety threshold was set to [threshold] by [key_name(usr)] via [src] at [AREACOORD(src)].") + return 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) + log_game("[chamber.occupant]'s nanites' cloud id was set to [cloud_id] by [key_name(usr)] via [src] at [AREACOORD(src)].") + return TRUE + if("connect_chamber") + find_chamber() + return TRUE + if("remove_nanites") + playsound(src, 'sound/machines/terminal_prompt.ogg', 25, FALSE) + chamber.remove_nanites() + log_combat(usr, chamber.occupant, "cleared nanites from", null, "via [src]") + log_game("[chamber.occupant]'s nanites were cleared by [key_name(usr)] via [src] at [AREACOORD(src)].") + return TRUE + if("nanite_injection") + playsound(src, 'sound/machines/terminal_prompt.ogg', 25, FALSE) + chamber.inject_nanites() + log_combat(usr, chamber.occupant, "injected", null, "with nanites via [src]") + log_game("[chamber.occupant] was injected with nanites by [key_name(usr)] via [src] at [AREACOORD(src)].") + return TRUE + +///Looks in all directions for a nanite chamber to sync to. +/obj/machinery/computer/nanite_chamber_control/proc/find_chamber() + for(var/direction in GLOB.cardinals) + var/found_chamber = locate(/obj/machinery/nanite_chamber, get_step(src, direction)) + if(!found_chamber) + continue + var/obj/machinery/nanite_chamber/nanite_chamber = found_chamber + chamber = nanite_chamber + nanite_chamber.linked_console = src diff --git a/massmeta/features/nanites/code/machines/nanite_cloud_control.dm b/massmeta/features/nanites/code/machines/nanite_cloud_control.dm new file mode 100644 index 0000000000000..326633b2fe97c --- /dev/null +++ b/massmeta/features/nanites/code/machines/nanite_cloud_control.dm @@ -0,0 +1,267 @@ +/obj/machinery/computer/nanite_cloud_controller + name = "nanite cloud controller" + desc = "Stores and controls nanite cloud backups." + icon = 'massmeta/features/nanites/icons/nanite_machines.dmi' + icon_state = "nanite_cloud_controller" + circuit = /obj/item/circuitboard/computer/nanite_cloud_controller + brightness_on = FALSE + icon_keyboard = null + icon_screen = null + + ///The disk currently inserted into the cloud control. + var/obj/item/disk/nanite_program/disk + ///The list of all cloud backups that we are a host to. + var/list/datum/nanite_cloud_backup/cloud_backups = list() + ///The current page we're viewing, 0 is the main menu, all others is their respective cloud backup ID. + var/current_view = 0 + ///The currently set Backup ID, if we create a new cloud backup it will take this ID if possible. + var/new_backup_id = 1 + ///The techweb we're linked to, required for the machine to work. + var/datum/techweb/linked_techweb + +/obj/machinery/computer/nanite_cloud_controller/Destroy() + QDEL_LIST(cloud_backups) //rip backups + linked_techweb = null + eject() + return ..() + +/obj/machinery/computer/nanite_cloud_controller/post_machine_initialize() + . = ..() + if(!CONFIG_GET(flag/no_default_techweb_link) && !linked_techweb) + CONNECT_TO_RND_SERVER_ROUNDSTART(linked_techweb, src) + +/obj/machinery/computer/nanite_cloud_controller/attackby(obj/item/weapon, mob/user, params) + if(!istype(weapon, /obj/item/disk/nanite_program)) + return ..() + if(!user.transferItemToLoc(weapon, src)) + return + if(disk) + balloon_alert(user, "disk swapped") + eject(user) + else + balloon_alert(user, "disk inserted") + playsound(src, 'sound/machines/terminal_insert_disc.ogg', 50, FALSE) + disk = weapon + +/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 + return TRUE + +/obj/machinery/computer/nanite_cloud_controller/attack_hand_secondary(mob/user, list/modifiers) + if(disk && user.can_perform_action(src, ALLOW_SILICON_REACH)) + balloon_alert(user, "disk ejected") + eject(user) + return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN + return ..() + +/obj/machinery/computer/nanite_cloud_controller/proc/eject(mob/living/user) + if(!disk || !user) + 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/datum/nanite_cloud_backup/backup as anything in cloud_backups) + 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, cloud_id) + var/datum/component/nanites/cloud_copy = backup.AddComponent(/datum/component/nanites, linked_techweb) + backup.set_nanites(cloud_copy) + log_game("[key_name(user)] created a new nanite cloud backup with id #[cloud_id]") + +/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_data() + var/list/data = list() + + data["can_rule"] = FALSE + if(disk) + data["has_disk"] = TRUE + var/list/disk_data = list() + var/datum/nanite_program/current_program = disk.program + if(current_program) + data["has_program"] = TRUE + disk_data["name"] = current_program.name + disk_data["desc"] = current_program.desc + disk_data["use_rate"] = current_program.use_rate + disk_data["can_trigger"] = current_program.can_trigger + disk_data["trigger_cost"] = current_program.trigger_cost + disk_data["trigger_cooldown"] = current_program.trigger_cooldown / 10 + + disk_data["activated"] = current_program.activated + disk_data["activation_code"] = current_program.activation_code + disk_data["deactivation_code"] = current_program.deactivation_code + disk_data["kill_code"] = current_program.kill_code + disk_data["trigger_code"] = current_program.trigger_code + disk_data["timer_restart"] = current_program.timer_restart / 10 + disk_data["timer_shutdown"] = current_program.timer_shutdown / 10 + disk_data["timer_trigger"] = current_program.timer_trigger / 10 + disk_data["timer_trigger_delay"] = current_program.timer_trigger_delay / 10 + + var/list/extra_settings = current_program.get_extra_settings_frontend() + disk_data["extra_settings"] = extra_settings + if(LAZYLEN(extra_settings)) + disk_data["has_extra_settings"] = TRUE + if(istype(current_program, /datum/nanite_program/sensor)) + var/datum/nanite_program/sensor/sensor = current_program + data["can_rule"] = sensor.can_rule + data["disk_data"] = 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/cloud_program as anything in nanites.programs) + var/list/cloud_program_data = list() + cloud_program_data["name"] = cloud_program.name + cloud_program_data["desc"] = cloud_program.desc + cloud_program_data["id"] = id + cloud_program_data["use_rate"] = cloud_program.use_rate + cloud_program_data["can_trigger"] = cloud_program.can_trigger + cloud_program_data["trigger_cost"] = cloud_program.trigger_cost + cloud_program_data["trigger_cooldown"] = cloud_program.trigger_cooldown / 10 + cloud_program_data["activated"] = cloud_program.activated + cloud_program_data["timer_restart"] = cloud_program.timer_restart / 10 + cloud_program_data["timer_shutdown"] = cloud_program.timer_shutdown / 10 + cloud_program_data["timer_trigger"] = cloud_program.timer_trigger / 10 + cloud_program_data["timer_trigger_delay"] = cloud_program.timer_trigger_delay / 10 + + cloud_program_data["activation_code"] = cloud_program.activation_code + cloud_program_data["deactivation_code"] = cloud_program.deactivation_code + cloud_program_data["kill_code"] = cloud_program.kill_code + cloud_program_data["trigger_code"] = cloud_program.trigger_code + var/list/rules = list() + var/rule_id = 1 + for(var/datum/nanite_rule/nanite_rule as anything in cloud_program.rules) + var/list/rule = list() + rule["display"] = nanite_rule.display() + rule["program_id"] = id + rule["id"] = rule_id + rules += list(rule) + rule_id++ + cloud_program_data["rules"] = rules + if(LAZYLEN(rules)) + cloud_program_data["has_rules"] = TRUE + cloud_program_data["all_rules_required"] = cloud_program.all_rules_required + + var/list/extra_settings = cloud_program.get_extra_settings_frontend() + cloud_program_data["extra_settings"] = extra_settings + if(LAZYLEN(extra_settings)) + cloud_program_data["has_extra_settings"] = TRUE + id++ + cloud_programs += list(cloud_program_data) + data["cloud_programs"] = cloud_programs + else + var/list/backup_list = list() + for(var/datum/nanite_cloud_backup/backup as anything in cloud_backups) + 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) + return TRUE + if("set_view") + current_view = text2num(params["view"]) + return TRUE + if("update_new_backup_value") + var/backup_value = text2num(params["value"]) + new_backup_id = backup_value + return TRUE + if("create_backup") + var/cloud_id = new_backup_id + if(!isnull(cloud_id)) + playsound(src, 'sound/machines/terminal_prompt.ogg', 50, FALSE) + cloud_id = clamp(round(cloud_id, 1),1,100) + generate_backup(cloud_id, usr) + return TRUE + if("delete_backup") + var/datum/nanite_cloud_backup/backup = get_backup(current_view) + if(!backup) + return TRUE + playsound(src, 'sound/machines/terminal_prompt.ogg', 50, FALSE) + qdel(backup) + log_game("[key_name(usr)] deleted the nanite cloud backup #[current_view]") + return TRUE + if("upload_program") + if(disk && disk.program) + var/datum/nanite_cloud_backup/backup = get_backup(current_view) + if(!backup) + return TRUE + playsound(src, 'sound/machines/terminal_prompt.ogg', 50, FALSE) + var/datum/component/nanites/nanites = backup.nanites + nanites.add_program(null, disk.program.copy()) + log_game("[key_name(usr)] uploaded program [disk.program.name] to cloud #[current_view]") + return TRUE + if("remove_program") + var/datum/nanite_cloud_backup/backup = get_backup(current_view) + if(!backup) + return TRUE + playsound(src, 'sound/machines/terminal_prompt.ogg', 50, FALSE) + var/datum/component/nanites/nanites = backup.nanites + var/datum/nanite_program/cloud_program = nanites.programs[text2num(params["program_id"])] + log_game("[key_name(usr)] deleted program [cloud_program.name] from cloud #[current_view]") + qdel(cloud_program) + return 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_prompt.ogg', 50, 0) + var/datum/component/nanites/nanites = backup.nanites + var/datum/nanite_program/ruled_program = nanites.programs[text2num(params["program_id"])] + var/datum/nanite_rule/rule = rule_template.make_rule(ruled_program) + log_game("[key_name(usr)] added rule [rule.display()] to program [ruled_program.name] in cloud #[current_view]") + return TRUE + if("remove_rule") + var/datum/nanite_cloud_backup/backup = get_backup(current_view) + if(!backup) + return TRUE + playsound(src, 'sound/machines/terminal_prompt.ogg', 50, 0) + var/datum/component/nanites/nanites = backup.nanites + var/datum/nanite_program/ruleless_program = nanites.programs[text2num(params["program_id"])] + var/datum/nanite_rule/rule = ruleless_program.rules[text2num(params["rule_id"])] + rule.remove() + log_game("[key_name(usr)] removed rule [rule.display()] from program [ruleless_program.name] in cloud #[current_view]") + return TRUE + if("toggle_rule_logic") + var/datum/nanite_cloud_backup/backup = get_backup(current_view) + if(!backup) + return TRUE + playsound(src, 'sound/machines/terminal_prompt.ogg', 50, FALSE) + var/datum/component/nanites/nanites = backup.nanites + var/datum/nanite_program/logical_program = nanites.programs[text2num(params["program_id"])] + logical_program.all_rules_required = !logical_program.all_rules_required + log_game("[key_name(usr)] edited rule logic for program [logical_program.name] into [logical_program.all_rules_required ? "All" : "Any"] in cloud #[current_view]") + return TRUE diff --git a/massmeta/features/nanites/code/machines/nanite_program_hub.dm b/massmeta/features/nanites/code/machines/nanite_program_hub.dm new file mode 100644 index 0000000000000..f5339a8f6610e --- /dev/null +++ b/massmeta/features/nanites/code/machines/nanite_program_hub.dm @@ -0,0 +1,200 @@ +/obj/machinery/nanite_program_hub + name = "nanite program hub" + desc = "Compiles nanite programs from the techweb servers and downloads them into disks." + icon = 'massmeta/features/nanites/icons/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 + + ///Boolean on whether the UI should give a detailed view of everything. + var/detail_view = TRUE + ///The disk currently inserted into the machine, that we upload programs onto. + var/obj/item/disk/nanite_program/inserted_disk + ///The techweb we're connected to, and get designs from. + var/datum/techweb/linked_techweb + ///List of all unlocked nanite designs, cached to only state when you receive a new one. + var/list/datum/design/nanites/cached_designs = list() + +/obj/machinery/nanite_program_hub/Destroy() + linked_techweb = null + return ..() + +/obj/machinery/nanite_program_hub/post_machine_initialize() + . = ..() + if(!CONFIG_GET(flag/no_default_techweb_link) && !linked_techweb) + CONNECT_TO_RND_SERVER_ROUNDSTART(linked_techweb, src) + if(linked_techweb) + on_connected_techweb() + +/obj/machinery/nanite_program_hub/proc/connect_techweb(datum/techweb/new_techweb) + if(linked_techweb) + UnregisterSignal(linked_techweb, list(COMSIG_TECHWEB_ADD_DESIGN)) + linked_techweb = new_techweb + if(!isnull(linked_techweb)) + on_connected_techweb() + +/obj/machinery/nanite_program_hub/proc/on_connected_techweb() + for (var/researched_design_id in linked_techweb.researched_designs) + var/datum/design/nanites/design = SSresearch.techweb_design_by_id(researched_design_id) + if (!ispath(design)) + continue + + cached_designs[design.program_type] = design.id + + RegisterSignal(linked_techweb, COMSIG_TECHWEB_ADD_DESIGN, PROC_REF(on_research)) + +/obj/machinery/nanite_program_hub/proc/on_research(datum/source, datum/design/nanites/added_design, custom) + SIGNAL_HANDLER + // We're probably going to get more than one update (design) at a time, so batch them together. + addtimer(CALLBACK(src, PROC_REF(update_menu_tech)), 2 SECONDS, TIMER_UNIQUE | TIMER_OVERRIDE) + +/** + * Updates the `final_sets` and `buildable_parts` for the current mecha fabricator. + */ +/obj/machinery/nanite_program_hub/proc/update_menu_tech() + var/previous_design_count = cached_designs.len + + cached_designs.Cut() + for(var/v in linked_techweb.researched_designs) + var/datum/design/nanites/design = SSresearch.techweb_design_by_id(v) + + if(istype(design)) + cached_designs |= design + + var/design_delta = cached_designs.len - previous_design_count + + if(design_delta > 0) + say("Received [design_delta] new design[design_delta == 1 ? "" : "s"].") + playsound(src, 'sound/machines/twobeep_high.ogg', 50, TRUE) + + update_static_data_for_all_viewers() + +/obj/machinery/nanite_program_hub/attackby(obj/item/weapon, mob/user, params) + if(!istype(weapon, /obj/item/disk/nanite_program)) + return ..() + if(!user.transferItemToLoc(weapon, src)) + return + if(inserted_disk) + balloon_alert(user, "disk swapped") + eject(user) + else + balloon_alert(user, "disk inserted") + playsound(src, 'sound/machines/terminal_insert_disc.ogg', 50, FALSE) + inserted_disk = weapon + +/obj/machinery/nanite_program_hub/crowbar_act(mob/living/user, obj/item/tool) + if(default_deconstruction_crowbar(tool)) + return ITEM_INTERACT_SUCCESS + return ..() + +/obj/machinery/nanite_program_hub/screwdriver_act(mob/living/user, obj/item/tool) + if(default_deconstruction_screwdriver(user, icon_state, icon_state, tool)) + return ITEM_INTERACT_SUCCESS + return ..() + +/obj/machinery/nanite_program_hub/multitool_act(mob/living/user, obj/item/multitool/tool) + if(!QDELETED(tool.buffer) && istype(tool.buffer, /datum/techweb)) + connect_techweb(tool.buffer) + return TRUE + +/obj/machinery/nanite_program_hub/proc/eject(mob/living/user) + if(!inserted_disk) + return + if(!istype(user) || !Adjacent(user) || !user.put_in_active_hand(inserted_disk)) + inserted_disk.forceMove(drop_location()) + inserted_disk = null + +/obj/machinery/nanite_program_hub/attack_hand_secondary(mob/user, list/modifiers) + if(inserted_disk && user.can_perform_action(src, ALLOW_SILICON_REACH)) + balloon_alert(user, "disk ejected") + eject(user) + return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN + return ..() + +/obj/machinery/nanite_program_hub/ui_interact(mob/user, datum/tgui/ui) + if(!linked_techweb) + visible_message("Warning: no linked server!") + SStgui.close_uis(src) + return + 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(inserted_disk) + data["has_disk"] = TRUE + var/list/disk_data = list() + var/datum/nanite_program/P = inserted_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() + data["programs"] = list() + data["categories"] = 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(!(cat_name in data["categories"])) + data["categories"] += cat_name + 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) + return TRUE + if("download") + if(!inserted_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(inserted_disk.program) + qdel(inserted_disk.program) + inserted_disk.program = new downloaded.program_type + inserted_disk.name = "[initial(inserted_disk.name)] \[[inserted_disk.program.name]\]" + playsound(src, 'sound/machines/terminal_prompt.ogg', 25, FALSE) + return TRUE + if("refresh") + update_static_data(usr) + return TRUE + if("toggle_details") + detail_view = !detail_view + return TRUE + if("clear") + if(inserted_disk && inserted_disk.program) + qdel(inserted_disk.program) + inserted_disk.program = null + inserted_disk.name = initial(inserted_disk.name) + playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 25, FALSE) + return TRUE diff --git a/massmeta/features/nanites/code/machines/nanite_programmer.dm b/massmeta/features/nanites/code/machines/nanite_programmer.dm new file mode 100644 index 0000000000000..246921944b37c --- /dev/null +++ b/massmeta/features/nanites/code/machines/nanite_programmer.dm @@ -0,0 +1,167 @@ +/obj/machinery/nanite_programmer + name = "nanite programmer" + desc = "A device that can edit nanite program disks to adjust their functionality." + icon = 'massmeta/features/nanites/icons/nanite_machines.dmi' + icon_state = "nanite_programmer" + use_power = IDLE_POWER_USE + anchored = TRUE + density = TRUE + circuit = /obj/item/circuitboard/machine/nanite_programmer + + ///The delay between 'when you code it' replies. + COOLDOWN_DECLARE(wyci_delay) + ///The disk inserted into the machine that we hold and get the program of. + var/obj/item/disk/nanite_program/disk + ///The program on the nanite disk that we are currently editing. + var/datum/nanite_program/program + +/obj/machinery/nanite_programmer/Initialize(mapload) + . = ..() + become_hearing_sensitive() + +/obj/machinery/nanite_programmer/attackby(obj/item/weapon, mob/user, params) + if(!istype(weapon, /obj/item/disk/nanite_program)) + return ..() + if(!user.transferItemToLoc(weapon, src)) + return + if(disk) + balloon_alert(user, "disk swapped") + eject(user) + else + balloon_alert(user, "disk inserted") + playsound(src, 'sound/machines/terminal_insert_disc.ogg', 50, FALSE) + disk = weapon + program = disk.program + +/obj/machinery/nanite_programmer/attack_hand_secondary(mob/user, list/modifiers) + if(disk && user.can_perform_action(src, ALLOW_SILICON_REACH)) + balloon_alert(user, "disk ejected") + eject(user) + return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN + return ..() + +/obj/machinery/nanite_programmer/Hear(message, atom/movable/speaker, message_language, raw_message, radio_freq, list/spans, list/message_mods = list(), message_range=0) + . = ..() + if(!COOLDOWN_FINISHED(src, wyci_delay)) + return + 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!!") + COOLDOWN_START(src, wyci_delay, 5 SECONDS) + +/obj/machinery/nanite_programmer/crowbar_act(mob/living/user, obj/item/tool) + if(default_deconstruction_crowbar(tool)) + return ITEM_INTERACT_SUCCESS + return ..() + +/obj/machinery/nanite_programmer/screwdriver_act(mob/living/user, obj/item/tool) + if(default_deconstruction_screwdriver(user, icon_state, icon_state, tool)) + return ITEM_INTERACT_SUCCESS + return ..() + +/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) + return 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 + return 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) + if("deactivation") + program.deactivation_code = clamp(round(new_code, 1),0,9999) + if("kill") + program.kill_code = clamp(round(new_code, 1),0,9999) + if("trigger") + program.trigger_code = clamp(round(new_code, 1),0,9999) + return TRUE + if("set_extra_setting") + program.set_extra_setting(params["target_setting"], params["value"]) + playsound(src, "terminal_type", 25, FALSE) + return 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 + return 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 + return 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 + return 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 + return TRUE + +/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 diff --git a/massmeta/features/nanites/code/mood_events.dm b/massmeta/features/nanites/code/mood_events.dm new file mode 100644 index 0000000000000..3cd6db1174e48 --- /dev/null +++ b/massmeta/features/nanites/code/mood_events.dm @@ -0,0 +1,14 @@ +/datum/mood_event/nanite_sadness + description = "+++++++HAPPINESS SUPPRESSION+++++++" + mood_change = -7 + +/datum/mood_event/nanite_sadness/add_effects(message) + description = "+++++++[message]+++++++" + + +/datum/mood_event/nanite_happiness + description = "+++++++HAPPINESS ENHANCEMENT+++++++" + mood_change = 7 + +/datum/mood_event/nanite_happiness/add_effects(message) + description = "+++++++[message]+++++++" diff --git a/massmeta/features/nanites/code/nanite_backup.dm b/massmeta/features/nanites/code/nanite_backup.dm new file mode 100644 index 0000000000000..92983bdd52d2f --- /dev/null +++ b/massmeta/features/nanites/code/nanite_backup.dm @@ -0,0 +1,22 @@ +/datum/nanite_cloud_backup + ///The ID the backup is set to that is shown in the cloud host. + var/cloud_id = 0 + ///The nanite component that the backup has, which handles listing programs and such. + var/datum/component/nanites/nanites + ///The host of our nanites in the cloud. + var/obj/machinery/computer/nanite_cloud_controller/cloud_host + +/datum/nanite_cloud_backup/New(obj/machinery/computer/nanite_cloud_controller/cloud_host_machine, cloud_host_id) + . = ..() + src.cloud_id = cloud_host_id + src.cloud_host = cloud_host_machine + cloud_host.cloud_backups += src + SSnanites.cloud_backups += src + +/datum/nanite_cloud_backup/proc/set_nanites(datum/component/nanites/cloud_copy) + src.nanites = cloud_copy + +/datum/nanite_cloud_backup/Destroy() + cloud_host.cloud_backups -= src + SSnanites.cloud_backups -= src + return ..() diff --git a/massmeta/features/nanites/code/nanite_component.dm b/massmeta/features/nanites/code/nanite_component.dm new file mode 100644 index 0000000000000..78b4cbf89d008 --- /dev/null +++ b/massmeta/features/nanites/code/nanite_component.dm @@ -0,0 +1,448 @@ +#define NANITE_DEFAULT_STARTING_VOLUME 100 +#define NANITE_DEFAULT_MAX_VOLUME 500 +#define NANITE_DEFAULT_REGEN_RATE 0.5 +#define NANITE_DEFAULT_SAFETY_THRESHOLD 50 + +///The default amount of nanite research points to generate per person per tick, if unmodified. +#define NANITE_BASE_RESEARCH 1.5 +///The chance at a Nanite program randomly failing when it cannot sync +#define NANITE_FAILURE_CHANCE 8 +///The max amount of nanite programs you can have in a cloud at once. +#define NANITE_PROGRAM_LIMIT 20 +///The delay between sync attempts for nanites to the cloud, if it fails then it will start to corrupt. +#define NANITE_SYNC_DELAY (30 SECONDS) + +/datum/component/nanites + dupe_mode = COMPONENT_DUPE_UNIQUE_PASSARGS + + ///The living person these nanites are attached onto + var/mob/living/host_mob + + ///amount of nanites in the system, used as fuel for nanite programs + var/nanite_volume = NANITE_DEFAULT_STARTING_VOLUME + ///maximum amount of nanites someone can have + var/max_nanites = NANITE_DEFAULT_MAX_VOLUME + ///nanites generated per second + var/regen_rate = NANITE_DEFAULT_REGEN_RATE + ///how low nanites will get before they stop processing/triggering + var/safety_threshold = NANITE_DEFAULT_SAFETY_THRESHOLD + ///0 if not connected to the cloud, 1-100 to set a determined cloud backup to draw from + var/cloud_id = 0 + ///How long until the next sync to cloud + var/next_sync = 0 + ///All nanite programs in the user + var/list/datum/nanite_program/programs = list() + + ///Separate list of protocol programs, to avoid looping through the whole programs list when checking for conflicts + var/list/datum/nanite_program/protocol/protocols = list() + ///Timestamp to when the nanites were first inserted in the host, used in some protocols. + var/start_time = 0 + ///Prevents nanites from appearing on HUDs and health scans + var/stealth = FALSE + ///if TRUE, displays program list when scanned by nanite scanners + var/diagnostics = TRUE + ///The techweb these Nanites are synced to, to generate Nanite research points + var/datum/techweb/linked_techweb + +/datum/component/nanites/Initialize( + datum/techweb/linked_techweb, + nanite_volume = NANITE_DEFAULT_STARTING_VOLUME, + cloud_id = 0, +) + if(!isliving(parent) && !istype(parent, /datum/nanite_cloud_backup)) + return COMPONENT_INCOMPATIBLE + + src.linked_techweb = linked_techweb + src.nanite_volume = nanite_volume + src.cloud_id = cloud_id + + if(!isliving(parent)) + return + + host_mob = parent + if(!(host_mob.mob_biotypes & (MOB_ORGANIC|MOB_UNDEAD))) //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_sync() + +/datum/component/nanites/RegisterWithParent() + RegisterSignal(parent, COMSIG_NANITE_IS_STEALTHY, PROC_REF(check_stealth)) + RegisterSignal(parent, COMSIG_NANITE_DELETE, PROC_REF(delete_nanites)) + RegisterSignal(parent, COMSIG_NANITE_UI_DATA, PROC_REF(nanite_ui_data)) + RegisterSignal(parent, COMSIG_NANITE_GET_PROGRAMS, PROC_REF(get_programs)) + RegisterSignal(parent, COMSIG_NANITE_SET_VOLUME, PROC_REF(set_volume)) + RegisterSignal(parent, COMSIG_NANITE_ADJUST_VOLUME, PROC_REF(adjust_nanites)) + RegisterSignal(parent, COMSIG_NANITE_SET_MAX_VOLUME, PROC_REF(set_max_volume)) + RegisterSignal(parent, COMSIG_NANITE_SET_CLOUD, PROC_REF(set_cloud)) + RegisterSignal(parent, COMSIG_NANITE_SET_SAFETY, PROC_REF(set_safety)) + RegisterSignal(parent, COMSIG_NANITE_SET_REGEN, PROC_REF(set_regen)) + RegisterSignal(parent, COMSIG_NANITE_ADD_PROGRAM, PROC_REF(add_program)) + RegisterSignal(parent, COMSIG_LIVING_HEALTHSCAN, PROC_REF(on_healthscan)) + RegisterSignal(parent, COMSIG_NANITE_SCAN, PROC_REF(nanite_scan)) + RegisterSignal(parent, COMSIG_NANITE_SYNC, PROC_REF(sync)) + if(isliving(parent)) + RegisterSignal(parent, COMSIG_ATOM_EMP_ACT, PROC_REF(on_emp)) + RegisterSignal(parent, COMSIG_LIVING_DEATH, PROC_REF(on_death)) + RegisterSignal(parent, COMSIG_MOB_TRIED_ACCESS, PROC_REF(on_tried_access)) + RegisterSignal(parent, COMSIG_LIVING_ELECTROCUTE_ACT, PROC_REF(on_shock)) + RegisterSignal(parent, COMSIG_LIVING_MINOR_SHOCK, PROC_REF(on_minor_shock)) + RegisterSignal(parent, COMSIG_SPECIES_GAIN, PROC_REF(check_viable_biotype)) + RegisterSignal(parent, COMSIG_NANITE_SIGNAL, PROC_REF(receive_signal)) + RegisterSignal(parent, COMSIG_NANITE_COMM_SIGNAL, PROC_REF(receive_comm_signal)) + +/datum/component/nanites/UnregisterFromParent() + UnregisterSignal(parent, list( + 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_SAFETY, + COMSIG_NANITE_SET_REGEN, + COMSIG_NANITE_ADD_PROGRAM, + COMSIG_LIVING_HEALTHSCAN, + 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, + )) + +/datum/component/nanites/Destroy() + STOP_PROCESSING(SSnanites, src) + QDEL_LIST(programs) + if(host_mob) + host_mob.hud_set_nanite_indicator(remove = TRUE) + set_nanite_bar(remove = TRUE) + host_mob = null + linked_techweb = 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() + if(!HAS_TRAIT(host_mob, TRAIT_STASIS)) + adjust_nanites(null, regen_rate) + add_research() + for(var/datum/nanite_program/active_programs as anything in programs) + active_programs.on_process() + if(cloud_id && world.time > next_sync) + cloud_sync() + next_sync = world.time + NANITE_SYNC_DELAY + set_nanite_bar() + +/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/datum/nanite_program/nanite_program as anything in programs) + for(var/datum/nanite_program/adding_program as anything in programs_to_add) + if(nanite_program.type == adding_program.type) + programs_to_remove -= nanite_program + programs_to_add -= adding_program + adding_program.copy_programming(nanite_program, copy_activation) + break + if(full_overwrite) + for(var/X in programs_to_remove) + qdel(X) + for(var/datum/nanite_program/adding_program as anything in programs_to_add) + add_program(null, adding_program.copy()) + +/datum/component/nanites/proc/cloud_sync() + if(!cloud_id) + return attempt_corrupt() + var/datum/nanite_cloud_backup/backup = SSnanites.get_cloud_backup(cloud_id) + if(!backup) + return attempt_corrupt() + var/datum/component/nanites/cloud_copy = backup.nanites + if(!cloud_copy) + return attempt_corrupt() + sync(null, cloud_copy) + +///Rolls for a chance to corrupt your nanites. +/datum/component/nanites/proc/attempt_corrupt() + if(prob(NANITE_FAILURE_CHANCE) && programs.len) + var/datum/nanite_program/random_program = pick(programs) + random_program.software_error() + +/datum/component/nanites/proc/add_program(datum/source, datum/nanite_program/new_program, datum/nanite_program/source_program) + SIGNAL_HANDLER + + if(istype(new_program, /datum/nanite_program/protocol)) + var/datum/nanite_program/protocol/protocol_nanite = new_program + for(var/datum/nanite_program/protocol/other_protocols as anything in protocols) + //skip over the same type so it continues on to delete it later. + if(other_protocols.type != new_program.type) + continue + if(other_protocols.protocol_class != protocol_nanite.protocol_class) + continue + return COMPONENT_PROGRAM_NOT_INSTALLED + + for(var/datum/nanite_program/all_program as anything in programs) + if(!all_program.unique || all_program.type != new_program.type) + continue + qdel(all_program) + + if(programs.len >= NANITE_PROGRAM_LIMIT) + 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) + +/datum/component/nanites/proc/adjust_nanites(datum/source, amount) + SIGNAL_HANDLER + + nanite_volume = clamp(nanite_volume + amount, 0, max_nanites) + if(nanite_volume <= 0) //oops we ran out + INVOKE_ASYNC(src, PROC_REF(delete_nanites)) + +/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/datum/nanite_program/all_program as anything in programs) + all_program.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, TRAIT_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/datum/nanite_program/all_program as anything in programs) + all_program.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/datum/nanite_program/all_program as anything in programs) + all_program.on_death(gibbed) + +/datum/component/nanites/proc/receive_signal(datum/source, code, signal_source = "an unidentified source") + SIGNAL_HANDLER + + for(var/datum/nanite_program/all_program as anything in programs) + all_program.receive_signal(code, signal_source) + +/datum/component/nanites/proc/receive_comm_signal(datum/source, comm_code, comm_message, comm_source = "an unidentified source") + SIGNAL_HANDLER + + for(var/datum/nanite_program/comm/comm_program in programs) + comm_program.receive_comm_signal(comm_code, comm_message, comm_source) + +/datum/component/nanites/proc/check_viable_biotype() + SIGNAL_HANDLER + + if(!(host_mob.mob_biotypes & (MOB_ORGANIC|MOB_UNDEAD))) + qdel(src) //bodytype no longer sustains nanites + +/datum/component/nanites/proc/on_tried_access(datum/source, atom/locked_thing) + SIGNAL_HANDLER + + if(!isobj(locked_thing)) + return LOCKED_ATOM_INCOMPATIBLE + + var/list/all_access = list() + var/obj/locked_object = locked_thing + for(var/datum/nanite_program/access/access_program in programs) + if(access_program.activated) + all_access += access_program.access + + if(locked_object.check_access_list(all_access)) + return ACCESS_ALLOWED + + return ACCESS_DISALLOWED + +/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, max_nanites) + +/datum/component/nanites/proc/set_cloud(datum/source, amount) + SIGNAL_HANDLER + + cloud_id = clamp(amount, 0, 100) + +/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/get_data(list/nanite_data) + nanite_data["nanite_volume"] = nanite_volume + nanite_data["max_nanites"] = max_nanites + nanite_data["cloud_id"] = cloud_id + nanite_data["regen_rate"] = regen_rate + nanite_data["safety_threshold"] = safety_threshold + nanite_data["stealth"] = stealth + +/datum/component/nanites/proc/get_programs(datum/source, list/nanite_programs) + SIGNAL_HANDLER + + nanite_programs |= programs + +///Adds nanite research points to the linked techweb based on the host's status. +/datum/component/nanites/proc/add_research() + if(host_mob.stat == DEAD || !host_mob.client) + return + var/research_value = NANITE_BASE_RESEARCH + if(!ishuman(host_mob)) + research_value *= 0.5 + linked_techweb.add_point_list(list(TECHWEB_POINT_TYPE_NANITES = research_value)) + +/datum/component/nanites/proc/on_healthscan(datum/source, list/render_list, advanced, mob/user, mode) + SIGNAL_HANDLER + nanite_scan(source, user, full_scan = FALSE) + +/datum/component/nanites/proc/nanite_scan(datum/source, mob/user, full_scan) + SIGNAL_HANDLER + + if(full_scan) + to_chat(user, span_boldnotice("Nanites Detected")) + 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("================")) + to_chat(user, span_info("Program List:")) + if(diagnostics) + for(var/datum/nanite_program/NP as anything in programs) + to_chat(user, span_info("[NP.name] | [NP.activated ? "Active" : "Inactive"]")) + return TRUE + to_chat(user, span_alert("Diagnostics Disabled")) + return TRUE + + if(stealth) + return TRUE + + to_chat(user, span_boldnotice("Nanites Detected")) + to_chat(user, span_notice("Saturation: [nanite_volume]/[max_nanites]")) + +/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"] = regen_rate + data["safety_threshold"] = safety_threshold + data["cloud_id"] = cloud_id + var/list/mob_programs = list() + var/id = 1 + for(var/datum/nanite_program/program as anything in programs) + var/list/mob_program = list() + mob_program["name"] = program.name + mob_program["desc"] = program.desc + mob_program["id"] = id + + if(scan_level >= 2) + mob_program["activated"] = program.activated + mob_program["use_rate"] = program.use_rate + mob_program["can_trigger"] = program.can_trigger + mob_program["trigger_cost"] = program.trigger_cost + mob_program["trigger_cooldown"] = program.trigger_cooldown / 10 + + if(scan_level >= 3) + mob_program["timer_restart"] = program.timer_restart / 10 + mob_program["timer_shutdown"] = program.timer_shutdown / 10 + mob_program["timer_trigger"] = program.timer_trigger / 10 + mob_program["timer_trigger_delay"] = program.timer_trigger_delay / 10 + var/list/extra_settings = program.get_extra_settings_frontend() + mob_program["extra_settings"] = extra_settings + if(LAZYLEN(extra_settings)) + mob_program["has_extra_settings"] = TRUE + else + mob_program["has_extra_settings"] = FALSE + + if(scan_level >= 4) + mob_program["activation_code"] = program.activation_code + mob_program["deactivation_code"] = program.deactivation_code + mob_program["kill_code"] = program.kill_code + mob_program["trigger_code"] = program.trigger_code + var/list/rules = list() + var/rule_id = 1 + for(var/datum/nanite_rule/nanite_rule as anything in program.rules) + 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 NANITE_DEFAULT_STARTING_VOLUME +#undef NANITE_DEFAULT_MAX_VOLUME +#undef NANITE_DEFAULT_REGEN_RATE +#undef NANITE_DEFAULT_SAFETY_THRESHOLD + +#undef NANITE_BASE_RESEARCH +#undef NANITE_FAILURE_CHANCE +#undef NANITE_PROGRAM_LIMIT +#undef NANITE_SYNC_DELAY diff --git a/massmeta/features/nanites/code/nanite_disease.dm b/massmeta/features/nanites/code/nanite_disease.dm new file mode 100644 index 0000000000000..17f98cde968c2 --- /dev/null +++ b/massmeta/features/nanites/code/nanite_disease.dm @@ -0,0 +1,83 @@ +/* + +/datum/symptom/nanite_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 + threshold_descs = list( + "Transmission 5" = "Increases the virus' growth rate while nanites are present.", + "Stage Speed 7" = "Increases the replication boost.", + ) + ///Whether nanites will also help boost the disease. + var/reverse_boost = FALSE + +/datum/symptom/nanite_boost/Start(datum/disease/advance/A) + . = ..() + if(!.) + return + if(A.properties["transmittable"] >= 5) //reverse boost + reverse_boost = TRUE + if(A.properties["stage_rate"] >= 7) //more nanites + power = 2 + +/datum/symptom/nanite_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 && M.GetComponent(/datum/component/nanites)) + if(prob(A.stage_prob)) + A.stage = min(A.stage + 1,A.max_stages) + +/datum/symptom/nanite_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 + 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.", + ) + ///Whether nanites will also help boost the disease. + var/reverse_boost = FALSE + +/datum/symptom/nanite_destroy/Start(datum/disease/advance/A) + . = ..() + if(!.) + return + if(A.properties["stage_rate"] >= 5) //reverse boost + reverse_boost = TRUE + if(A.properties["resistance"] >= 7) //less nanites + power = 3 + +/datum/symptom/nanite_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 && M.GetComponent(/datum/component/nanites)) + if(prob(A.stage_prob)) + A.stage = min(A.stage + 1,A.max_stages) + +/datum/disease_ability/symptom/medium/nano_boost + symptoms = list(/datum/symptom/nanite_boost) + +/datum/disease_ability/symptom/medium/nano_destroy + symptoms = list(/datum/symptom/nanite_destroy) + +*/ diff --git a/massmeta/features/nanites/code/nanite_hud.dm b/massmeta/features/nanites/code/nanite_hud.dm new file mode 100644 index 0000000000000..e12c1957196bd --- /dev/null +++ b/massmeta/features/nanites/code/nanite_hud.dm @@ -0,0 +1,25 @@ +#define NANITE_HUD "nanite_hud" +#define DIAG_NANITE_FULL_HUD "nanite_full_hud" + +/mob/living/proc/hud_set_nanite_indicator(remove = FALSE) + 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 = 'massmeta/features/nanites/icons/nanite_hud.dmi' + holder.icon_state = null + if(remove) + return //bye icon + holder.icon_state = "nanite_ping" + +///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 = 'massmeta/features/nanites/icons/nanite_hud.dmi' + holder.icon_state = "nanites[nanite_percent]" diff --git a/massmeta/features/nanites/code/nanite_remote.dm b/massmeta/features/nanites/code/nanite_remote.dm new file mode 100644 index 0000000000000..93ec0281b81f7 --- /dev/null +++ b/massmeta/features/nanites/code/nanite_remote.dm @@ -0,0 +1,238 @@ +#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_RESEARCH) + icon = 'massmeta/features/nanites/icons/nanite_device.dmi' + icon_state = "nanite_remote" + base_icon_state = "nanite_remote" + item_flags = NOBLUDGEON + + ///Boolean on whether the nanite remote has been locked, preventing changing of any setting. + var/locked = FALSE + 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)) + return + if(allowed(user)) + locked = !locked + user.balloon_alert(user, (locked ? "locked" : "unlocked")) + update_appearance(UPDATE_ICON) + else + user.balloon_alert(user, "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(UPDATE_ICON) + +/obj/item/nanite_remote/update_overlays() + . = ..() + if(obj_flags & EMAGGED) + . += "[base_icon_state]_emagged" + if(locked) + . += "[base_icon_state]_locked" + +/obj/item/nanite_remote/afterattack(atom/target, mob/user, proximity_flag, click_parameters, comms_message) + 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)) + return + 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)) + return + 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 + 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 + return ..() + +/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/datum/nanite_program/relay/relays as anything in SSnanites.nanite_relays) + relays.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 + return 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 + return TRUE + if("update_name") + current_program_name = params["name"] + return 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) + return 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"] + return 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 + return TRUE + if("select_mode") + if(locked) + return + mode = params["mode"] + return TRUE + if("lock") + if(!(obj_flags & EMAGGED)) + locked = TRUE + update_appearance() + return 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, proximity_flag, click_parameters) + 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, comm_message) + 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, comm_message, 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, comm_message, 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, comm_message, 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) + +/obj/item/nanite_remote/comm/signal_relay(code, relay_code, source) + for(var/datum/nanite_program/relay/relays as anything in SSnanites.nanite_relays) + relays.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 + return TRUE + +#undef REMOTE_MODE_OFF +#undef REMOTE_MODE_SELF +#undef REMOTE_MODE_TARGET +#undef REMOTE_MODE_AOE +#undef REMOTE_MODE_RELAY diff --git a/massmeta/features/nanites/code/nanite_scanner.dm b/massmeta/features/nanites/code/nanite_scanner.dm new file mode 100644 index 0000000000000..b9c06e8face88 --- /dev/null +++ b/massmeta/features/nanites/code/nanite_scanner.dm @@ -0,0 +1,25 @@ +/obj/item/nanite_scanner + name = "nanite scanner" + icon = 'massmeta/features/nanites/icons/nanite_device.dmi' + icon_state = "nanite_scanner" + worn_icon_state = "electronic" + lefthand_file = 'icons/mob/inhands/equipment/medical_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/medical_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=SMALL_MATERIAL_AMOUNT * 2) + +/obj/item/nanite_scanner/attack(mob/living/target_mob, mob/living/user, params) + add_fingerprint(user) + user.visible_message(span_notice("[user] analyzes [target_mob]'s nanites.")) + balloon_alert(user, "analyzing nanites") + playsound(user.loc, 'massmeta/features/nanites/sound/nanite_scan.mp3', 50) + var/response = SEND_SIGNAL(target_mob, COMSIG_NANITE_SCAN, user, TRUE) + if(!response) + to_chat(user, span_info("No nanites detected in the subject.")) diff --git a/massmeta/features/nanites/code/nanite_subsystem.dm b/massmeta/features/nanites/code/nanite_subsystem.dm new file mode 100644 index 0000000000000..68baf90729777 --- /dev/null +++ b/massmeta/features/nanites/code/nanite_subsystem.dm @@ -0,0 +1,22 @@ +PROCESSING_SUBSYSTEM_DEF(nanites) + name = "Nanites" + flags = SS_BACKGROUND|SS_POST_FIRE_TIMING|SS_NO_INIT + wait = 1 SECONDS + + ///List of all Nanite backups in the game. + var/list/datum/nanite_cloud_backup/cloud_backups = list() + ///List of all nanite relays in the game. + var/list/datum/nanite_program/relay/nanite_relays = list() + +/datum/controller/subsystem/processing/nanites/proc/check_hardware(datum/nanite_cloud_backup/backup) + if(QDELETED(backup.cloud_host) || (backup.cloud_host.machine_stat & (NOPOWER|BROKEN))) + return FALSE + return TRUE + +///Using a given cloud_id, will try to sync to the proper nanite cloud backup. +/datum/controller/subsystem/processing/nanites/proc/get_cloud_backup(cloud_id, force = FALSE) + for(var/datum/nanite_cloud_backup/backup as anything in cloud_backups) + if(!force && !check_hardware(backup)) + return + if(backup.cloud_id == cloud_id) + return backup diff --git a/massmeta/features/nanites/code/nanites_decal.dm b/massmeta/features/nanites/code/nanites_decal.dm new file mode 100644 index 0000000000000..94508afc9bf97 --- /dev/null +++ b/massmeta/features/nanites/code/nanites_decal.dm @@ -0,0 +1,8 @@ +/obj/structure/sign/departments/nanites + name = "\improper Nanite Lab sign" + sign_change_name = "Department - Science: Nanites" + desc = "A sign labelling an area where testing and development of nanites is performed." + icon = 'massmeta/features/nanites/icons/nanites_decal.dmi' + icon_state = "nanites" + +MAPPING_DIRECTIONAL_HELPERS(/obj/structure/sign/departments/nanites, 32) diff --git a/massmeta/features/nanites/code/programs/_programs.dm b/massmeta/features/nanites/code/programs/_programs.dm new file mode 100644 index 0000000000000..92ea0dcc99760 --- /dev/null +++ b/massmeta/features/nanites/code/programs/_programs.dm @@ -0,0 +1,331 @@ +/datum/nanite_program + ///The name of the Nanite Program. + var/name = "Generic Nanite Program" + ///The description of the nanite program for easy understanding. + var/desc = "Warn a coder if you can read this." + + ///The nanite program component we're connected to. + var/datum/component/nanites/nanites + ///The person hosting this nanite program, if any. + var/mob/living/host_mob + + ///How many nanites this uses while active. + var/use_rate = 0 + ///Boolean on whether there can have more than one of this nanite program in the same nanite component. + var/unique = TRUE + ///Boolean on whether the nanite program has a trigger function. + var/can_trigger = FALSE + ///Amount of nanites this uses when triggered. + var/trigger_cost = 0 + ///Dediseconds required between each trigger activation. + var/trigger_cooldown = 50 + ///World time required for the next trigger activation. + var/next_trigger = 0 + + ///Special program flags. + ///(NANITE_SHOCK_IMMUNE | NANITE_EMP_IMMUNE) + var/program_flags = NONE + ///Boolean on whether the nanites have an on/off style effect. + var/passive_enabled = FALSE + + ///What this program turns into if it glitches. This should be simpler, negative, waste of nanites, or affecting the same bodyparts. Generally. + var/list/rogue_types = list(/datum/nanite_program/glitch) + + ///Boolean whether the program is currently activated. Off programs won't proceess or have passive effects, but won't consume nanites. + var/activated = TRUE + + ///How many deciseconds to wait upon deactivation before self-reactivating. Also works if the program begins deactivated. + var/timer_restart = 0 + ///How many deciseconds to wait upon activation before self-deactivating. Also works if the program begins activated. + var/timer_shutdown = 0 + ///While active, how many dediseconds before the program attempts to trigger itself. + var/timer_trigger = 0 + ///While active, how many dediseconds the program will deelay its trigger signals. + var/timer_trigger_delay = 0 + + //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 + + ///Code that activates the program when sent. [1-9999, 0 ignores signals] + var/activation_code = 0 + ///Code that deactivates the program [1-9999, 0 ignores signals] + var/deactivation_code = 0 + ///Code that permanently removes the program [1-9999, 0 ignores signals] + var/kill_code = 0 + ///Code that triggers the program (if available) [1-9999, 0 ignores signals] + var/trigger_code = 0 + + ///Boolean on whether all rules are required for positive condition or any of specified. + var/all_rules_required = TRUE + ///Rules that automatically manage if the program's active without requiring separate sensor programs + var/list/datum/nanite_rule/rules = list() + + ///List of extra settings the nanite program is following. + VAR_FINAL/list/datum/nanite_extra_setting/extra_settings = list() + +/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 + nanites = null + 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/datum/nanite_rule/rule as anything in rules) + 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() + SHOULD_CALL_PARENT(TRUE) + 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() + SHOULD_CALL_PARENT(TRUE) + 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() + return + + if(passive_enabled) + disable_passive_effect() + +//If false, disables active and passive effects, but doesn't consume nanites +//Can be used to avoid consuming nanites for nothing +/datum/nanite_program/proc/check_conditions() + if(!LAZYLEN(rules)) + return TRUE + for(var/datum/nanite_rule/rule as anything in rules) + if(!all_rules_required && rule.check_rule()) + return TRUE + if(all_rules_required && !rule.check_rule()) + return FALSE + return all_rules_required + +//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(!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)) + software_error() + +/datum/nanite_program/proc/on_shock(shock_damage) + if(!(program_flags & NANITE_SHOCK_IMMUNE)) + if(prob(10)) + software_error() + else if(prob(33)) + qdel(src) + +/datum/nanite_program/proc/on_minor_shock() + if(!(program_flags & NANITE_SHOCK_IMMUNE)) + if(prob(10)) + software_error() + +/datum/nanite_program/proc/on_death(gibbed) + return + +#define SOFTWARE_ERROR_DELETE 1 +#define SOFTWARE_ERROR_DEPROGRAM 2 +#define SOFTWARE_ERROR_TOGGLE 3 +#define SOFTWARE_ERROR_TRIGGER 4 +#define SOFTWARE_ERROR_ROGUE 5 + +/datum/nanite_program/proc/software_error() + var/list/software_errors_weighted = list( + SOFTWARE_ERROR_DELETE = 1, + SOFTWARE_ERROR_DEPROGRAM = 2, + SOFTWARE_ERROR_TOGGLE = 2, + SOFTWARE_ERROR_TRIGGER = 2, + SOFTWARE_ERROR_ROGUE = 3, + ) + var/error_type = pick_weight(software_errors_weighted) + switch(error_type) + if(SOFTWARE_ERROR_DELETE) + qdel(src) //kill switch + if(SOFTWARE_ERROR_DEPROGRAM) //deprogram codes + activation_code = 0 + deactivation_code = 0 + kill_code = 0 + trigger_code = 0 + if(SOFTWARE_ERROR_TOGGLE) + toggle() //enable/disable + if(SOFTWARE_ERROR_TRIGGER) + if(can_trigger) + trigger() + else + toggle() //enable/disable + if(SOFTWARE_ERROR_ROGUE) //Program is scrambled and does something different + var/rogue_type = pick(rogue_types) + var/datum/nanite_program/rogue = new rogue_type + nanites.add_program(null, rogue, src) + qdel(src) + +#undef SOFTWARE_ERROR_DELETE +#undef SOFTWARE_ERROR_DEPROGRAM +#undef SOFTWARE_ERROR_TOGGLE +#undef SOFTWARE_ERROR_TRIGGER +#undef SOFTWARE_ERROR_ROGUE + +/datum/nanite_program/proc/receive_signal(code, source) + if(activation_code && code == activation_code && !activated) + activate() + log_game("[host_mob]'s [name] nanite program was activated by [source] with code [code].") + else if(deactivation_code && code == deactivation_code && activated) + deactivate() + log_game("[host_mob]'s [name] nanite program was deactivated by [source] with code [code].") + if(can_trigger && trigger_code && code == trigger_code) + trigger() + log_game("[host_mob]'s [name] nanite program was triggered by [source] with code [code].") + if(kill_code && code == kill_code) + log_game("[host_mob]'s [name] nanite program was deleted by [source] with code [code].") + qdel(src) + diff --git a/massmeta/features/nanites/code/programs/buffing.dm b/massmeta/features/nanites/code/programs/buffing.dm new file mode 100644 index 0000000000000..1e1e6e8a135a8 --- /dev/null +++ b/massmeta/features/nanites/code/programs/buffing.dm @@ -0,0 +1,128 @@ +/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/host_human = host_mob + host_human.physiology.stun_mod *= 0.5 + +/datum/nanite_program/nervous/disable_passive_effect() + . = ..() + if(ishuman(host_mob)) + var/mob/living/carbon/human/host_human = host_mob + host_human.physiology.stun_mod *= 2 + +/datum/nanite_program/adrenaline + name = "Adrenaline Burst" + desc = "The nanites cause a burst of adrenaline when triggered, waking the host from stuns and temporarily increasing their speed." + can_trigger = TRUE + trigger_cost = 25 + trigger_cooldown = 1200 + rogue_types = list(/datum/nanite_program/toxic, /datum/nanite_program/nerve_decay) + +/datum/nanite_program/adrenaline/on_trigger() + to_chat(host_mob, span_notice("You feel a sudden surge of energy!")) + host_mob.SetAllImmobility(0) + host_mob.adjustStaminaLoss(-75) + host_mob.set_resting(FALSE) + host_mob.reagents.add_reagent(/datum/reagent/medicine/stimulants, 1.5) + +/datum/nanite_program/hardening + name = "Dermal Hardening" + desc = "The nanites form a mesh under the host's skin, protecting them from melee and bullet impacts." + use_rate = 1.0 + rogue_types = list(/datum/nanite_program/skin_decay) + +/datum/armor/nanite_hardening + melee = 25 + bullet = 20 + +//TODO on_hit effect that turns skin grey for a moment + +/datum/nanite_program/hardening/enable_passive_effect() + . = ..() + if(ishuman(host_mob)) + var/mob/living/carbon/human/user = host_mob + user.physiology.armor = user.physiology.armor.add_other_armor(/datum/armor/nanite_hardening) + +/datum/nanite_program/hardening/disable_passive_effect() + . = ..() + if(ishuman(host_mob)) + var/mob/living/carbon/human/user = host_mob + user.physiology.armor = user.physiology.armor.subtract_other_armor(/datum/armor/nanite_hardening) + +/datum/nanite_program/refractive + name = "Dermal Refractive Surface" + desc = "The nanites form a membrane above the host's skin, reducing the effect of laser and energy impacts." + use_rate = 1.0 + rogue_types = list(/datum/nanite_program/skin_decay) + +/datum/armor/nanite_refractive + laser = 25 + energy = 20 + +/datum/nanite_program/refractive/enable_passive_effect() + . = ..() + if(ishuman(host_mob)) + var/mob/living/carbon/human/user = host_mob + user.physiology.armor = user.physiology.armor.add_other_armor(/datum/armor/nanite_refractive) + +/datum/nanite_program/refractive/disable_passive_effect() + . = ..() + if(ishuman(host_mob)) + var/mob/living/carbon/human/user = host_mob + user.physiology.armor = user.physiology.armor.subtract_other_armor(/datum/armor/nanite_refractive) + +/datum/nanite_program/coagulating + name = "Rapid Coagulation" + desc = "The nanites induce rapid coagulation when the host is wounded, dramatically reducing bleeding rate." + use_rate = 0.10 + rogue_types = list(/datum/nanite_program/suffocating) + +/datum/nanite_program/coagulating/enable_passive_effect() + . = ..() + if(ishuman(host_mob)) + var/mob/living/carbon/human/host_human = host_mob + host_human.physiology.bleed_mod *= 0.1 + +/datum/nanite_program/coagulating/disable_passive_effect() + . = ..() + if(ishuman(host_mob)) + var/mob/living/carbon/human/host_human = host_mob + host_human.physiology.bleed_mod *= 10 + +/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, TRAIT_NANITES) + +/datum/nanite_program/conductive/disable_passive_effect() + . = ..() + REMOVE_TRAIT(host_mob, TRAIT_SHOCKIMMUNE, TRAIT_NANITES) + +/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, TRAIT_NANITES) + host_mob.sec_hud_set_implants() + +/datum/nanite_program/mindshield/disable_passive_effect() + . = ..() + REMOVE_TRAIT(host_mob, TRAIT_MINDSHIELD, TRAIT_NANITES) + host_mob.sec_hud_set_implants() diff --git a/massmeta/features/nanites/code/programs/healing.dm b/massmeta/features/nanites/code/programs/healing.dm new file mode 100644 index 0000000000000..42cca6c0f953b --- /dev/null +++ b/massmeta/features/nanites/code/programs/healing.dm @@ -0,0 +1,230 @@ +/datum/nanite_program/regenerative + name = "Accelerated Regeneration" + desc = "The nanites boost the host's natural regeneration, increasing their healing speed. \ + Does not consume nanites if the host is unharmed. \ + Works better in low-pressure environments." + use_rate = 0.5 + rogue_types = list(/datum/nanite_program/necrotic) + +/datum/nanite_program/regenerative/check_conditions() + if(!host_mob.getBruteLoss() && !host_mob.getFireLoss()) + return FALSE + if(iscarbon(host_mob)) + var/mob/living/carbon/host_carbon = host_mob + var/list/parts = host_carbon.get_damaged_bodyparts(brute = TRUE, burn = TRUE, required_bodytype = BODYTYPE_ORGANIC) + if(!parts.len) + return FALSE + return ..() + +/datum/nanite_program/regenerative/active_effect() + if(!iscarbon(host_mob)) + host_mob.adjustBruteLoss(-0.5, TRUE) + host_mob.adjustFireLoss(-0.5, TRUE) + return + var/lavaland_bonus = (lavaland_equipment_pressure_check(get_turf(host_mob)) ? 1 : 0.6) // 0.5 on lavaland, 0.3 on station + host_mob.heal_overall_damage(brute = (0.5 * lavaland_bonus), brute = (0.5 * lavaland_bonus), required_bodytype = BODYTYPE_ORGANIC) + +/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. \ + Works better in low-pressure environments." + use_rate = 5.5 + rogue_types = list(/datum/nanite_program/suffocating, /datum/nanite_program/necrotic) + +/datum/nanite_program/regenerative_advanced/active_effect() + if(!iscarbon(host_mob)) + host_mob.adjustBruteLoss(-3, TRUE) + host_mob.adjustFireLoss(-3, TRUE) + return + var/lavaland_bonus = (lavaland_equipment_pressure_check(get_turf(host_mob)) ? 1 : 0.8) // 1.5 on Lavaland, 1.2 on station + host_mob.heal_overall_damage(brute = (1.5 * lavaland_bonus), brute = (1.5 * lavaland_bonus), required_bodytype = BODYTYPE_ROBOTIC) + +/datum/nanite_program/temperature + name = "Temperature Adjustment" + desc = "The nanites adjust the host's internal temperature to an ideal level. Does not consume nanites if the host has a nominal 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() + if(host_mob.bodytemperature > host_mob.get_body_temp_normal(apply_change=FALSE)) + host_mob.adjust_bodytemperature(-40 * TEMPERATURE_DAMAGE_COEFFICIENT, host_mob.get_body_temp_normal(apply_change=FALSE)) + else if(host_mob.bodytemperature < (host_mob.get_body_temp_normal(apply_change=FALSE) + 1)) + host_mob.adjust_bodytemperature(40 * TEMPERATURE_DAMAGE_COEFFICIENT, 0, host_mob.get_body_temp_normal(apply_change=FALSE)) + +/datum/nanite_program/purging + name = "Blood Purification" + desc = "The nanites purge toxins and chemicals from the host's bloodstream." + use_rate = 1 + rogue_types = list(/datum/nanite_program/suffocating, /datum/nanite_program/necrotic) + +/datum/nanite_program/purging/check_conditions() + var/foreign_reagent = length(host_mob.reagents?.reagent_list) + if(!host_mob.getToxLoss() && !foreign_reagent) + return FALSE + return ..() + +/datum/nanite_program/purging/active_effect() + host_mob.adjustToxLoss(-1) + for(var/datum/reagent/reagents as anything in host_mob.reagents.reagent_list) + host_mob.reagents.remove_reagent(reagents.type, amount = 1) + +/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." + use_rate = 1.5 + rogue_types = list(/datum/nanite_program/brain_decay) + +/datum/nanite_program/brain_heal/check_conditions() + var/problems = FALSE + if(iscarbon(host_mob)) + var/mob/living/carbon/carbon_host = host_mob + if(length(carbon_host.get_traumas())) + problems = TRUE + if(host_mob.get_organ_loss(ORGAN_SLOT_BRAIN) > 0) + problems = TRUE + return problems ? ..() : FALSE + +/datum/nanite_program/brain_heal/active_effect() + host_mob.adjustOrganLoss(ORGAN_SLOT_BRAIN, -1) + if(iscarbon(host_mob) && prob(10)) + var/mob/living/carbon/carbon_host = host_mob + carbon_host.cure_trauma_type(resilience = TRAUMA_RESILIENCE_BASIC) + +#define NANITE_BLOOD_RESTORE_DEFAULT 2 + +/datum/nanite_program/blood_restoring + name = "Blood Regeneration" + desc = "The nanites stimulate and boost blood cell production in the host." + use_rate = 1 + rogue_types = list(/datum/nanite_program/suffocating) + ///The amount of blood that we restore every active effect tick. + var/blood_restore_amount = NANITE_BLOOD_RESTORE_DEFAULT + +/datum/nanite_program/blood_restoring/check_conditions() + if(!iscarbon(host_mob)) + return FALSE + var/mob/living/carbon/carbon_host = host_mob + if(carbon_host.blood_volume >= BLOOD_VOLUME_SAFE) + return FALSE + return ..() + +/datum/nanite_program/blood_restoring/active_effect() + if(!iscarbon(host_mob)) + return + var/mob/living/carbon/carbon_host = host_mob + carbon_host.blood_volume += blood_restore_amount + +#undef NANITE_BLOOD_RESTORE_DEFAULT + +/datum/nanite_program/repairing + name = "Mechanical Repair" + desc = "The nanites fix damage in the host's mechanical limbs." + use_rate = 0.5 + rogue_types = list(/datum/nanite_program/necrotic) + +/datum/nanite_program/repairing/check_conditions() + if(!host_mob.getBruteLoss() && !host_mob.getFireLoss()) + return FALSE + + if(!iscarbon(host_mob)) + if(!(host_mob.mob_biotypes & MOB_ROBOTIC)) + return FALSE + return ..() + + var/mob/living/carbon/carbon_host = host_mob + var/list/parts = carbon_host.get_damaged_bodyparts(brute = TRUE, burn = TRUE, required_bodytype = BODYTYPE_ROBOTIC) + if(!parts.len) + return FALSE + return ..() + +/datum/nanite_program/repairing/active_effect(mob/living/M) + if(!iscarbon(host_mob)) + host_mob.adjustBruteLoss(-1.5, TRUE) + host_mob.adjustFireLoss(-1.5, TRUE) + return + host_mob.heal_overall_damage(brute = 1.5, brute = 1.5, required_bodytype = BODYTYPE_ROBOTIC) + +/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." + use_rate = 2 + rogue_types = list(/datum/nanite_program/suffocating, /datum/nanite_program/necrotic) + +/datum/nanite_program/purging_advanced/check_conditions() + var/foreign_reagent = FALSE + for(var/datum/reagent/toxin/toxic_reagents in host_mob.reagents.reagent_list) + foreign_reagent = TRUE + break + if(!host_mob.getToxLoss() && !foreign_reagent) + return FALSE + return ..() + +/datum/nanite_program/purging_advanced/active_effect() + host_mob.adjustToxLoss(-1) + for(var/datum/reagent/toxin/toxic_reagents in host_mob.reagents.reagent_list) + host_mob.reagents.remove_reagent(toxic_reagents.type, 1) + +/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." + use_rate = 3 + rogue_types = list(/datum/nanite_program/brain_decay, /datum/nanite_program/brain_misfire) + +/datum/nanite_program/brain_heal_advanced/check_conditions() + var/problems = FALSE + if(iscarbon(host_mob)) + var/mob/living/carbon/carbon_host = host_mob + if(length(carbon_host.get_traumas())) + problems = TRUE + if(host_mob.get_organ_loss(ORGAN_SLOT_BRAIN) > 0) + problems = TRUE + return problems ? ..() : FALSE + +/datum/nanite_program/brain_heal_advanced/active_effect() + host_mob.adjustOrganLoss(ORGAN_SLOT_BRAIN, -2) + if(iscarbon(host_mob) && prob(10)) + var/mob/living/carbon/carbon_host = host_mob + carbon_host.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!") + addtimer(CALLBACK(src, PROC_REF(start_defibrilation)), 5 SECONDS) + +/datum/nanite_program/defib/proc/check_revivable() + if(!iscarbon(host_mob)) + return FALSE + var/mob/living/carbon/carbon_host = host_mob + return carbon_host.can_defib() + +/datum/nanite_program/defib/proc/start_defibrilation() + playsound(host_mob, 'sound/machines/defib_charge.ogg', 50, FALSE) + addtimer(CALLBACK(src, PROC_REF(perform_defibrilation)), 3 SECONDS) + +/datum/nanite_program/defib/proc/perform_defibrilation() + var/mob/living/carbon/carbon_host = host_mob + playsound(carbon_host, 'sound/machines/defib_zap.ogg', 50, FALSE) + if(!check_revivable()) + playsound(carbon_host, 'sound/machines/defib_failed.ogg', 50, FALSE) + return + playsound(carbon_host, 'sound/machines/defib_success.ogg', 50, FALSE) + carbon_host.set_heartattack(FALSE) + carbon_host.revive() + carbon_host.emote("gasp") + carbon_host.set_timed_status_effect(10 SECONDS, /datum/status_effect/jitter, only_if_higher = TRUE) + SEND_SIGNAL(carbon_host, COMSIG_LIVING_MINOR_SHOCK) + log_game("[carbon_host] has been successfully defibrillated by nanites.") diff --git a/massmeta/features/nanites/code/programs/protocols.dm b/massmeta/features/nanites/code/programs/protocols.dm new file mode 100644 index 0000000000000..0d7c0f2d2b789 --- /dev/null +++ b/massmeta/features/nanites/code/programs/protocols.dm @@ -0,0 +1,320 @@ +/datum/nanite_program/protocol + name = "Nanite Protocol" + + ///If specified, you may only have one of these protocol types active at once. + ///Selection: (NANITE_PROTOCOL_REPLICATION | NANITE_PROTOCOL_STORAGE) + var/protocol_class = NONE + +/datum/nanite_program/protocol/on_add(datum/component/nanites/_nanites) + . = ..() + nanites.protocols += src + +/datum/nanite_program/protocol/Destroy() + if(nanites) + nanites.protocols -= src + return ..() + +/** + * 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." + use_rate = 0 + rogue_types = list(/datum/nanite_program/necrotic) + protocol_class = NANITE_PROTOCOL_REPLICATION + var/boost_duration = 2 MINUTES + +/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, 3.5) + +/datum/nanite_program/protocol/factory + name = "Factory Protocol" + desc = "Replication Protocol: the nanites build a factory matrix within the host, gradually increasing replication speed over time. \ + The factory decays if the protocol is not active, or if the nanites are disrupted by shocks or EMPs." + 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) + return ..() + +/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/tinker + name = "Tinker Protocol" + desc = "Replication Protocol: the nanites learn to use metallic material in the host's bloodstream and stomach to speed up the replication process." + use_rate = 0 + rogue_types = list(/datum/nanite_program/necrotic) + protocol_class = NANITE_PROTOCOL_REPLICATION + var/boost = 2 + var/list/datum/reagent/valid_reagents = list( + /datum/reagent/iron, + /datum/reagent/copper, + /datum/reagent/gold, + /datum/reagent/silver, + /datum/reagent/mercury, + /datum/reagent/aluminium, + /datum/reagent/silicon, + ) + +/datum/nanite_program/protocol/tinker/check_conditions() + if(!nanites.host_mob.reagents) + return FALSE + + var/found_reagent = FALSE + + var/datum/reagents/R = nanites.host_mob.reagents + for(var/VR in valid_reagents) + if(R.has_reagent(VR, 0.5)) + R.remove_reagent(VR, 0.5) + found_reagent = TRUE + break + if(!found_reagent) + return FALSE + return ..() + +/datum/nanite_program/protocol/tinker/active_effect() + nanites.adjust_nanites(null, boost) + +/datum/nanite_program/protocol/offline + name = "Offline Production 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) + +/** + * Storage Protocols + */ +/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 by 250." + use_rate = 0 + rogue_types = list(/datum/nanite_program/necrotic) + protocol_class = NANITE_PROTOCOL_STORAGE + ///How much extra volume the protocol gives the nanite user. + 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 + ///How much extra volume the protocol gives the nanite user. + 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.5 + rogue_types = list(/datum/nanite_program/necrotic) + protocol_class = NANITE_PROTOCOL_STORAGE + ///How much extra volume the protocol gives the nanite user. Since this is negative, we take away. + 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/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 + ///How much extra volume the protocol gives the nanite user. + var/extra_volume = 1500 + ///The timer between warnings. + COOLDOWN_DECLARE(next_warning) + + var/list/volume_warnings_stage_1 = list( + "You feel a dull pain in your abdomen.", + "You feel a tickling sensation in your abdomen.", + ) + var/list/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/list/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/list/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/list/volume_warnings_stage_5 = list( + "You feel sick.", + "You feel a dull pain from every part of your body.", + "You feel nauseous.", + ) + var/list/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(nanites.nanite_volume < 500 || !prob(10)) + return + return host_mob.adjustBruteLoss(((max(nanites.nanite_volume - 450, 0) / 450) ** 2 ) * 0.5) // 0.5 -> 2 -> 4.5 -> 8 damage per successful tick + + if(nanites.nanite_volume < 500) + return + + var/current_stage = 0 + var/list/organs_to_damage = list() + + 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)) + organs_to_damage += ORGAN_SLOT_LIVER + current_stage++ + if(nanites.nanite_volume > 750) //Extra volume spills out in other central organs + if(prob(10)) + organs_to_damage += ORGAN_SLOT_STOMACH + if(prob(10)) + organs_to_damage += ORGAN_SLOT_LUNGS + current_stage++ + if(nanites.nanite_volume > 1000) //Extra volume spills out in more critical organs + if(prob(20)) + organs_to_damage += ORGAN_SLOT_HEART + current_stage++ + if(nanites.nanite_volume > 1250) //Excess nanites start invading smaller organs for more space, including sensory organs + if(prob(30)) + organs_to_damage += ORGAN_SLOT_EYES + current_stage++ + if(nanites.nanite_volume > 1500) //Nanites start spilling into the bloodstream, causing toxicity + if(prob(15)) + host_mob.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)) + host_mob.adjustBruteLoss(0.75, TRUE) + current_stage++ + + if(length(organs_to_damage)) + var/mob/living/carbon/carbon_host = host_mob + for(var/organ_slot in organs_to_damage) + var/obj/item/organ/damaged_organ = carbon_host.get_organ_slot(organ_slot) + if(damaged_organ) + damaged_organ.apply_organ_damage(0.75) + + volume_warning(current_stage) + +#define MINIMUM_WARNING_COOLDOWN (12 SECONDS) +#define MAXIMUM_WARNING_COOLDOWN (35 SECONDS) + +/datum/nanite_program/protocol/unsafe_storage/proc/volume_warning(tier) + if(!COOLDOWN_FINISHED(src, next_warning)) + return + + var/main_warning + var/extra_warning + + switch(tier) + if(1) + main_warning = pick(volume_warnings_stage_1) + extra_warning = null + if(2) + main_warning = pick(volume_warnings_stage_2) + extra_warning = pick(volume_warnings_stage_1) + if(3) + main_warning = pick(volume_warnings_stage_3) + extra_warning = pick(volume_warnings_stage_1 + volume_warnings_stage_2) + if(4) + main_warning = pick(volume_warnings_stage_4) + extra_warning = pick(volume_warnings_stage_1 + volume_warnings_stage_2 + volume_warnings_stage_3) + if(5) + main_warning = pick(volume_warnings_stage_5) + extra_warning = pick(volume_warnings_stage_1 + volume_warnings_stage_2 + volume_warnings_stage_3 + volume_warnings_stage_4) + if(6) + main_warning = pick(volume_warnings_stage_6) + extra_warning = pick(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("[main_warning]")) + COOLDOWN_START(src, next_warning, rand(MINIMUM_WARNING_COOLDOWN, MAXIMUM_WARNING_COOLDOWN)) + return + if(extra_warning) + to_chat(host_mob, span_warning("[extra_warning]")) + COOLDOWN_START(src, next_warning, rand(MINIMUM_WARNING_COOLDOWN, MAXIMUM_WARNING_COOLDOWN)) + +#undef MINIMUM_WARNING_COOLDOWN +#undef MAXIMUM_WARNING_COOLDOWN diff --git a/massmeta/features/nanites/code/programs/rogue.dm b/massmeta/features/nanites/code/programs/rogue.dm new file mode 100644 index 0000000000000..8be1df64f8992 --- /dev/null +++ b/massmeta/features/nanites/code/programs/rogue.dm @@ -0,0 +1,119 @@ +/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(1.5 SECONDS) + 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)) + return + switch(rand(1,4)) + if(1) + host_mob.set_hallucinations(1.5 SECONDS) + if(2) + host_mob.adjust_timed_status_effect(3 SECONDS, /datum/status_effect/confusion) + if(3) + host_mob.adjust_timed_status_effect(3 SECONDS, /datum/status_effect/dizziness) + if(4) + host_mob.adjust_timed_status_effect(3 SECONDS, /datum/status_effect/speech/slurring/drunk) + +//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_timed_status_effect(5 SECONDS, /datum/status_effect/confusion) + return + if(prob(4)) + to_chat(host_mob, span_warning("You can't feel your hands!")) + host_mob.drop_all_held_items() + return + if(prob(4)) + to_chat(host_mob, span_warning("You can't feel your legs!")) + host_mob.Paralyze(30) diff --git a/massmeta/features/nanites/code/programs/sensor.dm b/massmeta/features/nanites/code/programs/sensor.dm new file mode 100644 index 0000000000000..7fc75b58205b9 --- /dev/null +++ b/massmeta/features/nanites/code/programs/sensor.dm @@ -0,0 +1,309 @@ +/datum/nanite_program/sensor + name = NANITES_CATEGORY_SENSOR + 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 + +/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_REF(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 + +/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_REF(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/datum/nanite_program/relay/relays as anything in SSnanites.nanite_relays) + var/datum/nanite_extra_setting/code = extra_settings[NES_SENT_CODE] + relays.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 + +/datum/nanite_program/sensor/death/on_death(gibbed) + 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 = "Sends a signal when the nanites hear a determined word or sentence." + +/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_REF(on_hear)) + +/datum/nanite_program/sensor/voice/on_mob_remove() + UnregisterSignal(host_mob, COMSIG_MOVABLE_HEAR, PROC_REF(on_hear)) + return ..() + +/datum/nanite_program/sensor/voice/proc/on_hear(datum/source, list/hearing_args) + 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(lowertext(hearing_args[HEARING_RAW_MESSAGE]) == lowertext(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 output a signal depending on the conditions set in the settings." + can_trigger = TRUE + trigger_cost = 0 + trigger_cooldown = 5 + + ///List of all species we can add special sensors for. + var/static/list/allowed_species = list( + "Human" = /datum/species/human, + "Lizard" = /datum/species/lizard, + "Moth" = /datum/species/moth, + "Ethereal" = /datum/species/ethereal, + //"Beefeman" = /datum/species/beefman, //No beefeman + "Pod" = /datum/species/pod, + "Fly" = /datum/species/fly, + "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/massmeta/features/nanites/code/programs/suppression.dm b/massmeta/features/nanites/code/programs/suppression.dm new file mode 100644 index 0000000000000..526074b3e6d24 --- /dev/null +++ b/massmeta/features/nanites/code/programs/suppression.dm @@ -0,0 +1,210 @@ +/datum/nanite_program/sleepy + name = "Sleep Induction" + desc = "The nanites cause 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(2 SECONDS) + addtimer(CALLBACK(host_mob, TYPE_PROC_REF(/mob/living, 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, TRAIT_NANITES) + +/datum/nanite_program/pacifying/disable_passive_effect() + . = ..() + REMOVE_TRAIT(host_mob, TRAIT_PACIFISM, TRAIT_NANITES) + +/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(TRAIT_NANITES) + +/datum/nanite_program/blinding/disable_passive_effect() + . = ..() + host_mob.cure_blind(TRAIT_NANITES) + +/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, TRAIT_NANITES) + +/datum/nanite_program/mute/disable_passive_effect() + . = ..() + REMOVE_TRAIT(host_mob, TRAIT_MUTE, TRAIT_NANITES) + +/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") + +/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) + return + if(signal_comm_code == comm_code) + log_game("[host_mob]'s [name] nanite program was messaged by [comm_source] with comm code [signal_comm_code] and message '[comm_message]'.") + trigger(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", + ) + +/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... \"[sent_message]\"") + +/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_sadness", /datum/mood_event/nanite_sadness, get_extra_setting_value(NES_MOOD_MESSAGE)) + +/datum/nanite_program/bad_mood/disable_passive_effect() + . = ..() + host_mob.clear_mood_event("nanite_sadness") diff --git a/massmeta/features/nanites/code/programs/utility.dm b/massmeta/features/nanites/code/programs/utility.dm new file mode 100644 index 0000000000000..564fbc7aa6a98 --- /dev/null +++ b/massmeta/features/nanites/code/programs/utility.dm @@ -0,0 +1,330 @@ +//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) + + ///The cooldown between pulses. + COOLDOWN_DECLARE(pulse_cooldown) + +/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(!COOLDOWN_FINISHED(src, 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/living/people_in_range in orange(host_mob, 5)) + if(SEND_SIGNAL(people_in_range, COMSIG_NANITE_IS_STEALTHY)) + continue + switch(program.get_value()) + if("Overwrite") + SEND_SIGNAL(people_in_range, COMSIG_NANITE_SYNC, nanites, TRUE) + if("Add To") + SEND_SIGNAL(people_in_range, COMSIG_NANITE_SYNC, nanites, FALSE) + SEND_SIGNAL(people_in_range, COMSIG_NANITE_SET_CLOUD, cloud.get_value()) + COOLDOWN_START(src, pulse_cooldown, 7.5 SECONDS) + +/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", "Nanite")) + +/datum/nanite_program/self_scan/on_trigger(comm_message) + if(host_mob.stat == DEAD) + return + + var/datum/nanite_extra_setting/scanned_nanites = extra_settings[NES_SCAN_TYPE] + switch(scanned_nanites.get_value()) + if("Medical") + healthscan(host_mob, host_mob) + if("Chemical") + chemscan(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 + host_mob.hud_set_nanite_indicator(remove = TRUE) + +/datum/nanite_program/stealth/disable_passive_effect() + . = ..() + nanites.stealth = FALSE + host_mob.hud_set_nanite_indicator() + +/datum/nanite_program/reduced_diagnostics + name = "Reduced Diagnostics" + desc = "Disables some high-cost diagnostics in the nanites, making them unable to communicate their program list to portable scanners. \ + Doing so saves some power, slightly increasing their replication speed." + rogue_types = list(/datum/nanite_program/toxic) + use_rate = -0.1 + +/datum/nanite_program/reduced_diagnostics/enable_passive_effect() + . = ..() + nanites.diagnostics = FALSE + +/datum/nanite_program/reduced_diagnostics/disable_passive_effect() + . = ..() + nanites.diagnostics = TRUE + +/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/nanite_setting = extra_settings[NES_RELAY_CHANNEL] + if(relay_code != nanite_setting.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/nanite_setting = extra_settings[NES_RELAY_CHANNEL] + if(relay_code != nanite_setting.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) + ///List of all access that the Subdermal ID is currently holding onto. + var/list/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/new_access = list() + var/obj/item/current_item + current_item = host_mob.get_active_held_item() + if(current_item) + new_access += current_item.GetAccess() + current_item = host_mob.get_inactive_held_item() + if(current_item) + new_access += current_item.GetAccess() + if(ishuman(host_mob)) + var/mob/living/carbon/human/human_host = host_mob + current_item = human_host.wear_id + if(current_item) + new_access += current_item.GetAccess() + else if(isanimal(host_mob)) + var/mob/living/simple_animal/animal_host = host_mob + current_item = animal_host.access_card + if(current_item) + new_access += current_item.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) + COOLDOWN_DECLARE(spread_delay) + +/datum/nanite_program/spreading/active_effect() + if(!COOLDOWN_FINISHED(src, spread_delay)) + return + COOLDOWN_START(src, spread_delay, 8 SECONDS) + + var/list/mob/living/carbon/human/target_hosts = list() + for(var/mob/living/carbon/human/nearby_humans in oview(5, host_mob)) + if(!prob(25)) + continue + if(!(nearby_humans.mob_biotypes & (MOB_ORGANIC|MOB_UNDEAD))) + continue + target_hosts += nearby_humans + if(!target_hosts.len) + return + var/mob/living/carbon/human/infectee = pick(target_hosts) + if(!(infectee.wear_suit) || prob(100 - infectee.wear_suit.get_armor_rating(BIO))) + //this will potentially take over existing nanites! + infectee.AddComponent(/datum/component/nanites, null, 10) + SEND_SIGNAL(infectee, COMSIG_NANITE_SYNC, nanites) + log_game("[infectee] was infected by spreading nanites by [key_name(host_mob)] at [AREACOORD(infectee)].") + +/datum/nanite_program/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." + can_trigger = TRUE + trigger_cost = 5 + trigger_cooldown = 100 + rogue_types = list(/datum/nanite_program/glitch, /datum/nanite_program/toxic) + +/datum/nanite_program/nanite_sting/on_trigger(comm_message) + var/list/mob/living/carbon/human/target_hosts = list() + for(var/mob/living/carbon/human/nearby_humans in oview(1, host_mob)) + var/datum/component/nanites/nanites = nearby_humans.GetComponent(/datum/component/nanites) + if(!(nearby_humans.mob_biotypes & (MOB_ORGANIC|MOB_UNDEAD)) || nanites || !nearby_humans.Adjacent(host_mob)) + continue + target_hosts += nearby_humans + if(!target_hosts.len) + consume_nanites(-5) + return + var/mob/living/carbon/human/infectee = pick(target_hosts) + if(!(infectee.wear_suit) || prob(100 - infectee.wear_suit.get_armor_rating(BIO))) + //unlike with Infective Exo-Locomotion, this can't take over existing nanites, because Nanite Sting only targets non-hosts. + infectee.AddComponent(/datum/component/nanites, null, 5) + SEND_SIGNAL(infectee, COMSIG_NANITE_SYNC, nanites) + log_game("[infectee] was infected by a nanite cluster by [key_name(host_mob)] at [AREACOORD(infectee)].") + to_chat(infectee, span_warning("You feel a tiny prick.")) + +/datum/nanite_program/mitosis + name = "Mitosis" + desc = "The nanites gain the ability to self-replicate, using bluespace to power the process. Becomes more effective the more nanites are already in the host.\ + The replication has also a chance to corrupt the nanite programming due to copy faults - cloud sync is highly recommended." + 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() + +/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(button) + return ..() + +/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 = 'massmeta/features/nanites/icons/nanite_actions.dmi' + button_icon_state = "power_green" + check_flags = AB_CHECK_HANDS_BLOCKED|AB_CHECK_IMMOBILE|AB_CHECK_CONSCIOUS + var/datum/nanite_program/dermal_button/program + +/datum/action/innate/nanite_button/New(datum/nanite_program/dermal_button/program, program_name, program_icon) + ..() + src.program = program + src.name = program_name + button_icon_state = "nanite_[program_icon]" + +/datum/action/innate/nanite_button/Activate() + program.press() diff --git a/massmeta/features/nanites/code/programs/weapon.dm b/massmeta/features/nanites/code/programs/weapon.dm new file mode 100644 index 0000000000000..718a3cd185382 --- /dev/null +++ b/massmeta/features/nanites/code/programs/weapon.dm @@ -0,0 +1,188 @@ +//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/carbon_host = host_mob + carbon_host.take_bodypart_damage(1, 0, 0) + 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(vomit_type = /obj/effect/decal/cleanable/vomit/nanites) + +/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() + +/datum/nanite_program/aggressive_replication + name = "Aggressive Replication" + desc = "Nanites will consume organic matter to improve their replication rate, damaging the host. The efficiency increases with the volume of nanites, requiring 200 to break even." + use_rate = 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_REF(boom)), clamp((nanites.nanite_volume * 0.35), 25, 150)) + +/datum/nanite_program/explosive/proc/boom() + var/nanite_amount = nanites.nanite_volume + var/dev_range = FLOOR(nanite_amount/200, 1) - 1 + var/heavy_range = FLOOR(nanite_amount/100, 1) - 1 + var/light_range = FLOOR(nanite_amount/50, 1) - 1 + explosion(host_mob, dev_range, heavy_range, light_range) + qdel(nanites) + +/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)) + return + var/mob/living/carbon/C = host_mob + var/obj/item/organ/internal/heart/heart = C.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.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]'.") + addtimer(CALLBACK(src, PROC_REF(end_brainwashing)), 60 SECONDS) + +/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/massmeta/features/nanites/code/research.dm b/massmeta/features/nanites/code/research.dm new file mode 100644 index 0000000000000..ad398134a0d49 --- /dev/null +++ b/massmeta/features/nanites/code/research.dm @@ -0,0 +1,232 @@ +/datum/techweb_node/nanite_base + id = "nanite_base" + display_name = "Basic Nanite Programming" + description = "The basics of nanite construction and programming." + prereq_ids = list("datatheory") + design_ids = list( + "nanite_disk", + "nanite_remote", + "nanite_comm_remote", + "nanite_scanner", + "nanite_chamber", + "nanite_chamber_control", + "nanite_programmer", + "nanite_program_hub", + "nanite_cloud_control", + "relay_nanites", + "access_nanites", + "repairing_nanites", + "sensor_nanite_volume", + "repeater_nanites", + "relay_repeater_nanites", + "red_diag_nanites", + ) + research_costs = list( + TECHWEB_POINT_TYPE_GENERIC = 1000, + ) + +/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", + "robotics", + ) + design_ids = list( + "purging_nanites", + "metabolic_nanites", + "stealth_nanites", + "memleak_nanites", + "sensor_voice_nanites", + "voice_nanites", + ) + research_costs = list( + TECHWEB_POINT_TYPE_GENERIC = 750, + TECHWEB_POINT_TYPE_NANITES = 500, + ) + +/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", + "engineering", + ) + design_ids = list( + "hardening_nanites", + "dermal_button_nanites", + "refractive_nanites", + "cryo_nanites", + "conductive_nanites", + "shock_nanites", + "emp_nanites", + "temperature_nanites", + ) + research_costs = list( + TECHWEB_POINT_TYPE_GENERIC = 750, + TECHWEB_POINT_TYPE_NANITES = 500, + ) + +/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", + "biotech", + ) + design_ids = list( + "regenerative_nanites", + "bloodheal_nanites", + "coagulating_nanites", + "poison_nanites", + "flesheating_nanites", + "sensor_crit_nanites", + "sensor_death_nanites", + "sensor_health_nanites", + "sensor_damage_nanites", + "sensor_species_nanites", + ) + research_costs = list( + TECHWEB_POINT_TYPE_GENERIC = 750, + TECHWEB_POINT_TYPE_NANITES = 500, + ) + +/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( + "nervous_nanites", + "brainheal_nanites", + "paralyzing_nanites", + "stun_nanites", + "selfscan_nanites", + "good_mood_nanites", + "bad_mood_nanites", + ) + research_costs = list( + TECHWEB_POINT_TYPE_GENERIC = 1500, + TECHWEB_POINT_TYPE_NANITES = 1000, + ) + +/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", + "neural_programming", + ) + design_ids = list( + "mindshield_nanites", + "pacifying_nanites", + "blinding_nanites", + "sleep_nanites", + "mute_nanites", + "speech_nanites", + ) + research_costs = list( + TECHWEB_POINT_TYPE_GENERIC = 1500, + TECHWEB_POINT_TYPE_NANITES = 1000, + ) + +/datum/techweb_node/nanite_harmonic + id = "nanite_harmonic" + display_name = "Harmonic Nanite Programming" + description = "Nanite programs that require seamless integration between nanites and biology." + prereq_ids = list( + "nanite_bio", + "nanite_smart", + "nanite_mesh", + ) + design_ids = list( + "fakedeath_nanites", + "aggressive_nanites", + "defib_nanites", + "regenerative_plus_nanites", + "brainheal_plus_nanites", + "purging_plus_nanites", + "adrenaline_nanites", + ) + research_costs = list( + TECHWEB_POINT_TYPE_GENERIC = 2750, + TECHWEB_POINT_TYPE_NANITES = 3000, + ) + +/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", + "syndicate_basic", + ) + design_ids = list( + "explosive_nanites", + "pyro_nanites", + "meltdown_nanites", + "viral_nanites", + "nanite_sting_nanites", + ) + research_costs = list( + TECHWEB_POINT_TYPE_GENERIC = 3500, + TECHWEB_POINT_TYPE_NANITES = 2500, + ) + +/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", + "alientech", + ) + design_ids = list( + "spreading_nanites", + "mindcontrol_nanites", + "mitosis_nanites", + ) + research_costs = list( + TECHWEB_POINT_TYPE_GENERIC = 3500, + TECHWEB_POINT_TYPE_NANITES = 4000, + ) + +/datum/techweb_node/nanite_replication_protocols + id = "nanite_replication_protocols" + display_name = "Nanite Replication Protocols" + description = "Advanced behaviours that allow nanites to exploit certain circumstances to replicate faster." + prereq_ids = list("nanite_smart") + design_ids = list( + "kickstart_nanites", + "factory_nanites", + "tinker_nanites", + "offline_nanites", + ) + research_costs = list( + TECHWEB_POINT_TYPE_GENERIC = 2000, + TECHWEB_POINT_TYPE_NANITES = 2500, + ) + 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 = 1000, + TECHWEB_POINT_TYPE_NANITES = 2500, + ) + hidden = TRUE + experimental = TRUE diff --git a/massmeta/features/nanites/code/settings/extra_settings.dm b/massmeta/features/nanites/code/settings/extra_settings.dm new file mode 100644 index 0000000000000..5a35078f70e68 --- /dev/null +++ b/massmeta/features/nanites/code/settings/extra_settings.dm @@ -0,0 +1,87 @@ +/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 + +/** + * Boolean + */ +/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, + )) + +/** + * Number + */ +/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/massmeta/features/nanites/code/settings/rules.dm b/massmeta/features/nanites/code/settings/rules.dm new file mode 100644 index 0000000000000..9a6d3ffc5d2f9 --- /dev/null +++ b/massmeta/features/nanites/code/settings/rules.dm @@ -0,0 +1,130 @@ +/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/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/massmeta/features/nanites/code/settings/text.dm b/massmeta/features/nanites/code/settings/text.dm new file mode 100644 index 0000000000000..f2158f34b9d6c --- /dev/null +++ b/massmeta/features/nanites/code/settings/text.dm @@ -0,0 +1,18 @@ +/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_frontend_list(name) + return list(list( + "name" = name, + "type" = setting_type, + "value" = value, + )) diff --git a/massmeta/features/nanites/code/settings/type.dm b/massmeta/features/nanites/code/settings/type.dm new file mode 100644 index 0000000000000..2085ba510c42a --- /dev/null +++ b/massmeta/features/nanites/code/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/massmeta/features/nanites/code/spawners.dm b/massmeta/features/nanites/code/spawners.dm new file mode 100644 index 0000000000000..59b166feaa01c --- /dev/null +++ b/massmeta/features/nanites/code/spawners.dm @@ -0,0 +1,9 @@ +/obj/effect/spawner/random/techstorage/nanite + name = "RnD nanite board spawner" + 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, + ) diff --git a/massmeta/features/nanites/icons/areas.dmi b/massmeta/features/nanites/icons/areas.dmi new file mode 100644 index 0000000000000..b8f55aa45d606 Binary files /dev/null and b/massmeta/features/nanites/icons/areas.dmi differ diff --git a/massmeta/features/nanites/icons/computer.dmi b/massmeta/features/nanites/icons/computer.dmi new file mode 100644 index 0000000000000..ebbf7d90418f2 Binary files /dev/null and b/massmeta/features/nanites/icons/computer.dmi differ diff --git a/massmeta/features/nanites/icons/decals.dmi b/massmeta/features/nanites/icons/decals.dmi new file mode 100644 index 0000000000000..a4384c24bd0ec Binary files /dev/null and b/massmeta/features/nanites/icons/decals.dmi differ diff --git a/massmeta/features/nanites/icons/disk.dmi b/massmeta/features/nanites/icons/disk.dmi new file mode 100644 index 0000000000000..4a296f0f5d561 Binary files /dev/null and b/massmeta/features/nanites/icons/disk.dmi differ diff --git a/massmeta/features/nanites/icons/nanite_actions.dmi b/massmeta/features/nanites/icons/nanite_actions.dmi new file mode 100644 index 0000000000000..ce2f24a03e2ad Binary files /dev/null and b/massmeta/features/nanites/icons/nanite_actions.dmi differ diff --git a/massmeta/features/nanites/icons/nanite_chamber.dmi b/massmeta/features/nanites/icons/nanite_chamber.dmi new file mode 100644 index 0000000000000..5eec17e72978e Binary files /dev/null and b/massmeta/features/nanites/icons/nanite_chamber.dmi differ diff --git a/massmeta/features/nanites/icons/nanite_device.dmi b/massmeta/features/nanites/icons/nanite_device.dmi new file mode 100644 index 0000000000000..e9cec8222bf4a Binary files /dev/null and b/massmeta/features/nanites/icons/nanite_device.dmi differ diff --git a/massmeta/features/nanites/icons/nanite_hud.dmi b/massmeta/features/nanites/icons/nanite_hud.dmi new file mode 100644 index 0000000000000..84ff0b39d9d06 Binary files /dev/null and b/massmeta/features/nanites/icons/nanite_hud.dmi differ diff --git a/massmeta/features/nanites/icons/nanite_machines.dmi b/massmeta/features/nanites/icons/nanite_machines.dmi new file mode 100644 index 0000000000000..8794084841dd9 Binary files /dev/null and b/massmeta/features/nanites/icons/nanite_machines.dmi differ diff --git a/massmeta/features/nanites/icons/nanites_decal.dmi b/massmeta/features/nanites/icons/nanites_decal.dmi new file mode 100644 index 0000000000000..a4384c24bd0ec Binary files /dev/null and b/massmeta/features/nanites/icons/nanites_decal.dmi differ diff --git a/massmeta/features/nanites/includes.dm b/massmeta/features/nanites/includes.dm new file mode 100644 index 0000000000000..4b3901d8f575b --- /dev/null +++ b/massmeta/features/nanites/includes.dm @@ -0,0 +1,45 @@ +#include "code\areas.dm" +#include "code\circuitboards.dm" +#include "code\items.dm" +#include "code\machines.dm" +#include "code\mood_events.dm" +#include "code\nanite_backup.dm" +#include "code\nanite_component.dm" +#include "code\nanite_disease.dm" +#include "code\nanite_hud.dm" +#include "code\nanite_remote.dm" +#include "code\nanite_scanner.dm" +#include "code\nanite_subsystem.dm" +#include "code\nanites_decal.dm" +#include "code\research.dm" +#include "code\spawners.dm" +#include "code\designs\_nanite.dm" +#include "code\designs\_tools.dm" +#include "code\designs\nanite_augmentation.dm" +#include "code\designs\nanite_defective.dm" +#include "code\designs\nanite_medical.dm" +#include "code\designs\nanite_protocols.dm" +#include "code\designs\nanite_sensor.dm" +#include "code\designs\nanite_suppression.dm" +#include "code\designs\nanite_utilities.dm" +#include "code\designs\nanite_weaponized.dm" +#include "code\machines\nanite_chamber.dm" +#include "code\machines\nanite_chamber_control.dm" +#include "code\machines\nanite_cloud_control.dm" +#include "code\machines\nanite_program_hub.dm" +#include "code\machines\nanite_programmer.dm" +#include "code\programs\_programs.dm" +#include "code\programs\buffing.dm" +#include "code\programs\healing.dm" +#include "code\programs\protocols.dm" +#include "code\programs\rogue.dm" +#include "code\programs\sensor.dm" +#include "code\programs\suppression.dm" +#include "code\programs\utility.dm" +#include "code\programs\weapon.dm" +#include "code\settings\extra_settings.dm" +#include "code\settings\rules.dm" +#include "code\settings\text.dm" +#include "code\settings\type.dm" +#include "code\Z_edits\hud_edits.dm" +#include "code\Z_edits\research_edits.dm" diff --git a/massmeta/features/nanites/readme.md b/massmeta/features/nanites/readme.md new file mode 100644 index 0000000000000..583a3abc7e676 --- /dev/null +++ b/massmeta/features/nanites/readme.md @@ -0,0 +1,35 @@ +## Module ID: NANITES + +### Description: + +Добавляет ниниты. + + +### TG Proc/File Changes: + +- N/A + + +### Modular Overrides: + +- N/A + + +### Defines: + +~meta_defines/nanites.dm +~meta_defines/_signals/nanites.dm + + +### TGUI Files: + +- tgui\packages\tgui\interfaces\NaniteChamberControl.tsx +- tgui\packages\tgui\interfaces\NaniteCloudControl.tsx +- tgui\packages\tgui\interfaces\NaniteProgramHub.tsx +- tgui\packages\tgui\interfaces\NaniteProgrammer.tsx +- tgui\packages\tgui\interfaces\NaniteRemote.tsx + + +### Credits: + +- https://github.com/fulpstation/fulpstation/pull/1100 - original PR diff --git a/massmeta/features/nanites/sound/nanite_chamber.wav b/massmeta/features/nanites/sound/nanite_chamber.wav new file mode 100644 index 0000000000000..c75e6fbb77adb Binary files /dev/null and b/massmeta/features/nanites/sound/nanite_chamber.wav differ diff --git a/massmeta/features/nanites/sound/nanite_install.wav b/massmeta/features/nanites/sound/nanite_install.wav new file mode 100644 index 0000000000000..c6e8ed77888a9 Binary files /dev/null and b/massmeta/features/nanites/sound/nanite_install.wav differ diff --git a/massmeta/features/nanites/sound/nanite_install_short.mp3 b/massmeta/features/nanites/sound/nanite_install_short.mp3 new file mode 100644 index 0000000000000..c653a03398213 Binary files /dev/null and b/massmeta/features/nanites/sound/nanite_install_short.mp3 differ diff --git a/massmeta/features/nanites/sound/nanite_scan.mp3 b/massmeta/features/nanites/sound/nanite_scan.mp3 new file mode 100644 index 0000000000000..61efa46dc6ac6 Binary files /dev/null and b/massmeta/features/nanites/sound/nanite_scan.mp3 differ diff --git a/massmeta/modular_meta.dm b/massmeta/modular_meta.dm index 443a6a9aa55cf..9bc19d6a34897 100644 --- a/massmeta/modular_meta.dm +++ b/massmeta/modular_meta.dm @@ -15,6 +15,7 @@ #include "features\additional_circuit\includes.dm" #include "features\hardsuits\includes.dm" #include "features\kvass\includes.dm" +#include "features\nanites\includes.dm" #include "features\smites\includes.dm" #include "features\soviet_crate\includes.dm" // END_INCLUDE diff --git a/massmeta/modular_meta_defines.dm b/massmeta/modular_meta_defines.dm index 899edc4d54590..af0c13508441c 100644 --- a/massmeta/modular_meta_defines.dm +++ b/massmeta/modular_meta_defines.dm @@ -7,4 +7,6 @@ // BEGIN_INCLUDE #include "~meta_defines\butt_furrt.dm" #include "~meta_defines\inventory.dm" +#include "~meta_defines\nanites.dm" +#include "~meta_defines\_signals\nanites.dm" // END_INCLUDE diff --git a/massmeta/~meta_defines/_signals/nanites.dm b/massmeta/~meta_defines/_signals/nanites.dm new file mode 100644 index 0000000000000..4949766ab4f80 --- /dev/null +++ b/massmeta/~meta_defines/_signals/nanites.dm @@ -0,0 +1,36 @@ +///() returns TRUE if nanites have stealth +#define COMSIG_NANITE_IS_STEALTHY "nanite_is_stealthy" +///() deletes the nanite component +#define COMSIG_NANITE_DELETE "nanite_delete" +///() 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" +///(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 + ///Installation failed, but there are still nanites + #define COMPONENT_PROGRAM_NOT_INSTALLED 2 +///(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/massmeta/~meta_defines/nanites.dm b/massmeta/~meta_defines/nanites.dm new file mode 100644 index 0000000000000..fe748750c5694 --- /dev/null +++ b/massmeta/~meta_defines/nanites.dm @@ -0,0 +1,50 @@ +///The research point type that Nanites generates. +#define TECHWEB_POINT_TYPE_NANITES "Nanite Research" + +///Trait given by Nanites +#define TRAIT_NANITES "Nanites" + +#define NANITE_SHOCK_IMMUNE (1<<0) +#define NANITE_EMP_IMMUNE (1<<1) + +///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_COMM_CODE "Comm Code" +#define NES_RELAY_CHANNEL "Relay Channel" +#define NES_HEALTH_PERCENT "Health Percent" +#define NES_NANITE_PERCENT "Nanite Percent" +#define NES_DIRECTION "Direction" +#define NES_DAMAGE_TYPE "Damage Type" +#define NES_DAMAGE "Damage" +#define NES_MESSAGE "Message" +#define NES_DIRECTIVE "Directive" +#define NES_SENTENCE "Sentence" +#define NES_INCLUSIVE_MODE "Inclusive Mode" +#define NES_RACE "Race" +#define NES_MODE "Mode" +#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_CATEGORY_UTILITIES "Utility Nanites" +#define NANITE_CATEGORY_MEDICAL "Medical Nanites" +#define NANITES_CATEGORY_SENSOR "Sensor Nanites" +#define NANITES_CATEGORY_AUGMENTATION "Augmentation Nanites" +#define NANITES_CATEGORY_SUPPRESSION "Suppression Nanites" +#define NANITES_CATEGORY_WEAPONIZED "Weaponized Nanites" +#define NANITES_CATEGORY_PROTOCOLS "Protocols Nanites" +#define NANITES_CATEGORY_DEFECTIVE "Defective Nanites" diff --git a/tgui/packages/tgui/interfaces/NaniteChamberControl.tsx b/tgui/packages/tgui/interfaces/NaniteChamberControl.tsx new file mode 100644 index 0000000000000..7ccb45d557f65 --- /dev/null +++ b/tgui/packages/tgui/interfaces/NaniteChamberControl.tsx @@ -0,0 +1,314 @@ +import { BooleanLike } from 'common/react'; + +import { useBackend } from '../backend'; +import { + Box, + Button, + Collapsible, + LabeledList, + NoticeBox, + NumberInput, + Section, + Table, +} from '../components'; +import { Window } from '../layouts'; + +type Data = { + status_msg: string; + locked: BooleanLike; + occupant_name: string; + has_nanites: BooleanLike; + nanite_volume: number; + regen_rate: number; + safety_threshold: number; + cloud_id: number; + scan_level: number; + mob_programs: MobData[]; +}; + +type MobData = { + extra_settings: ExtraSettingsData[]; + rules: RulesData[]; + name: string; + desc: string; + activated: BooleanLike; + use_rate: number; + can_trigger: BooleanLike; + trigger_cost: number; + trigger_cooldown: number; + timer_trigger_delay: number; + timer_trigger: number; + timer_restart: number; + timer_shutdown: number; + has_extra_settings: BooleanLike; + activation_code: number; + deactivation_code: number; + kill_code: number; + trigger_code: number; + has_rules: BooleanLike; +}; + +type ExtraSettingsData = { + name: string; + value: string; +}; + +type RulesData = { + display: string; +}; + +export const NaniteChamberControl = (props) => { + return ( + + + + + + ); +}; + +const NaniteChamberControlContent = (props) => { + 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')} + /> + } + > + {!has_nanites ? ( + <> + + No Nanites Detected + +
+ ); +}; diff --git a/tgui/packages/tgui/interfaces/NaniteCloudControl.tsx b/tgui/packages/tgui/interfaces/NaniteCloudControl.tsx new file mode 100644 index 0000000000000..c0392c6343a18 --- /dev/null +++ b/tgui/packages/tgui/interfaces/NaniteCloudControl.tsx @@ -0,0 +1,375 @@ +import { BooleanLike } from 'common/react'; + +import { useBackend } from '../backend'; +import { + Box, + Button, + Collapsible, + LabeledList, + NoticeBox, + NumberInput, + Section, + Table, +} from '../components'; +import { Window } from '../layouts'; + +type Data = { + has_disk: BooleanLike; + has_program: BooleanLike; + disk_data: ProgramData[]; + new_backup_id: number; + current_view: number; + cloud_backup: BooleanLike; + can_rule: BooleanLike; + cloud_programs: ProgramData[]; + cloud_backups: CloudBackupData[]; +}; + +type ProgramData = { + name: string; + desc: string; + id: number; + use_rate: number; + can_trigger: BooleanLike; + trigger_cost: number; + trigger_cooldown: number; + activated: BooleanLike; + activation_code: number; + deactivation_code: number; + kill_code: number; + trigger_code: number; + timer_restart: number; + timer_shutdown: number; + timer_trigger: number; + timer_trigger_delay: number; + has_rules: BooleanLike; + all_rules_required: BooleanLike; + rules: RuleData[]; + extra_settings: ExtraSettingsData[]; + has_extra_settings: BooleanLike; +}; + +type ExtraSettingsData = { + name: string; + type: string; + value: string; + unit: string; + true_text: string; + false_text: string; +}; + +type RuleData = { + display: string; + program_id: number; + id: number; +}; + +type CloudBackupData = { + cloud_id: number; +}; + +const NaniteDiskBox = (props) => { + const { data } = useBackend(); + const { has_disk, has_program, disk_data } = data; + if (!has_disk) { + return No disk inserted; + } + if (!has_program) { + return Inserted disk has no program; + } + return ; +}; + +const NaniteInfoBox = (props) => { + const { act } = useBackend(); + 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 = [], + rules = [], + } = 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]} + + ); + })} + +
+
+ ); +}; + +const NaniteCloudBackupDetails = (props) => { + const { act, data } = useBackend(); + const { current_view, has_program, can_rule, cloud_backup, cloud_programs } = + data; + if (!cloud_backup) { + return ERROR: Backup not found; + } + return ( +
act('upload_program')} + /> + ) + } + > + {cloud_programs.map((program) => { + return ( + + act('remove_program', { + program_id: program.id, + }) + } + /> + } + > +
+ +
+ {!!can_rule && ( +
+
+
+ ); + })} +
+ ); +}; + +export const NaniteCloudControl = (props) => { + const { act, data } = useBackend(); + const { has_disk, current_view, new_backup_id, cloud_backups } = data; + return ( + + +
act('eject')} + /> + } + > + +
+
+ act('set_view', { + view: 0, + }) + } + /> + ) : ( + <> + {'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 0000000000000..fe0cb7a4fb084 --- /dev/null +++ b/tgui/packages/tgui/interfaces/NaniteProgramHub.tsx @@ -0,0 +1,174 @@ +import { BooleanLike } from 'common/react'; + +import { useBackend, useSharedState } from '../backend'; +import { + Button, + Flex, + LabeledList, + NoticeBox, + Section, + Tabs, +} from '../components'; +import { Window } from '../layouts'; + +type Data = { + detail_view: string; + disk: DiskData; + has_disk: BooleanLike; + has_program: BooleanLike; + programs: ProgramData[]; + categories: string[]; +}; + +type DiskData = { + name: string; + desc: string; +}; + +type ProgramData = { + name: string; + desc: string; + id: string; +}; + +export const NaniteProgramHub = (props, context) => { + const { act, data } = useBackend(); + const { + detail_view, + disk, + has_disk, + has_program, + programs = [], + categories, + } = data; + const [selectedCategory, setSelectedCategory] = useSharedState( + context, + 'category', + ); + const programsInCategory = (programs && programs[selectedCategory]) || []; + + return ( + + +
+
+
+
+
+
+ ); +}; diff --git a/tgui/packages/tgui/interfaces/NaniteProgrammer.tsx b/tgui/packages/tgui/interfaces/NaniteProgrammer.tsx new file mode 100644 index 0000000000000..cbd69a9666b9d --- /dev/null +++ b/tgui/packages/tgui/interfaces/NaniteProgrammer.tsx @@ -0,0 +1,394 @@ +import { BooleanLike } from 'common/react'; + +import { useBackend } from '../backend'; +import { + Button, + Dropdown, + Input, + LabeledList, + NoticeBox, + NumberInput, + Section, + Table, +} from '../components'; +import { Window } from '../layouts'; + +type Data = { + has_disk: BooleanLike; + has_program: BooleanLike; + name: string; + desc: string; + use_rate: string; + can_trigger: BooleanLike; + trigger_cost: number; + trigger_cooldown: number; + activated: BooleanLike; + activation_code: number; + deactivation_code: number; + kill_code: number; + trigger_code: number; + timer_restart: number; + timer_shutdown: number; + timer_trigger: number; + timer_trigger_delay: number; + has_extra_settings: BooleanLike; + extra_settings: ExtraSettingsData[]; +}; + +type ExtraSettingsData = { + name: string; + setting: string; +}; + +const NaniteCodes = (props) => { + const { act, data } = useBackend(); + const { + activation_code, + deactivation_code, + kill_code, + can_trigger, + 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, + }) + } + /> + + {!!can_trigger && ( + + + act('set_code', { + target_code: 'trigger', + code: value, + }) + } + /> + + )} + +
+ ); +}; + +const NaniteDelays = (props) => { + const { act, data } = useBackend(); + const { + timer_restart, + timer_shutdown, + can_trigger, + timer_trigger, + timer_trigger_delay, + } = data; + return ( +
+ + + + act('set_restart_timer', { + delay: value, + }) + } + /> + + + + act('set_shutdown_timer', { + delay: value, + }) + } + /> + + {!!can_trigger && ( + <> + + + act('set_trigger_timer', { + delay: value, + }) + } + /> + + + + act('set_timer_trigger_delay', { + delay: value, + }) + } + /> + + + )} + +
+ ); +}; + +const NaniteExtraEntry = (props) => { + const { extra_setting } = props; + const { name, type } = extra_setting; + const typeComponentMap = { + number: , + text: , + type: , + boolean: , + }; + return ( + {typeComponentMap[type]} + ); +}; + +const NaniteExtraNumber = (props) => { + 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, + }) + } + /> + ); +}; + +const NaniteExtraText = (props) => { + const { extra_setting } = props; + const { act } = useBackend(); + const { name, value } = extra_setting; + return ( + + act('set_extra_setting', { + target_setting: name, + value: val, + }) + } + /> + ); +}; + +const NaniteExtraType = (props) => { + const { extra_setting } = props; + const { act } = useBackend(); + const { name, value, types } = extra_setting; + return ( + + act('set_extra_setting', { + target_setting: name, + value: val, + }) + } + /> + ); +}; + +const NaniteExtraBoolean = (props) => { + const { extra_setting } = props; + const { act } = useBackend(); + const { name, value, true_text, false_text } = extra_setting; + return ( + + act('set_extra_setting', { + target_setting: name, + }) + } + /> + ); +}; + +export const NaniteProgrammer = (props) => { + return ( + + + + + + ); +}; + +const NaniteProgrammerContent = (props) => { + 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')} /> + } + /> + ); + } + return ( +
act('eject')} /> + } + > +
+ + {desc} + + + {use_rate} + {!!can_trigger && ( + <> + + {trigger_cost} + + + {trigger_cooldown} + + + )} + + +
+
+
act('toggle_active')} + /> + } + > + + + + + + + +
+ {!!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 0000000000000..03b9f44eb8d83 --- /dev/null +++ b/tgui/packages/tgui/interfaces/NaniteRemote.tsx @@ -0,0 +1,198 @@ +import { BooleanLike } from 'common/react'; + +import { useBackend } from '../backend'; +import { + Button, + Input, + LabeledList, + NoticeBox, + NumberInput, + Section, + Table, +} from '../components'; +import { Window } from '../layouts'; + +export const NaniteRemote = (props) => { + return ( + + + + + + ); +}; + +type Data = { + code: number; + locked: BooleanLike; + mode: string; + program_name: string; + relay_code: number; + comms: string; + message: string; + saved_settings: SettingsData[]; +}; + +type SettingsData = { + id: number; + name: string; + mode: string; + relay_code: number; + code: number; +}; + +export const NaniteRemoteContent = (props) => { + 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')} + /> + } + > + + + + act('update_name', { + name: value, + }) + } + /> +
+
+ {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 + )} +
+ + ); +};