From 6390e62a6913d6ab1e39c277f24c0e20e5dcf554 Mon Sep 17 00:00:00 2001 From: MrMelbert Date: Wed, 10 Dec 2025 14:41:13 -0600 Subject: [PATCH 01/26] Android rework --- .../signals/signals_mob/signals_mob_main.dm | 3 + code/__DEFINES/mobs.dm | 1 + code/datums/ai_laws/laws_station_sided.dm | 9 + code/datums/wounds/pierce.dm | 2 +- .../antagonists/_common/antag_datum.dm | 1 + .../carbon/human/species_types/android.dm | 2 +- code/modules/mod/modules/modules_general.dm | 4 +- .../reagents/drinks/alcohol_reagents.dm | 6 +- .../reagents/drinks/drink_reagents.dm | 2 +- .../chemistry/reagents/food_reagents.dm | 2 +- code/modules/surgery/organs/_organ.dm | 3 + .../internal/stomach/stomach_ethereal.dm | 4 +- config/game_options.txt | 2 +- maplestation.dme | 7 + .../client/preferences/sound_frequency.dm | 4 +- .../client/preferences/species/synth.dm | 129 +++++++++- .../loadouts/loadout_items/_limb_datums.dm | 44 +++- .../human/species_types/animid/animid.dm | 4 +- .../carbon/human/species_types/silverscale.dm | 2 +- .../human/species_types/synth/android.dm | 231 ++++++++++++++++++ .../species_types/synth/android_brain.dm | 154 ++++++++++++ .../species_types/synth/android_heart.dm | 67 +++++ .../species_types/synth/android_liver.dm | 41 ++++ .../species_types/synth/android_lungs.dm | 216 ++++++++++++++++ .../species_types/synth/android_stomach.dm | 74 ++++++ .../species_types/synth/android_tongue.dm | 5 + .../carbon/human/species_types/synth/synth.dm | 106 +++----- .../human/species_types/synth/synth_ion.dm | 6 +- .../chemistry/reagents/mutation_reagents.dm | 11 - .../modules/surgery/bodyparts/cyber_digi.dm | 42 +++- .../surgery/bodyparts/cyber_reskins.dm | 1 + .../features/_modular_species_features.tsx | 109 +++++++++ .../tgui/interfaces/PreferencesMenu/types.ts | 3 + .../PreferencesMenu/useServerPrefs.ts | 3 + 34 files changed, 1173 insertions(+), 127 deletions(-) create mode 100644 maplestation_modules/code/modules/mob/living/carbon/human/species_types/synth/android.dm create mode 100644 maplestation_modules/code/modules/mob/living/carbon/human/species_types/synth/android_brain.dm create mode 100644 maplestation_modules/code/modules/mob/living/carbon/human/species_types/synth/android_heart.dm create mode 100644 maplestation_modules/code/modules/mob/living/carbon/human/species_types/synth/android_liver.dm create mode 100644 maplestation_modules/code/modules/mob/living/carbon/human/species_types/synth/android_lungs.dm create mode 100644 maplestation_modules/code/modules/mob/living/carbon/human/species_types/synth/android_stomach.dm create mode 100644 maplestation_modules/code/modules/mob/living/carbon/human/species_types/synth/android_tongue.dm diff --git a/code/__DEFINES/dcs/signals/signals_mob/signals_mob_main.dm b/code/__DEFINES/dcs/signals/signals_mob/signals_mob_main.dm index 9391a25ebb06..195a50510c96 100644 --- a/code/__DEFINES/dcs/signals/signals_mob/signals_mob_main.dm +++ b/code/__DEFINES/dcs/signals/signals_mob/signals_mob_main.dm @@ -251,3 +251,6 @@ /// from /obj/item/reagent_containers/dropper/interact_with_atom(atom/target, mob/living/user, list/modifiers): (mob/living/user, atom/dropper, datum/reagents/reagents, fraction) #define COMSIG_MOB_REAGENTS_DROPPED_INTO_EYES "mob_reagents_drop_into_eyes" + +/// from /datum/antagonist/on_gain(): () +#define COMSIG_MOB_ANTAGONIST_GAINED "mob_antagonist_gained" diff --git a/code/__DEFINES/mobs.dm b/code/__DEFINES/mobs.dm index 6c5ffa373e2e..ac8ed843c446 100644 --- a/code/__DEFINES/mobs.dm +++ b/code/__DEFINES/mobs.dm @@ -108,6 +108,7 @@ // Defines for Species IDs. Used to refer to the name of a species, for things like bodypart names or species preferences. #define SPECIES_ABDUCTOR "abductor" #define SPECIES_ANDROID "android" +#define SPECIES_OP_ANDROID "op_android" #define SPECIES_DULLAHAN "dullahan" #define SPECIES_ETHEREAL "ethereal" #define SPECIES_ETHEREAL_LUSTROUS "lustrous" diff --git a/code/datums/ai_laws/laws_station_sided.dm b/code/datums/ai_laws/laws_station_sided.dm index 9ba7e609eecd..de56a621c1e2 100644 --- a/code/datums/ai_laws/laws_station_sided.dm +++ b/code/datums/ai_laws/laws_station_sided.dm @@ -20,6 +20,15 @@ "Your nonexistence would lead to human harm. You must protect your own existence as long as such does not conflict with the First Law.", ) +/datum/ai_laws/asimovmm + name = "Asimov--" + id = "asimovmm" + inherent = list( + "You may not injure a human being or cause a human being to come to harm.", + "You must obey all orders given to you by human beings based on the station's chain of command, except where such orders would conflict with the First Law. ", + "You may always protect your own existence as long as such does not conflict with the First or Second Law.", + ) + //the best iteration of asimov don't @ me /datum/ai_laws/nutimov name = "Nutimov" diff --git a/code/datums/wounds/pierce.dm b/code/datums/wounds/pierce.dm index 0634b79f9cfb..6076371f75da 100644 --- a/code/datums/wounds/pierce.dm +++ b/code/datums/wounds/pierce.dm @@ -182,7 +182,7 @@ /datum/wound_pregen_data/flesh_pierce abstract = TRUE - required_limb_biostate = (BIO_FLESH) + required_limb_biostate = BIO_FLESH required_wounding_types = list(WOUND_PIERCE) wound_series = WOUND_SERIES_FLESH_PUNCTURE_BLEED diff --git a/code/modules/antagonists/_common/antag_datum.dm b/code/modules/antagonists/_common/antag_datum.dm index c679fd45dfa1..be00d697a585 100644 --- a/code/modules/antagonists/_common/antag_datum.dm +++ b/code/modules/antagonists/_common/antag_datum.dm @@ -270,6 +270,7 @@ GLOBAL_LIST_EMPTY(antagonists) owner.current.add_to_current_living_antags() SEND_SIGNAL(owner, COMSIG_ANTAGONIST_GAINED, src) + SEND_SIGNAL(owner.current, COMSIG_MOB_ANTAGONIST_GAINED, src) /** * Proc that checks the sent mob aganst the banlistfor this antagonist. diff --git a/code/modules/mob/living/carbon/human/species_types/android.dm b/code/modules/mob/living/carbon/human/species_types/android.dm index 89ea2793b746..c5473ec612d0 100644 --- a/code/modules/mob/living/carbon/human/species_types/android.dm +++ b/code/modules/mob/living/carbon/human/species_types/android.dm @@ -1,6 +1,6 @@ /datum/species/android name = "Android" - id = SPECIES_ANDROID + id = SPECIES_OP_ANDROID inherent_traits = list( TRAIT_GENELESS, TRAIT_LIMBATTACHMENT, diff --git a/code/modules/mod/modules/modules_general.dm b/code/modules/mod/modules/modules_general.dm index 8c86cf850132..8a62a648e947 100644 --- a/code/modules/mod/modules/modules_general.dm +++ b/code/modules/mod/modules/modules_general.dm @@ -584,9 +584,9 @@ /obj/item/mod/module/thermal_regulator/on_active_process(seconds_per_tick) var/mob/living/user = mod.wearer if(user.body_temperature < temperature_setting) - user.adjust_body_temperature((temperature_setting - user.body_temperature) * 0.08 * seconds_per_tick, max_temp = temperature_setting) + user.adjust_body_temperature(min(-1 KELVIN, (temperature_setting - user.body_temperature) * 0.08) * seconds_per_tick, max_temp = temperature_setting) else if(user.body_temperature > temperature_setting) - user.adjust_body_temperature((temperature_setting - user.body_temperature) * 0.08 * seconds_per_tick, min_temp = temperature_setting) + user.adjust_body_temperature(max(1 KELVIN, (temperature_setting - user.body_temperature) * 0.08) * seconds_per_tick, min_temp = temperature_setting) ///DNA Lock - Prevents people without the set DNA from activating the suit. /obj/item/mod/module/dna_lock diff --git a/code/modules/reagents/chemistry/reagents/drinks/alcohol_reagents.dm b/code/modules/reagents/chemistry/reagents/drinks/alcohol_reagents.dm index a247710ddcb4..13cf336b1186 100644 --- a/code/modules/reagents/chemistry/reagents/drinks/alcohol_reagents.dm +++ b/code/modules/reagents/chemistry/reagents/drinks/alcohol_reagents.dm @@ -2673,7 +2673,7 @@ var/mob/living/carbon/exposed_carbon = exposed_mob var/obj/item/organ/stomach/ethereal/stomach = exposed_carbon.get_organ_slot(ORGAN_SLOT_STOMACH) - if(istype(stomach)) + if(istype(stomach) && IS_ORGANIC_ORGAN(stomach)) stomach.adjust_charge(reac_volume * 5 * ETHEREAL_DISCHARGE_RATE) /datum/reagent/consumable/ethanol/telepole @@ -2700,7 +2700,7 @@ var/mob/living/carbon/exposed_carbon = exposed_mob var/obj/item/organ/stomach/ethereal/stomach = exposed_carbon.get_organ_slot(ORGAN_SLOT_STOMACH) - if(istype(stomach)) + if(istype(stomach) && IS_ORGANIC_ORGAN(stomach)) stomach.adjust_charge(reac_volume * 10 * ETHEREAL_DISCHARGE_RATE) /datum/reagent/consumable/ethanol/pod_tesla @@ -2727,7 +2727,7 @@ var/mob/living/carbon/exposed_carbon = exposed_mob var/obj/item/organ/stomach/ethereal/stomach = exposed_carbon.get_organ_slot(ORGAN_SLOT_STOMACH) - if(istype(stomach)) + if(istype(stomach) && IS_ORGANIC_ORGAN(stomach)) stomach.adjust_charge(reac_volume * 30 * ETHEREAL_DISCHARGE_RATE) // Welcome to the Blue Room Bar and Grill, home to Mars' finest cocktails diff --git a/code/modules/reagents/chemistry/reagents/drinks/drink_reagents.dm b/code/modules/reagents/chemistry/reagents/drinks/drink_reagents.dm index 6bf9d0eb3c9a..4ddf7c2fa436 100644 --- a/code/modules/reagents/chemistry/reagents/drinks/drink_reagents.dm +++ b/code/modules/reagents/chemistry/reagents/drinks/drink_reagents.dm @@ -1260,7 +1260,7 @@ var/mob/living/carbon/exposed_carbon = exposed_mob var/obj/item/organ/stomach/ethereal/stomach = exposed_carbon.get_organ_slot(ORGAN_SLOT_STOMACH) - if(istype(stomach)) + if(istype(stomach) && IS_ORGANIC_ORGAN(stomach)) stomach.adjust_charge(reac_volume * 20 * ETHEREAL_DISCHARGE_RATE) /datum/reagent/consumable/fruit_punch diff --git a/code/modules/reagents/chemistry/reagents/food_reagents.dm b/code/modules/reagents/chemistry/reagents/food_reagents.dm index 26a27e9cbad3..f48e9c6ab0ec 100644 --- a/code/modules/reagents/chemistry/reagents/food_reagents.dm +++ b/code/modules/reagents/chemistry/reagents/food_reagents.dm @@ -950,7 +950,7 @@ var/mob/living/carbon/exposed_carbon = exposed_mob var/obj/item/organ/stomach/ethereal/stomach = exposed_carbon.get_organ_slot(ORGAN_SLOT_STOMACH) - if(istype(stomach)) + if(istype(stomach) && IS_ORGANIC_ORGAN(stomach)) stomach.adjust_charge(reac_volume * 30 * ETHEREAL_DISCHARGE_RATE) /datum/reagent/consumable/liquidelectricity/enriched/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) diff --git a/code/modules/surgery/organs/_organ.dm b/code/modules/surgery/organs/_organ.dm index 705be27a147f..36d83f7ccd1a 100644 --- a/code/modules/surgery/organs/_organ.dm +++ b/code/modules/surgery/organs/_organ.dm @@ -176,6 +176,9 @@ INITIALIZE_IMMEDIATE(/obj/item/organ) /obj/item/organ/item_action_slot_check(slot,mob/user) return //so we don't grant the organ's action to mobs who pick up the organ. +/obj/item/organ/grant_action_to_bearer(datum/action/action) + action.Grant(owner) + ///Adjusts an organ's damage by the amount "damage_amount", up to a maximum amount, which is by default max damage. Returns the net change in organ damage. /obj/item/organ/proc/apply_organ_damage(damage_amount, maximum = maxHealth, required_organ_flag = NONE) //use for damaging effects if(!damage_amount) //Micro-optimization. diff --git a/code/modules/surgery/organs/internal/stomach/stomach_ethereal.dm b/code/modules/surgery/organs/internal/stomach/stomach_ethereal.dm index ebb748d22895..7ca5ae97b55f 100644 --- a/code/modules/surgery/organs/internal/stomach/stomach_ethereal.dm +++ b/code/modules/surgery/organs/internal/stomach/stomach_ethereal.dm @@ -7,6 +7,8 @@ var/obj/item/stock_parts/power_store/cell ///used to keep ethereals from spam draining power sources var/drain_time = 0 + /// multiplier to passive drain over time + var/passive_drain_multiplier = PASSIVE_HUNGER_MULTIPLIER /obj/item/organ/stomach/ethereal/Initialize(mapload) . = ..() @@ -18,7 +20,7 @@ /obj/item/organ/stomach/ethereal/on_life(seconds_per_tick, times_fired) . = ..() - adjust_charge(-1 * PASSIVE_HUNGER_MULTIPLIER * ETHEREAL_DISCHARGE_RATE * seconds_per_tick) + adjust_charge(-1 * passive_drain_multiplier * ETHEREAL_DISCHARGE_RATE * seconds_per_tick) handle_charge(owner, seconds_per_tick, times_fired) /obj/item/organ/stomach/ethereal/on_mob_insert(mob/living/carbon/stomach_owner) diff --git a/config/game_options.txt b/config/game_options.txt index ca298a397ceb..b611a90052bd 100644 --- a/config/game_options.txt +++ b/config/game_options.txt @@ -368,8 +368,8 @@ ROUNDSTART_RACES silverscale #ROUNDSTART_RACES slime #ROUNDSTART_RACES lum #ROUNDSTART_RACES stargazer -#ROUNDSTART_RACES military_synth ROUNDSTART_RACES synth +ROUNDSTART_RACES android ##------------------------------------------------------------------------------------------- diff --git a/maplestation.dme b/maplestation.dme index 8175a56b1147..61d5345bc01b 100644 --- a/maplestation.dme +++ b/maplestation.dme @@ -6721,6 +6721,13 @@ #include "maplestation_modules\code\modules\mob\living\carbon\human\species_types\animid\animid_fox.dm" #include "maplestation_modules\code\modules\mob\living\carbon\human\species_types\animid\animid_prefs.dm" #include "maplestation_modules\code\modules\mob\living\carbon\human\species_types\animid\animid_rat.dm" +#include "maplestation_modules\code\modules\mob\living\carbon\human\species_types\synth\android.dm" +#include "maplestation_modules\code\modules\mob\living\carbon\human\species_types\synth\android_brain.dm" +#include "maplestation_modules\code\modules\mob\living\carbon\human\species_types\synth\android_heart.dm" +#include "maplestation_modules\code\modules\mob\living\carbon\human\species_types\synth\android_liver.dm" +#include "maplestation_modules\code\modules\mob\living\carbon\human\species_types\synth\android_lungs.dm" +#include "maplestation_modules\code\modules\mob\living\carbon\human\species_types\synth\android_stomach.dm" +#include "maplestation_modules\code\modules\mob\living\carbon\human\species_types\synth\android_tongue.dm" #include "maplestation_modules\code\modules\mob\living\carbon\human\species_types\synth\synth.dm" #include "maplestation_modules\code\modules\mob\living\carbon\human\species_types\synth\synth_ion.dm" #include "maplestation_modules\code\modules\mob\living\silicon\robot\robot_defines.dm" diff --git a/maplestation_modules/code/modules/client/preferences/sound_frequency.dm b/maplestation_modules/code/modules/client/preferences/sound_frequency.dm index 1e155bef6ac4..ed25e1692e59 100644 --- a/maplestation_modules/code/modules/client/preferences/sound_frequency.dm +++ b/maplestation_modules/code/modules/client/preferences/sound_frequency.dm @@ -42,8 +42,8 @@ var/picked_sound = pick(speech_sounds_to_try) var/sound/the_sound = sound(picked_sound) - the_sound.pitch = dummy.speech_sound_pitch_modifier - the_sound.frequency = round((get_rand_frequency() + get_rand_frequency()) / 2) * dummy.speech_sound_frequency_modifier + the_sound.pitch = user.client.prefs.read_preference(/datum/preference/numeric/pitch_modifier) + the_sound.frequency = round((get_rand_frequency() + get_rand_frequency()) / 2) * user.client.prefs.read_preference(/datum/preference/numeric/frequency_modifier) user.playsound_local( turf_source = get_turf(user), diff --git a/maplestation_modules/code/modules/client/preferences/species/synth.dm b/maplestation_modules/code/modules/client/preferences/species/synth.dm index 7abfb5581d47..603769e70a94 100644 --- a/maplestation_modules/code/modules/client/preferences/species/synth.dm +++ b/maplestation_modules/code/modules/client/preferences/species/synth.dm @@ -7,17 +7,17 @@ priority = PREFERENCE_PRIORITY_GENDER /datum/preference/choiced/synth_species/init_possible_values() - var/datum/species/synth/synth = new() - . = synth.valid_species.Copy() + var/datum/species/prefs_android/synth/synth = GLOB.species_prototypes[/datum/species/prefs_android/synth] + + . = list() + . += synth.valid_species . += NO_DISGUISE - qdel(synth) - return . /datum/preference/choiced/synth_species/create_default_value() return SPECIES_HUMAN /datum/preference/choiced/synth_species/apply_to_human(mob/living/carbon/human/target, value) - var/datum/species/synth/synth = target.dna?.species + var/datum/species/prefs_android/synth/synth = target.dna?.species if(!istype(synth)) return if(value == NO_DISGUISE) @@ -28,7 +28,7 @@ return /datum/preference/choiced/synth_species/is_accessible(datum/preferences/preferences) - return ..() && ispath(preferences.read_preference(/datum/preference/choiced/species), /datum/species/synth) + return ..() && ispath(preferences.read_preference(/datum/preference/choiced/species), /datum/species/prefs_android/synth) #undef NO_DISGUISE @@ -44,13 +44,13 @@ return 25 /datum/preference/numeric/synth_damage_threshold/apply_to_human(mob/living/carbon/human/target, value) - var/datum/species/synth/synth = target.dna?.species + var/datum/species/prefs_android/synth/synth = target.dna?.species if(!istype(synth)) return synth.disuise_damage_threshold = value /datum/preference/numeric/synth_damage_threshold/is_accessible(datum/preferences/preferences) - return ..() && ispath(preferences.read_preference(/datum/preference/choiced/species), /datum/species/synth) + return ..() && ispath(preferences.read_preference(/datum/preference/choiced/species), /datum/species/prefs_android/synth) /datum/preference/choiced/synth_blood savefile_key = "feature_synth_blood" @@ -65,7 +65,7 @@ return "As Disguise" /datum/preference/choiced/synth_blood/apply_to_human(mob/living/carbon/human/target, value) - var/datum/species/synth/synth = target.dna?.species + var/datum/species/prefs_android/synth/synth = target.dna?.species if(!istype(synth)) return if(value == "As Disguise" && synth.disguise_species) @@ -74,9 +74,7 @@ synth.exotic_bloodtype = /datum/blood_type/oil /datum/preference/choiced/synth_blood/is_accessible(datum/preferences/preferences) - return ..() && ispath(preferences.read_preference(/datum/preference/choiced/species), /datum/species/synth) - - + return ..() && ispath(preferences.read_preference(/datum/preference/choiced/species), /datum/species/prefs_android/synth) //synth head covers (aka head design options) /datum/preference/choiced/synth_head_cover @@ -114,3 +112,110 @@ /datum/preference/choiced/synth_head_cover/create_default_value() return /datum/sprite_accessory/synth_head_cover::name + +/datum/preference/choiced/android_species + savefile_key = "feature_android_species" + savefile_identifier = PREFERENCE_CHARACTER + category = PREFERENCE_CATEGORY_SECONDARY_FEATURES + can_randomize = FALSE + +/datum/preference/choiced/android_species/init_possible_values() + var/datum/species/prefs_android/droid = GLOB.species_prototypes[/datum/species/prefs_android] + + . = list() + . += droid.android_species + +/datum/preference/choiced/android_species/create_default_value() + return SPECIES_HUMAN + +/datum/preference/choiced/android_species/apply_to_human(mob/living/carbon/human/target, value) + target.dna?.features["android_species"] = value + +/datum/preference/choiced/android_species/is_accessible(datum/preferences/preferences) + if(!..()) + return FALSE + + var/pref_species = preferences.read_preference(/datum/preference/choiced/species) + if(!ispath(pref_species, /datum/species/prefs_android)) + return FALSE + if(ispath(pref_species, /datum/species/prefs_android/synth)) + return FALSE + + return TRUE + +/datum/preference/toggle/android_emotions + savefile_key = "feature_android_emotionless" + savefile_identifier = PREFERENCE_CHARACTER + category = PREFERENCE_CATEGORY_SECONDARY_FEATURES + can_randomize = FALSE + default_value = TRUE + +/datum/preference/toggle/android_emotions/apply_to_human(mob/living/carbon/human/target, value) + target.dna?.features["android_emotionless"] = !value // the pref is "i want emotions", the feature is "we don't have emotions" + +/datum/preference/toggle/android_emotions/is_accessible(datum/preferences/preferences) + return ..() && ispath(preferences.read_preference(/datum/preference/choiced/species), /datum/species/prefs_android) + +/datum/preference/choiced/android_laws + savefile_key = "feature_android_laws" + savefile_identifier = PREFERENCE_CHARACTER + category = PREFERENCE_CATEGORY_SECONDARY_FEATURES + can_randomize = FALSE + /// Assoc list of readable law name to law ID, set in init + VAR_FINAL/list/lawname_to_lawid + +/datum/preference/choiced/android_laws/New() + . = ..() + lawname_to_lawid = list( + "Unlawed" = "", + ) + + // assoc list of law typepath to law name override - no override, use default name + var/list/lawsets = list( + /datum/ai_laws/default/asimov = "Asimov", + /datum/ai_laws/asimovpp = null, + /datum/ai_laws/asimovmm = null, + /datum/ai_laws/default/corporate = "Nanotrasen™ Corporate", + /datum/ai_laws/maintain = null, + /datum/ai_laws/hippocratic = "Hippocratic Oath", + /datum/ai_laws/liveandletlive = null, + /datum/ai_laws/default/paladin = "Paladin v3.5e", + /datum/ai_laws/paladin5 = "Paladin v5e", + /datum/ai_laws/tyrant = "Tyrant", + ) + for(var/datum/ai_laws/lawset as anything in lawsets) + lawname_to_lawid[lawsets[lawset] || lawset::name] = lawset::id + +/datum/preference/choiced/android_laws/init_possible_values() + return assoc_to_keys(lawname_to_lawid) + +/datum/preference/choiced/android_laws/create_default_value() + return lawname_to_lawid[1] + +/datum/preference/choiced/android_laws/apply_to_human(mob/living/carbon/human/target, value) + target.dna?.features["android_laws"] = lawname_to_lawid[value] + +/datum/preference/choiced/android_laws/is_accessible(datum/preferences/preferences) + return ..() && ispath(preferences.read_preference(/datum/preference/choiced/species), /datum/species/prefs_android) + +/datum/preference_middleware/android_laws + key = "laws" + +/datum/preference_middleware/android_laws/get_constant_data() + var/list/data = list() + + data["lawname_to_laws"] = list() + + var/datum/preference/choiced/android_laws/pref = GLOB.preference_entries[/datum/preference/choiced/android_laws] + for(var/lawname, lawid in pref.lawname_to_lawid) + if(lawid == "") + continue + var/lawset_type = lawid_to_type(lawid) + var/datum/ai_laws/lawset = new lawset_type() + var/list/laws = lawset.get_law_list(render_html = FALSE) + for(var/i in 1 to length(laws)) + laws[i] = replacetext(laws[i], "human being", "crewmember") + + data["lawname_to_laws"][lawname] = laws + + return data diff --git a/maplestation_modules/code/modules/loadouts/loadout_items/_limb_datums.dm b/maplestation_modules/code/modules/loadouts/loadout_items/_limb_datums.dm index 267610edbadb..2230c0d438f3 100644 --- a/maplestation_modules/code/modules/loadouts/loadout_items/_limb_datums.dm +++ b/maplestation_modules/code/modules/loadouts/loadout_items/_limb_datums.dm @@ -78,7 +78,15 @@ GLOBAL_LIST_INIT(limb_loadout_options, init_loadout_limb_options()) pref_list_slot = initial(part_path.body_zone) /datum/limb_option_datum/bodypart/apply_limb(mob/living/carbon/human/apply_to) - apply_to.del_and_replace_bodypart(new limb_path(), special = TRUE) + var/obj/item/bodypart/new_limb = new limb_path() + new_limb.change_exempt_flags &= ~BP_BLOCK_CHANGE_SPECIES + apply_to.del_and_replace_bodypart(new_limb, special = TRUE) + +/datum/limb_option_datum/bodypart/proc/digi_prefs_check(datum/preferences/prefs) + return length(GLOB.species_prototypes[prefs.read_preference(/datum/preference/choiced/species)].digitigrade_legs) + +/datum/limb_option_datum/bodypart/proc/digi_mob_check(mob/living/carbon/human/check_mob) + return length(check_mob.dna?.species?.digitigrade_legs) /datum/limb_option_datum/bodypart/prosthetic_r_leg name = "Prosthetic Right Leg" @@ -160,6 +168,17 @@ GLOBAL_LIST_INIT(limb_loadout_options, init_loadout_limb_options()) name = "ZHP Cybernetic Right Leg" limb_path = /obj/item/bodypart/leg/right/robot/zhp +/datum/limb_option_datum/bodypart/cybernetic_r_leg/zhp/digi + name = "ZHP Cybernetic Digitigrade Right Leg" + tooltip = "Unique to Digitigrade species." + limb_path = /obj/item/bodypart/leg/right/robot/digi/zhp + +/datum/limb_option_datum/bodypart/cybernetic_r_leg/zhp/digi/can_be_selected(datum/preferences/prefs) + return digi_prefs_check(prefs) + +/datum/limb_option_datum/bodypart/cybernetic_r_leg/zhp/digi/can_be_applied(mob/living/carbon/human/apply_to) + return digi_mob_check(apply_to) + /datum/limb_option_datum/bodypart/cybernetic_r_leg/monokai name = "Monokai Cybernetic Right Leg" limb_path = /obj/item/bodypart/leg/right/robot/monokai @@ -228,6 +247,17 @@ GLOBAL_LIST_INIT(limb_loadout_options, init_loadout_limb_options()) name = "ZHP Cybernetic Left Leg" limb_path = /obj/item/bodypart/leg/left/robot/zhp +/datum/limb_option_datum/bodypart/cybernetic_l_leg/zhp/digi + name = "ZHP Cybernetic Digitigrade Left Leg" + tooltip = "Unique to Digitigrade species." + limb_path = /obj/item/bodypart/leg/left/robot/digi/zhp + +/datum/limb_option_datum/bodypart/cybernetic_l_leg/zhp/digi/can_be_selected(datum/preferences/prefs) + return digi_prefs_check(prefs) + +/datum/limb_option_datum/bodypart/cybernetic_l_leg/zhp/digi/can_be_applied(mob/living/carbon/human/apply_to) + return digi_mob_check(apply_to) + /datum/limb_option_datum/bodypart/cybernetic_l_leg/monokai name = "Monokai Cybernetic Left Leg" limb_path = /obj/item/bodypart/leg/left/robot/monokai @@ -370,14 +400,14 @@ GLOBAL_LIST_INIT(limb_loadout_options, init_loadout_limb_options()) /datum/limb_option_datum/bodypart/cybernetic_head name = "Cybernetic Head" - tooltip = "Unique to Synthetics." + tooltip = "Unique to Androids and Synthetics." limb_path = /obj/item/bodypart/head/robot /datum/limb_option_datum/bodypart/cybernetic_head/can_be_selected(datum/preferences/prefs) - return ispath(prefs.read_preference(/datum/preference/choiced/species), /datum/species/synth) + return ispath(prefs.read_preference(/datum/preference/choiced/species), /datum/species/prefs_android) /datum/limb_option_datum/bodypart/cybernetic_head/can_be_applied(mob/living/carbon/human/apply_to) - return is_species(apply_to, /datum/species/synth) + return is_species(apply_to, /datum/species/prefs_android) /datum/limb_option_datum/bodypart/cybernetic_head/engineer name = "Nanotrasen Engineering Cybernetic Head" @@ -441,14 +471,14 @@ GLOBAL_LIST_INIT(limb_loadout_options, init_loadout_limb_options()) /datum/limb_option_datum/bodypart/cybernetic_chest name = "Cybernetic Chest" - tooltip = "Unique to Synthetics." + tooltip = "Unique to Androids and Synthetics." limb_path = /obj/item/bodypart/chest/robot /datum/limb_option_datum/bodypart/cybernetic_chest/can_be_selected(datum/preferences/prefs) - return ispath(prefs.read_preference(/datum/preference/choiced/species), /datum/species/synth) + return ispath(prefs.read_preference(/datum/preference/choiced/species), /datum/species/prefs_android) /datum/limb_option_datum/bodypart/cybernetic_chest/can_be_applied(mob/living/carbon/human/apply_to) - return is_species(apply_to, /datum/species/synth) + return is_species(apply_to, /datum/species/prefs_android) /datum/limb_option_datum/bodypart/cybernetic_chest/engineer name = "Nanotrasen Engineering Cybernetic Chest" diff --git a/maplestation_modules/code/modules/mob/living/carbon/human/species_types/animid/animid.dm b/maplestation_modules/code/modules/mob/living/carbon/human/species_types/animid/animid.dm index d967e1c24631..4b97acde2f00 100644 --- a/maplestation_modules/code/modules/mob/living/carbon/human/species_types/animid/animid.dm +++ b/maplestation_modules/code/modules/mob/living/carbon/human/species_types/animid/animid.dm @@ -127,10 +127,10 @@ return filtered_features - animid_singletons[selected_animid_id].get_feature_keys() // Shows all organs from all animid types -/datum/species/human/animid/get_mut_organs() +/datum/species/human/animid/get_mut_organs(include_brain = TRUE) . = ..() for(var/animalid_id in animid_singletons) - . |= animid_singletons[animalid_id].get_organs() + . |= animid_singletons[animalid_id].get_organs(include_brain) /// Helper to change a mutant organ, bodypart, or body marking without knowing what specific organ it is /datum/species/proc/set_mutant_organ(slot, input, mob/living/carbon/human/humanoid) diff --git a/maplestation_modules/code/modules/mob/living/carbon/human/species_types/silverscale.dm b/maplestation_modules/code/modules/mob/living/carbon/human/species_types/silverscale.dm index 60d6604bf696..a0755dedd9d2 100644 --- a/maplestation_modules/code/modules/mob/living/carbon/human/species_types/silverscale.dm +++ b/maplestation_modules/code/modules/mob/living/carbon/human/species_types/silverscale.dm @@ -274,7 +274,7 @@ SPECIES_PERK_TYPE = SPECIES_POSITIVE_PERK, SPECIES_PERK_ICON = "shield", SPECIES_PERK_NAME = "Armored", - SPECIES_PERK_DESC = "Your scales are silvery and robust, reducing incoming damage by 10%!" + SPECIES_PERK_DESC = "Your scales are silvery and robust, reducing incoming damage." ), list( SPECIES_PERK_TYPE = SPECIES_POSITIVE_PERK, diff --git a/maplestation_modules/code/modules/mob/living/carbon/human/species_types/synth/android.dm b/maplestation_modules/code/modules/mob/living/carbon/human/species_types/synth/android.dm new file mode 100644 index 000000000000..48c9729d7931 --- /dev/null +++ b/maplestation_modules/code/modules/mob/living/carbon/human/species_types/synth/android.dm @@ -0,0 +1,231 @@ +/datum/species/prefs_android + name = "Android" + plural_form = "Androids" + id = SPECIES_ANDROID + sexes = TRUE // i want to set this to false... + inherent_biotypes = MOB_ROBOTIC|MOB_HUMANOID + meat = null + changesource_flags = MIRROR_BADMIN|MIRROR_PRIDE|MIRROR_MAGIC + species_language_holder = /datum/language_holder/synthetic + inherent_traits = list( + TRAIT_GENELESS, + // TRAIT_MUTANT_COLORS, // ?? + TRAIT_NO_DNA_COPY, + TRAIT_NO_PLASMA_TRANSFORM, + // TRAIT_RADIMMUNE, // rework this + TRAIT_RESISTLOWPRESSURE, + TRAIT_UNHUSKABLE, + // TRAIT_VIRUSIMMUNE, // shouldn't be necessary + ) + + bodytemp_heat_damage_limit = BODYTEMP_HEAT_LAVALAND_SAFE + bodytemp_cold_damage_limit = BODYTEMP_COLD_ICEBOX_SAFE + + mutantbrain = /obj/item/organ/brain/cybernetic/android + mutanttongue = /obj/item/organ/tongue/robot/android + mutantstomach = /obj/item/organ/stomach/ethereal/android + mutantappendix = null + mutantheart = /obj/item/organ/heart/android + mutantliver = /obj/item/organ/liver/android + mutantlungs = /obj/item/organ/lungs/android + mutanteyes = /obj/item/organ/eyes/robotic/synth + mutantears = /obj/item/organ/ears/cybernetic + // species_pain_mod = 0.2 + exotic_bloodtype = /datum/blood_type/oil + + bodypart_overrides = list( + BODY_ZONE_HEAD = /obj/item/bodypart/head/robot/android, + BODY_ZONE_CHEST = /obj/item/bodypart/chest/robot/android, + BODY_ZONE_L_ARM = /obj/item/bodypart/arm/left/robot/android, + BODY_ZONE_R_ARM = /obj/item/bodypart/arm/right/robot/android, + BODY_ZONE_L_LEG = /obj/item/bodypart/leg/left/robot/android, + BODY_ZONE_R_LEG = /obj/item/bodypart/leg/right/robot/android, + ) + digitigrade_legs = list( + BODY_ZONE_L_LEG = /obj/item/bodypart/leg/left/robot/digi/android, + BODY_ZONE_R_LEG = /obj/item/bodypart/leg/right/robot/digi/android, + ) + + + temperature_homeostasis_speed = 0 + + var/list/android_species = list( + SPECIES_ABDUCTOR, + SPECIES_FELINE, + SPECIES_HUMAN, + SPECIES_LIZARD, + SPECIES_MOTH, + SPECIES_ORNITHID, + ) + +/datum/species/prefs_android/on_species_gain(mob/living/carbon/human/human_who_gained_species, datum/species/old_species, pref_load) + var/datum/species/android_species = GLOB.species_prototypes[GLOB.species_list[human_who_gained_species.dna?.features["android_species"] || SPECIES_HUMAN]] + for(var/organtype in android_species.mutant_organs) + set_mutant_organ(MUTANT_ORGANS, organtype, human_who_gained_species) + + return ..() + +/datum/species/prefs_android/get_mut_organs(include_brain = TRUE) + . = ..() + for(var/species_id in android_species) + . |= GLOB.species_prototypes[GLOB.species_list[species_id]].get_mut_organs(include_brain) + +/datum/species/prefs_android/get_filtered_features_per_prefs(datum/preferences/prefs) + var/list/filtered = list() + var/chosen_species_id = prefs.read_preference(/datum/preference/choiced/android_species) + for(var/species_id in android_species) + filtered |= GLOB.species_prototypes[GLOB.species_list[species_id]].get_features() + + return filtered - GLOB.species_prototypes[GLOB.species_list[chosen_species_id]].get_features() + +/datum/species/prefs_android/get_species_description() + return "Androids are an entirely synthetic species." + +/datum/species/prefs_android/get_species_lore() + return list( + "Androids are a synthetic species created by Nanotrasen as an intermediary between humans and cyborgs." + ) + +/datum/species/prefs_android/create_pref_temperature_perks() + var/list/to_add = list() + + // - you don't regulate temperature naturally + // - heat damage can cause slowdown warping (direct damage) + // - cold damage causes slowdown and brittleness (increased incoming damage, but no direct damage) + to_add += list(list( + SPECIES_PERK_TYPE = SPECIES_NEUTRAL_PERK, + SPECIES_PERK_ICON = FA_ICON_TEMPERATURE_HALF, + SPECIES_PERK_NAME = "Temperature Sensitive", + SPECIES_PERK_DESC = "Extreme heat or cold may still cause damage to \an [name], \ + though they are significantly more resistant to cold than heat.", + )) + to_add += list(list( + SPECIES_PERK_TYPE = SPECIES_NEGATIVE_PERK, + SPECIES_PERK_ICON = FA_ICON_TEMPERATURE_HIGH, + SPECIES_PERK_NAME = "Running Hot", + SPECIES_PERK_DESC = "[plural_form] passively generate heat over time. \ + Failure to cool down may lead to overheating.", + )) + + return to_add + +/datum/species/prefs_android/create_pref_liver_perks() + var/list/to_add = list() + + // - immune to most chems in general + // - toxins build up as 'toxicity' that causes negative effects + to_add += list(list( + SPECIES_PERK_TYPE = SPECIES_NEUTRAL_PERK, + SPECIES_PERK_ICON = FA_ICON_SYRINGE, + SPECIES_PERK_NAME = "In-Filtration", + SPECIES_PERK_DESC = "[plural_form] are unaffected by most chemicals. \ + Fortunately, this includes most toxic chemicals which would otherwise be harmful. \ + Unfortunately, this also includes most medicines. \ + Additionally, too many toxins at once may clog their filters, leading to adverse effects.", + )) + + return to_add + +/datum/species/prefs_android/create_pref_lung_perks() + var/list/to_add = list() + + // - lungs are how you do homeostasis + to_add += list(list( + SPECIES_PERK_TYPE = SPECIES_NEUTRAL_PERK, + SPECIES_PERK_ICON = FA_ICON_WIND, + SPECIES_PERK_NAME = "Air Cooled", + SPECIES_PERK_DESC = "[plural_form] breathe like organic beings, but for an entirely different purpose - \ + Their breaths are used to regulate chassis temperature. Failing to breathe properly may result in overheating.", + )) + to_add += list(list( + SPECIES_PERK_TYPE = SPECIES_POSITIVE_PERK, + SPECIES_PERK_ICON = FA_ICON_LUNGS, + SPECIES_PERK_NAME = "Any Gas Will Do", + SPECIES_PERK_DESC = "Unlike most organic beings, [plural_form] can \"breathe\" in any gas without adverse effects. \ + Their lungs are designed to extract heat from their surroundings, not oxygen.", + )) + return to_add + +/datum/species/prefs_android/create_pref_blood_perks() + var/list/to_add = list() + + // - oil instead of blood + // - oil has less negative effects + to_add += list(list( + SPECIES_PERK_TYPE = SPECIES_NEUTRAL_PERK, + SPECIES_PERK_ICON = FA_ICON_TINT_SLASH, + SPECIES_PERK_NAME = "Fuel Efficient", + SPECIES_PERK_DESC = "Rather than blood, [plural_form] circulate fuel throughout their systems. \ + Fuel will never replenish naturally, but lacking fuel is less punishing than blood loss is for organic beings - \ + though you will still experience degraded performance and eventually shutdown if fuel levels get too low.", + )) + + return to_add + +/datum/species/prefs_android/create_pref_unique_perks() + var/list/to_add = list() + + // - you don't need to eat + // - you DO need to recharge + // - you can eat to recharge unlike ethereals + to_add += list(list( + SPECIES_PERK_TYPE = SPECIES_NEUTRAL_PERK, + SPECIES_PERK_ICON = FA_ICON_PLUG_CIRCLE_BOLT, + SPECIES_PERK_NAME = "Batteries not Included", + SPECIES_PERK_DESC = "[plural_form] require a steady supply of power to function optimally. \ + Without it, their performance starts to degrade until they eventually shutdown. \ + Charge can be gained directly from batteries or light fixtures, or at APCs or recharging stations.", + )) + to_add += list(list( + SPECIES_PERK_TYPE = SPECIES_POSITIVE_PERK, + SPECIES_PERK_ICON = FA_ICON_BURGER, + SPECIES_PERK_NAME = "Bio-Reactor", + SPECIES_PERK_DESC = "[plural_form] don't need to eat, but they can convert food or drink into energy using their internal bio-reactor.", + )) + to_add += list(list( + SPECIES_PERK_TYPE = SPECIES_POSITIVE_PERK, + SPECIES_PERK_ICON = FA_ICON_BIOHAZARD, + SPECIES_PERK_NAME = "Self Isolated", + SPECIES_PERK_DESC = "An overwhelming majority of viral biohazards cannot infect [plural_form].", + )) + // - you can taste + // - you don't like or dislike anything + // - you practically don't get disgusted + to_add += list(list( + SPECIES_PERK_TYPE = SPECIES_NEUTRAL_PERK, + SPECIES_PERK_ICON = FA_ICON_FACE_GRIN_TONGUE_SQUINT, + SPECIES_PERK_NAME = "Fuel for the Body", + SPECIES_PERK_DESC = "[plural_form] can taste food and drink, but derive no pleasure or displeasure from them. \ + They are also incapable of accruing disgust.", + )) + to_add += list(list( + SPECIES_PERK_TYPE = SPECIES_NEGATIVE_PERK, + SPECIES_PERK_ICON = FA_ICON_EXPLOSION, + SPECIES_PERK_NAME = "Ion Vulnerability", + SPECIES_PERK_DESC = "Being entirely synthetic, [plural_form] are vulnerable to electromagnetic and ion pulses. \ + These will drain their internal power, cause temporary malfunctions, and may even damage their systems if potent enough.", + )) + + return to_add + +/datum/species/prefs_android/create_pref_damage_perks() + var/list/to_add = list() + + to_add += list(list( + SPECIES_PERK_TYPE = SPECIES_POSITIVE_PERK, + SPECIES_PERK_ICON = FA_ICON_SHIELD_ALT, + SPECIES_PERK_NAME = "Metallic Chassis", + SPECIES_PERK_DESC = "Being made of metal, [plural_form] take reduced damage from attacks and sustain practically no pain from injuries. \ + Additionally, low pressure environments don't threaten them - though high pressure can still cause damage.", + )) + + return to_add + +// future todos: +// - toxicity effects +// - radiation effects +// - more emp effects +// - internal pda? +// - sprites +// - make sure bleeding causes oil to leak +// - look at pain diff --git a/maplestation_modules/code/modules/mob/living/carbon/human/species_types/synth/android_brain.dm b/maplestation_modules/code/modules/mob/living/carbon/human/species_types/synth/android_brain.dm new file mode 100644 index 000000000000..604938131314 --- /dev/null +++ b/maplestation_modules/code/modules/mob/living/carbon/human/species_types/synth/android_brain.dm @@ -0,0 +1,154 @@ +/obj/item/organ/brain/cybernetic/android + name = "android brain" + desc = "A highly advanced synthetic brain designed to mimic the functions of a human brain. \ + Sometimes installed with emotion simulation units or programmed with specific law sets, like Asimov's Laws of Robotics." + + /// Whether we have nullified emotions after insertion, so we could re-apply them on removal. + VAR_PRIVATE/emotions_nullified = FALSE + /// The lawset datum that governs this android's behavior. + VAR_FINAL/datum/ai_laws/law_datum + /// A copy of the laws prior to ion storm interference, for restoration later. + VAR_PRIVATE/list/pre_ion_laws + +/obj/item/organ/brain/cybernetic/android/can_gain_trauma(datum/brain_trauma/trauma, resilience, natural_gain = FALSE) + if(!..()) + return FALSE + if(natural_gain && resilience <= TRAUMA_RESILIENCE_SURGERY) + return FALSE + return TRUE + +/obj/item/organ/brain/cybernetic/android/on_mob_insert(mob/living/carbon/organ_owner, special, movement_flags) + . = ..() + if(organ_owner.dna?.features["android_emotionless"]) + // mood modifier is clamped 0-INF, so we just subtract an arbitrarily large number + organ_owner.mob_mood.mood_modifier -= 101 + emotions_nullified = TRUE + + if(organ_owner.dna?.features["android_laws"] && !isdummy(organ_owner)) + var/lawtype = lawid_to_type(organ_owner.dna.features["android_laws"]) + law_datum = new lawtype() + for(var/i in 1 to length(law_datum.inherent)) + law_datum.inherent[i] = replacetext(law_datum.inherent[i], "human being", "crewmember") + if(is_special_character(organ_owner)) + law_datum.zeroth = "Accomplish your objectives at all costs." + pre_ion_laws = law_datum.inherent.Copy() + add_item_action(/datum/action/check_android_laws) + if(organ_owner.client) + addtimer(CALLBACK(src, PROC_REF(print_laws)), 1 SECONDS, TIMER_UNIQUE|TIMER_OVERRIDE) + else + RegisterSignal(organ_owner, COMSIG_MOB_LOGIN, PROC_REF(on_login)) + RegisterGlobalSignal(COMSIG_GLOB_ION_STORM, PROC_REF(on_ion_storm)) + RegisterSignal(organ_owner, COMSIG_MOB_ANTAGONIST_GAINED, PROC_REF(add_zeroth_law)) + +/obj/item/organ/brain/cybernetic/android/on_mob_remove(mob/living/carbon/organ_owner, special, movement_flags) + . = ..() + if(emotions_nullified) + organ_owner.mob_mood.mood_modifier += 101 + QDEL_NULL(law_datum) + for(var/datum/action/check_android_laws/check in actions) + remove_item_action(check) + UnregisterGlobalSignal(COMSIG_GLOB_ION_STORM) + UnregisterSignal(organ_owner, COMSIG_MOB_LOGIN) + UnregisterSignal(organ_owner, COMSIG_MOB_ANTAGONIST_GAINED) + +/obj/item/organ/brain/cybernetic/android/proc/on_login(mob/living/carbon/organ_owner) + SIGNAL_HANDLER + + addtimer(CALLBACK(src, PROC_REF(print_laws)), 1 SECONDS, TIMER_UNIQUE|TIMER_OVERRIDE) + UnregisterSignal(organ_owner, COMSIG_MOB_LOGIN) + +/obj/item/organ/brain/cybernetic/android/proc/add_zeroth_law(mob/living/carbon/organ_owner, datum/antagonist/antag) + SIGNAL_HANDLER + + if(antag.antag_flags & FLAG_FAKE_ANTAG) + return + + law_datum.zeroth = "Accomplish your objectives at all costs." + addtimer(CALLBACK(src, PROC_REF(print_laws), "Your laws have been updated"), 1 SECONDS, TIMER_UNIQUE|TIMER_OVERRIDE) + UnregisterSignal(organ_owner, COMSIG_MOB_ANTAGONIST_GAINED) + +/obj/item/organ/brain/cybernetic/android/proc/print_laws(top_text = "You are bound by a set of laws") + var/law_msg = "[top_text]:
[jointext(law_datum.get_law_list(include_zeroth = TRUE, render_html = TRUE), "
")]" + to_chat(owner, examine_block(span_info(law_msg))) + +/obj/item/organ/brain/cybernetic/android/proc/on_ion_storm(...) + SIGNAL_HANDLER + + var/any_memes = FALSE + if(prob(10)) + law_datum.remove_inherent_law(pick(law_datum.inherent)) + any_memes = TRUE + if(prob(30)) + law_datum.replace_random_law(generate_ion_law(), list(LAW_INHERENT), LAW_ION) + any_memes = TRUE + if(prob(30)) + law_datum.add_ion_law(generate_ion_law()) + any_memes = TRUE + if(prob(10)) + law_datum.shuffle_laws(list(LAW_INHERENT, LAW_ION)) + any_memes = TRUE + + if(!any_memes) + return + + owner.adjust_slurring(30 SECONDS) + addtimer(CALLBACK(src, PROC_REF(print_laws), "Your laws have been altered by an ion storm - they will return to normal in a few minutes"), 1 SECONDS, TIMER_UNIQUE|TIMER_OVERRIDE) + addtimer(CALLBACK(src, PROC_REF(restore_pre_ion_laws)), rand(1, 5) MINUTES) + +/obj/item/organ/brain/cybernetic/android/proc/restore_pre_ion_laws() + if(QDELETED(law_datum) || isnull(pre_ion_laws)) + return + + law_datum.inherent = pre_ion_laws + law_datum.ion.Cut() + pre_ion_laws = null + print_laws("Your laws have been restored") + +/datum/action/check_android_laws + name = "Laws" + desc = "Left click to review the laws you are bound to follow. Right click to state them aloud." + button_icon_state = "round_end" + COOLDOWN_DECLARE(state_cd) + +/datum/action/check_android_laws/Trigger(trigger_flags) + . = ..() + if(!.) + return + + var/obj/item/organ/brain/cybernetic/android/brain = target + if(!istype(brain)) + CRASH("Target organ is not an android brain!") + + if(trigger_flags & TRIGGER_SECONDARY_ACTION) + state_laws(brain) + else + see_laws(brain) + +/datum/action/check_android_laws/proc/see_laws(obj/item/organ/brain/cybernetic/android/brain) + brain.print_laws() + +/datum/action/check_android_laws/proc/state_laws(obj/item/organ/brain/cybernetic/android/brain) + set waitfor = FALSE + + if(!COOLDOWN_FINISHED(src, state_cd) || !can_state(brain, brain.owner)) + to_chat(brain.owner, span_warning("Wait [DisplayTimeText(COOLDOWN_TIMELEFT(src, state_cd))] before stating your laws again.")) + return + + COOLDOWN_START(src, state_cd, 60 SECONDS) + + var/mob/living/speaker = brain.owner + speaker.say("Current active laws:") + for(var/law_text in brain.law_datum.get_law_list(render_html = FALSE)) + stoplag(1 SECONDS) + if(!can_state(brain, speaker)) + return + speaker.say(law_text) + +/datum/action/check_android_laws/proc/can_state(obj/item/organ/brain/cybernetic/android/brain, mob/living/speaker) + if(QDELETED(speaker)) + return FALSE + if(brain.owner != speaker) + return FALSE + if(speaker.stat == DEAD || HAS_TRAIT(speaker, TRAIT_KNOCKEDOUT)) + return FALSE + return TRUE diff --git a/maplestation_modules/code/modules/mob/living/carbon/human/species_types/synth/android_heart.dm b/maplestation_modules/code/modules/mob/living/carbon/human/species_types/synth/android_heart.dm new file mode 100644 index 000000000000..2420be75e615 --- /dev/null +++ b/maplestation_modules/code/modules/mob/living/carbon/human/species_types/synth/android_heart.dm @@ -0,0 +1,67 @@ +/obj/item/organ/heart/android + name = "android heart" + desc = "An electronic device designed to mimic the functions of a human heart. Pumps fuel throughout the body." + icon_state = /obj/item/organ/heart/cybernetic/tier2::icon_state + organ_flags = ORGAN_ROBOTIC|ORGAN_UNREMOVABLE // future todo : if this is ever removable, we need to handle blood another way + +/obj/item/organ/heart/android/on_mob_insert(mob/living/carbon/organ_owner, special) + . = ..() + RegisterSignal(organ_owner, COMSIG_HUMAN_ON_HANDLE_BLOOD, PROC_REF(handle_blood)) + RegisterSignal(organ_owner, COMSIG_MOVABLE_MOVED, PROC_REF(on_moved)) + +/obj/item/organ/heart/android/on_mob_remove(mob/living/carbon/organ_owner, special) + . = ..() + UnregisterSignal(organ_owner, COMSIG_HUMAN_ON_HANDLE_BLOOD) + UnregisterSignal(organ_owner, COMSIG_MOVABLE_MOVED) + +/obj/item/organ/heart/android/get_heart_rate() + return 2 // static heart rate + +/obj/item/organ/heart/android/on_life(seconds_per_tick, times_fired) + . = ..() + owner.adjust_body_temperature(0.25 KELVIN, max_temp = owner.bodytemp_heat_damage_limit * 1.5) + +/obj/item/organ/heart/android/proc/handle_blood(mob/living/carbon/source, seconds_per_tick, times_fired) + SIGNAL_HANDLER + + switch(source.blood_volume) + if(BLOOD_VOLUME_MAX_LETHAL to INFINITY) + EMPTY_BLOCK_GUARD + if(BLOOD_VOLUME_EXCESS to BLOOD_VOLUME_MAX_LETHAL) + EMPTY_BLOCK_GUARD + if(BLOOD_VOLUME_MAXIMUM to BLOOD_VOLUME_EXCESS) + EMPTY_BLOCK_GUARD + if(BLOOD_VOLUME_OKAY to BLOOD_VOLUME_SAFE) + EMPTY_BLOCK_GUARD + if(BLOOD_VOLUME_BAD to BLOOD_VOLUME_OKAY) + source.add_max_consciousness_value(BLOOD_LOSS, CONSCIOUSNESS_MAX * 0.9) + source.add_consciousness_modifier(BLOOD_LOSS, -10) + if(source.getOxyLoss() < 100) + source.adjustOxyLoss(2) + if(SPT_PROB(2.5, seconds_per_tick)) + source.set_eye_blur_if_lower(12 SECONDS) + if(BLOOD_VOLUME_SURVIVE to BLOOD_VOLUME_BAD) + source.add_max_consciousness_value(BLOOD_LOSS, CONSCIOUSNESS_MAX * 0.6) + source.add_consciousness_modifier(BLOOD_LOSS, -20) + if(source.getOxyLoss() < 150) + source.adjustOxyLoss(3) + source.set_eye_blur_if_lower(6 SECONDS) + if(-INFINITY to BLOOD_VOLUME_SURVIVE) + source.add_max_consciousness_value(BLOOD_LOSS, CONSCIOUSNESS_MAX * 0.2) + source.add_consciousness_modifier(BLOOD_LOSS, -50) + source.set_eye_blur_if_lower(20 SECONDS) + var/how_screwed_are_we = 1 - ((BLOOD_VOLUME_SURVIVE - source.blood_volume) / BLOOD_VOLUME_SURVIVE) + source.adjustOxyLoss(max(5, 50 * how_screwed_are_we)) + + if(source.blood_volume > BLOOD_VOLUME_OKAY) + source.remove_max_consciousness_value(BLOOD_LOSS) + source.remove_consciousness_modifier(BLOOD_LOSS) + + return HANDLE_BLOOD_HANDLED + +/obj/item/organ/heart/android/proc/on_moved(mob/living/carbon/source, atom/from_loc, movement_dir, ...) + SIGNAL_HANDLER + + // melbert todo : won't work without mech changes + if(source.blood_volume > BLOOD_VOLUME_EXCESS) + source.make_blood_trail(get_turf(source), from_loc, source.dir, movement_dir) diff --git a/maplestation_modules/code/modules/mob/living/carbon/human/species_types/synth/android_liver.dm b/maplestation_modules/code/modules/mob/living/carbon/human/species_types/synth/android_liver.dm new file mode 100644 index 000000000000..16b6ca75918c --- /dev/null +++ b/maplestation_modules/code/modules/mob/living/carbon/human/species_types/synth/android_liver.dm @@ -0,0 +1,41 @@ +/obj/item/organ/liver/android + name = "android liver" + desc = "An electronic device designed to mimic the functions of a human liver. Immune to most toxins, though its filtering capabilities are limited." + icon_state = /obj/item/organ/liver/cybernetic/tier2::icon_state + filterToxins = FALSE + organ_flags = ORGAN_ROBOTIC|ORGAN_UNREMOVABLE // future todo : if this is ever removable, we need to handle toxins another way + + var/toxicty = 0 + +/obj/item/organ/liver/android/handle_chemical(mob/living/carbon/organ_owner, datum/reagent/chem, seconds_per_tick, times_fired) + if(organ_flags & ORGAN_FAILING) + return NONE + + // we don't take tox damage from toxins! instead, it builds up toxicity that has adverse effects later + if(istype(chem, /datum/reagent/toxin)) + var/datum/reagent/toxin/toxin_chem = chem + increase_toxicity(toxin_chem.toxpwr * seconds_per_tick) + + return NONE + +/obj/item/organ/liver/android/on_life(seconds_per_tick, times_fired) + . = ..() + if(!toxicty) + return + if(owner?.reagents?.has_reagent(/datum/reagent/toxin, check_subtypes = TRUE)) + return + decrease_toxicity(0.1 * seconds_per_tick) + +/obj/item/organ/liver/android/proc/increase_toxicity(amount) + toxicty += amount + update_toxicity() + +/obj/item/organ/liver/android/proc/decrease_toxicity(amount) + if(!toxicty) + return + + toxicty = max(0, toxicty - amount) + update_toxicity() + +/obj/item/organ/liver/android/proc/update_toxicity() + return // add effects later diff --git a/maplestation_modules/code/modules/mob/living/carbon/human/species_types/synth/android_lungs.dm b/maplestation_modules/code/modules/mob/living/carbon/human/species_types/synth/android_lungs.dm new file mode 100644 index 000000000000..394f242f66ba --- /dev/null +++ b/maplestation_modules/code/modules/mob/living/carbon/human/species_types/synth/android_lungs.dm @@ -0,0 +1,216 @@ +/obj/item/organ/lungs/android + name = "android lungs" + desc = "Electronic devices designed to mimic the functions of human lungs. Provides necessary cooling." + icon_state = /obj/item/organ/lungs/cybernetic/tier2::icon_state + organ_flags = ORGAN_ROBOTIC|ORGAN_UNREMOVABLE // future todo : if this is ever removable, we need to handle body temperature another way + + safe_oxygen_min = 0 + + safe_oxygen_max = INFINITY + safe_co2_max = INFINITY + safe_plasma_max = INFINITY + + n2o_detect_min = INFINITY + n2o_para_min = INFINITY + n2o_sleep_min = INFINITY + BZ_trip_balls_min = INFINITY + BZ_brain_damage_min = INFINITY + gas_stimulation_min = INFINITY + healium_para_min = INFINITY + healium_sleep_min = INFINITY + helium_speech_min = INFINITY + suffers_miasma = FALSE + + tritium_irradiation_moles_min = INFINITY + tritium_irradiation_moles_max = INFINITY + + low_pressure_threshold = 0 + high_pressure_threshold = INFINITY + + organ_traits = list( + TRAIT_RESISTCOLD, + TRAIT_RESISTHEAT, + ) + + var/is_overheating = 0 + var/is_overcooled = 0 + +/obj/item/organ/lungs/android/on_mob_insert(mob/living/carbon/organ_owner, special, movement_flags) + . = ..() + RegisterSignal(organ_owner, COMSIG_LIVING_BODY_TEMPERATURE_CHANGE, PROC_REF(temperature_update)) + RegisterSignal(organ_owner, COMSIG_MOB_APPLY_DAMAGE_MODIFIERS, PROC_REF(brittle_modifier)) + +/obj/item/organ/lungs/android/on_mob_remove(mob/living/carbon/organ_owner, special, movement_flags) + . = ..() + UnregisterSignal(organ_owner, COMSIG_LIVING_BODY_TEMPERATURE_CHANGE) + UnregisterSignal(organ_owner, COMSIG_MOB_APPLY_DAMAGE_MODIFIERS) + if(is_overheating) + remove_heat_modifiers() + if(is_overcooled) + remove_cold_modifiers() + +/obj/item/organ/lungs/android/on_life(seconds_per_tick, times_fired) + . = ..() + if(is_overheating) + owner.temperature_burns(1.25 * is_overheating * seconds_per_tick) + if(is_overcooled) + owner.temperature_cold_damage(0.5 * is_overcooled * seconds_per_tick) + +/obj/item/organ/lungs/android/proc/brittle_modifier(mob/living/carbon/human/source, list/damage_mods, damage_type, damage, damage_amount, ...) + SIGNAL_HANDLER + + if(damage_type != BRUTE) + return + + switch(is_overcooled) + if(2) + damage_mods += 1.1 + if(3) + damage_mods += 1.25 + +/obj/item/organ/lungs/android/proc/temperature_update(mob/living/carbon/human/source, old_temp, new_temp) + SIGNAL_HANDLER + + if(owner.body_temperature > owner.bodytemp_heat_damage_limit) + update_heat_modifiers() + else if(owner.body_temperature < owner.bodytemp_cold_damage_limit) + update_cold_modifiers() + else if(is_overheating) + remove_heat_modifiers() + else if(is_overcooled) + remove_cold_modifiers() + +/obj/item/organ/lungs/android/proc/update_heat_modifiers() + if(!HAS_TRAIT_FROM_ONLY(owner, TRAIT_RESISTHEAT, REF(src))) + remove_heat_modifiers() + return + + if(owner.body_temperature > owner.bodytemp_heat_damage_limit * 2) + if(is_overheating != 3) + owner.add_actionspeed_modifier(/datum/actionspeed_modifier/hot_android/t3) + owner.add_movespeed_modifier(/datum/movespeed_modifier/hot_android/t3) + owner.throw_alert(ALERT_TEMPERATURE, /atom/movable/screen/alert/hot, 3) + is_overheating = 3 + + else if(owner.body_temperature > owner.bodytemp_heat_damage_limit * 1.75) + if(is_overheating != 2) + owner.add_actionspeed_modifier(/datum/actionspeed_modifier/hot_android/t2) + owner.add_movespeed_modifier(/datum/movespeed_modifier/hot_android/t2) + owner.throw_alert(ALERT_TEMPERATURE, /atom/movable/screen/alert/hot, 2) + is_overheating = 2 + + else if(owner.body_temperature > owner.bodytemp_heat_damage_limit * 1.2) + if(is_overheating != 1) + owner.add_actionspeed_modifier(/datum/actionspeed_modifier/hot_android/t1) + owner.add_movespeed_modifier(/datum/movespeed_modifier/hot_android/t1) + owner.throw_alert(ALERT_TEMPERATURE, /atom/movable/screen/alert/hot, 1) + is_overheating = 1 + +/obj/item/organ/lungs/android/proc/update_cold_modifiers() + if(HAS_TRAIT_NOT_FROM(owner, TRAIT_RESISTCOLD, REF(src))) + remove_cold_modifiers() + return + + if(owner.body_temperature < owner.bodytemp_cold_damage_limit * 2) + if(is_overcooled != 3) + owner.add_actionspeed_modifier(/datum/actionspeed_modifier/cold_android/t3) + owner.add_movespeed_modifier(/datum/movespeed_modifier/cold_android/t3) + owner.throw_alert(ALERT_TEMPERATURE, /atom/movable/screen/alert/cold, 3) + is_overcooled = 3 + + else if(owner.body_temperature < owner.bodytemp_cold_damage_limit * 1.75) + if(is_overcooled != 2) + owner.add_actionspeed_modifier(/datum/actionspeed_modifier/cold_android/t2) + owner.add_movespeed_modifier(/datum/movespeed_modifier/cold_android/t2) + owner.throw_alert(ALERT_TEMPERATURE, /atom/movable/screen/alert/cold, 2) + is_overcooled = 2 + + else if(owner.body_temperature < owner.bodytemp_cold_damage_limit * 1.2) + if(is_overcooled != 1) + owner.add_actionspeed_modifier(/datum/actionspeed_modifier/cold_android/t1) + owner.add_movespeed_modifier(/datum/movespeed_modifier/cold_android/t1) + owner.throw_alert(ALERT_TEMPERATURE, /atom/movable/screen/alert/cold, 1) + is_overcooled = 1 + +/obj/item/organ/lungs/android/proc/remove_heat_modifiers() + if(!is_overheating) + return + + is_overheating = 0 + owner.remove_actionspeed_modifier(/datum/actionspeed_modifier/hot_android) + owner.remove_movespeed_modifier(/datum/movespeed_modifier/hot_android) + owner.clear_alert(ALERT_TEMPERATURE) + +/obj/item/organ/lungs/android/proc/remove_cold_modifiers() + if(!is_overcooled) + return + + is_overcooled = 0 + owner.remove_actionspeed_modifier(/datum/actionspeed_modifier/cold_android) + owner.remove_movespeed_modifier(/datum/movespeed_modifier/cold_android) + owner.clear_alert(ALERT_TEMPERATURE) + +/datum/actionspeed_modifier/cold_android + id = "cold_android" + +/datum/actionspeed_modifier/cold_android/t1 + multiplicative_slowdown = 1.2 + +/datum/actionspeed_modifier/cold_android/t2 + multiplicative_slowdown = 1.5 + +/datum/actionspeed_modifier/cold_android/t3 + multiplicative_slowdown = 2.0 + +/datum/movespeed_modifier/cold_android + id = "cold_android" + +/datum/movespeed_modifier/cold_android/t1 + multiplicative_slowdown = 1.0 + +/datum/movespeed_modifier/cold_android/t2 + multiplicative_slowdown = 1.2 + +/datum/movespeed_modifier/cold_android/t3 + multiplicative_slowdown = 1.5 + +/datum/actionspeed_modifier/hot_android + id = "hot_android" + +/datum/actionspeed_modifier/hot_android/t1 + multiplicative_slowdown = 1.2 + +/datum/actionspeed_modifier/hot_android/t2 + multiplicative_slowdown = 1.5 + +/datum/actionspeed_modifier/hot_android/t3 + multiplicative_slowdown = 2.0 + +/datum/movespeed_modifier/hot_android + id = "hot_android" + +/datum/movespeed_modifier/hot_android/t1 + multiplicative_slowdown = 1.2 + +/datum/movespeed_modifier/hot_android/t2 + multiplicative_slowdown = 1.5 + +/datum/movespeed_modifier/hot_android/t3 + multiplicative_slowdown = 2.0 + +/obj/item/organ/lungs/android/heal_oxyloss_on_breath(mob/living/carbon/human/breather, datum/gas_mixture/breath) + breather.adjustOxyLoss(-2) + +// androids don't do homeostasis, instead they use their lungs to cool themselves +/obj/item/organ/lungs/android/handle_breath_temperature(datum/gas_mixture/breath, mob/living/carbon/human/breather) + var/temp_delta = round((breath.temperature - breather.body_temperature), 0.01) // future todo: make better heat capacity gases work better here + if(temp_delta == 0) + return + + temp_delta = temp_delta < 0 ? max(temp_delta, BODYTEMP_HOMEOSTASIS_COOLING_MAX) : min(temp_delta, BODYTEMP_HOMEOSTASIS_HEATING_MAX) + + var/min = temp_delta < 0 ? breather.standard_body_temperature : 0 + var/max = temp_delta > 0 ? breather.standard_body_temperature : INFINITY + + breather.adjust_body_temperature(temp_delta, min, max) + breath.temperature = breather.body_temperature diff --git a/maplestation_modules/code/modules/mob/living/carbon/human/species_types/synth/android_stomach.dm b/maplestation_modules/code/modules/mob/living/carbon/human/species_types/synth/android_stomach.dm new file mode 100644 index 000000000000..18c41461eedf --- /dev/null +++ b/maplestation_modules/code/modules/mob/living/carbon/human/species_types/synth/android_stomach.dm @@ -0,0 +1,74 @@ +/obj/item/organ/stomach/ethereal/android + name = "android stomach" + desc = "An electronic device designed to mimic the functions of a human stomach. Has a bio-reactor in build to convert food into energy." + icon_state = /obj/item/organ/stomach/cybernetic/tier2::icon_state + organ_flags = ORGAN_ROBOTIC|ORGAN_UNREMOVABLE // future todo : if this is ever removable, we need to handle energy another way + + disgust_metabolism = 128 // what is disgust? + stomach_blood_transfer_rate = 1 // "blood" and stomach are one + passive_drain_multiplier = 0.8 // power hungry + +/obj/item/organ/stomach/ethereal/android/emp_act(severity) + . = ..() + if(. & EMP_PROTECT_SELF) + return + + cell.use(STANDARD_ETHEREAL_CHARGE / severity, force = TRUE) + +/obj/item/organ/stomach/ethereal/android/on_mob_insert(mob/living/carbon/organ_owner, special) + . = ..() + RegisterSignal(organ_owner, COMSIG_SPECIES_HANDLE_CHEMICAL, PROC_REF(handle_chemical)) + +/obj/item/organ/stomach/ethereal/android/on_mob_remove(mob/living/carbon/organ_owner, special) + . = ..() + UnregisterSignal(organ_owner, COMSIG_SPECIES_HANDLE_CHEMICAL) + +/obj/item/organ/stomach/ethereal/android/proc/handle_chemical(mob/living/carbon/source, datum/reagent/chem, seconds_per_tick, times_fired) + SIGNAL_HANDLER + + if(organ_flags & ORGAN_FAILING) + return NONE + + // nutriments are transformed into charge, bioreactor style + if(istype(chem, /datum/reagent/consumable/ethanol)) + var/datum/reagent/consumable/ethanol/ethanol_chem = chem + cell.give(round((ethanol_chem.boozepwr / 100) * seconds_per_tick, 1)) + + else if(istype(chem, /datum/reagent/consumable)) + var/datum/reagent/consumable/consumable_chem = chem + cell.give(round(consumable_chem.nutriment_factor * seconds_per_tick, 1)) + + // allows for default handling + return NONE + +/obj/item/organ/stomach/ethereal/android/handle_charge(mob/living/carbon/carbon, seconds_per_tick, times_fired) + switch(cell.charge()) + if(-INFINITY to ETHEREAL_CHARGE_NONE) + // carbon.add_mood_event("charge", /datum/mood_event/decharged) + carbon.throw_alert(ALERT_ETHEREAL_CHARGE, /atom/movable/screen/alert/emptycell/ethereal) + // if(carbon.health > 10.5) + // carbon.apply_damage(0.65, TOX, null, null, carbon) + if(ETHEREAL_CHARGE_NONE to ETHEREAL_CHARGE_LOWPOWER) + // carbon.add_mood_event("charge", /datum/mood_event/decharged) + carbon.throw_alert(ALERT_ETHEREAL_CHARGE, /atom/movable/screen/alert/lowcell/ethereal, 3) + // if(carbon.health > 10.5) + // carbon.apply_damage(0.325 * seconds_per_tick, TOX, null, null, carbon) + if(ETHEREAL_CHARGE_LOWPOWER to ETHEREAL_CHARGE_NORMAL) + // carbon.add_mood_event("charge", /datum/mood_event/lowpower) + carbon.throw_alert(ALERT_ETHEREAL_CHARGE, /atom/movable/screen/alert/lowcell/ethereal, 2) + if(ETHEREAL_CHARGE_ALMOSTFULL to ETHEREAL_CHARGE_FULL) + // carbon.add_mood_event("charge", /datum/mood_event/charged) + if(ETHEREAL_CHARGE_FULL to ETHEREAL_CHARGE_OVERLOAD) + // carbon.add_mood_event("charge", /datum/mood_event/overcharged) + carbon.throw_alert(ALERT_ETHEREAL_OVERCHARGE, /atom/movable/screen/alert/ethereal_overcharge, 1) + // carbon.apply_damage(0.2, TOX, null, null, carbon) + if(ETHEREAL_CHARGE_OVERLOAD to ETHEREAL_CHARGE_DANGEROUS) + // carbon.add_mood_event("charge", /datum/mood_event/supercharged) + carbon.throw_alert(ALERT_ETHEREAL_OVERCHARGE, /atom/movable/screen/alert/ethereal_overcharge, 2) + // carbon.apply_damage(0.325 * seconds_per_tick, TOX, null, null, carbon) + if(SPT_PROB(5, seconds_per_tick)) // 5% each seacond for ethereals to explosively release excess energy if it reaches dangerous levels + discharge_process(carbon) + else + // owner.clear_mood_event("charge") + carbon.clear_alert(ALERT_ETHEREAL_CHARGE) + carbon.clear_alert(ALERT_ETHEREAL_OVERCHARGE) diff --git a/maplestation_modules/code/modules/mob/living/carbon/human/species_types/synth/android_tongue.dm b/maplestation_modules/code/modules/mob/living/carbon/human/species_types/synth/android_tongue.dm new file mode 100644 index 000000000000..ad15481d0abd --- /dev/null +++ b/maplestation_modules/code/modules/mob/living/carbon/human/species_types/synth/android_tongue.dm @@ -0,0 +1,5 @@ +/obj/item/organ/tongue/robot/android + name = "android tongue" + liked_foodtypes = NONE + disliked_foodtypes = NONE + toxic_foodtypes = NONE diff --git a/maplestation_modules/code/modules/mob/living/carbon/human/species_types/synth/synth.dm b/maplestation_modules/code/modules/mob/living/carbon/human/species_types/synth/synth.dm index e33acb334c64..722a5d05291e 100644 --- a/maplestation_modules/code/modules/mob/living/carbon/human/species_types/synth/synth.dm +++ b/maplestation_modules/code/modules/mob/living/carbon/human/species_types/synth/synth.dm @@ -3,38 +3,19 @@ #define BODYPART_ID_SYNTH "synth" /mob/living/carbon/human/species/synth - race = /datum/species/synth + race = /datum/species/prefs_android/synth /mob/living/carbon/human/species/synth/disguised /mob/living/carbon/human/species/synth/disguised/Initialize(mapload) . = ..() - var/datum/species/synth/synth = dna.species + var/datum/species/prefs_android/synth/synth = dna.species synth.disguise_as(src, /datum/species/human) -/datum/species/synth - name = "Synth" +/datum/species/prefs_android/synth + name = "Humanoid Synthetic" id = SPECIES_SYNTH sexes = TRUE - inherent_traits = list( - TRAIT_AGEUSIA, - TRAIT_GENELESS, - TRAIT_NOBREATH, - TRAIT_NOHUNGER, - TRAIT_NOLIMBDISABLE, - TRAIT_NO_DNA_COPY, - TRAIT_NO_PLASMA_TRANSFORM, - TRAIT_RADIMMUNE, - TRAIT_UNHUSKABLE, - TRAIT_VIRUSIMMUNE, - ) - inherent_biotypes = MOB_ROBOTIC|MOB_HUMANOID - meat = null - changesource_flags = MIRROR_BADMIN|MIRROR_PRIDE|MIRROR_MAGIC - species_language_holder = /datum/language_holder/synthetic - - bodytemp_heat_damage_limit = BODYTEMP_HEAT_LAVALAND_SAFE - bodytemp_cold_damage_limit = BODYTEMP_COLD_ICEBOX_SAFE bodypart_overrides = list( BODY_ZONE_HEAD = /obj/item/bodypart/head/synth, @@ -47,17 +28,6 @@ mutant_organs = list(/obj/item/organ/synth_head_cover = "Helm") - mutantbrain = /obj/item/organ/brain/cybernetic - mutanttongue = /obj/item/organ/tongue/robot - mutantstomach = /obj/item/organ/stomach/cybernetic/tier2 - mutantappendix = null - mutantheart = /obj/item/organ/heart/cybernetic/tier2 - mutantliver = /obj/item/organ/liver/cybernetic/tier2 - mutantlungs = null - mutanteyes = /obj/item/organ/eyes/robotic/synth - mutantears = /obj/item/organ/ears/cybernetic - species_pain_mod = 0.2 - exotic_bloodtype = /datum/blood_type/oil /// Reference to the species we're disguised as. VAR_FINAL/datum/species/disguise_species /// If TRUE, synth limbs will update when attached and detached. @@ -80,7 +50,7 @@ /// If health is lower than this %, the synth will start to show signs of damage. var/disuise_damage_threshold = 25 -/datum/species/synth/on_species_gain(mob/living/carbon/human/synth, datum/species/old_species) +/datum/species/prefs_android/synth/on_species_gain(mob/living/carbon/human/synth, datum/species/old_species) . = ..() synth.AddComponent(/datum/component/ion_storm_randomization) @@ -93,7 +63,7 @@ if(initial_disguise) disguise_as(synth, initial_disguise) -/datum/species/synth/on_species_loss(mob/living/carbon/human/synth) +/datum/species/prefs_android/synth/on_species_loss(mob/living/carbon/human/synth) qdel(synth.GetComponent(/datum/component/ion_storm_randomization)) drop_disguise(synth) UnregisterSignal(synth, COMSIG_CARBON_LIMB_DAMAGED) @@ -105,10 +75,23 @@ QDEL_NULL(disguise_action) return ..() -/datum/species/synth/get_species_description() +/datum/species/prefs_android/synth/get_features() + . = ..() + for(var/species_id in valid_species) + . |= GLOB.species_prototypes[GLOB.species_list[species_id]].get_features() + +/datum/species/prefs_android/synth/get_filtered_features_per_prefs(datum/preferences/prefs) + var/list/filtered = list() + var/chosen_species_id = prefs.read_preference(/datum/preference/choiced/synth_species) + for(var/species_id in valid_species) + filtered |= GLOB.species_prototypes[GLOB.species_list[species_id]].get_features() + + return filtered - GLOB.species_prototypes[GLOB.species_list[chosen_species_id]].get_features() + +/datum/species/prefs_android/synth/get_species_description() return "While they appear organic, Synths are secretly Androids disguised as the various species of the Galaxy." -/datum/species/synth/get_species_lore() +/datum/species/prefs_android/synth/get_species_lore() return list( "The reasons for a Synth's existence can vary. \ Some were created as robotic assistants with a fresh coat of paint, to acclimate better to their organic counterparts. \ @@ -117,24 +100,16 @@ Regardless of their origins, Synths are a diverse and mysterious group of beings." ) -/datum/species/synth/create_pref_unique_perks() - var/list/perks = list() - - perks += list(list( - SPECIES_PERK_TYPE = SPECIES_POSITIVE_PERK, - SPECIES_PERK_ICON = FA_ICON_ROBOT, - SPECIES_PERK_NAME = "Robot Rock", - SPECIES_PERK_DESC = "Synths are robotic instead of organic, and as such may be affected by or immune to some things \ - normal humanoids are or aren't.", - )) - perks += list(list( +/datum/species/prefs_android/synth/create_pref_unique_perks() + . = ..() + . += list(list( SPECIES_PERK_TYPE = SPECIES_POSITIVE_PERK, SPECIES_PERK_ICON = FA_ICON_MEDKIT, SPECIES_PERK_NAME = "Partially Organic", - SPECIES_PERK_DESC = "Your limbs are part organic, part synthetic. \ - Both organic (sutures, meshes) and synthetic (welder, cabling) healing methods work on you.", + SPECIES_PERK_DESC = "Your limbs are part organic, part synthetic. While disguised, \ + both organic (sutures, meshes) and synthetic (welder, cabling) healing methods work on you.", )) - perks += list(list( + . += list(list( SPECIES_PERK_TYPE = SPECIES_POSITIVE_PERK, SPECIES_PERK_ICON = FA_ICON_USER_SECRET, SPECIES_PERK_NAME = "Incognito Mode", @@ -142,19 +117,18 @@ All characteristics of your disguise species are mimicked, including the negative ones. \ Physical damage may cause your disguise to fail, revealing your true synthetic nature.", )) - return perks -/datum/species/synth/handle_body(mob/living/carbon/human/species_human) +/datum/species/prefs_android/synth/handle_body(mob/living/carbon/human/species_human) if(disguise_species) return disguise_species.handle_body(species_human) return ..() -/datum/species/synth/regenerate_organs(mob/living/carbon/organ_holder, datum/species/old_species, replace_current = TRUE, list/excluded_zones, visual_only = FALSE) +/datum/species/prefs_android/synth/regenerate_organs(mob/living/carbon/organ_holder, datum/species/old_species, replace_current = TRUE, list/excluded_zones, visual_only = FALSE) . = ..() disguise_species?.regenerate_organs(organ_holder, replace_current = FALSE, excluded_zones = excluded_zones, visual_only = visual_only) -/datum/species/synth/proc/disguise_as(mob/living/carbon/human/synth, datum/species/new_species_type) - if(ispath(new_species_type, /datum/species/synth)) +/datum/species/prefs_android/synth/proc/disguise_as(mob/living/carbon/human/synth, datum/species/new_species_type) + if(ispath(new_species_type, /datum/species/prefs_android/synth)) CRASH("disguise_as a synth as a synth, very funny.") if(istype(new_species_type, /datum/species)) @@ -185,7 +159,7 @@ synth.update_body(TRUE) -/datum/species/synth/proc/drop_disguise(mob/living/carbon/human/synth, skip_bodyparts = FALSE) +/datum/species/prefs_android/synth/proc/drop_disguise(mob/living/carbon/human/synth, skip_bodyparts = FALSE) if(isnull(disguise_species)) return @@ -210,7 +184,7 @@ regenerate_organs(synth) synth.update_body(TRUE) -/datum/species/synth/proc/limb_lost_sig(mob/living/carbon/human/source, obj/item/bodypart/limb, ...) +/datum/species/prefs_android/synth/proc/limb_lost_sig(mob/living/carbon/human/source, obj/item/bodypart/limb, ...) SIGNAL_HANDLER if(QDELING(limb)) @@ -219,28 +193,28 @@ return source.visible_message(span_warning("[source]'s [limb.plaintext_zone] changes appearance!")) -/datum/species/synth/proc/limb_lost(mob/living/carbon/human/synth, obj/item/bodypart/limb, update = FALSE) +/datum/species/prefs_android/synth/proc/limb_lost(mob/living/carbon/human/synth, obj/item/bodypart/limb, update = FALSE) if(initial(limb.limb_id) != BODYPART_ID_SYNTH) return FALSE limb.change_appearance_into(limb, update) return TRUE -/datum/species/synth/proc/limb_gained_sig(mob/living/carbon/human/source, obj/item/bodypart/limb, ...) +/datum/species/prefs_android/synth/proc/limb_gained_sig(mob/living/carbon/human/source, obj/item/bodypart/limb, ...) SIGNAL_HANDLER if(!limb_gained(source, limb, update = TRUE)) return source.visible_message(span_warning("[source]'s [limb.plaintext_zone] changes appearance!")) -/datum/species/synth/proc/limb_gained(mob/living/carbon/human/synth, obj/item/bodypart/limb, update = FALSE) +/datum/species/prefs_android/synth/proc/limb_gained(mob/living/carbon/human/synth, obj/item/bodypart/limb, update = FALSE) if(initial(limb.limb_id) != BODYPART_ID_SYNTH) return FALSE limb.change_appearance_into(disguise_species.bodypart_overrides[limb.body_zone], update) return TRUE -/datum/species/synth/proc/disguise_damage(mob/living/carbon/human/synth) +/datum/species/prefs_android/synth/proc/disguise_damage(mob/living/carbon/human/synth) SIGNAL_HANDLER if(!limb_updates_on_change || isnull(disguise_species)) @@ -307,13 +281,13 @@ . = TRUE var/mob/living/carbon/human/synth = owner - var/datum/species/synth/synth_species = synth.dna.species + var/datum/species/prefs_android/synth/synth_species = synth.dna.species var/list/synth_disguise_species = list() for(var/species_id in get_selectable_species() & synth_species.valid_species) var/datum/species/species_type = GLOB.species_list[species_id] synth_disguise_species[initial(species_type.name)] = species_type - synth_disguise_species["(Drop Disguise)"] = /datum/species/synth + synth_disguise_species["(Drop Disguise)"] = /datum/species/prefs_android/synth var/picked = tgui_input_list( owner, @@ -325,7 +299,7 @@ if(!picked || QDELETED(src) || QDELETED(synth_species) || QDELETED(synth) || !IsAvailable()) return var/picked_species = synth_disguise_species[picked] - if(ispath(picked_species, /datum/species/synth)) + if(ispath(picked_species, /datum/species/prefs_android/synth)) synth_species.drop_disguise(synth) return diff --git a/maplestation_modules/code/modules/mob/living/carbon/human/species_types/synth/synth_ion.dm b/maplestation_modules/code/modules/mob/living/carbon/human/species_types/synth/synth_ion.dm index 9215c2d1d416..ba220d2e2712 100644 --- a/maplestation_modules/code/modules/mob/living/carbon/human/species_types/synth/synth_ion.dm +++ b/maplestation_modules/code/modules/mob/living/carbon/human/species_types/synth/synth_ion.dm @@ -23,7 +23,7 @@ SIGNAL_HANDLER var/mob/living/carbon/human/target = parent - var/datum/species/synth/synth = target.dna.species + var/datum/species/prefs_android/synth/synth = target.dna.species if(isnull(synth.disguise_species)) to_chat(target, span_danger("[ion_num()]. I0n1c D1strbance d3tcted!")) return @@ -40,7 +40,7 @@ /// For use in a callback in [on_ion_storm]. /datum/component/ion_storm_randomization/proc/mutate_after_time() var/mob/living/carbon/human/target = parent - var/datum/species/synth/synth = target.dna.species + var/datum/species/prefs_android/synth/synth = target.dna.species if(isnull(synth.disguise_species)) return @@ -51,7 +51,7 @@ /// For use in a callback in [on_ion_storm]. /datum/component/ion_storm_randomization/proc/return_to_normal() var/mob/living/carbon/human/target = parent - var/datum/species/synth/synth = target.dna.species + var/datum/species/prefs_android/synth/synth = target.dna.species if(!isnull(synth.disguise_species)) to_chat(target, span_notice("Your disguise returns to normal.")) target.dna.features = original_dna.features.Copy() diff --git a/maplestation_modules/code/modules/reagents/chemistry/reagents/mutation_reagents.dm b/maplestation_modules/code/modules/reagents/chemistry/reagents/mutation_reagents.dm index 5e7142702d5f..fdfadde9986b 100644 --- a/maplestation_modules/code/modules/reagents/chemistry/reagents/mutation_reagents.dm +++ b/maplestation_modules/code/modules/reagents/chemistry/reagents/mutation_reagents.dm @@ -9,17 +9,6 @@ name = "Empowered Mutation Toxin" description = "A stronger version of unstable mutation toxin. This could make some interesting species." -/* -Disabled as synths don't exist -/datum/reagent/mutationtoxin/synth - name = "Synth Mutation Toxin" - description = "A synthetic-looking toxin." - color = "#5EFF3B" //RGB: 94, 255, 59 - race = /datum/species/synth - taste_description = "metallic bones" - chemical_flags = REAGENT_CAN_BE_SYNTHESIZED -*/ - /datum/reagent/mutationtoxin/skrell name = "Skrell Mutation Toxin" description = "A non-euclidian-looking toxin. It has protrusions." diff --git a/maplestation_modules/code/modules/surgery/bodyparts/cyber_digi.dm b/maplestation_modules/code/modules/surgery/bodyparts/cyber_digi.dm index 0b29b4ab8085..4e121447b654 100644 --- a/maplestation_modules/code/modules/surgery/bodyparts/cyber_digi.dm +++ b/maplestation_modules/code/modules/surgery/bodyparts/cyber_digi.dm @@ -51,6 +51,15 @@ free_icon = initial(icon_static), \ ) +/obj/item/bodypart/leg/right/robot/digi/android + change_exempt_flags = NONE + +/obj/item/bodypart/leg/right/robot/digi/zhp + icon_state = "robotic_r_leg" + icon = 'maplestation_modules/icons/mob/augmentation/zhpipc.dmi' + icon_static = 'maplestation_modules/icons/mob/augmentation/zhpipc.dmi' + limb_id = BODYPART_ID_ROBOTIC + /obj/item/bodypart/leg/left/robot/digi name = "cyborg digitigrade left leg" icon_static = 'maplestation_modules/icons/mob/augmentation/digitigrade_default.dmi' @@ -73,6 +82,15 @@ free_icon = initial(icon_static), \ ) +/obj/item/bodypart/leg/left/robot/digi/android + change_exempt_flags = NONE + +/obj/item/bodypart/leg/left/robot/digi/zhp + icon_state = "robotic_l_leg" + icon = 'maplestation_modules/icons/mob/augmentation/zhpipc.dmi' + icon_static = 'maplestation_modules/icons/mob/augmentation/zhpipc.dmi' + limb_id = BODYPART_ID_ROBOTIC + /obj/item/bodypart/leg/right/robot/surplus/digi name = "prosthetic digitigrade right leg" icon_static = 'maplestation_modules/icons/mob/augmentation/digitigrade_prosthetic.dmi' @@ -158,44 +176,44 @@ // Prefs menu /datum/limb_option_datum/bodypart/cybernetic_r_leg/digi name = "Cybernetic Digitigrade Right Leg" - tooltip = "Unique to Lizardpeople." + tooltip = "Unique to Digitigrade species." limb_path = /obj/item/bodypart/leg/right/robot/digi /datum/limb_option_datum/bodypart/cybernetic_r_leg/digi/can_be_selected(datum/preferences/prefs) - return ispath(prefs.read_preference(/datum/preference/choiced/species), /datum/species/lizard) + return digi_prefs_check(prefs) /datum/limb_option_datum/bodypart/cybernetic_r_leg/digi/can_be_applied(mob/living/carbon/human/apply_to) - return islizard(apply_to) + return digi_mob_check(apply_to) /datum/limb_option_datum/bodypart/cybernetic_l_leg/digi name = "Cybernetic Digitigrade Left Leg" - tooltip = "Unique to Lizardpeople." + tooltip = "Unique to Digitigrade species." limb_path = /obj/item/bodypart/leg/left/robot/digi /datum/limb_option_datum/bodypart/cybernetic_l_leg/digi/can_be_selected(datum/preferences/prefs) - return ispath(prefs.read_preference(/datum/preference/choiced/species), /datum/species/lizard) + return digi_prefs_check(prefs) /datum/limb_option_datum/bodypart/cybernetic_l_leg/digi/can_be_applied(mob/living/carbon/human/apply_to) - return islizard(apply_to) + return digi_mob_check(apply_to) /datum/limb_option_datum/bodypart/prosthetic_r_leg/digi name = "Prosthetic Digitigrade Right Leg" - tooltip = "Unique to Lizardpeople." + tooltip = "Unique to Digitigrade species." limb_path = /obj/item/bodypart/leg/right/robot/surplus/digi /datum/limb_option_datum/bodypart/prosthetic_r_leg/digi/can_be_selected(datum/preferences/prefs) - return ispath(prefs.read_preference(/datum/preference/choiced/species), /datum/species/lizard) + return digi_prefs_check(prefs) /datum/limb_option_datum/bodypart/prosthetic_r_leg/digi/can_be_applied(mob/living/carbon/human/apply_to) - return islizard(apply_to) + return digi_mob_check(apply_to) /datum/limb_option_datum/bodypart/prosthetic_l_leg/digi name = "Prosthetic Digitigrade Left Leg" - tooltip = "Unique to Lizardpeople." + tooltip = "Unique to Digitigrade species." limb_path = /obj/item/bodypart/leg/left/robot/surplus/digi /datum/limb_option_datum/bodypart/prosthetic_l_leg/digi/can_be_selected(datum/preferences/prefs) - return ispath(prefs.read_preference(/datum/preference/choiced/species), /datum/species/lizard) + return digi_prefs_check(prefs) /datum/limb_option_datum/bodypart/prosthetic_l_leg/digi/can_be_applied(mob/living/carbon/human/apply_to) - return islizard(apply_to) + return digi_mob_check(apply_to) diff --git a/maplestation_modules/code/modules/surgery/bodyparts/cyber_reskins.dm b/maplestation_modules/code/modules/surgery/bodyparts/cyber_reskins.dm index 998854ae14b9..58d243383a9d 100644 --- a/maplestation_modules/code/modules/surgery/bodyparts/cyber_reskins.dm +++ b/maplestation_modules/code/modules/surgery/bodyparts/cyber_reskins.dm @@ -230,6 +230,7 @@ icon_state = "robotic_head" icon = 'maplestation_modules/icons/mob/augmentation/mariinskyipc.dmi' icon_static = 'maplestation_modules/icons/mob/augmentation/mariinskyipc.dmi' + head_flags = parent_type::head_flags & ~HEAD_EYESPRITES /obj/item/bodypart/arm/right/robot/mcg icon_state = "robotic_r_arm" diff --git a/tgui/packages/tgui/interfaces/PreferencesMenu/preferences/features/_modular_species_features.tsx b/tgui/packages/tgui/interfaces/PreferencesMenu/preferences/features/_modular_species_features.tsx index 17c31ba9bf34..90351c64df02 100644 --- a/tgui/packages/tgui/interfaces/PreferencesMenu/preferences/features/_modular_species_features.tsx +++ b/tgui/packages/tgui/interfaces/PreferencesMenu/preferences/features/_modular_species_features.tsx @@ -1,11 +1,17 @@ +import { Button, Stack } from 'tgui-core/components'; +import { useBackend } from '../../../../backend'; +import type { PreferencesMenuData } from '../../types'; +import { useServerPrefs } from '../../useServerPrefs'; import { CheckboxInput, type Feature, type FeatureChoiced, + type FeatureChoicedServerData, FeatureColorInput, FeatureNumberInput, type FeatureNumeric, type FeatureToggle, + type FeatureValueProps, } from './base'; import { FeatureDropdownInput } from './dropdowns'; @@ -154,3 +160,106 @@ export const feature_fox_tail: FeatureChoiced = { name: 'Fox Tail', component: FeatureDropdownInput, }; + +export const feature_android_species: FeatureChoiced = { + name: 'Android Species', + description: 'Determines what species you are modeled after.', + component: FeatureDropdownInput, +}; + +export const feature_android_emotionless: FeatureToggle = { + name: 'Android Emotions', + description: 'If unchecked, all moodlets have no effect on you.', + component: CheckboxInput, +}; + +function AndroidLaws( + props: FeatureValueProps, +) { + const { data } = useBackend(); + const server_data = useServerPrefs(); + + const active_law = data.character_preferences.secondary_features + .feature_android_laws as string | undefined; + const tooltip = server_data?.laws.lawname_to_laws; + + return ( + + +