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/living.dm b/code/__DEFINES/living.dm index 56cc96b506f0..8fbb4ccab9cb 100644 --- a/code/__DEFINES/living.dm +++ b/code/__DEFINES/living.dm @@ -42,6 +42,11 @@ #define COMSIG_LIVING_CAN_ALLOW_THROUGH "living_can_allow_through" #define COMPONENT_LIVING_PASSABLE (1<<0) +/// Send when sharing body temperature to breath +#define COMSIG_HUMAN_ON_HANDLE_BREATH_TEMPERATURE "human_on_handle_breath_temperature" + /// Stops further processing + #define HANDLE_BREATH_TEMPERATURE_HANDLED (1<<0) + /// Various lists of body zones affected by pain. #define BODY_ZONES_ALL list(BODY_ZONE_HEAD, BODY_ZONE_CHEST, BODY_ZONE_L_ARM, BODY_ZONE_R_ARM, BODY_ZONE_L_LEG, BODY_ZONE_R_LEG) @@ -211,3 +216,6 @@ #define examining_span_normal(msg) span_infoplain(span_italics(msg)) /// For consistent examine span formatting (small size) #define examining_span_small(msg) span_slightly_smaller(span_infoplain(span_italics(msg))) + +/// Species ID to typepath helper +#define ID_TO_TYPEPATH(id) GLOB.species_list[id] diff --git a/code/__DEFINES/mobs.dm b/code/__DEFINES/mobs.dm index 6c5ffa373e2e..473ba710f1c7 100644 --- a/code/__DEFINES/mobs.dm +++ b/code/__DEFINES/mobs.dm @@ -119,6 +119,7 @@ #define SPECIES_SLIMEPERSON "slime" #define SPECIES_LUMINESCENT "luminescent" #define SPECIES_STARGAZER "stargazer" +#define SPECIES_SYNTH "synth" #define SPECIES_LIZARD "lizard" #define SPECIES_LIZARD_ASH "ashwalker" #define SPECIES_LIZARD_SILVER "silverscale" diff --git a/code/__DEFINES/surgery.dm b/code/__DEFINES/surgery.dm index 2f98f69b10e8..fc49de994f46 100644 --- a/code/__DEFINES/surgery.dm +++ b/code/__DEFINES/surgery.dm @@ -73,8 +73,11 @@ #define HEAD_NO_DISFIGURE (1<<7) /// Show organs (like brain) when examined with examine verb #define HEAD_SHOW_ORGANS_ON_EXAMINE (1<<8) + /// Default for most heads #define HEAD_DEFAULT_FEATURES (HEAD_HAIR|HEAD_FACIAL_HAIR|HEAD_LIPS|HEAD_EYESPRITES|HEAD_EYECOLOR|HEAD_EYEHOLES|HEAD_DEBRAIN|HEAD_SHOW_ORGANS_ON_EXAMINE) +/// All hair related flags +#define HEAD_ALL_HAIR_FLAGS (HEAD_HAIR | HEAD_FACIAL_HAIR) /// Return value when the surgery step fails :( #define SURGERY_STEP_FAIL -1 diff --git a/code/__DEFINES/traits/declarations.dm b/code/__DEFINES/traits/declarations.dm index 9d2895aecb11..b1774daaaff8 100644 --- a/code/__DEFINES/traits/declarations.dm +++ b/code/__DEFINES/traits/declarations.dm @@ -240,6 +240,8 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai #define TRAIT_MESSAGE_IN_A_BOTTLE_LOCATION "message_in_a_bottle_location" /// Stops other objects of the same type from being inserted inside the same aquarium it's in. #define TRAIT_UNIQUE_AQUARIUM_CONTENT "unique_aquarium_content" +/// Mobs with this trait are allowed to use silicon emotes +#define TRAIT_SILICON_EMOTES_ALLOWED "silicon_emotes_allowed" /// Trait that says you're shaded by something (ie partially in the dark) #define TRAIT_SHADED "shaded" diff --git a/code/_globalvars/traits/_traits.dm b/code/_globalvars/traits/_traits.dm index 01e13fb5df4a..110cd3a3a47b 100644 --- a/code/_globalvars/traits/_traits.dm +++ b/code/_globalvars/traits/_traits.dm @@ -436,6 +436,7 @@ GLOBAL_LIST_INIT(traits_by_type, list( "TRAIT_SIGN_LANG" = TRAIT_SIGN_LANG, "TRAIT_PAPER_MASTER" = TRAIT_PAPER_MASTER, "TRAIT_SILENT_FOOTSTEPS" = TRAIT_SILENT_FOOTSTEPS, + "TRAIT_SILICON_EMOTES_ALLOWED" = TRAIT_SILICON_EMOTES_ALLOWED, "TRAIT_SIXTHSENSE" = TRAIT_SIXTHSENSE, "TRAIT_SKITTISH" = TRAIT_SKITTISH, "TRAIT_SLEEPIMMUNE" = TRAIT_SLEEPIMMUNE, diff --git a/code/_globalvars/traits/admin_tooling.dm b/code/_globalvars/traits/admin_tooling.dm index 8c878d4c0481..1be69f19fe24 100644 --- a/code/_globalvars/traits/admin_tooling.dm +++ b/code/_globalvars/traits/admin_tooling.dm @@ -211,6 +211,7 @@ GLOBAL_LIST_INIT(admin_visible_traits, list( "TRAIT_SHOCKIMMUNE" = TRAIT_SHOCKIMMUNE, "TRAIT_SIGN_LANG" = TRAIT_SIGN_LANG, "TRAIT_SILENT_FOOTSTEPS" = TRAIT_SILENT_FOOTSTEPS, + "TRAIT_SILICON_EMOTES_ALLOWED" = TRAIT_SILICON_EMOTES_ALLOWED, "TRAIT_SIXTHSENSE" = TRAIT_SIXTHSENSE, "TRAIT_SKITTISH" = TRAIT_SKITTISH, "TRAIT_SLEEPIMMUNE" = TRAIT_SLEEPIMMUNE, diff --git a/code/_onclick/hud/alert.dm b/code/_onclick/hud/alert.dm index 063444bb95d2..57db3a686d4e 100644 --- a/code/_onclick/hud/alert.dm +++ b/code/_onclick/hud/alert.dm @@ -716,7 +716,7 @@ or shoot a gun to move around via Newton's 3rd Law of Motion." . = ..() desc = initial(desc) if(length(GLOB.roundstart_station_borgcharger_areas)) - desc += " Recharging stations are available in [english_list(GLOB.roundstart_station_borgcharger_areas)]." + desc += "

Recharging stations are available in [english_list(GLOB.roundstart_station_borgcharger_areas)]." /atom/movable/screen/alert/lowcell name = "Low Charge" @@ -731,7 +731,7 @@ or shoot a gun to move around via Newton's 3rd Law of Motion." . = ..() desc = initial(desc) if(length(GLOB.roundstart_station_borgcharger_areas)) - desc += " Recharging stations are available in [english_list(GLOB.roundstart_station_borgcharger_areas)]." + desc += "

Recharging stations are available in [english_list(GLOB.roundstart_station_borgcharger_areas)]." //MECH @@ -739,13 +739,13 @@ or shoot a gun to move around via Newton's 3rd Law of Motion." . = ..() desc = initial(desc) if(length(GLOB.roundstart_station_mechcharger_areas)) - desc += " Power ports are available in [english_list(GLOB.roundstart_station_mechcharger_areas)]." + desc += "

Power ports are available in [english_list(GLOB.roundstart_station_mechcharger_areas)]." /atom/movable/screen/alert/emptycell/mech/update_desc() . = ..() desc = initial(desc) if(length(GLOB.roundstart_station_mechcharger_areas)) - desc += " Power ports are available in [english_list(GLOB.roundstart_station_mechcharger_areas)]." + desc += "

Power ports are available in [english_list(GLOB.roundstart_station_mechcharger_areas)]." //Ethereal diff --git a/code/_onclick/hud/screen_objects.dm b/code/_onclick/hud/screen_objects.dm index f00e3564e8f5..ecf008b943b5 100644 --- a/code/_onclick/hud/screen_objects.dm +++ b/code/_onclick/hud/screen_objects.dm @@ -923,11 +923,7 @@ INITIALIZE_IMMEDIATE(/atom/movable/screen/splash) screen_loc = ui_mood // Slot in where mood normally is if mood is disabled // Burger next to the bar - food_image = image(icon = food_icon, icon_state = food_icon_state, pixel_x = -5) - food_image.plane = plane - food_image.appearance_flags |= KEEP_APART // To be unaffected by filters applied to src - food_image.add_filter("simple_outline", 2, outline_filter(1, COLOR_BLACK)) - underlays += food_image // To be below filters applied to src + set_food_image(food_icon, food_icon_state) // The actual bar hunger_bar = new(src, null) @@ -935,21 +931,31 @@ INITIALIZE_IMMEDIATE(/atom/movable/screen/splash) update_hunger_bar(instant = TRUE) -/atom/movable/screen/hunger/proc/update_hunger_state() - var/mob/living/hungry = hud?.mymob - if(!istype(hungry)) - return +/atom/movable/screen/hunger/proc/set_food_image(new_icon, new_icon_state) + if(food_image) + underlays -= food_image - if(HAS_TRAIT(hungry, TRAIT_NOHUNGER) || !hungry.get_organ_slot(ORGAN_SLOT_STOMACH)) - fullness = NUTRITION_LEVEL_FED - state = HUNGER_STATE_FINE - return - if(HAS_TRAIT(hungry, TRAIT_FAT)) - fullness = NUTRITION_LEVEL_FAT - state = HUNGER_STATE_FAT - return + food_image = image(icon = new_icon, icon_state = new_icon_state, pixel_x = -5) + food_image.plane = plane + food_image.appearance_flags |= KEEP_APART // To be unaffected by filters applied to src + food_image.add_filter("simple_outline", 2, outline_filter(1, COLOR_BLACK)) + underlays += food_image // To be below filters applied to src + +/atom/movable/screen/hunger/proc/reset_food_image() + set_food_image(food_icon, food_icon_state) + +/atom/movable/screen/hunger/update_appearance(updates) + update_hunger_bar() + return ..() + +/// Updates the hunger bar's appearance. +/// If `instant` is TRUE, the bar will update immediately rather than animating. +/atom/movable/screen/hunger/proc/update_hunger_bar(instant = FALSE) + var/old_state = state + var/old_fullness = fullness - fullness = round(hungry.get_fullness(only_consumable = TRUE), 0.05) + var/obj/item/organ/stomach/tumby = hud?.mymob?.get_organ_slot(ORGAN_SLOT_STOMACH) + fullness = round(tumby?.get_hungerbar_fullness(skip_contents = FALSE) || NUTRITION_LEVEL_FED, 0.05) switch(fullness) if(1 + NUTRITION_LEVEL_FULL to INFINITY) state = HUNGER_STATE_FULL @@ -962,26 +968,16 @@ INITIALIZE_IMMEDIATE(/atom/movable/screen/splash) if(0 to NUTRITION_LEVEL_STARVING) state = HUNGER_STATE_STARVING -/atom/movable/screen/hunger/update_appearance(updates) - update_hunger_bar() - return ..() - -/// Updates the hunger bar's appearance. -/// If `instant` is TRUE, the bar will update immediately rather than animating. -/atom/movable/screen/hunger/proc/update_hunger_bar(instant = FALSE) - var/old_state = state - var/old_fullness = fullness - update_hunger_state() if(old_state != state || old_fullness != fullness) // Fades out if we ARE "fine" AND if our stomach has no food digesting - var/mob/living/hungry = hud?.mymob - if(alpha == 255 && (state == HUNGER_STATE_FINE && abs(fullness - hungry.nutrition) < 1)) + var/raw_fullness = round(tumby?.get_hungerbar_fullness(skip_contents = TRUE) || hud?.mymob?.nutrition, 0.05) + if(alpha == 255 && (state == HUNGER_STATE_FINE && abs(fullness - raw_fullness) < 1)) if(instant) alpha = 0 else animate(src, alpha = 0, time = 1 SECONDS) // Fades in if we WERE "fine" OR if our stomach has food digesting - else if(alpha == 0 && (state != HUNGER_STATE_FINE || abs(fullness - hungry.nutrition) >= 1)) + else if(alpha == 0 && (state != HUNGER_STATE_FINE || abs(fullness - raw_fullness) >= 1)) if(instant) alpha = 255 else 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/bodypart_overlays/emote_bodypart_overlay.dm b/code/datums/bodypart_overlays/emote_bodypart_overlay.dm index 524dd1760561..be4135163ba0 100644 --- a/code/datums/bodypart_overlays/emote_bodypart_overlay.dm +++ b/code/datums/bodypart_overlays/emote_bodypart_overlay.dm @@ -29,10 +29,6 @@ if(!referenced_bodypart) return ..() referenced_bodypart.remove_bodypart_overlay(src) - if(referenced_bodypart.owner) //Keep in mind that the bodypart could have been severed from the owner by now - referenced_bodypart.owner.update_body_parts() - else - referenced_bodypart.update_icon_dropped() return ..() /** @@ -48,8 +44,9 @@ var/obj/item/bodypart/bodypart = src.get_bodypart(overlay.attached_body_zone) if(!bodypart) return null + if(locate(overlay_typepath) in bodypart.bodypart_overlays) + return null bodypart.add_bodypart_overlay(overlay) - src.update_body_parts() return overlay /datum/bodypart_overlay/simple/emote/blush diff --git a/code/datums/bodypart_overlays/mutant_bodypart_overlay.dm b/code/datums/bodypart_overlays/mutant_bodypart_overlay.dm index dd9e86beaa57..a90a5557325a 100644 --- a/code/datums/bodypart_overlays/mutant_bodypart_overlay.dm +++ b/code/datums/bodypart_overlays/mutant_bodypart_overlay.dm @@ -127,18 +127,16 @@ switch(color_source) if(ORGAN_COLOR_OVERRIDE) - draw_color = override_color(bodypart_owner) + var/tmpv = override_color(bodypart_owner) + draw_color = tmpv if(ORGAN_COLOR_INHERIT) draw_color = bodypart_owner.draw_color if(ORGAN_COLOR_HAIR) - if(!ishuman(bodypart_owner.owner)) - return - var/mob/living/carbon/human/human_owner = bodypart_owner.owner - var/obj/item/bodypart/head/my_head = human_owner.get_bodypart(BODY_ZONE_HEAD) //not always the same as bodypart_owner - //head hair color takes priority, owner hair color is a backup if we lack a head or something - if(my_head) - draw_color = my_head.hair_color - else + var/obj/item/bodypart/head/my_head = astype(bodypart_owner) || bodypart_owner.owner?.get_bodypart(BODY_ZONE_HEAD) + + draw_color = my_head?.hair_color + if(isnull(draw_color) && ishuman(bodypart_owner.owner)) + var/mob/living/carbon/human/human_owner = bodypart_owner.owner draw_color = human_owner.hair_color return TRUE diff --git a/code/datums/components/face_decal.dm b/code/datums/components/face_decal.dm index df70f8a3f498..6ba57aa2f01f 100644 --- a/code/datums/components/face_decal.dm +++ b/code/datums/components/face_decal.dm @@ -50,7 +50,6 @@ bodypart_overlay.draw_color = color my_head.add_bodypart_overlay(bodypart_overlay) RegisterSignals(my_head, list(COMSIG_BODYPART_REMOVED, COMSIG_QDELETING), PROC_REF(lost_head)) - carbon_parent.update_body_parts() else normal_overlay = get_normal_overlay() normal_overlay.color = color @@ -79,14 +78,9 @@ if(my_head) if(bodypart_overlay) my_head.remove_bodypart_overlay(bodypart_overlay) - if(!my_head.owner) - my_head.update_icon_dropped() QDEL_NULL(bodypart_overlay) UnregisterSignal(my_head, list(COMSIG_BODYPART_REMOVED, COMSIG_QDELETING)) my_head = null - if(iscarbon(parent)) - var/mob/living/carbon/carbon_parent = parent - carbon_parent.update_body_parts() if(normal_overlay) var/atom/atom_parent = parent UnregisterSignal(atom_parent, COMSIG_ATOM_UPDATE_OVERLAYS) diff --git a/code/datums/elements/empprotection.dm b/code/datums/elements/empprotection.dm index c0dacc7bf1f0..55db13d2809b 100644 --- a/code/datums/elements/empprotection.dm +++ b/code/datums/elements/empprotection.dm @@ -26,18 +26,18 @@ if(flags & EMP_NO_EXAMINE) return - if(flags & EMP_PROTECT_ALL == EMP_PROTECT_ALL) + if((flags & EMP_PROTECT_ALL) == EMP_PROTECT_ALL) examine_list["EMP proof"] = "[source.p_They()] [source.p_are()] unaffected by electromagnetic pulses, and shields [source.p_their()] contents and wiring from them." return if(flags & EMP_PROTECT_SELF) examine_list["EMP resilient"] = "[source.p_They()] [source.p_are()] unaffected by electromagnetic pulses." - if(flags & (EMP_PROTECT_CONTENTS|EMP_PROTECT_WIRES) == (EMP_PROTECT_CONTENTS|EMP_PROTECT_WIRES)) - examine_list["EMP blocking"] = "[source.p_They()] protects [source.p_their()] wiring and contents from electromagnetic pulses." + if((flags & (EMP_PROTECT_CONTENTS|EMP_PROTECT_WIRES)) == (EMP_PROTECT_CONTENTS|EMP_PROTECT_WIRES)) + examine_list["partially EMP blocking"] = "[source.p_They()] protects [source.p_their()] wiring and contents from electromagnetic pulses." else if(flags & EMP_PROTECT_CONTENTS) - examine_list["EMP blocking"] = "[source.p_They()] protects [source.p_their()] contents from electromagnetic pulses." + examine_list["partially EMP blocking"] = "[source.p_They()] protects [source.p_their()] contents from electromagnetic pulses." else if(flags & EMP_PROTECT_WIRES) - examine_list["EMP blocking"] = "[source.p_They()] protects [source.p_their()] wiring from electromagnetic pulses." + examine_list["partially EMP blocking"] = "[source.p_They()] protects [source.p_their()] wiring from electromagnetic pulses." diff --git a/code/datums/emotes.dm b/code/datums/emotes.dm index 2f5b44101760..d0a71dfcd243 100644 --- a/code/datums/emotes.dm +++ b/code/datums/emotes.dm @@ -44,6 +44,8 @@ var/list/mob_type_blacklist_typecache /// Types that can use this emote regardless of their state. var/list/mob_type_ignore_stat_typecache + /// Trait that is required to use this emote. + var/trait_required /// In which state can you use this emote? (Check stat.dm for a full list of them) var/stat_allowed = CONSCIOUS /// Sound to play when emote is called. @@ -318,6 +320,8 @@ * Returns a bool about whether or not the user can run the emote. */ /datum/emote/proc/can_run_emote(mob/user, status_check = TRUE, intentional = FALSE) + if(trait_required && !HAS_TRAIT(user, trait_required)) + return FALSE if(!is_type_in_typecache(user, mob_type_allowed_typecache)) return FALSE if(is_type_in_typecache(user, mob_type_blacklist_typecache)) diff --git a/code/datums/quirks/negative_quirks/all_nighter.dm b/code/datums/quirks/negative_quirks/all_nighter.dm index 253ce12b41f3..0bee11301cf9 100644 --- a/code/datums/quirks/negative_quirks/all_nighter.dm +++ b/code/datums/quirks/negative_quirks/all_nighter.dm @@ -57,7 +57,6 @@ var/obj/item/bodypart/head/face = sleepy_head.get_bodypart(BODY_ZONE_HEAD) bodypart_overlay = new() //creates our overlay face.add_bodypart_overlay(bodypart_overlay) - sleepy_head.update_body_parts() //make sure to update icon ///removes the bag overlay /datum/quirk/all_nighter/proc/remove_bags(client/client_source) @@ -66,7 +65,6 @@ //our overlay is stored as a datum var, so referencing it is easy face.remove_bodypart_overlay(bodypart_overlay) QDEL_NULL(bodypart_overlay) - sleepy_head.update_body_parts() /** *Here we actively handle our moodlet & eye bags, adding/removing them as necessary diff --git a/code/datums/status_effects/debuffs/debuffs.dm b/code/datums/status_effects/debuffs/debuffs.dm index d43c8717ccf4..4e527a82b1ac 100644 --- a/code/datums/status_effects/debuffs/debuffs.dm +++ b/code/datums/status_effects/debuffs/debuffs.dm @@ -237,7 +237,7 @@ carbon_owner.handle_dreams() if(prob(5) && owner.stat == CONSCIOUS) - owner.emote("snore") + owner.organ_emote(ORGAN_SLOT_TONGUE, "snore") /atom/movable/screen/alert/status_effect/asleep name = "Asleep" diff --git a/code/datums/status_effects/debuffs/genetic_damage.dm b/code/datums/status_effects/debuffs/genetic_damage.dm index fe86bb8d5a6d..9c52b19c5e4b 100644 --- a/code/datums/status_effects/debuffs/genetic_damage.dm +++ b/code/datums/status_effects/debuffs/genetic_damage.dm @@ -59,7 +59,7 @@ if(message) render_list += "" - render_list += conditional_tooltip("message]", "Irreparable under normal circumstances - will decay over time.", tochat) + render_list += conditional_tooltip("[message]", "Irreparable under normal circumstances - will decay over time.", tochat) render_list += "
" #undef GORILLA_MUTATION_CHANCE_PER_SECOND 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/game/machinery/rechargestation.dm b/code/game/machinery/rechargestation.dm index 45610b47e7a4..6408f99a2af7 100644 --- a/code/game/machinery/rechargestation.dm +++ b/code/game/machinery/rechargestation.dm @@ -37,7 +37,7 @@ return var/area/my_area = get_area(src) - if(!(my_area.type in GLOB.the_station_areas)) + if(istype(my_area, /area/station/maintenance) || !(my_area.type in GLOB.the_station_areas)) return var/area_name = get_area_name(src, format_text = TRUE) diff --git a/code/game/objects/items/inducer.dm b/code/game/objects/items/inducer.dm index 893dd87bcae0..96803b9f5f83 100644 --- a/code/game/objects/items/inducer.dm +++ b/code/game/objects/items/inducer.dm @@ -192,7 +192,7 @@ break //transfer of charge - var/transferred = min(our_cell.charge, target_cell.used_charge(), target_cell.rating_base * target_cell.rating * power_transfer_multiplier) + var/transferred = min(our_cell.charge, target_cell.used_charge(), target_cell.chargerate, target_cell.rating_base * target_cell.rating * power_transfer_multiplier) if(!transferred) break our_cell.use(target_cell.give(transferred)) diff --git a/code/game/objects/items/stacks/golem_food/golem_status_effects.dm b/code/game/objects/items/stacks/golem_food/golem_status_effects.dm index 932ad5320260..9d3dc08fcef1 100644 --- a/code/game/objects/items/stacks/golem_food/golem_status_effects.dm +++ b/code/game/objects/items/stacks/golem_food/golem_status_effects.dm @@ -120,17 +120,11 @@ /datum/bodypart_overlay/simple/golem_overlay/proc/add_to_bodypart(prefix, obj/item/bodypart/part) icon_state = "[prefix]_[part.body_zone]" attached_bodypart = WEAKREF(part) - part.add_bodypart_overlay(src) + part.add_bodypart_overlay(src, update = FALSE) /datum/bodypart_overlay/simple/golem_overlay/Destroy(force) var/obj/item/bodypart/referenced_bodypart = attached_bodypart.resolve() - if(!referenced_bodypart) - return ..() - referenced_bodypart.remove_bodypart_overlay(src) - if(referenced_bodypart.owner) //Keep in mind that the bodypart could have been severed from the owner by now - referenced_bodypart.owner.update_body_parts() - else - referenced_bodypart.update_icon_dropped() + referenced_bodypart?.remove_bodypart_overlay(src) return ..() /// Freezes hunger for the duration 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/client/preferences/_preference.dm b/code/modules/client/preferences/_preference.dm index cd46d6f6b17b..03f47672900a 100644 --- a/code/modules/client/preferences/_preference.dm +++ b/code/modules/client/preferences/_preference.dm @@ -329,7 +329,7 @@ GLOBAL_LIST_INIT(preference_entries_by_key, init_preference_entries_by_key()) /datum/preference/proc/current_species_has_savekey(datum/preferences/preferences) var/species_type = preferences.read_preference(/datum/preference/choiced/species) var/datum/species/species = GLOB.species_prototypes[species_type] - return (savefile_key in (species.get_features() - species.get_filtered_features_per_prefs(preferences))) + return (savefile_key in species.get_filtered_features_per_prefs(preferences)) /// Checks if this preference is relevant and thus visible to the passed preferences object. /datum/preference/proc/has_relevant_feature(datum/preferences/preferences) @@ -450,18 +450,20 @@ GLOBAL_LIST_INIT(preference_entries_by_key, init_preference_entries_by_key()) /// Will give the value as 6 hex digits, without a hash. /datum/preference/color abstract_type = /datum/preference/color + /// If TRUE, the color may be set to null, representing no color selected. + var/nullable = FALSE /datum/preference/color/deserialize(input, datum/preferences/preferences) - return sanitize_hexcolor(input) + return (nullable && isnull(input)) ? input : sanitize_hexcolor(input) /datum/preference/color/create_default_value() return random_color() /datum/preference/color/serialize(input) - return sanitize_hexcolor(input) + return (nullable && isnull(input)) ? input : sanitize_hexcolor(input) /datum/preference/color/is_valid(value) - return findtext(value, GLOB.is_color) + return (nullable && isnull(value)) || findtext(value, GLOB.is_color) /// A numeric preference with a minimum and maximum value /datum/preference/numeric diff --git a/code/modules/client/preferences/species_features/lizard.dm b/code/modules/client/preferences/species_features/lizard.dm index 7dce8c4e6a2e..11f0d6374ea9 100644 --- a/code/modules/client/preferences/species_features/lizard.dm +++ b/code/modules/client/preferences/species_features/lizard.dm @@ -61,6 +61,11 @@ should_generate_icons = TRUE relevant_external_organ = /obj/item/organ/frills +/datum/preference/choiced/lizard_frills/compile_constant_data() + var/list/data = ..() + data[SUPPLEMENTAL_FEATURE_KEY] = "feature_lizard_frill_color" + return data + /datum/preference/choiced/lizard_frills/init_possible_values() return assoc_to_keys_features(SSaccessories.frills_list) @@ -78,6 +83,11 @@ should_generate_icons = TRUE relevant_external_organ = /obj/item/organ/horns +/datum/preference/choiced/lizard_horns/compile_constant_data() + var/list/data = ..() + data[SUPPLEMENTAL_FEATURE_KEY] = "feature_lizard_horn_color" + return data + /datum/preference/choiced/lizard_horns/init_possible_values() return assoc_to_keys_features(SSaccessories.horns_list) diff --git a/code/modules/client/preferences_savefile.dm b/code/modules/client/preferences_savefile.dm index 0ac47d29c92d..3cd89e9abf5c 100644 --- a/code/modules/client/preferences_savefile.dm +++ b/code/modules/client/preferences_savefile.dm @@ -5,7 +5,7 @@ /// You do not need to raise this if you are adding new values that have sane defaults. /// Only raise this value when changing the meaning/format/name/layout of an existing value /// where you would want the updater procs below to run -#define SAVEFILE_VERSION_MAX 50 +#define SAVEFILE_VERSION_MAX 50.1 /* SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Carn @@ -104,6 +104,9 @@ SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Car if (current_version < 43) migrate_legacy_sound_toggles(savefile) + if(current_version < 43.1) + save_loadout(src, save_data?["loadout_list"]) + if (current_version < 45) migrate_quirk_to_loadout( quirk_to_migrate = "Pride Pin", @@ -156,6 +159,11 @@ SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Car new_typepath = /datum/personality/spiritual, ) + if(current_version < 50.1) + // #aaaaaa used to be the color for randomizing runechat - now it's "null" + if(save_data?["runechat_color"] == "#aaaaaa") + save_data["runechat_color"] = null + /// checks through keybindings for outdated unbound keys and updates them /datum/preferences/proc/check_keybindings() if(!parent) diff --git a/code/modules/mapping/mapping_helpers.dm b/code/modules/mapping/mapping_helpers.dm index 1f9c958418f0..7217d90ba587 100644 --- a/code/modules/mapping/mapping_helpers.dm +++ b/code/modules/mapping/mapping_helpers.dm @@ -857,6 +857,8 @@ INITIALIZE_IMMEDIATE(/obj/effect/mapping_helpers/no_lava) SPECIES_ETHEREAL, // they revive on death which is bad juju SPECIES_HUMAN, // already have a 50% chance of being selected SPECIES_LIZARD_SILVER, // NON-MODULE CHANGE - Nope + SPECIES_SYNTH, // NON-MODULE CHANGE - Nope + SPECIES_ANDROID, // NON-MODULE CHANGE - Nope ) /obj/effect/mapping_helpers/dead_body_placer/Initialize(mapload) diff --git a/code/modules/mob/living/basic/bots/_bots.dm b/code/modules/mob/living/basic/bots/_bots.dm index ecf52f92ecd6..4e9571ea7ae8 100644 --- a/code/modules/mob/living/basic/bots/_bots.dm +++ b/code/modules/mob/living/basic/bots/_bots.dm @@ -106,8 +106,8 @@ GLOBAL_LIST_INIT(command_strings, list( RegisterSignal(src, COMSIG_MOVABLE_MOVED, PROC_REF(handle_loop_movement)) RegisterSignal(src, COMSIG_ATOM_WAS_ATTACKED, PROC_REF(after_attacked)) RegisterSignal(src, COMSIG_MOB_TRIED_ACCESS, PROC_REF(attempt_access)) - ADD_TRAIT(src, TRAIT_NO_GLIDE, INNATE_TRAIT) LoadComponent(/datum/component/bloodysoles/bot) + add_traits(list(TRAIT_NO_GLIDE, TRAIT_SILICON_EMOTES_ALLOWED), INNATE_TRAIT) GLOB.bots_list += src // Give bots a fancy new ID card that can hold any access. diff --git a/code/modules/mob/living/basic/pets/orbie/orbie.dm b/code/modules/mob/living/basic/pets/orbie/orbie.dm index bf834365c6bd..46f8f5702a34 100644 --- a/code/modules/mob/living/basic/pets/orbie/orbie.dm +++ b/code/modules/mob/living/basic/pets/orbie/orbie.dm @@ -51,6 +51,7 @@ var/static/list/food_types = list(/obj/item/food/virtual_chocolate) AddComponent(/datum/component/obeys_commands, pet_commands) AddElement(/datum/element/basic_eating, food_types = food_types) + ADD_TRAIT(src, TRAIT_SILICON_EMOTES_ALLOWED, INNATE_TRAIT) RegisterSignal(src, COMSIG_VIRTUAL_PET_LEVEL_UP, PROC_REF(on_level_up)) RegisterSignal(src, COMSIG_ATOM_UPDATE_LIGHT_ON, PROC_REF(on_lights)) ai_controller.set_blackboard_key(BB_BASIC_FOODS, typecacheof(food_types)) diff --git a/code/modules/mob/living/brain/brain.dm b/code/modules/mob/living/brain/brain.dm index da3cfd725208..e69caa41012a 100644 --- a/code/modules/mob/living/brain/brain.dm +++ b/code/modules/mob/living/brain/brain.dm @@ -16,6 +16,7 @@ forceMove(OB) if(!container?.mecha) //Unless inside a mecha, brains are rather helpless. add_traits(list(TRAIT_IMMOBILIZED, TRAIT_HANDS_BLOCKED), BRAIN_UNAIDED) + ADD_TRAIT(src, TRAIT_SILICON_EMOTES_ALLOWED, INNATE_TRAIT) /mob/living/brain/on_changed_z_level(turf/old_turf, turf/new_turf, same_z_layer, notify_contents) diff --git a/code/modules/mob/living/carbon/carbon_defense.dm b/code/modules/mob/living/carbon/carbon_defense.dm index 25ac715ee08d..b05de5c90c5b 100644 --- a/code/modules/mob/living/carbon/carbon_defense.dm +++ b/code/modules/mob/living/carbon/carbon_defense.dm @@ -460,16 +460,31 @@ switch(damage) if(1) - to_chat(src, span_warning("Your eyes sting a little.")) + organ_feedback_message( + eyes, + "warning", + "Your eyes sting a little.", + "Your visual sensors flash briefly.", + ) if(prob(40)) eyes.apply_organ_damage(1) if(2) - to_chat(src, span_warning("Your eyes burn.")) + organ_feedback_message( + eyes, + "warning", + "Your eyes burn.", + "Your visual sensors malfunction.", + ) eyes.apply_organ_damage(rand(2, 4)) if(3 to INFINITY) - to_chat(src, span_warning("Your eyes itch and burn severely!")) + organ_feedback_message( + eyes, + "warning", + "Your eyes itch and burn severely!", + "Your visual sensors malfunction severely!", + ) eyes.apply_organ_damage(rand(12, 16)) if(eyes.damage > 10) @@ -478,15 +493,30 @@ if(eyes.damage > eyes.low_threshold) if(!is_nearsighted_from(EYE_DAMAGE) && prob(eyes.damage - eyes.low_threshold)) - to_chat(src, span_warning("Your eyes start to burn badly!")) + organ_feedback_message( + eyes, + "warning", + "Your eyes start to burn badly!", + "Your visual sensors report severe damage!", + ) eyes.apply_organ_damage(eyes.low_threshold) else if(!is_blind() && prob(eyes.damage - eyes.high_threshold)) - to_chat(src, span_warning("You can't see anything!")) + organ_feedback_message( + eyes, + "boldwarning", + "You can't see anything!", + "Your visual sensors fail completely!", + ) eyes.apply_organ_damage(eyes.maxHealth) else - to_chat(src, span_warning("Your eyes are really starting to hurt. This can't be good for you!")) + organ_feedback_message( + eyes, + "warning", + "Your eyes are really starting to hurt. This can't be good for you!", + "Your visual sensors flash. This can't be good.", + ) return TRUE else if(damage == 0 && prob(20)) // just enough protection diff --git a/code/modules/mob/living/carbon/human/_species.dm b/code/modules/mob/living/carbon/human/_species.dm index 980de7d296fa..ab970986f238 100644 --- a/code/modules/mob/living/carbon/human/_species.dm +++ b/code/modules/mob/living/carbon/human/_species.dm @@ -411,7 +411,7 @@ GLOBAL_LIST_EMPTY(features_by_species) //Resets blood if it is excessively high so they don't gib normalize_blood(human_who_gained_species) - add_body_markings(human_who_gained_species) + add_body_markings(human_who_gained_species, update = FALSE) if(length(inherent_traits)) human_who_gained_species.add_traits(inherent_traits, SPECIES_TRAIT) @@ -472,7 +472,7 @@ GLOBAL_LIST_EMPTY(features_by_species) C.physiology?.cold_mod /= coldmod C.physiology?.heat_mod /= heatmod - remove_body_markings(C) + remove_body_markings(C, update = FALSE) // Removes all languages previously associated with [LANGUAGE_SPECIES], gaining our new species will add new ones back var/datum/language_holder/losing_holder = GLOB.prototype_language_holders[species_language_holder] @@ -999,7 +999,7 @@ GLOBAL_LIST_EMPTY(features_by_species) /// Returns a list of strings representing features this species has. /// Used by the preferences UI to know what buttons to show. -/datum/species/proc/get_features() +/datum/species/proc/get_features() as /list var/cached_features = GLOB.features_by_species[type] if (!isnull(cached_features)) return cached_features @@ -1025,9 +1025,15 @@ GLOBAL_LIST_EMPTY(features_by_species) return features -/// Returns a list of features not applicable to the species given a preference set. +/// Returns a list of strings representing features this species has, filtered by the given preferences. /datum/species/proc/get_filtered_features_per_prefs(datum/preferences/prefs) - return list() + var/list/features = get_features().Copy() // don't mutate the cached version + filter_features_per_prefs(features, prefs) // filter, pass by ref + return features + +/// Passed a list of features, will filter them according to the given preferences. +/datum/species/proc/filter_features_per_prefs(list/to_filter, datum/preferences/prefs) + return /// Given a human, will adjust it before taking a picture for the preferences UI. /// This should create a CONSISTENT result, so the icons don't randomly change. @@ -1620,26 +1626,36 @@ GLOBAL_LIST_EMPTY(features_by_species) return null /// Add species appropriate body markings -/datum/species/proc/add_body_markings(mob/living/carbon/human/hooman) +/datum/species/proc/add_body_markings(mob/living/carbon/human/hooman, update = FALSE) + var/need_update = FALSE for(var/markings_type in body_markings) //loop through possible species markings var/datum/bodypart_overlay/simple/body_marking/markings = new markings_type() // made to die... mostly because we cant use initial on lists but its convenient and organized var/accessory_name = hooman.dna.features[markings.dna_feature_key] || body_markings[markings_type] //get the accessory name from dna for(var/obj/item/bodypart/part as anything in markings.applies_to) //check through our limbs var/obj/item/bodypart/people_part = hooman.get_bodypart(initial(part.body_zone)) // and see if we have a compatible marking for that limb - if(isnull(people_part)) + if(isnull(people_part) || (locate(markings_type) in people_part.bodypart_overlays)) continue var/datum/bodypart_overlay/simple/body_marking/overlay = new markings_type() overlay.set_appearance(accessory_name, hooman.dna.features["mcolor"]) - people_part.add_bodypart_overlay(overlay) + people_part.add_bodypart_overlay(overlay, update = FALSE) + need_update = TRUE qdel(markings) + if(need_update && update) + hooman.update_body_parts() + /// Remove body markings -/datum/species/proc/remove_body_markings(mob/living/carbon/human/hooman) +/datum/species/proc/remove_body_markings(mob/living/carbon/human/hooman, update = TRUE) + var/need_update = FALSE for(var/obj/item/bodypart/part as anything in hooman.bodyparts) for(var/datum/bodypart_overlay/simple/body_marking/marking in part.bodypart_overlays) - part.remove_bodypart_overlay(marking) + part.remove_bodypart_overlay(marking, update = FALSE) + need_update = TRUE + + if(need_update && update) + hooman.update_body_parts() /** * Calculates the expected height values for this species diff --git a/code/modules/mob/living/carbon/human/species_types/android.dm b/code/modules/mob/living/carbon/human/species_types/android.dm deleted file mode 100644 index 89ea2793b746..000000000000 --- a/code/modules/mob/living/carbon/human/species_types/android.dm +++ /dev/null @@ -1,101 +0,0 @@ -/datum/species/android - name = "Android" - id = SPECIES_ANDROID - inherent_traits = list( - TRAIT_GENELESS, - TRAIT_LIMBATTACHMENT, - TRAIT_LIVERLESS_METABOLISM, - TRAIT_NOBLOOD, - TRAIT_NOBREATH, - TRAIT_NOCRITDAMAGE, - TRAIT_NOFIRE, - TRAIT_NOHUNGER, - TRAIT_NO_DNA_COPY, - TRAIT_NO_PLASMA_TRANSFORM, - TRAIT_NO_UNDERWEAR, - TRAIT_OVERDOSEIMMUNE, - TRAIT_PIERCEIMMUNE, - TRAIT_RADIMMUNE, - TRAIT_RESISTCOLD, - TRAIT_RESISTHEAT, - TRAIT_RESISTHIGHPRESSURE, - TRAIT_RESISTLOWPRESSURE, - TRAIT_TOXIMMUNE, - ) - - inherent_biotypes = MOB_ROBOTIC|MOB_HUMANOID - meat = null - mutantbrain = /obj/item/organ/brain/cybernetic - mutanttongue = /obj/item/organ/tongue/robot - mutantstomach = null - mutantappendix = null - mutantheart = null - mutantliver = null - mutantlungs = null - mutanteyes = /obj/item/organ/eyes/robotic - mutantears = /obj/item/organ/ears/cybernetic - species_language_holder = /datum/language_holder/synthetic - changesource_flags = MIRROR_BADMIN | WABBAJACK | MIRROR_PRIDE | MIRROR_MAGIC | RACE_SWAP | ERT_SPAWN | SLIME_EXTRACT - - 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, - ) - -/datum/species/android/get_physical_attributes() - return "Androids are almost, but not quite, identical to fully augmented humans. \ - Unlike those, though, they're completely immune to toxin damage, don't have blood or organs (besides their head), don't get hungry, and can reattach their limbs! \ - That said, an EMP will devastate them and they cannot process any chemicals." - -/datum/species/android/get_species_description() - return "Androids are an entirely synthetic species." - -/datum/species/android/get_species_lore() - return list( - "Androids are a synthetic species created by Nanotrasen as an intermediary between humans and cyborgs." - ) - -/datum/species/android/create_pref_traits_perks() - var/list/perks = list() - perks += list(list( - SPECIES_PERK_TYPE = SPECIES_POSITIVE_PERK, - SPECIES_PERK_ICON = FA_ICON_SHIELD_ALT, - SPECIES_PERK_NAME = "Android Aptitude", - SPECIES_PERK_DESC = "As a synthetic lifeform, Androids are immune to many forms of damage humans are susceptible to. \ - Fire, cold, heat, pressure, radiation, and toxins are all ineffective against them. \ - They also can't overdose on drugs, don't need to breathe or eat, can't catch on fire, and are immune to being pierced.", - )) - perks += list(list( - SPECIES_PERK_TYPE = SPECIES_POSITIVE_PERK, - SPECIES_PERK_ICON = FA_ICON_COGS, - SPECIES_PERK_NAME = "Modular Lifeform", - SPECIES_PERK_DESC = "Android limbs are modular, allowing them to easily reattach severed bodyparts.", - )) - perks += list(list( - SPECIES_PERK_TYPE = SPECIES_NEGATIVE_PERK, - SPECIES_PERK_ICON = FA_ICON_DNA, - SPECIES_PERK_NAME = "Not Human After All", - SPECIES_PERK_DESC = "There is no humanity behind the eyes of the Android, and as such, they have no DNA to genetically alter.", - )) - return perks - -/datum/species/android/create_pref_unique_perks() - var/list/perks = list() - perks += list(list( - SPECIES_PERK_TYPE = SPECIES_NEUTRAL_PERK, - SPECIES_PERK_ICON = FA_ICON_SHIELD_HEART, - SPECIES_PERK_NAME = "Some Components Optional", - SPECIES_PERK_DESC = "Androids have very few internal organs. While they can survive without many of them, \ - they don't have any benefits from them either.", - )) - perks += list(list( - SPECIES_PERK_TYPE = SPECIES_NEGATIVE_PERK, - SPECIES_PERK_ICON = FA_ICON_ROBOT, - SPECIES_PERK_NAME = "Synthetic", - SPECIES_PERK_DESC = "Being synthetic, Androids are vulnernable to EMPs.", - )) - return perks diff --git a/code/modules/mob/living/emote.dm b/code/modules/mob/living/emote.dm index 0113118903cf..4eb0410de9bd 100644 --- a/code/modules/mob/living/emote.dm +++ b/code/modules/mob/living/emote.dm @@ -761,15 +761,6 @@ /datum/emote/living/custom/replace_pronoun(mob/user, message) return message -/datum/emote/living/beep - key = "beep" - key_third_person = "beeps" - message = "beeps." - message_param = "beeps at %t." - sound = 'sound/machines/twobeep.ogg' - mob_type_allowed_typecache = list(/mob/living/brain, /mob/living/silicon, /mob/living/basic/orbie) - emote_type = EMOTE_AUDIBLE - /datum/emote/living/inhale key = "inhale" key_third_person = "inhales" diff --git a/code/modules/mob/living/living.dm b/code/modules/mob/living/living.dm index 5a5a54f91239..51861883e1ff 100644 --- a/code/modules/mob/living/living.dm +++ b/code/modules/mob/living/living.dm @@ -1090,13 +1090,7 @@ if(!storage_is_important_recurisve && !can_reach_active_storage) active_storage.hide_contents(src) - if(!HAS_TRAIT(src, TRAIT_NOBLOOD) && !buckled && !moving_diagonally && get_turf(src) != was_loc) - // melbert todo : moving diagonally messes things up particularly if you fail to move (ie against a wall) - var/blood_flow = get_bleed_rate() - var/health_check = body_position == LYING_DOWN && prob(getBruteLoss() * 200 / maxHealth) - var/bleeding_check = blood_flow > 3 && prob(blood_flow * 16) - if(health_check || bleeding_check) - make_blood_trail(newloc, was_loc, was_facing, direct) + passive_blood_trail(newloc, was_loc, was_facing, dir) ///Called by mob Move() when the lying_angle is different than zero, to better visually simulate crawling. /mob/living/proc/lying_angle_on_movement(direct) @@ -1108,6 +1102,45 @@ /mob/living/carbon/alien/adult/lying_angle_on_movement(direct) return +/mob/living/proc/passive_blood_trail(atom/new_loc, atom/was_loc, was_facing, now_facing) + if(HAS_TRAIT(src, TRAIT_NOBLOOD) || buckled || moving_diagonally || get_turf(src) == was_loc) + return + // melbert todo : moving diagonally messes things up particularly if you fail to move (ie against a wall) + var/blood_flow = get_bleed_rate() + var/health_check = body_position == LYING_DOWN && prob(getBruteLoss() * 200 / maxHealth) + var/bleeding_check = blood_flow > 3 && prob(blood_flow * 16) + if(!health_check && !bleeding_check) + return + + var/blood_to_add = 0 + var/base_bleed_rate = get_bleed_rate() + var/base_brute = getBruteLoss() + + var/brute_ratio = round(base_brute / (maxHealth * 4), 0.1) + var/bleeding_rate = round(base_bleed_rate / 4, 0.1) + // we only leave a trail if we're below a certain blood threshold + // the more brute damage we have, or the more we're bleeding, the less blood we need to leave a trail + if(blood_volume < max(BLOOD_VOLUME_NORMAL * (1 - max(bleeding_rate, brute_ratio)), 0)) + return + + if(isnull(blood_to_add)) + blood_to_add = BLOOD_AMOUNT_PER_DECAL * 0.1 + blood_to_add += (body_position == LYING_DOWN) ? bleedDragAmount() : base_bleed_rate + // if we're very damaged or bleeding a lot, add even more blood to the trail + if(base_brute >= 300 || base_bleed_rate >= 7) + blood_to_add *= 2 + + // this is where people losing extra blood from being dragged is handled + if(body_position == LYING_DOWN) + bleed(blood_to_add, leave_pool = FALSE) + + make_blood_trail(new_loc, was_loc, was_facing, now_facing, blood_to_add, get_blood_dna_list(), get_static_viruses()) + +/mob/living/carbon/human/passive_blood_trail(atom/new_loc, atom/was_loc, was_facing, now_facing) + if(!is_bleeding()) + return + return ..() + /** * Leaves a trail of blood. * @@ -1183,41 +1216,6 @@ trail_component.add_blood_DNA(blood_dna) trail_component.adjust_bloodiness(blood_to_add) -/mob/living/make_blood_trail(turf/target_turf, turf/start, was_facing, movement_direction, blood_to_add, blood_dna, list/static_viruses) - if(HAS_TRAIT(src, TRAIT_NOBLOOD)) - return - var/base_bleed_rate = get_bleed_rate() - var/base_brute = getBruteLoss() - - var/brute_ratio = round(base_brute / (maxHealth * 4), 0.1) - var/bleeding_rate = round(base_bleed_rate / 4, 0.1) - // we only leave a trail if we're below a certain blood threshold - // the more brute damage we have, or the more we're bleeding, the less blood we need to leave a trail - if(blood_volume < max(BLOOD_VOLUME_NORMAL * (1 - max(bleeding_rate, brute_ratio)), 0)) - return - - if(isnull(static_viruses)) - static_viruses = get_static_viruses() - if(isnull(blood_dna)) - blood_dna = get_blood_dna_list() - if(isnull(blood_to_add)) - blood_to_add = BLOOD_AMOUNT_PER_DECAL * 0.1 - blood_to_add += (body_position == LYING_DOWN) ? bleedDragAmount() : base_bleed_rate - // if we're very damaged or bleeding a lot, add even more blood to the trail - if(base_brute >= 300 || base_bleed_rate >= 7) - blood_to_add *= 2 - - // this is where people losing extra blood from being dragged is handled - if(body_position == LYING_DOWN) - bleed(blood_to_add, leave_pool = FALSE) - - return ..() - -/mob/living/carbon/human/make_blood_trail(turf/target_turf, turf/start, direction, blood_to_add, blood_dna, list/static_viruses) - if(!is_bleeding()) - return - return ..() - ///Returns how much blood we're losing from being dragged a tile, from [/mob/living/proc/make_blood_trail] /mob/living/proc/bleedDragAmount() var/brute_ratio = round(getBruteLoss() / maxHealth, 0.1) diff --git a/code/modules/mob/living/silicon/robot/emote.dm b/code/modules/mob/living/silicon/robot/emote.dm index fb7857d45854..221586478232 100644 --- a/code/modules/mob/living/silicon/robot/emote.dm +++ b/code/modules/mob/living/silicon/robot/emote.dm @@ -1,5 +1,5 @@ /datum/emote/silicon - mob_type_allowed_typecache = list(/mob/living/silicon, /mob/living/simple_animal/bot, /mob/living/basic/bot) + trait_required = TRAIT_SILICON_EMOTES_ALLOWED emote_type = EMOTE_AUDIBLE /datum/emote/silicon/boop @@ -7,6 +7,14 @@ key_third_person = "boops" message = "boops." +/datum/emote/silicon/beep + key = "beep" + key_third_person = "beeps" + message = "beeps." + message_param = "beeps at %t." + emote_type = EMOTE_AUDIBLE + sound = 'sound/machines/twobeep.ogg' + /datum/emote/silicon/buzz key = "buzz" key_third_person = "buzzes" @@ -15,7 +23,6 @@ emote_type = EMOTE_AUDIBLE sound = 'sound/machines/buzz-sigh.ogg' - /datum/emote/silicon/buzz2 key = "buzz2" message = "buzzes twice." diff --git a/code/modules/mob/living/silicon/robot/robot.dm b/code/modules/mob/living/silicon/robot/robot.dm index d4dada446278..d204540fbb56 100644 --- a/code/modules/mob/living/silicon/robot/robot.dm +++ b/code/modules/mob/living/silicon/robot/robot.dm @@ -11,7 +11,7 @@ post_tipped_callback = CALLBACK(src, PROC_REF(after_tip_over)), \ post_untipped_callback = CALLBACK(src, PROC_REF(after_righted)), \ roleplay_friendly = TRUE, \ - roleplay_emotes = list(/datum/emote/silicon/buzz, /datum/emote/silicon/buzz2, /datum/emote/living/beep), \ + roleplay_emotes = list(/datum/emote/silicon/buzz, /datum/emote/silicon/buzz2, /datum/emote/silicon/beep), \ roleplay_callback = CALLBACK(src, PROC_REF(untip_roleplay))) set_wires(new /datum/wires/robot(src)) diff --git a/code/modules/mob/living/silicon/silicon.dm b/code/modules/mob/living/silicon/silicon.dm index 907bff9b8b32..7918c755c70e 100644 --- a/code/modules/mob/living/silicon/silicon.dm +++ b/code/modules/mob/living/silicon/silicon.dm @@ -77,6 +77,7 @@ ) add_traits(traits_to_apply, ROUNDSTART_TRAIT) + ADD_TRAIT(src, TRAIT_SILICON_EMOTES_ALLOWED, INNATE_TRAIT) /mob/living/silicon/Destroy() QDEL_NULL(radio) diff --git a/code/modules/mob/living/simple_animal/bot/bot.dm b/code/modules/mob/living/simple_animal/bot/bot.dm index 8705f1ca2baa..82ee4d23fe33 100644 --- a/code/modules/mob/living/simple_animal/bot/bot.dm +++ b/code/modules/mob/living/simple_animal/bot/bot.dm @@ -203,6 +203,7 @@ pa_system = new(src, automated_announcements = automated_announcements) pa_system.Grant(src) + ADD_TRAIT(src, TRAIT_SILICON_EMOTES_ALLOWED, INNATE_TRAIT) /mob/living/simple_animal/bot/Destroy() GLOB.bots_list -= src 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/modular_computers/file_system/programs/virtual_pet.dm b/code/modules/modular_computers/file_system/programs/virtual_pet.dm index e2582c60fe5e..c4016e1e5625 100644 --- a/code/modules/modular_computers/file_system/programs/virtual_pet.dm +++ b/code/modules/modular_computers/file_system/programs/virtual_pet.dm @@ -432,7 +432,7 @@ GLOBAL_LIST_EMPTY(virtual_pets_list) /datum/emote/living/jump, /datum/emote/living/shiver, /datum/emote/spin, - /datum/emote/living/beep, + /datum/emote/silicon/beep, ) data["possible_emotes"] = list("none") for(var/datum/emote/target_emote as anything in possible_emotes) diff --git a/code/modules/power/cell.dm b/code/modules/power/cell.dm index 6ef004f20c1c..efb3aef85613 100644 --- a/code/modules/power/cell.dm +++ b/code/modules/power/cell.dm @@ -255,6 +255,7 @@ connector_type = null custom_materials = null grind_results = null + rating = 0 /obj/item/stock_parts/power_store/cell/ethereal/examine(mob/user) . = ..() diff --git a/code/modules/reagents/chemistry/reagents/drinks/alcohol_reagents.dm b/code/modules/reagents/chemistry/reagents/drinks/alcohol_reagents.dm index fc871a182177..f68d73fea4ef 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 ad619f077474..44870e091687 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/bodyparts/_bodyparts.dm b/code/modules/surgery/bodyparts/_bodyparts.dm index fd01d4da1e15..5ebc806b5c80 100644 --- a/code/modules/surgery/bodyparts/_bodyparts.dm +++ b/code/modules/surgery/bodyparts/_bodyparts.dm @@ -959,9 +959,9 @@ var/mob/living/carbon/human/human_owner = owner limb_gender = (human_owner.physique == MALE) ? "m" : "f" - if(HAS_TRAIT(human_owner, TRAIT_USES_SKINTONES)) + if(HAS_TRAIT(human_owner, TRAIT_USES_SKINTONES) || HAS_TRAIT(src, TRAIT_USES_SKINTONES)) skin_tone = human_owner.skin_tone - else if(HAS_TRAIT(human_owner, TRAIT_MUTANT_COLORS)) + else if(HAS_TRAIT(human_owner, TRAIT_MUTANT_COLORS) || HAS_TRAIT(src, TRAIT_MUTANT_COLORS)) skin_tone = "" var/datum/species/owner_species = human_owner.dna.species if(owner_species.fixed_mut_color) diff --git a/code/modules/surgery/bodyparts/dismemberment.dm b/code/modules/surgery/bodyparts/dismemberment.dm index 6a11c94b3c4a..832a4511264b 100644 --- a/code/modules/surgery/bodyparts/dismemberment.dm +++ b/code/modules/surgery/bodyparts/dismemberment.dm @@ -123,7 +123,6 @@ update_icon_dropped() phantom_owner.update_health_hud() //update the healthdoll phantom_owner.update_body() - phantom_owner.update_body_parts() if(bodypart_flags & BODYPART_PSEUDOPART) drop_organs(phantom_owner) //Psuedoparts shouldn't have organs, but just in case diff --git a/code/modules/surgery/coronary_bypass.dm b/code/modules/surgery/coronary_bypass.dm index a8fab60a710b..87318d8b7095 100644 --- a/code/modules/surgery/coronary_bypass.dm +++ b/code/modules/surgery/coronary_bypass.dm @@ -111,7 +111,7 @@ /datum/surgery_step/coronary_bypass/success(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery, default_display_results = FALSE) target.setOrganLoss(ORGAN_SLOT_HEART, 60) var/obj/item/organ/heart/target_heart = target.get_organ_slot(ORGAN_SLOT_HEART) - if(target_heart) //slightly worrying if we lost our heart mid-operation, but that's life + if(target_heart && !(target_heart.organ_flags & ORGAN_UNREMOVABLE)) //slightly worrying if we lost our heart mid-operation, but that's life target_heart.operated = TRUE display_results( user, diff --git a/code/modules/surgery/gastrectomy.dm b/code/modules/surgery/gastrectomy.dm index 4b74b1015cad..569ee301d430 100644 --- a/code/modules/surgery/gastrectomy.dm +++ b/code/modules/surgery/gastrectomy.dm @@ -53,7 +53,7 @@ var/mob/living/carbon/human/target_human = target var/obj/item/organ/stomach/target_stomach = target.get_organ_slot(ORGAN_SLOT_STOMACH) target_human.setOrganLoss(ORGAN_SLOT_STOMACH, 20) // Stomachs have a threshold for being able to even digest food, so I might tweak this number - if(target_stomach) + if(target_stomach && !(target_stomach.organ_flags & ORGAN_UNREMOVABLE)) target_stomach.operated = TRUE display_results( user, diff --git a/code/modules/surgery/hepatectomy.dm b/code/modules/surgery/hepatectomy.dm index 887f7db9f37f..d8951774925e 100644 --- a/code/modules/surgery/hepatectomy.dm +++ b/code/modules/surgery/hepatectomy.dm @@ -52,7 +52,7 @@ var/mob/living/carbon/human/human_target = target var/obj/item/organ/liver/target_liver = target.get_organ_slot(ORGAN_SLOT_LIVER) human_target.setOrganLoss(ORGAN_SLOT_LIVER, 10) //not bad, not great - if(target_liver) + if(target_liver && !(target_liver.organ_flags & ORGAN_UNREMOVABLE)) target_liver.operated = TRUE display_results( user, diff --git a/code/modules/surgery/lobectomy.dm b/code/modules/surgery/lobectomy.dm index 553012bc9d60..237576a02387 100644 --- a/code/modules/surgery/lobectomy.dm +++ b/code/modules/surgery/lobectomy.dm @@ -51,7 +51,8 @@ if(ishuman(target)) var/mob/living/carbon/human/human_target = target var/obj/item/organ/lungs/target_lungs = human_target.get_organ_slot(ORGAN_SLOT_LUNGS) - target_lungs.operated = TRUE + if(target_lungs && !(target_lungs.organ_flags & ORGAN_UNREMOVABLE)) + target_lungs.operated = TRUE human_target.setOrganLoss(ORGAN_SLOT_LUNGS, 60) display_results( user, diff --git a/code/modules/surgery/organs/_organ.dm b/code/modules/surgery/organs/_organ.dm index 705be27a147f..4804daef2b50 100644 --- a/code/modules/surgery/organs/_organ.dm +++ b/code/modules/surgery/organs/_organ.dm @@ -176,6 +176,11 @@ 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) + if(isnull(owner)) + return + 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/external/_visual_organs.dm b/code/modules/surgery/organs/external/_visual_organs.dm index b045516570be..faf58d3e54bd 100644 --- a/code/modules/surgery/organs/external/_visual_organs.dm +++ b/code/modules/surgery/organs/external/_visual_organs.dm @@ -122,6 +122,10 @@ Unlike normal organs, we're actually inside a persons limbs at all times layers = EXTERNAL_ADJACENT feature_key = "horns" dyable = TRUE + color_source = ORGAN_COLOR_OVERRIDE + +/datum/bodypart_overlay/mutant/horns/override_color(obj/item/bodypart/bodypart_owner) + return bodypart_owner?.owner?.dna?.features["lizard_horn_color"] /datum/bodypart_overlay/mutant/horns/can_draw_on_bodypart(obj/item/bodypart/bodypart_owner) return !(bodypart_owner.owner?.obscured_slots & HIDEHAIR) @@ -157,10 +161,17 @@ Unlike normal organs, we're actually inside a persons limbs at all times return SSaccessories.frills_list /datum/bodypart_overlay/mutant/frills/override_color(obj/item/bodypart/bodypart_owner) - if(!bodypart_owner.owner || HAS_TRAIT(bodypart_owner.owner, TRAIT_MUTANT_COLORS)) + // prioritize a specific color they have set + if(bodypart_owner?.owner?.dna?.features["lizard_frill_color"]) + return bodypart_owner.owner.dna.features["lizard_frill_color"] + // then use body color if we should be mutant colored + if(isnull(bodypart_owner.owner) || HAS_TRAIT(bodypart_owner.owner, TRAIT_MUTANT_COLORS) || HAS_TRAIT(bodypart_owner, TRAIT_MUTANT_COLORS)) return bodypart_owner.draw_color - - return bodypart_owner.owner.dna?.features["forced_fish_color"] || bodypart_owner.draw_color + // then use forced color - for non-mutant-colored species, like piscinids + if(bodypart_owner?.owner?.dna?.features["forced_fish_color"]) + return bodypart_owner.owner.dna.features["forced_fish_color"] + // then default to body color - though this will probably be skin color, some forced color, or pure white + return bodypart_owner.draw_color ///Guess what part of the lizard this is? /obj/item/organ/snout diff --git a/code/modules/surgery/organs/internal/ears/_ears.dm b/code/modules/surgery/organs/internal/ears/_ears.dm index a1db3415f969..eb08c8baa972 100644 --- a/code/modules/surgery/organs/internal/ears/_ears.dm +++ b/code/modules/surgery/organs/internal/ears/_ears.dm @@ -42,7 +42,7 @@ if(organ_flags & ORGAN_FAILING) return adjustEarDamage(0, -0.5 * seconds_per_tick) - if((damage > low_threshold) && SPT_PROB(damage / 60, seconds_per_tick)) + if((damage > low_threshold) && IS_ORGANIC_ORGAN(src) && SPT_PROB(damage / 60, seconds_per_tick)) adjustEarDamage(0, 4) SEND_SOUND(owner, sound('sound/weapons/flash_ring.ogg')) diff --git a/code/modules/surgery/organs/internal/lungs/_lungs.dm b/code/modules/surgery/organs/internal/lungs/_lungs.dm index 52d3bc50833e..30901f61a62f 100644 --- a/code/modules/surgery/organs/internal/lungs/_lungs.dm +++ b/code/modules/surgery/organs/internal/lungs/_lungs.dm @@ -782,6 +782,9 @@ /obj/item/organ/lungs/proc/handle_breath_temperature(datum/gas_mixture/breath, mob/living/carbon/human/breather) // called by human/life, handles temperatures + if(SEND_SIGNAL(breather, COMSIG_HUMAN_ON_HANDLE_BREATH_TEMPERATURE, breath, src) & HANDLE_BREATH_TEMPERATURE_HANDLED) + return + var/breath_temperature = breath.temperature if(breath_temperature < cold_level_warning_threshold && !HAS_TRAIT(breather, TRAIT_RESISTCOLD) && !breather.has_reagent(/datum/reagent/medicine/cryoxadone, needs_metabolizing = TRUE)) // COLD DAMAGE diff --git a/code/modules/surgery/organs/internal/stomach/_stomach.dm b/code/modules/surgery/organs/internal/stomach/_stomach.dm index 4e34e729b6cb..61c96a81704b 100644 --- a/code/modules/surgery/organs/internal/stomach/_stomach.dm +++ b/code/modules/surgery/organs/internal/stomach/_stomach.dm @@ -264,6 +264,17 @@ return span_warning("Your stomach hurts.") return span_boldwarning("Your stomach cramps in pain!") +/// Returns how full this stomach is for the hunger bar +/// If you pass skip_contents = TRUE, it does not factor in any contents of the stomach +/obj/item/organ/stomach/proc/get_hungerbar_fullness(skip_contents = FALSE) + if(HAS_TRAIT(owner, TRAIT_NOHUNGER)) + return NUTRITION_LEVEL_FED + if(HAS_TRAIT(owner, TRAIT_FAT)) + return NUTRITION_LEVEL_FAT + if(skip_contents) + return owner.nutrition + return owner.get_fullness(only_consumable = TRUE) + /obj/item/organ/stomach/bone name = "mass of bones" desc = "You have no idea what this strange ball of bones does." diff --git a/code/modules/surgery/organs/internal/stomach/stomach_ethereal.dm b/code/modules/surgery/organs/internal/stomach/stomach_ethereal.dm index ebb748d22895..313b0c6875cd 100644 --- a/code/modules/surgery/organs/internal/stomach/stomach_ethereal.dm +++ b/code/modules/surgery/organs/internal/stomach/stomach_ethereal.dm @@ -3,10 +3,14 @@ icon_state = "stomach-p" //Welp. At least it's more unique in functionaliy. desc = "A crystal-like organ that stores the electric charge of ethereals." organ_traits = list(TRAIT_NOHUNGER) // We have our own hunger mechanic. + food_reagents = list() + organ_flags = parent_type::organ_flags & ~ORGAN_EDIBLE /// Where the energy of the stomach is stored. 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 +22,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) @@ -27,6 +31,9 @@ RegisterSignal(stomach_owner, COMSIG_LIVING_ELECTROCUTE_ACT, PROC_REF(on_electrocute)) RegisterSignal(stomach_owner, COMSIG_LIVING_HOMEOSTASIS, PROC_REF(handle_temp)) RegisterSignal(stomach_owner, COMSIG_MOVABLE_MOVED, PROC_REF(handle_move)) + RegisterSignal(stomach_owner, COMSIG_MOB_HUD_CREATED, PROC_REF(update_hunger_icon)) + RegisterSignal(stomach_owner, COMSIG_LIVING_POST_FULLY_HEAL, PROC_REF(on_aheal)) + update_hunger_icon() /obj/item/organ/stomach/ethereal/on_mob_remove(mob/living/carbon/stomach_owner) . = ..() @@ -34,13 +41,34 @@ UnregisterSignal(stomach_owner, COMSIG_LIVING_ELECTROCUTE_ACT) UnregisterSignal(stomach_owner, COMSIG_LIVING_HOMEOSTASIS) UnregisterSignal(stomach_owner, COMSIG_MOVABLE_MOVED) + UnregisterSignal(stomach_owner, COMSIG_LIVING_POST_FULLY_HEAL) stomach_owner.clear_mood_event("charge") - stomach_owner.clear_alert(ALERT_ETHEREAL_CHARGE) - stomach_owner.clear_alert(ALERT_ETHEREAL_OVERCHARGE) + // stomach_owner.clear_alert(ALERT_ETHEREAL_CHARGE) + // stomach_owner.clear_alert(ALERT_ETHEREAL_OVERCHARGE) + UnregisterSignal(stomach_owner, COMSIG_MOB_HUD_CREATED) + stomach_owner.hud_used?.hunger?.reset_food_image() + +/obj/item/organ/stomach/ethereal/get_hungerbar_fullness(skip_contents = FALSE) + // we need to convert charge (which is on a scale 0-20000+) + // into effective fullness, which is on a scale of 0-600+ + return (skip_contents ? cell.charge() : effective_charge()) / ETHEREAL_CHARGE_OVERLOAD * 600 + +/// Calculates effective charge, rather than actual charge, though they may often be the same. +/obj/item/organ/stomach/ethereal/proc/effective_charge() + return cell.charge() + +/obj/item/organ/stomach/ethereal/proc/update_hunger_icon(datum/source) + SIGNAL_HANDLER + owner.hud_used?.hunger?.set_food_image('icons/obj/machines/cell_charger.dmi', "9v_cell") /obj/item/organ/stomach/ethereal/handle_hunger_slowdown(mob/living/carbon/human/human) human.add_or_update_variable_movespeed_modifier(/datum/movespeed_modifier/hunger, multiplicative_slowdown = (1.5 * (1 - cell.charge() / 100))) +/obj/item/organ/stomach/ethereal/proc/on_aheal(datum/source, heal_flags) + SIGNAL_HANDLER + if(heal_flags & HEAL_STATUS) + cell.charge = ETHEREAL_CHARGE_FULL + /obj/item/organ/stomach/ethereal/proc/charge(datum/source, datum/callback/charge_cell, seconds_per_tick) SIGNAL_HANDLER @@ -83,33 +111,33 @@ 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) + // 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) + // 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) + // 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.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.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) + // carbon.clear_alert(ALERT_ETHEREAL_CHARGE) + // carbon.clear_alert(ALERT_ETHEREAL_OVERCHARGE) /obj/item/organ/stomach/ethereal/proc/discharge_process(mob/living/carbon/carbon) to_chat(carbon, span_warning("You begin to lose control over your charge!")) @@ -119,13 +147,8 @@ overcharge = overcharge || mutable_appearance('icons/effects/effects.dmi', "electricity", EFFECTS_LAYER) carbon.add_overlay(overcharge) - if(do_after(carbon, 5 SECONDS, timed_action_flags = (IGNORE_USER_LOC_CHANGE|IGNORE_HELD_ITEM|IGNORE_INCAPACITATED))) - if(ishuman(carbon)) - var/mob/living/carbon/human/human = carbon - if(human.dna?.species) - //fixed_mut_color is also ethereal color (for some reason) - carbon.flash_lighting_fx(5, 7, human.dna.species.fixed_mut_color ? human.dna.species.fixed_mut_color : human.dna.features["mcolor"]) - + if(do_after(carbon, 5 SECONDS, timed_action_flags = (IGNORE_USER_LOC_CHANGE|IGNORE_HELD_ITEM|IGNORE_INCAPACITATED|IGNORE_SLOWDOWNS))) + carbon.flash_lighting_fx(5, 7, carbon.dna?.species?.fixed_mut_color || carbon.dna?.features["mcolor"]) playsound(carbon, 'sound/magic/lightningshock.ogg', 100, TRUE, extrarange = 5) carbon.cut_overlay(overcharge) // Only a small amount of the energy gets discharged as the zap. The rest dissipates as heat. Keeps the damage and energy from the zap the same regardless of what STANDARD_CELL_CHARGE is. diff --git a/code/modules/surgery/organs/internal/tongue/_tongue.dm b/code/modules/surgery/organs/internal/tongue/_tongue.dm index 61bd02e8e5f6..58df13353e5f 100644 --- a/code/modules/surgery/organs/internal/tongue/_tongue.dm +++ b/code/modules/surgery/organs/internal/tongue/_tongue.dm @@ -561,6 +561,7 @@ GLOBAL_LIST_INIT(english_to_zombie, list()) attack_verb_simple = list("beep", "boop") modifies_speech = TRUE taste_sensitivity = 25 // not as good as an organic tongue + organ_traits = list(TRAIT_SILICON_EMOTES_ALLOWED) voice_filter = "alimiter=0.9,acompressor=threshold=0.2:ratio=20:attack=10:release=50:makeup=2,highpass=f=1000" /obj/item/organ/tongue/robot/can_speak_language(language) diff --git a/code/modules/unit_tests/screenshots/screenshot_humanoids__datum_species_synth.png b/code/modules/unit_tests/screenshots/screenshot_humanoids__datum_species_android_synth.png similarity index 100% rename from code/modules/unit_tests/screenshots/screenshot_humanoids__datum_species_synth.png rename to code/modules/unit_tests/screenshots/screenshot_humanoids__datum_species_android_synth.png 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 beceaf9aa32c..8409229016a7 100644 --- a/maplestation.dme +++ b/maplestation.dme @@ -5038,7 +5038,6 @@ #include "code\modules\mob\living\carbon\human\status_procs.dm" #include "code\modules\mob\living\carbon\human\suicides.dm" #include "code\modules\mob\living\carbon\human\species_types\abductors.dm" -#include "code\modules\mob\living\carbon\human\species_types\android.dm" #include "code\modules\mob\living\carbon\human\species_types\dullahan.dm" #include "code\modules\mob\living\carbon\human\species_types\ethereal.dm" #include "code\modules\mob\living\carbon\human\species_types\felinid.dm" @@ -6615,7 +6614,14 @@ #include "maplestation_modules\code\modules\language\skrellian.dm" #include "maplestation_modules\code\modules\library\skill_learning\job_skillchips\medbay.dm" #include "maplestation_modules\code\modules\library\skill_learning\job_skillchips\mining.dm" -#include "maplestation_modules\code\modules\loadouts\loadout_items\_limb_datums.dm" +#include "maplestation_modules\code\modules\loadouts\limb_items\_limb_datums.dm" +#include "maplestation_modules\code\modules\loadouts\limb_items\limb_base.dm" +#include "maplestation_modules\code\modules\loadouts\limb_items\limb_mimics.dm" +#include "maplestation_modules\code\modules\loadouts\limb_items\limb_organs.dm" +#include "maplestation_modules\code\modules\loadouts\limb_items\limb_reskin_chests.dm" +#include "maplestation_modules\code\modules\loadouts\limb_items\limb_reskin_heads.dm" +#include "maplestation_modules\code\modules\loadouts\limb_items\limb_reskins_arms.dm" +#include "maplestation_modules\code\modules\loadouts\limb_items\limb_reskins_legs.dm" #include "maplestation_modules\code\modules\loadouts\loadout_items\_loadout_categories.dm" #include "maplestation_modules\code\modules\loadouts\loadout_items\_loadout_datum.dm" #include "maplestation_modules\code\modules\loadouts\loadout_items\loadout_datum_accessory.dm" @@ -6726,6 +6732,16 @@ #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_ears.dm" +#include "maplestation_modules\code\modules\mob\living\carbon\human\species_types\synth\android_effects.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\organ_feedback.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" @@ -6768,6 +6784,7 @@ #include "maplestation_modules\code\modules\robotic_limb_detach\robot_limb_detach_quirk.dm" #include "maplestation_modules\code\modules\surgery\bodyparts\cyber_arms.dm" #include "maplestation_modules\code\modules\surgery\bodyparts\cyber_digi.dm" +#include "maplestation_modules\code\modules\surgery\bodyparts\cyber_humanoid.dm" #include "maplestation_modules\code\modules\surgery\bodyparts\cyber_reskins.dm" #include "maplestation_modules\code\modules\surgery\bodyparts\cyber_tech.dm" #include "maplestation_modules\code\modules\surgery\organs\augments_arms.dm" diff --git a/maplestation_modules/code/__DEFINES/DNA.dm b/maplestation_modules/code/__DEFINES/DNA.dm index c4249c288de3..8ad4b5d8fdbb 100644 --- a/maplestation_modules/code/__DEFINES/DNA.dm +++ b/maplestation_modules/code/__DEFINES/DNA.dm @@ -3,5 +3,4 @@ //Species Defines #define SPECIES_SKRELL "skrell" #define SPECIES_REPLOID "reploid" -#define SPECIES_SYNTH "synth" #define SPECIES_ORNITHID "ornithid" diff --git a/maplestation_modules/code/modules/client/preferences/limbs_preference.dm b/maplestation_modules/code/modules/client/preferences/limbs_preference.dm index c051242d451e..6464605b17f2 100644 --- a/maplestation_modules/code/modules/client/preferences/limbs_preference.dm +++ b/maplestation_modules/code/modules/client/preferences/limbs_preference.dm @@ -28,10 +28,18 @@ else in_order_datums["Other"] += equipping + var/need_remarking = FALSE for(var/to_apply_key in in_order_datums) for(var/datum/limb_option_datum/equipping_datum as anything in in_order_datums[to_apply_key]) - if(equipping_datum.can_be_applied(target)) - equipping_datum.apply_limb(target) + if(!equipping_datum.can_be_applied(target)) + continue + equipping_datum.apply_limb(target) + if(ispath(equipping_datum.limb_path, /obj/item/bodypart)) + need_remarking = TRUE + + // Reapply body markings after changing limbs, to ensure they are on the correct bodyparts + if(need_remarking) + target.dna.species.add_body_markings(target, update = FALSE) /datum/preference/limbs/deserialize(input, datum/preferences/preferences) var/list/corrected_list = list() diff --git a/maplestation_modules/code/modules/client/preferences/loadout_preference.dm b/maplestation_modules/code/modules/client/preferences/loadout_preference.dm index cf224e4d7508..9135366d7e27 100644 --- a/maplestation_modules/code/modules/client/preferences/loadout_preference.dm +++ b/maplestation_modules/code/modules/client/preferences/loadout_preference.dm @@ -63,8 +63,3 @@ LAZYSET(sanitized_list, real_path, LAZYLISTDUPLICATE(data)) return sanitized_list - -/datum/preferences/update_character(current_version, list/save_data) - . = ..() - if(current_version < 43.1) - save_loadout(src, save_data?["loadout_list"]) diff --git a/maplestation_modules/code/modules/client/preferences/runechat_color.dm b/maplestation_modules/code/modules/client/preferences/runechat_color.dm index 20e70ebcec96..bb2d7ac424c0 100644 --- a/maplestation_modules/code/modules/client/preferences/runechat_color.dm +++ b/maplestation_modules/code/modules/client/preferences/runechat_color.dm @@ -4,12 +4,13 @@ savefile_identifier = PREFERENCE_CHARACTER priority = PREFERENCE_PRIORITY_NAME_MODIFICATIONS // go after names please category = PREFERENCE_CATEGORY_NON_CONTEXTUAL + nullable = TRUE /datum/preference/color/runechat_color/create_default_value() - return "#aaaaaa" + return null /datum/preference/color/runechat_color/apply_to_human(mob/living/carbon/human/target, value) - if(value == create_default_value()) + if(isnull(value)) return target.chat_color = value @@ -18,7 +19,4 @@ GLOB.forced_runechat_names[target.name] = value /datum/preference/color/runechat_color/is_valid(value) - if (!..(value)) - return FALSE - - return !is_color_dark(value) + return ..() && (isnull(value) || !is_color_dark(value)) 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/lizard.dm b/maplestation_modules/code/modules/client/preferences/species/lizard.dm index 8e193062f03d..0e142b8815f5 100644 --- a/maplestation_modules/code/modules/client/preferences/species/lizard.dm +++ b/maplestation_modules/code/modules/client/preferences/species/lizard.dm @@ -41,8 +41,9 @@ /datum/species/lizard/get_features() return ..() | HAIR_PREFERENCES -/datum/species/lizard/get_filtered_features_per_prefs(datum/preferences/prefs) - return prefs.read_preference(/datum/preference/toggle/hair_lizard) ? list() : HAIR_PREFERENCES +/datum/species/lizard/filter_features_per_prefs(list/to_filter, datum/preferences/prefs) + if(!prefs.read_preference(/datum/preference/toggle/hair_lizard)) + to_filter -= HAIR_PREFERENCES #undef HAIR_PREFERENCES @@ -68,24 +69,6 @@ return tongue.draw_length = value -// -- Allows lizard horns to be colorable -- -// (Because some choices are greyscaled) -/datum/preference/choiced/lizard_horns - relevant_external_organ = /obj/item/organ/horns - -/datum/preference/choiced/lizard_horns/compile_constant_data() - var/list/data = ..() - data[SUPPLEMENTAL_FEATURE_KEY] = "feature_lizard_horn_color" - return data - -// Makes the bodypart update correctly -/datum/bodypart_overlay/mutant/horns - color_source = ORGAN_COLOR_OVERRIDE - -/datum/bodypart_overlay/mutant/horns/inherit_color(obj/item/bodypart/bodypart_owner, force) - draw_color = bodypart_owner?.owner?.dna?.features["lizard_horn_color"] || "#dddddd" - return TRUE - // The actual preference /datum/preference/color/horn_color savefile_key = "feature_lizard_horn_color" @@ -93,12 +76,13 @@ category = PREFERENCE_CATEGORY_SUPPLEMENTAL_FEATURES relevant_external_organ = /obj/item/organ/horns can_randomize = FALSE + nullable = TRUE /datum/preference/color/horn_color/apply_to_human(mob/living/carbon/human/target, value) target.dna.features["lizard_horn_color"] = value /datum/preference/color/horn_color/create_default_value() - return "#dddddd" + return null // -- Lizard Horn Layer selection -- // Makes it actually work @@ -134,6 +118,20 @@ /datum/preference/choiced/lizard_horn_layer/init_possible_values() return layer_to_layer +/datum/preference/color/lizard_frill_color + savefile_key = "feature_lizard_frill_color" + savefile_identifier = PREFERENCE_CHARACTER + category = PREFERENCE_CATEGORY_SUPPLEMENTAL_FEATURES + relevant_external_organ = /obj/item/organ/frills + can_randomize = FALSE + nullable = TRUE + +/datum/preference/color/lizard_frill_color/apply_to_human(mob/living/carbon/human/target, value) + target.dna.features["lizard_frill_color"] = value + +/datum/preference/color/lizard_frill_color/create_default_value() + return null + // -- Lets you change layer of lizard frills -- // Makes the bodypart update correctly /datum/bodypart_overlay/mutant/frills/get_image(image_layer, obj/item/bodypart/limb) diff --git a/maplestation_modules/code/modules/client/preferences/species/synth.dm b/maplestation_modules/code/modules/client/preferences/species/synth.dm index 7abfb5581d47..273a6ca35a20 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/android/synth/synth = GLOB.species_prototypes[/datum/species/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/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/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/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/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/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/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/android/droid = GLOB.species_prototypes[/datum/species/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/android)) + return FALSE + if(ispath(pref_species, /datum/species/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/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/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/limb_items/_limb_datums.dm b/maplestation_modules/code/modules/loadouts/limb_items/_limb_datums.dm new file mode 100644 index 000000000000..7a95d8b5e1a5 --- /dev/null +++ b/maplestation_modules/code/modules/loadouts/limb_items/_limb_datums.dm @@ -0,0 +1,108 @@ +/// An assoc list of [limb typepath] to [singleton limb datum]s used in the limb manager +GLOBAL_LIST_INIT(limb_loadout_options, init_loadout_limb_options()) + +/// Inits the limb manager global list +/proc/init_loadout_limb_options() + var/list/created = list() + for(var/datum/limb_option_datum/to_create as anything in typesof(/datum/limb_option_datum)) + var/obj/item/limb_path = initial(to_create.limb_path) + if(isnull(limb_path)) + continue + + created[limb_path] = new to_create() + + return created + +/** + * Used as holders for paths to be used in the limb editor menu + * + * Similar to loadout datums but, for limbs and organs that one can start roundstart with + * + * I could've just tied this into loadout datums (they're pretty much the same thing) + * but I would rather keep the typepaths separate for ease of use + */ +/datum/limb_option_datum + /// Name shown up in UI + var/name + /// Used in UI tooltips + var/tooltip + /// The actual item that is created and equipped to the player + var/obj/item/limb_path + /// Determines what body zone this is slotted into in the UI + /// Uses the following limb body zones: + /// [BODY_ZONE_HEAD], [BODY_ZONE_CHEST], [BODY_ZONE_R_ARM], [BODY_ZONE_L_ARM], [BODY_ZONE_R_LEG], [BODY_ZONE_L_LEG] + var/ui_zone + /// Determines what key the path of this is slotted into in the assoc list of preferences + /// A bodypart might use their body zone while an organ may use their organ slot + /// This essently determines what other datums this datum is incompatible with + var/pref_list_slot + /// Icon used in the UI + var/ui_icon + /// Icon state used in the UI + var/ui_icon_state + +/datum/limb_option_datum/New() + . = ..() + if(isnull(name)) + name = capitalize(initial(limb_path.name)) + if(isnull(ui_icon)) + ui_icon = initial(limb_path.icon) + if(isnull(ui_icon_state)) + ui_icon_state = replacetext(initial(limb_path.icon_state), "borg", BODYPART_ID_ROBOTIC) + +/* + * Can this datum be selected by the user? + * + * Return TRUE if the user can select this datum + * Return FALSE if the user cannot select this datum + */ +/datum/limb_option_datum/proc/can_be_selected(datum/preferences/prefs) + return TRUE + +/// Can this datum be applied to the mob? +/datum/limb_option_datum/proc/can_be_applied(mob/living/carbon/human/apply_to) + return TRUE + +/// Applies the datum to the mob. +/datum/limb_option_datum/proc/apply_limb(mob/living/carbon/human/apply_to) + return + +/datum/limb_option_datum/bodypart + +/datum/limb_option_datum/bodypart/New() + . = ..() + var/obj/item/bodypart/part_path = limb_path + if(isnull(ui_zone)) + ui_zone = initial(part_path.body_zone) + if(isnull(pref_list_slot)) + pref_list_slot = initial(part_path.body_zone) + +/datum/limb_option_datum/bodypart/apply_limb(mob/living/carbon/human/apply_to) + 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/organ + +/datum/limb_option_datum/organ/New() + . = ..() + var/obj/item/organ/organ_path = limb_path + if(isnull(ui_zone)) + ui_zone = deprecise_zone(initial(organ_path.zone)) + if(isnull(pref_list_slot)) + pref_list_slot = initial(organ_path.slot) + +/datum/limb_option_datum/organ/apply_limb(mob/living/carbon/human/apply_to) + if(istype(apply_to, /mob/living/carbon/human/dummy)) + var/obj/item/organ/organ_path = limb_path + if(!organ_path::visual) + return + + var/obj/item/organ/new_organ = new limb_path() + new_organ.Insert(apply_to, special = TRUE, movement_flags = DELETE_IF_REPLACED) diff --git a/maplestation_modules/code/modules/loadouts/limb_items/limb_base.dm b/maplestation_modules/code/modules/loadouts/limb_items/limb_base.dm new file mode 100644 index 000000000000..667d01c6e55e --- /dev/null +++ b/maplestation_modules/code/modules/loadouts/limb_items/limb_base.dm @@ -0,0 +1,55 @@ +// Prosthetic +/datum/limb_option_datum/bodypart/prosthetic_r_leg + name = "Prosthetic Right Leg" + limb_path = /obj/item/bodypart/leg/right/robot/surplus + +/datum/limb_option_datum/bodypart/prosthetic_l_leg + name = "Prosthetic Left Leg" + limb_path = /obj/item/bodypart/leg/left/robot/surplus + +/datum/limb_option_datum/bodypart/prosthetic_r_arm + name = "Prosthetic Right Arm" + limb_path = /obj/item/bodypart/arm/right/robot/surplus + +/datum/limb_option_datum/bodypart/prosthetic_l_arm + name = "Prosthetic Left Arm" + limb_path = /obj/item/bodypart/arm/left/robot/surplus + +// Cyber +/datum/limb_option_datum/bodypart/cybernetic_chest + name = "Cybernetic Chest" + 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/android) + +/datum/limb_option_datum/bodypart/cybernetic_chest/can_be_applied(mob/living/carbon/human/apply_to) + return is_species(apply_to, /datum/species/android) + +/datum/limb_option_datum/bodypart/cybernetic_head + name = "Cybernetic Head" + 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/android) + +/datum/limb_option_datum/bodypart/cybernetic_head/can_be_applied(mob/living/carbon/human/apply_to) + return is_species(apply_to, /datum/species/android) + +/datum/limb_option_datum/bodypart/cybernetic_r_leg + name = "Cybernetic Right Leg" + limb_path = /obj/item/bodypart/leg/right/robot + +/datum/limb_option_datum/bodypart/cybernetic_l_leg + name = "Cybernetic Left Leg" + limb_path = /obj/item/bodypart/leg/left/robot + +/datum/limb_option_datum/bodypart/cybernetic_r_arm + name = "Cybernetic Right Arm" + limb_path = /obj/item/bodypart/arm/right/robot + +/datum/limb_option_datum/bodypart/cybernetic_l_arm + name = "Cybernetic Left Arm" + limb_path = /obj/item/bodypart/arm/left/robot diff --git a/maplestation_modules/code/modules/loadouts/limb_items/limb_mimics.dm b/maplestation_modules/code/modules/loadouts/limb_items/limb_mimics.dm new file mode 100644 index 000000000000..6893b86abf8d --- /dev/null +++ b/maplestation_modules/code/modules/loadouts/limb_items/limb_mimics.dm @@ -0,0 +1,70 @@ + +/datum/limb_option_datum/bodypart/lizardlike_android + tooltip = "Unique to Lizardlike Androids." + +/datum/limb_option_datum/bodypart/lizardlike_android/can_be_selected(datum/preferences/prefs) + return ispath(prefs.read_preference(/datum/preference/choiced/species), /datum/species/android) \ + && prefs.read_preference(/datum/preference/choiced/android_species) == SPECIES_LIZARD + +/datum/limb_option_datum/bodypart/lizardlike_android/can_be_applied(mob/living/carbon/human/apply_to) + return istype(apply_to.dna?.species, /datum/species/android) \ + && apply_to.dna?.features["android_species"] == SPECIES_LIZARD + +/datum/limb_option_datum/bodypart/humanoid_android + tooltip = "Unique to Humanoid Androids." + +/datum/limb_option_datum/bodypart/humanoid_android/can_be_selected(datum/preferences/prefs) + return ispath(prefs.read_preference(/datum/preference/choiced/species), /datum/species/android) \ + && prefs.read_preference(/datum/preference/choiced/android_species) == SPECIES_HUMAN + +/datum/limb_option_datum/bodypart/humanoid_android/can_be_applied(mob/living/carbon/human/apply_to) + return is_species(apply_to, /datum/species/android) \ + && apply_to.dna?.features["android_species"] == SPECIES_HUMAN + +/datum/limb_option_datum/bodypart/humanoid_android/head + name = "Humanoid Cybernetic Head" + limb_path = /obj/item/bodypart/head/robot/humanoid + +/datum/limb_option_datum/bodypart/lizardlike_android/head + name = "Lizardlike Cybernetic Head" + limb_path = /obj/item/bodypart/head/robot/lizardlike + +/datum/limb_option_datum/bodypart/humanoid_android/chest + name = "Humanoid Cybernetic Chest" + limb_path = /obj/item/bodypart/chest/robot/humanoid + +/datum/limb_option_datum/bodypart/lizardlike_android/chest + name = "Lizardlike Cybernetic Chest" + limb_path = /obj/item/bodypart/chest/robot/lizardlike + +/datum/limb_option_datum/bodypart/humanoid_android/r_leg + name = "Humanoid Right Leg" + limb_path = /obj/item/bodypart/leg/right/robot/humanoid + +/datum/limb_option_datum/bodypart/humanoid_android/l_leg + name = "Humanoid Left Leg" + limb_path = /obj/item/bodypart/leg/left/robot/humanoid + +/datum/limb_option_datum/bodypart/humanoid_android/r_arm + name = "Humanoid Right Arm" + limb_path = /obj/item/bodypart/arm/right/robot/humanoid + +/datum/limb_option_datum/bodypart/humanoid_android/l_arm + name = "Humanoid Left Arm" + limb_path = /obj/item/bodypart/arm/left/robot/humanoid + +/datum/limb_option_datum/bodypart/lizardlike_android/r_leg + name = "Lizardlike Right Leg" + limb_path = /obj/item/bodypart/leg/right/robot/lizardlike + +/datum/limb_option_datum/bodypart/lizardlike_android/l_leg + name = "Lizardlike Left Leg" + limb_path = /obj/item/bodypart/leg/left/robot/lizardlike + +/datum/limb_option_datum/bodypart/lizardlike_android/r_arm + name = "Lizardlike Right Arm" + limb_path = /obj/item/bodypart/arm/right/robot/lizardlike + +/datum/limb_option_datum/bodypart/lizardlike_android/l_arm + name = "Lizardlike Left Arm" + limb_path = /obj/item/bodypart/arm/left/robot/lizardlike diff --git a/maplestation_modules/code/modules/loadouts/limb_items/limb_organs.dm b/maplestation_modules/code/modules/loadouts/limb_items/limb_organs.dm new file mode 100644 index 000000000000..55c9cda35d40 --- /dev/null +++ b/maplestation_modules/code/modules/loadouts/limb_items/limb_organs.dm @@ -0,0 +1,54 @@ +/datum/limb_option_datum/organ/cyber + tooltip = "Cannot be selected by Androids or Synthetics." + +/datum/limb_option_datum/organ/cyber/can_be_selected(datum/preferences/prefs) + return !ispath(prefs.read_preference(/datum/preference/choiced/species), /datum/species/android) + +/datum/limb_option_datum/organ/cyber/can_be_applied(mob/living/carbon/human/apply_to) + return !is_species(apply_to, /datum/species/android) + +/datum/limb_option_datum/organ/cyber/heart + name = "Cybernetic Heart" + limb_path = /obj/item/organ/heart/cybernetic + +/datum/limb_option_datum/organ/cyber/liver + name = "Cybernetic Liver" + limb_path = /obj/item/organ/liver/cybernetic + +/datum/limb_option_datum/organ/cyber/lungs + name = "Cybernetic Lungs" + limb_path = /obj/item/organ/lungs/cybernetic + +/datum/limb_option_datum/organ/cyber/stomach + name = "Cybernetic Stomach" + limb_path = /obj/item/organ/stomach/cybernetic + +/datum/limb_option_datum/organ/cyber/eyes + name = "Cybernetic Eyes" + limb_path = /obj/item/organ/eyes/robotic/basic + +/datum/limb_option_datum/organ/cyber/ears + name = "Cybernetic Ears" + limb_path = /obj/item/organ/ears/cybernetic + +/datum/limb_option_datum/organ/cyber/ears/cat + name = "Cybernetic Cat Ears" + limb_path = /obj/item/organ/ears/cat/cybernetic + +/datum/limb_option_datum/organ/cyber/robotongue + name = "Voicebox" + tooltip = "Replaces the tongue. Makes you sound like a robot. Cannot be selected by Androids or Synthetics." + limb_path = /obj/item/organ/tongue/robot + +/datum/limb_option_datum/organ/lighter_implant + name = "Lighter Implant" + tooltip = "A lighter implanted into the tip of your finger. Light it with a snap... like a badass." + limb_path = /obj/item/organ/cyberimp/arm/lighter + ui_zone = BODY_ZONE_R_ARM + pref_list_slot = ORGAN_SLOT_RIGHT_ARM_AUG + +/datum/limb_option_datum/organ/lighter_implant/left + limb_path = /obj/item/organ/cyberimp/arm/lighter/left + ui_zone = BODY_ZONE_L_ARM + pref_list_slot = ORGAN_SLOT_LEFT_ARM_AUG + // Yeah you can have one in both arms if you want, don't really care diff --git a/maplestation_modules/code/modules/loadouts/limb_items/limb_reskin_chests.dm b/maplestation_modules/code/modules/loadouts/limb_items/limb_reskin_chests.dm new file mode 100644 index 000000000000..5d30732d8ab2 --- /dev/null +++ b/maplestation_modules/code/modules/loadouts/limb_items/limb_reskin_chests.dm @@ -0,0 +1,59 @@ +/datum/limb_option_datum/bodypart/cybernetic_chest/engineer + name = "Nanotrasen Engineering Cybernetic Chest" + limb_path = /obj/item/bodypart/chest/robot/engineer + +/datum/limb_option_datum/bodypart/cybernetic_chest/security + name = "Nanotrasen Security Cybernetic Chest" + limb_path = /obj/item/bodypart/chest/robot/security + +/datum/limb_option_datum/bodypart/cybernetic_chest/mining + name = "Nanotrasen Mining Cybernetic Chest" + limb_path = /obj/item/bodypart/chest/robot/mining + +/datum/limb_option_datum/bodypart/cybernetic_chest/bishop + name = "Bishop Cybernetic Chest" + limb_path = /obj/item/bodypart/chest/robot/bishop + +/datum/limb_option_datum/bodypart/cybernetic_chest/bishop/mk2 + name = "Bishop MK2 Cybernetic Chest" + limb_path = /obj/item/bodypart/chest/robot/bishop/mk2 + +/datum/limb_option_datum/bodypart/cybernetic_chest/hephaestus + name = "Hephaestus Cybernetic Chest" + limb_path = /obj/item/bodypart/chest/robot/hephaestus + +/datum/limb_option_datum/bodypart/cybernetic_chest/hephaestus/mk2 + name = "Hephaestus MK2 Cybernetic Chest" + limb_path = /obj/item/bodypart/chest/robot/hephaestus/mk2 + +/datum/limb_option_datum/bodypart/cybernetic_chest/mariinsky + name = "Mariinsky Cybernetic Chest" + limb_path = /obj/item/bodypart/chest/robot/mariinsky + +/datum/limb_option_datum/bodypart/cybernetic_chest/mcg + name = "MCG Cybernetic Chest" + limb_path = /obj/item/bodypart/chest/robot/mcg + +/datum/limb_option_datum/bodypart/cybernetic_chest/sgm + name = "SGM Cybernetic Chest" + limb_path = /obj/item/bodypart/chest/robot/sgm + +/datum/limb_option_datum/bodypart/cybernetic_chest/wtm + name = "WTM Cybernetic Chest" + limb_path = /obj/item/bodypart/chest/robot/wtm + +/datum/limb_option_datum/bodypart/cybernetic_chest/xmg + name = "XMG Cybernetic Chest" + limb_path = /obj/item/bodypart/chest/robot/xmg + +/datum/limb_option_datum/bodypart/cybernetic_chest/zhenkov + name = "Zhenkov Cybernetic Chest" + limb_path = /obj/item/bodypart/chest/robot/zhenkov + +/datum/limb_option_datum/bodypart/cybernetic_chest/zhenkov/dark + name = "Zhenkov (Dark) Cybernetic Chest" + limb_path = /obj/item/bodypart/chest/robot/zhenkov/dark + +/datum/limb_option_datum/bodypart/cybernetic_chest/zhp + name = "ZHP Cybernetic Chest" + limb_path = /obj/item/bodypart/chest/robot/zhp diff --git a/maplestation_modules/code/modules/loadouts/limb_items/limb_reskin_heads.dm b/maplestation_modules/code/modules/loadouts/limb_items/limb_reskin_heads.dm new file mode 100644 index 000000000000..6cc8ecfda130 --- /dev/null +++ b/maplestation_modules/code/modules/loadouts/limb_items/limb_reskin_heads.dm @@ -0,0 +1,59 @@ +/datum/limb_option_datum/bodypart/cybernetic_head/engineer + name = "Nanotrasen Engineering Cybernetic Head" + limb_path = /obj/item/bodypart/head/robot/engineer + +/datum/limb_option_datum/bodypart/cybernetic_head/security + name = "Nanotrasen Security Cybernetic Head" + limb_path = /obj/item/bodypart/head/robot/security + +/datum/limb_option_datum/bodypart/cybernetic_head/mining + name = "Nanotrasen Mining Cybernetic Head" + limb_path = /obj/item/bodypart/head/robot/mining + +/datum/limb_option_datum/bodypart/cybernetic_head/bishop + name = "Bishop Cybernetic Head" + limb_path = /obj/item/bodypart/head/robot/bishop + +/datum/limb_option_datum/bodypart/cybernetic_head/bishop/mk2 + name = "Bishop MK2 Cybernetic Head" + limb_path = /obj/item/bodypart/head/robot/bishop/mk2 + +/datum/limb_option_datum/bodypart/cybernetic_head/hephaestus + name = "Hephaestus Cybernetic Head" + limb_path = /obj/item/bodypart/head/robot/hephaestus + +/datum/limb_option_datum/bodypart/cybernetic_head/hephaestus/mk2 + name = "Hephaestus MK2 Cybernetic Head" + limb_path = /obj/item/bodypart/head/robot/hephaestus/mk2 + +/datum/limb_option_datum/bodypart/cybernetic_head/mariinsky + name = "Mariinsky Cybernetic Head" + limb_path = /obj/item/bodypart/head/robot/mariinsky + +/datum/limb_option_datum/bodypart/cybernetic_head/mcg + name = "MCG Cybernetic Head" + limb_path = /obj/item/bodypart/head/robot/mcg + +/datum/limb_option_datum/bodypart/cybernetic_head/sgm + name = "SGM Cybernetic Head" + limb_path = /obj/item/bodypart/head/robot/sgm + +/datum/limb_option_datum/bodypart/cybernetic_head/wtm + name = "WTM Cybernetic Head" + limb_path = /obj/item/bodypart/head/robot/wtm + +/datum/limb_option_datum/bodypart/cybernetic_head/xmg + name = "XMG Cybernetic Head" + limb_path = /obj/item/bodypart/head/robot/xmg + +/datum/limb_option_datum/bodypart/cybernetic_head/zhenkov + name = "Zhenkov Cybernetic Head" + limb_path = /obj/item/bodypart/head/robot/zhenkov + +/datum/limb_option_datum/bodypart/cybernetic_head/zhenkov/dark + name = "Zhenkov (Dark) Cybernetic Head" + limb_path = /obj/item/bodypart/head/robot/zhenkov/dark + +/datum/limb_option_datum/bodypart/cybernetic_head/zhp + name = "ZHP Cybernetic Head" + limb_path = /obj/item/bodypart/head/robot/zhp diff --git a/maplestation_modules/code/modules/loadouts/limb_items/limb_reskins_arms.dm b/maplestation_modules/code/modules/loadouts/limb_items/limb_reskins_arms.dm new file mode 100644 index 000000000000..0b992a3958d5 --- /dev/null +++ b/maplestation_modules/code/modules/loadouts/limb_items/limb_reskins_arms.dm @@ -0,0 +1,129 @@ +// Left +/datum/limb_option_datum/bodypart/cybernetic_l_arm/engineer + name = "Nanotrasen Engineering Cybernetic Left Arm" + limb_path = /obj/item/bodypart/arm/left/robot/engineer + +/datum/limb_option_datum/bodypart/cybernetic_l_arm/security + name = "Nanotrasen Security Cybernetic Left Arm" + limb_path = /obj/item/bodypart/arm/left/robot/security + +/datum/limb_option_datum/bodypart/cybernetic_l_arm/mining + name = "Nanotrasen Mining Cybernetic Left Arm" + limb_path = /obj/item/bodypart/arm/left/robot/mining + +/datum/limb_option_datum/bodypart/cybernetic_l_arm/bishop + name = "Bishop Cybernetic Left Arm" + limb_path = /obj/item/bodypart/arm/left/robot/bishop + +/datum/limb_option_datum/bodypart/cybernetic_l_arm/bishop/mk2 + name = "Bishop MK2 Cybernetic Left Arm" + limb_path = /obj/item/bodypart/arm/left/robot/bishop/mk2 + +/datum/limb_option_datum/bodypart/cybernetic_l_arm/hephaestus + name = "Hephaestus Cybernetic Left Arm" + limb_path = /obj/item/bodypart/arm/left/robot/hephaestus + +/datum/limb_option_datum/bodypart/cybernetic_l_arm/hephaestus/mk2 + name = "Hephaestus MK2 Cybernetic Left Arm" + limb_path = /obj/item/bodypart/arm/left/robot/hephaestus/mk2 + +/datum/limb_option_datum/bodypart/cybernetic_l_arm/mariinsky + name = "Mariinsky Cybernetic Left Arm" + limb_path = /obj/item/bodypart/arm/left/robot/mariinsky + +/datum/limb_option_datum/bodypart/cybernetic_l_arm/mcg + name = "MCG Cybernetic Left Arm" + limb_path = /obj/item/bodypart/arm/left/robot/mcg + +/datum/limb_option_datum/bodypart/cybernetic_l_arm/sgm + name = "SGM Cybernetic Left Arm" + limb_path = /obj/item/bodypart/arm/left/robot/sgm + +/datum/limb_option_datum/bodypart/cybernetic_l_arm/wtm + name = "WTM Cybernetic Left Arm" + limb_path = /obj/item/bodypart/arm/left/robot/wtm + +/datum/limb_option_datum/bodypart/cybernetic_l_arm/xmg + name = "XMG Cybernetic Left Arm" + limb_path = /obj/item/bodypart/arm/left/robot/xmg + +/datum/limb_option_datum/bodypart/cybernetic_l_arm/zhenkov + name = "Zhenkov Cybernetic Left Arm" + limb_path = /obj/item/bodypart/arm/left/robot/zhenkov + +/datum/limb_option_datum/bodypart/cybernetic_l_arm/zhenkov/dark + name = "Zhenkov (Dark) Cybernetic Left Arm" + limb_path = /obj/item/bodypart/arm/left/robot/zhenkov/dark + +/datum/limb_option_datum/bodypart/cybernetic_l_arm/zhp + name = "ZHP Cybernetic Left Arm" + limb_path = /obj/item/bodypart/arm/left/robot/zhp + +/datum/limb_option_datum/bodypart/cybernetic_l_arm/monokai + name = "Monokai Cybernetic Left Arm" + limb_path = /obj/item/bodypart/arm/left/robot/monokai + +// Right +/datum/limb_option_datum/bodypart/cybernetic_r_arm/engineer + name = "Nanotrasen Engineering Cybernetic Right Arm" + limb_path = /obj/item/bodypart/arm/right/robot/engineer + +/datum/limb_option_datum/bodypart/cybernetic_r_arm/security + name = "Nanotrasen Security Cybernetic Right Arm" + limb_path = /obj/item/bodypart/arm/right/robot/security + +/datum/limb_option_datum/bodypart/cybernetic_r_arm/mining + name = "Nanotrasen Mining Cybernetic Right Arm" + limb_path = /obj/item/bodypart/arm/right/robot/mining + +/datum/limb_option_datum/bodypart/cybernetic_r_arm/bishop + name = "Bishop Cybernetic Right Arm" + limb_path = /obj/item/bodypart/arm/right/robot/bishop + +/datum/limb_option_datum/bodypart/cybernetic_r_arm/bishop/mk2 + name = "Bishop MK2 Cybernetic Right Arm" + limb_path = /obj/item/bodypart/arm/right/robot/bishop/mk2 + +/datum/limb_option_datum/bodypart/cybernetic_r_arm/hephaestus + name = "Hephaestus Cybernetic Right Arm" + limb_path = /obj/item/bodypart/arm/right/robot/hephaestus + +/datum/limb_option_datum/bodypart/cybernetic_r_arm/hephaestus/mk2 + name = "Hephaestus MK2 Cybernetic Right Arm" + limb_path = /obj/item/bodypart/arm/right/robot/hephaestus/mk2 + +/datum/limb_option_datum/bodypart/cybernetic_r_arm/mariinsky + name = "Mariinsky Cybernetic Right Arm" + limb_path = /obj/item/bodypart/arm/right/robot/mariinsky + +/datum/limb_option_datum/bodypart/cybernetic_r_arm/mcg + name = "MCG Cybernetic Right Arm" + limb_path = /obj/item/bodypart/arm/right/robot/mcg + +/datum/limb_option_datum/bodypart/cybernetic_r_arm/sgm + name = "SGM Cybernetic Right Arm" + limb_path = /obj/item/bodypart/arm/right/robot/sgm + +/datum/limb_option_datum/bodypart/cybernetic_r_arm/wtm + name = "WTM Cybernetic Right Arm" + limb_path = /obj/item/bodypart/arm/right/robot/wtm + +/datum/limb_option_datum/bodypart/cybernetic_r_arm/xmg + name = "XMG Cybernetic Right Arm" + limb_path = /obj/item/bodypart/arm/right/robot/xmg + +/datum/limb_option_datum/bodypart/cybernetic_r_arm/zhenkov + name = "Zhenkov Cybernetic Right Arm" + limb_path = /obj/item/bodypart/arm/right/robot/zhenkov + +/datum/limb_option_datum/bodypart/cybernetic_r_arm/zhenkov/dark + name = "Zhenkov (Dark) Cybernetic Right Arm" + limb_path = /obj/item/bodypart/arm/right/robot/zhenkov/dark + +/datum/limb_option_datum/bodypart/cybernetic_r_arm/zhp + name = "ZHP Cybernetic Right Arm" + limb_path = /obj/item/bodypart/arm/right/robot/zhp + +/datum/limb_option_datum/bodypart/cybernetic_r_arm/monokai + name = "Monokai Cybernetic Right Arm" + limb_path = /obj/item/bodypart/arm/right/robot/monokai diff --git a/maplestation_modules/code/modules/loadouts/limb_items/limb_reskins_legs.dm b/maplestation_modules/code/modules/loadouts/limb_items/limb_reskins_legs.dm new file mode 100644 index 000000000000..78f2b6716ac6 --- /dev/null +++ b/maplestation_modules/code/modules/loadouts/limb_items/limb_reskins_legs.dm @@ -0,0 +1,151 @@ +// Left +/datum/limb_option_datum/bodypart/cybernetic_l_leg/engineer + name = "Nanotrasen Engineering Cybernetic Left Leg" + limb_path = /obj/item/bodypart/leg/left/robot/engineer + +/datum/limb_option_datum/bodypart/cybernetic_l_leg/security + name = "Nanotrasen Security Cybernetic Left Leg" + limb_path = /obj/item/bodypart/leg/left/robot/security + +/datum/limb_option_datum/bodypart/cybernetic_l_leg/mining + name = "Nanotrasen Mining Cybernetic Left Leg" + limb_path = /obj/item/bodypart/leg/left/robot/mining + +/datum/limb_option_datum/bodypart/cybernetic_l_leg/bishop + name = "Bishop Cybernetic Left Leg" + limb_path = /obj/item/bodypart/leg/left/robot/bishop + +/datum/limb_option_datum/bodypart/cybernetic_l_leg/bishop/mk2 + name = "Bishop MK2 Cybernetic Left Leg" + limb_path = /obj/item/bodypart/leg/left/robot/bishop/mk2 + +/datum/limb_option_datum/bodypart/cybernetic_l_leg/hephaestus + name = "Hephaestus Cybernetic Left Leg" + limb_path = /obj/item/bodypart/leg/left/robot/hephaestus + +/datum/limb_option_datum/bodypart/cybernetic_l_leg/hephaestus/mk2 + name = "Hephaestus MK2 Cybernetic Left Leg" + limb_path = /obj/item/bodypart/leg/left/robot/hephaestus/mk2 + +/datum/limb_option_datum/bodypart/cybernetic_l_leg/mariinsky + name = "Mariinsky Cybernetic Left Leg" + limb_path = /obj/item/bodypart/leg/left/robot/mariinsky + +/datum/limb_option_datum/bodypart/cybernetic_l_leg/mcg + name = "MCG Cybernetic Left Leg" + limb_path = /obj/item/bodypart/leg/left/robot/mcg + +/datum/limb_option_datum/bodypart/cybernetic_l_leg/sgm + name = "SGM Cybernetic Left Leg" + limb_path = /obj/item/bodypart/leg/left/robot/sgm + +/datum/limb_option_datum/bodypart/cybernetic_l_leg/wtm + name = "WTM Cybernetic Left Leg" + limb_path = /obj/item/bodypart/leg/left/robot/wtm + +/datum/limb_option_datum/bodypart/cybernetic_l_leg/xmg + name = "XMG Cybernetic Left Leg" + limb_path = /obj/item/bodypart/leg/left/robot/xmg + +/datum/limb_option_datum/bodypart/cybernetic_l_leg/zhenkov + name = "Zhenkov Cybernetic Left Leg" + limb_path = /obj/item/bodypart/leg/left/robot/zhenkov + +/datum/limb_option_datum/bodypart/cybernetic_l_leg/zhenkov/dark + name = "Zhenkov (Dark) Cybernetic Left Leg" + limb_path = /obj/item/bodypart/leg/left/robot/zhenkov/dark + +/datum/limb_option_datum/bodypart/cybernetic_l_leg/zhp + 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 + +// Right +/datum/limb_option_datum/bodypart/cybernetic_r_leg/engineer + name = "Nanotrasen Engineering Cybernetic Right Leg" + limb_path = /obj/item/bodypart/leg/right/robot/engineer + +/datum/limb_option_datum/bodypart/cybernetic_r_leg/security + name = "Nanotrasen Security Cybernetic Right Leg" + limb_path = /obj/item/bodypart/leg/right/robot/security + +/datum/limb_option_datum/bodypart/cybernetic_r_leg/mining + name = "Nanotrasen Mining Cybernetic Right Leg" + limb_path = /obj/item/bodypart/leg/right/robot/mining + +/datum/limb_option_datum/bodypart/cybernetic_r_leg/bishop + name = "Bishop Cybernetic Right Leg" + limb_path = /obj/item/bodypart/leg/right/robot/bishop + +/datum/limb_option_datum/bodypart/cybernetic_r_leg/bishop/mk2 + name = "Bishop MK2 Cybernetic Right Leg" + limb_path = /obj/item/bodypart/leg/right/robot/bishop/mk2 + +/datum/limb_option_datum/bodypart/cybernetic_r_leg/hephaestus + name = "Hephaestus Cybernetic Right Leg" + limb_path = /obj/item/bodypart/leg/right/robot/hephaestus + +/datum/limb_option_datum/bodypart/cybernetic_r_leg/hephaestus/mk2 + name = "Hephaestus MK2 Cybernetic Right Leg" + limb_path = /obj/item/bodypart/leg/right/robot/hephaestus/mk2 + +/datum/limb_option_datum/bodypart/cybernetic_r_leg/mariinsky + name = "Mariinsky Cybernetic Right Leg" + limb_path = /obj/item/bodypart/leg/right/robot/mariinsky + +/datum/limb_option_datum/bodypart/cybernetic_r_leg/mcg + name = "MCG Cybernetic Right Leg" + limb_path = /obj/item/bodypart/leg/right/robot/mcg + +/datum/limb_option_datum/bodypart/cybernetic_r_leg/sgm + name = "SGM Cybernetic Right Leg" + limb_path = /obj/item/bodypart/leg/right/robot/sgm + +/datum/limb_option_datum/bodypart/cybernetic_r_leg/wtm + name = "WTM Cybernetic Right Leg" + limb_path = /obj/item/bodypart/leg/right/robot/wtm + +/datum/limb_option_datum/bodypart/cybernetic_r_leg/xmg + name = "XMG Cybernetic Right Leg" + limb_path = /obj/item/bodypart/leg/right/robot/xmg + +/datum/limb_option_datum/bodypart/cybernetic_r_leg/zhenkov + name = "Zhenkov Cybernetic Right Leg" + limb_path = /obj/item/bodypart/leg/right/robot/zhenkov + +/datum/limb_option_datum/bodypart/cybernetic_r_leg/zhenkov/dark + name = "Zhenkov (Dark) Cybernetic Right Leg" + limb_path = /obj/item/bodypart/leg/right/robot/zhenkov/dark + +/datum/limb_option_datum/bodypart/cybernetic_r_leg/zhp + 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 diff --git a/maplestation_modules/code/modules/loadouts/loadout_items/_limb_datums.dm b/maplestation_modules/code/modules/loadouts/loadout_items/_limb_datums.dm deleted file mode 100644 index 267610edbadb..000000000000 --- a/maplestation_modules/code/modules/loadouts/loadout_items/_limb_datums.dm +++ /dev/null @@ -1,576 +0,0 @@ -/// An assoc list of [limb typepath] to [singleton limb datum]s used in the limb manager -GLOBAL_LIST_INIT(limb_loadout_options, init_loadout_limb_options()) - -/// Inits the limb manager global list -/proc/init_loadout_limb_options() - var/list/created = list() - for(var/datum/limb_option_datum/to_create as anything in typesof(/datum/limb_option_datum)) - var/obj/item/limb_path = initial(to_create.limb_path) - if(isnull(limb_path)) - continue - - created[limb_path] = new to_create() - - return created - -/** - * Used as holders for paths to be used in the limb editor menu - * - * Similar to loadout datums but, for limbs and organs that one can start roundstart with - * - * I could've just tied this into loadout datums (they're pretty much the same thing) - * but I would rather keep the typepaths separate for ease of use - */ -/datum/limb_option_datum - /// Name shown up in UI - var/name - /// Used in UI tooltips - var/tooltip - /// The actual item that is created and equipped to the player - var/obj/item/limb_path - /// Determines what body zone this is slotted into in the UI - /// Uses the following limb body zones: - /// [BODY_ZONE_HEAD], [BODY_ZONE_CHEST], [BODY_ZONE_R_ARM], [BODY_ZONE_L_ARM], [BODY_ZONE_R_LEG], [BODY_ZONE_L_LEG] - var/ui_zone - /// Determines what key the path of this is slotted into in the assoc list of preferences - /// A bodypart might use their body zone while an organ may use their organ slot - /// This essently determines what other datums this datum is incompatible with - var/pref_list_slot - /// Icon used in the UI - var/ui_icon - /// Icon state used in the UI - var/ui_icon_state - -/datum/limb_option_datum/New() - . = ..() - if(isnull(name)) - name = capitalize(initial(limb_path.name)) - if(isnull(ui_icon)) - ui_icon = initial(limb_path.icon) - if(isnull(ui_icon_state)) - ui_icon_state = replacetext(initial(limb_path.icon_state), "borg", BODYPART_ID_ROBOTIC) - -/* - * Can this datum be selected by the user? - * - * Return TRUE if the user can select this datum - * Return FALSE if the user cannot select this datum - */ -/datum/limb_option_datum/proc/can_be_selected(datum/preferences/prefs) - return TRUE - -/// Can this datum be applied to the mob? -/datum/limb_option_datum/proc/can_be_applied(mob/living/carbon/human/apply_to) - return TRUE - -/// Applies the datum to the mob. -/datum/limb_option_datum/proc/apply_limb(mob/living/carbon/human/apply_to) - return - -/datum/limb_option_datum/bodypart - -/datum/limb_option_datum/bodypart/New() - . = ..() - var/obj/item/bodypart/part_path = limb_path - if(isnull(ui_zone)) - ui_zone = initial(part_path.body_zone) - if(isnull(pref_list_slot)) - 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) - -/datum/limb_option_datum/bodypart/prosthetic_r_leg - name = "Prosthetic Right Leg" - limb_path = /obj/item/bodypart/leg/right/robot/surplus - -/datum/limb_option_datum/bodypart/prosthetic_l_leg - name = "Prosthetic Left Leg" - limb_path = /obj/item/bodypart/leg/left/robot/surplus - -/datum/limb_option_datum/bodypart/prosthetic_r_arm - name = "Prosthetic Right Arm" - limb_path = /obj/item/bodypart/arm/right/robot/surplus - -/datum/limb_option_datum/bodypart/prosthetic_l_arm - name = "Prosthetic Left Arm" - limb_path = /obj/item/bodypart/arm/left/robot/surplus - -/datum/limb_option_datum/bodypart/cybernetic_r_leg - name = "Cybernetic Right Leg" - limb_path = /obj/item/bodypart/leg/right/robot - -/datum/limb_option_datum/bodypart/cybernetic_r_leg/engineer - name = "Nanotrasen Engineering Cybernetic Right Leg" - limb_path = /obj/item/bodypart/leg/right/robot/engineer - -/datum/limb_option_datum/bodypart/cybernetic_r_leg/security - name = "Nanotrasen Security Cybernetic Right Leg" - limb_path = /obj/item/bodypart/leg/right/robot/security - -/datum/limb_option_datum/bodypart/cybernetic_r_leg/mining - name = "Nanotrasen Mining Cybernetic Right Leg" - limb_path = /obj/item/bodypart/leg/right/robot/mining - -/datum/limb_option_datum/bodypart/cybernetic_r_leg/bishop - name = "Bishop Cybernetic Right Leg" - limb_path = /obj/item/bodypart/leg/right/robot/bishop - -/datum/limb_option_datum/bodypart/cybernetic_r_leg/bishop/mk2 - name = "Bishop MK2 Cybernetic Right Leg" - limb_path = /obj/item/bodypart/leg/right/robot/bishop/mk2 - -/datum/limb_option_datum/bodypart/cybernetic_r_leg/hephaestus - name = "Hephaestus Cybernetic Right Leg" - limb_path = /obj/item/bodypart/leg/right/robot/hephaestus - -/datum/limb_option_datum/bodypart/cybernetic_r_leg/hephaestus/mk2 - name = "Hephaestus MK2 Cybernetic Right Leg" - limb_path = /obj/item/bodypart/leg/right/robot/hephaestus/mk2 - -/datum/limb_option_datum/bodypart/cybernetic_r_leg/mariinsky - name = "Mariinsky Cybernetic Right Leg" - limb_path = /obj/item/bodypart/leg/right/robot/mariinsky - -/datum/limb_option_datum/bodypart/cybernetic_r_leg/mcg - name = "MCG Cybernetic Right Leg" - limb_path = /obj/item/bodypart/leg/right/robot/mcg - -/datum/limb_option_datum/bodypart/cybernetic_r_leg/sgm - name = "SGM Cybernetic Right Leg" - limb_path = /obj/item/bodypart/leg/right/robot/sgm - -/datum/limb_option_datum/bodypart/cybernetic_r_leg/wtm - name = "WTM Cybernetic Right Leg" - limb_path = /obj/item/bodypart/leg/right/robot/wtm - -/datum/limb_option_datum/bodypart/cybernetic_r_leg/xmg - name = "XMG Cybernetic Right Leg" - limb_path = /obj/item/bodypart/leg/right/robot/xmg - -/datum/limb_option_datum/bodypart/cybernetic_r_leg/zhenkov - name = "Zhenkov Cybernetic Right Leg" - limb_path = /obj/item/bodypart/leg/right/robot/zhenkov - -/datum/limb_option_datum/bodypart/cybernetic_r_leg/zhenkov/dark - name = "Zhenkov (Dark) Cybernetic Right Leg" - limb_path = /obj/item/bodypart/leg/right/robot/zhenkov/dark - -/datum/limb_option_datum/bodypart/cybernetic_r_leg/zhp - name = "ZHP Cybernetic Right Leg" - limb_path = /obj/item/bodypart/leg/right/robot/zhp - -/datum/limb_option_datum/bodypart/cybernetic_r_leg/monokai - name = "Monokai Cybernetic Right Leg" - limb_path = /obj/item/bodypart/leg/right/robot/monokai - -/datum/limb_option_datum/bodypart/cybernetic_l_leg - name = "Cybernetic Left Leg" - limb_path = /obj/item/bodypart/leg/left/robot - -/datum/limb_option_datum/bodypart/cybernetic_l_leg/engineer - name = "Nanotrasen Engineering Cybernetic Left Leg" - limb_path = /obj/item/bodypart/leg/left/robot/engineer - -/datum/limb_option_datum/bodypart/cybernetic_l_leg/security - name = "Nanotrasen Security Cybernetic Left Leg" - limb_path = /obj/item/bodypart/leg/left/robot/security - -/datum/limb_option_datum/bodypart/cybernetic_l_leg/mining - name = "Nanotrasen Mining Cybernetic Left Leg" - limb_path = /obj/item/bodypart/leg/left/robot/mining - -/datum/limb_option_datum/bodypart/cybernetic_l_leg/bishop - name = "Bishop Cybernetic Left Leg" - limb_path = /obj/item/bodypart/leg/left/robot/bishop - -/datum/limb_option_datum/bodypart/cybernetic_l_leg/bishop/mk2 - name = "Bishop MK2 Cybernetic Left Leg" - limb_path = /obj/item/bodypart/leg/left/robot/bishop/mk2 - -/datum/limb_option_datum/bodypart/cybernetic_l_leg/hephaestus - name = "Hephaestus Cybernetic Left Leg" - limb_path = /obj/item/bodypart/leg/left/robot/hephaestus - -/datum/limb_option_datum/bodypart/cybernetic_l_leg/hephaestus/mk2 - name = "Hephaestus MK2 Cybernetic Left Leg" - limb_path = /obj/item/bodypart/leg/left/robot/hephaestus/mk2 - -/datum/limb_option_datum/bodypart/cybernetic_l_leg/mariinsky - name = "Mariinsky Cybernetic Left Leg" - limb_path = /obj/item/bodypart/leg/left/robot/mariinsky - -/datum/limb_option_datum/bodypart/cybernetic_l_leg/mcg - name = "MCG Cybernetic Left Leg" - limb_path = /obj/item/bodypart/leg/left/robot/mcg - -/datum/limb_option_datum/bodypart/cybernetic_l_leg/sgm - name = "SGM Cybernetic Left Leg" - limb_path = /obj/item/bodypart/leg/left/robot/sgm - -/datum/limb_option_datum/bodypart/cybernetic_l_leg/wtm - name = "WTM Cybernetic Left Leg" - limb_path = /obj/item/bodypart/leg/left/robot/wtm - -/datum/limb_option_datum/bodypart/cybernetic_l_leg/xmg - name = "XMG Cybernetic Left Leg" - limb_path = /obj/item/bodypart/leg/left/robot/xmg - -/datum/limb_option_datum/bodypart/cybernetic_l_leg/zhenkov - name = "Zhenkov Cybernetic Left Leg" - limb_path = /obj/item/bodypart/leg/left/robot/zhenkov - -/datum/limb_option_datum/bodypart/cybernetic_l_leg/zhenkov/dark - name = "Zhenkov (Dark) Cybernetic Left Leg" - limb_path = /obj/item/bodypart/leg/left/robot/zhenkov/dark - -/datum/limb_option_datum/bodypart/cybernetic_l_leg/zhp - name = "ZHP Cybernetic Left Leg" - limb_path = /obj/item/bodypart/leg/left/robot/zhp - -/datum/limb_option_datum/bodypart/cybernetic_l_leg/monokai - name = "Monokai Cybernetic Left Leg" - limb_path = /obj/item/bodypart/leg/left/robot/monokai - -/datum/limb_option_datum/bodypart/cybernetic_r_arm - name = "Cybernetic Right Arm" - limb_path = /obj/item/bodypart/arm/right/robot - -/datum/limb_option_datum/bodypart/cybernetic_r_arm/engineer - name = "Nanotrasen Engineering Cybernetic Right Arm" - limb_path = /obj/item/bodypart/arm/right/robot/engineer - -/datum/limb_option_datum/bodypart/cybernetic_r_arm/security - name = "Nanotrasen Security Cybernetic Right Arm" - limb_path = /obj/item/bodypart/arm/right/robot/security - -/datum/limb_option_datum/bodypart/cybernetic_r_arm/mining - name = "Nanotrasen Mining Cybernetic Right Arm" - limb_path = /obj/item/bodypart/arm/right/robot/mining - -/datum/limb_option_datum/bodypart/cybernetic_r_arm/bishop - name = "Bishop Cybernetic Right Arm" - limb_path = /obj/item/bodypart/arm/right/robot/bishop - -/datum/limb_option_datum/bodypart/cybernetic_r_arm/bishop/mk2 - name = "Bishop MK2 Cybernetic Right Arm" - limb_path = /obj/item/bodypart/arm/right/robot/bishop/mk2 - -/datum/limb_option_datum/bodypart/cybernetic_r_arm/hephaestus - name = "Hephaestus Cybernetic Right Arm" - limb_path = /obj/item/bodypart/arm/right/robot/hephaestus - -/datum/limb_option_datum/bodypart/cybernetic_r_arm/hephaestus/mk2 - name = "Hephaestus MK2 Cybernetic Right Arm" - limb_path = /obj/item/bodypart/arm/right/robot/hephaestus/mk2 - -/datum/limb_option_datum/bodypart/cybernetic_r_arm/mariinsky - name = "Mariinsky Cybernetic Right Arm" - limb_path = /obj/item/bodypart/arm/right/robot/mariinsky - -/datum/limb_option_datum/bodypart/cybernetic_r_arm/mcg - name = "MCG Cybernetic Right Arm" - limb_path = /obj/item/bodypart/arm/right/robot/mcg - -/datum/limb_option_datum/bodypart/cybernetic_r_arm/sgm - name = "SGM Cybernetic Right Arm" - limb_path = /obj/item/bodypart/arm/right/robot/sgm - -/datum/limb_option_datum/bodypart/cybernetic_r_arm/wtm - name = "WTM Cybernetic Right Arm" - limb_path = /obj/item/bodypart/arm/right/robot/wtm - -/datum/limb_option_datum/bodypart/cybernetic_r_arm/xmg - name = "XMG Cybernetic Right Arm" - limb_path = /obj/item/bodypart/arm/right/robot/xmg - -/datum/limb_option_datum/bodypart/cybernetic_r_arm/zhenkov - name = "Zhenkov Cybernetic Right Arm" - limb_path = /obj/item/bodypart/arm/right/robot/zhenkov - -/datum/limb_option_datum/bodypart/cybernetic_r_arm/zhenkov/dark - name = "Zhenkov (Dark) Cybernetic Right Arm" - limb_path = /obj/item/bodypart/arm/right/robot/zhenkov/dark - -/datum/limb_option_datum/bodypart/cybernetic_r_arm/zhp - name = "ZHP Cybernetic Right Arm" - limb_path = /obj/item/bodypart/arm/right/robot/zhp - -/datum/limb_option_datum/bodypart/cybernetic_r_arm/monokai - name = "Monokai Cybernetic Right Arm" - limb_path = /obj/item/bodypart/arm/right/robot/monokai - -/datum/limb_option_datum/bodypart/cybernetic_l_arm - name = "Cybernetic Left Arm" - limb_path = /obj/item/bodypart/arm/left/robot - -/datum/limb_option_datum/bodypart/cybernetic_l_arm/engineer - name = "Nanotrasen Engineering Cybernetic Left Arm" - limb_path = /obj/item/bodypart/arm/left/robot/engineer - -/datum/limb_option_datum/bodypart/cybernetic_l_arm/security - name = "Nanotrasen Security Cybernetic Left Arm" - limb_path = /obj/item/bodypart/arm/left/robot/security - -/datum/limb_option_datum/bodypart/cybernetic_l_arm/mining - name = "Nanotrasen Mining Cybernetic Left Arm" - limb_path = /obj/item/bodypart/arm/left/robot/mining - -/datum/limb_option_datum/bodypart/cybernetic_l_arm/bishop - name = "Bishop Cybernetic Left Arm" - limb_path = /obj/item/bodypart/arm/left/robot/bishop - -/datum/limb_option_datum/bodypart/cybernetic_l_arm/bishop/mk2 - name = "Bishop MK2 Cybernetic Left Arm" - limb_path = /obj/item/bodypart/arm/left/robot/bishop/mk2 - -/datum/limb_option_datum/bodypart/cybernetic_l_arm/hephaestus - name = "Hephaestus Cybernetic Left Arm" - limb_path = /obj/item/bodypart/arm/left/robot/hephaestus - -/datum/limb_option_datum/bodypart/cybernetic_l_arm/hephaestus/mk2 - name = "Hephaestus MK2 Cybernetic Left Arm" - limb_path = /obj/item/bodypart/arm/left/robot/hephaestus/mk2 - -/datum/limb_option_datum/bodypart/cybernetic_l_arm/mariinsky - name = "Mariinsky Cybernetic Left Arm" - limb_path = /obj/item/bodypart/arm/left/robot/mariinsky - -/datum/limb_option_datum/bodypart/cybernetic_l_arm/mcg - name = "MCG Cybernetic Left Arm" - limb_path = /obj/item/bodypart/arm/left/robot/mcg - -/datum/limb_option_datum/bodypart/cybernetic_l_arm/sgm - name = "SGM Cybernetic Left Arm" - limb_path = /obj/item/bodypart/arm/left/robot/sgm - -/datum/limb_option_datum/bodypart/cybernetic_l_arm/wtm - name = "WTM Cybernetic Left Arm" - limb_path = /obj/item/bodypart/arm/left/robot/wtm - -/datum/limb_option_datum/bodypart/cybernetic_l_arm/xmg - name = "XMG Cybernetic Left Arm" - limb_path = /obj/item/bodypart/arm/left/robot/xmg - -/datum/limb_option_datum/bodypart/cybernetic_l_arm/zhenkov - name = "Zhenkov Cybernetic Left Arm" - limb_path = /obj/item/bodypart/arm/left/robot/zhenkov - -/datum/limb_option_datum/bodypart/cybernetic_l_arm/zhenkov/dark - name = "Zhenkov (Dark) Cybernetic Left Arm" - limb_path = /obj/item/bodypart/arm/left/robot/zhenkov/dark - -/datum/limb_option_datum/bodypart/cybernetic_l_arm/zhp - name = "ZHP Cybernetic Left Arm" - limb_path = /obj/item/bodypart/arm/left/robot/zhp - -/datum/limb_option_datum/bodypart/cybernetic_l_arm/monokai - name = "Monokai Cybernetic Left Arm" - limb_path = /obj/item/bodypart/arm/left/robot/monokai - -/datum/limb_option_datum/bodypart/cybernetic_head - name = "Cybernetic Head" - tooltip = "Unique to 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) - -/datum/limb_option_datum/bodypart/cybernetic_head/can_be_applied(mob/living/carbon/human/apply_to) - return is_species(apply_to, /datum/species/synth) - -/datum/limb_option_datum/bodypart/cybernetic_head/engineer - name = "Nanotrasen Engineering Cybernetic Head" - limb_path = /obj/item/bodypart/head/robot/engineer - -/datum/limb_option_datum/bodypart/cybernetic_head/security - name = "Nanotrasen Security Cybernetic Head" - limb_path = /obj/item/bodypart/head/robot/security - -/datum/limb_option_datum/bodypart/cybernetic_head/mining - name = "Nanotrasen Mining Cybernetic Head" - limb_path = /obj/item/bodypart/head/robot/mining - -/datum/limb_option_datum/bodypart/cybernetic_head/bishop - name = "Bishop Cybernetic Head" - limb_path = /obj/item/bodypart/head/robot/bishop - -/datum/limb_option_datum/bodypart/cybernetic_head/bishop/mk2 - name = "Bishop MK2 Cybernetic Head" - limb_path = /obj/item/bodypart/head/robot/bishop/mk2 - -/datum/limb_option_datum/bodypart/cybernetic_head/hephaestus - name = "Hephaestus Cybernetic Head" - limb_path = /obj/item/bodypart/head/robot/hephaestus - -/datum/limb_option_datum/bodypart/cybernetic_head/hephaestus/mk2 - name = "Hephaestus MK2 Cybernetic Head" - limb_path = /obj/item/bodypart/head/robot/hephaestus/mk2 - -/datum/limb_option_datum/bodypart/cybernetic_head/mariinsky - name = "Mariinsky Cybernetic Head" - limb_path = /obj/item/bodypart/head/robot/mariinsky - -/datum/limb_option_datum/bodypart/cybernetic_head/mcg - name = "MCG Cybernetic Head" - limb_path = /obj/item/bodypart/head/robot/mcg - -/datum/limb_option_datum/bodypart/cybernetic_head/sgm - name = "SGM Cybernetic Head" - limb_path = /obj/item/bodypart/head/robot/sgm - -/datum/limb_option_datum/bodypart/cybernetic_head/wtm - name = "WTM Cybernetic Head" - limb_path = /obj/item/bodypart/head/robot/wtm - -/datum/limb_option_datum/bodypart/cybernetic_head/xmg - name = "XMG Cybernetic Head" - limb_path = /obj/item/bodypart/head/robot/xmg - -/datum/limb_option_datum/bodypart/cybernetic_head/zhenkov - name = "Zhenkov Cybernetic Head" - limb_path = /obj/item/bodypart/head/robot/zhenkov - -/datum/limb_option_datum/bodypart/cybernetic_head/zhenkov/dark - name = "Zhenkov (Dark) Cybernetic Head" - limb_path = /obj/item/bodypart/head/robot/zhenkov/dark - -/datum/limb_option_datum/bodypart/cybernetic_head/zhp - name = "ZHP Cybernetic Head" - limb_path = /obj/item/bodypart/head/robot/zhp - -/datum/limb_option_datum/bodypart/cybernetic_chest - name = "Cybernetic Chest" - tooltip = "Unique to 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) - -/datum/limb_option_datum/bodypart/cybernetic_chest/can_be_applied(mob/living/carbon/human/apply_to) - return is_species(apply_to, /datum/species/synth) - -/datum/limb_option_datum/bodypart/cybernetic_chest/engineer - name = "Nanotrasen Engineering Cybernetic Chest" - limb_path = /obj/item/bodypart/chest/robot/engineer - -/datum/limb_option_datum/bodypart/cybernetic_chest/security - name = "Nanotrasen Security Cybernetic Chest" - limb_path = /obj/item/bodypart/chest/robot/security - -/datum/limb_option_datum/bodypart/cybernetic_chest/mining - name = "Nanotrasen Mining Cybernetic Chest" - limb_path = /obj/item/bodypart/chest/robot/mining - -/datum/limb_option_datum/bodypart/cybernetic_chest/bishop - name = "Bishop Cybernetic Chest" - limb_path = /obj/item/bodypart/chest/robot/bishop - -/datum/limb_option_datum/bodypart/cybernetic_chest/bishop/mk2 - name = "Bishop MK2 Cybernetic Chest" - limb_path = /obj/item/bodypart/chest/robot/bishop/mk2 - -/datum/limb_option_datum/bodypart/cybernetic_chest/hephaestus - name = "Hephaestus Cybernetic Chest" - limb_path = /obj/item/bodypart/chest/robot/hephaestus - -/datum/limb_option_datum/bodypart/cybernetic_chest/hephaestus/mk2 - name = "Hephaestus MK2 Cybernetic Chest" - limb_path = /obj/item/bodypart/chest/robot/hephaestus/mk2 - -/datum/limb_option_datum/bodypart/cybernetic_chest/mariinsky - name = "Mariinsky Cybernetic Chest" - limb_path = /obj/item/bodypart/chest/robot/mariinsky - -/datum/limb_option_datum/bodypart/cybernetic_chest/mcg - name = "MCG Cybernetic Chest" - limb_path = /obj/item/bodypart/chest/robot/mcg - -/datum/limb_option_datum/bodypart/cybernetic_chest/sgm - name = "SGM Cybernetic Chest" - limb_path = /obj/item/bodypart/chest/robot/sgm - -/datum/limb_option_datum/bodypart/cybernetic_chest/wtm - name = "WTM Cybernetic Chest" - limb_path = /obj/item/bodypart/chest/robot/wtm - -/datum/limb_option_datum/bodypart/cybernetic_chest/xmg - name = "XMG Cybernetic Chest" - limb_path = /obj/item/bodypart/chest/robot/xmg - -/datum/limb_option_datum/bodypart/cybernetic_chest/zhenkov - name = "Zhenkov Cybernetic Chest" - limb_path = /obj/item/bodypart/chest/robot/zhenkov - -/datum/limb_option_datum/bodypart/cybernetic_chest/zhenkov/dark - name = "Zhenkov (Dark) Cybernetic Chest" - limb_path = /obj/item/bodypart/chest/robot/zhenkov/dark - -/datum/limb_option_datum/bodypart/cybernetic_chest/zhp - name = "ZHP Cybernetic Chest" - limb_path = /obj/item/bodypart/chest/robot/zhp - -/datum/limb_option_datum/organ - -/datum/limb_option_datum/organ/New() - . = ..() - var/obj/item/organ/organ_path = limb_path - if(isnull(ui_zone)) - ui_zone = deprecise_zone(initial(organ_path.zone)) - if(isnull(pref_list_slot)) - pref_list_slot = initial(organ_path.slot) - -/datum/limb_option_datum/organ/apply_limb(mob/living/carbon/human/apply_to) - if(istype(apply_to, /mob/living/carbon/human/dummy)) - var/obj/item/organ/organ_path = limb_path - if(!organ_path::visual) - return - - var/obj/item/organ/new_organ = new limb_path() - new_organ.Insert(apply_to, special = TRUE, movement_flags = DELETE_IF_REPLACED) - -/datum/limb_option_datum/organ/cyberheart - name = "Cybernetic Heart" - limb_path = /obj/item/organ/heart/cybernetic - -/datum/limb_option_datum/organ/cyberliver - name = "Cybernetic Liver" - limb_path = /obj/item/organ/liver/cybernetic - -/datum/limb_option_datum/organ/cyberlungs - name = "Cybernetic Lungs" - limb_path = /obj/item/organ/lungs/cybernetic - -/datum/limb_option_datum/organ/cyberstomach - name = "Cybernetic Stomach" - limb_path = /obj/item/organ/stomach/cybernetic - -/datum/limb_option_datum/organ/cybereyes - name = "Cybernetic Eyes" - limb_path = /obj/item/organ/eyes/robotic/basic - -/datum/limb_option_datum/organ/cyberears - name = "Cybernetic Ears" - limb_path = /obj/item/organ/ears/cybernetic - -/datum/limb_option_datum/organ/cyberears/cat - name = "Cybernetic Cat Ears" - limb_path = /obj/item/organ/ears/cat/cybernetic - -/datum/limb_option_datum/organ/robotongue - name = "Voicebox" - tooltip = "A voice synthesizer that replaces the tongue. Makes you sound like a robot." - limb_path = /obj/item/organ/tongue/robot - -/datum/limb_option_datum/organ/lighter_implant - name = "Lighter Implant" - tooltip = "A lighter implanted into the tip of your finger. Light it with a snap... like a badass." - limb_path = /obj/item/organ/cyberimp/arm/lighter - ui_zone = BODY_ZONE_R_ARM - pref_list_slot = ORGAN_SLOT_RIGHT_ARM_AUG - -/datum/limb_option_datum/organ/lighter_implant/left - limb_path = /obj/item/organ/cyberimp/arm/lighter/left - ui_zone = BODY_ZONE_L_ARM - pref_list_slot = ORGAN_SLOT_LEFT_ARM_AUG - // Yeah you can have one in both arms if you want, don't really care diff --git a/maplestation_modules/code/modules/mob/dead/new_player/sprite_accessories.dm b/maplestation_modules/code/modules/mob/dead/new_player/sprite_accessories.dm index e58a75e26ab1..8cfaaba1d6b6 100644 --- a/maplestation_modules/code/modules/mob/dead/new_player/sprite_accessories.dm +++ b/maplestation_modules/code/modules/mob/dead/new_player/sprite_accessories.dm @@ -369,6 +369,12 @@ icon_state = "snaketail" spine_key = NONE +// From Effigy +/datum/sprite_accessory/tails/lizard/cybernetic + name = "Cybernetic" + icon = 'maplestation_modules/icons/mob/tails.dmi' + icon_state = "synthliz" + // https://github.com/Skyrat-SS13/Skyrat-tg/pull/969 (same for Divinity/Big below. TODO group) /datum/sprite_accessory/frills/horns name = "Horns" diff --git a/maplestation_modules/code/modules/mob/living/carbon/human/species_types/android.dm b/maplestation_modules/code/modules/mob/living/carbon/human/species_types/android.dm index 922f1336ed11..222d9a1b3698 100644 --- a/maplestation_modules/code/modules/mob/living/carbon/human/species_types/android.dm +++ b/maplestation_modules/code/modules/mob/living/carbon/human/species_types/android.dm @@ -1,8 +1,4 @@ // -- Android species additions -- -/datum/species/android - species_pain_mod = 0.2 - exotic_bloodtype = /datum/blood_type/oil - /obj/item/organ/tongue/robot speech_sound_list = list('goon/sound/voice/radio_ai.ogg' = 100) speech_sound_list_question = null 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..e65f24844ec8 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 @@ -1,3 +1,12 @@ +GLOBAL_LIST_INIT_TYPED(animid_singletons, /datum/animid_type, init_animid_singletons()) + +/proc/init_animid_singletons() + . = list() + for(var/datum/animid_type/atype as anything in typesof(/datum/animid_type)) + if(!atype::id) + continue + .[atype::id] = new atype(src) + /datum/species/human/animid name = "Animid" id = SPECIES_ANIMALID @@ -7,29 +16,13 @@ changesource_flags = MIRROR_BADMIN | WABBAJACK | MIRROR_PRIDE | MIRROR_MAGIC | RACE_SWAP | ERT_SPAWN | SLIME_EXTRACT payday_modifier = 1.0 species_language_holder = /datum/language_holder/kuiperian - /// A mapping of all animid ids to their singleton instances - var/static/list/datum/animid_type/animid_singletons - -/datum/species/human/animid/New() - . = ..() - if(animid_singletons) - return - animid_singletons = list() - for(var/datum/animid_type/atype as anything in typesof(/datum/animid_type)) - if(!atype::id) - continue - animid_singletons[atype::id] = new atype(src) - -/datum/species/human/animid/on_species_gain(mob/living/carbon/human/human_who_gained_species, datum/species/old_species, pref_load) - var/animid_id = human_who_gained_species.dna?.features["animid_type"] || pick(animid_singletons) - for(var/organ_slot, input in animid_singletons[animid_id].components) - set_mutant_organ(organ_slot, input, human_who_gained_species) - animid_singletons[animid_id].pre_species_gain(src, human_who_gained_species) +/datum/species/human/animid/on_species_gain(mob/living/carbon/human/gained_species, datum/species/old_species, pref_load) + apply_animid_features(gained_species, gained_species.dna?.features["animid_type"] || pick(GLOB.animid_singletons)) . = ..() // replace body is not called when going from same species to same species, but we need it for swapping animid types if(old_species.type == type) - replace_body(human_who_gained_species, src) + replace_body(gained_species, src) // So the primary page doesn't show a diet that is largely irrelevant /datum/species/human/animid/get_species_diet() @@ -107,30 +100,40 @@ /datum/species/human/animid/randomize_features() var/list/features = ..() - features["animid_type"] = pick(animid_singletons) + features["animid_type"] = pick(GLOB.animid_singletons) return features // Gets all features from all animid types /datum/species/human/animid/get_features() . = ..() - for(var/animalid_id in animid_singletons) - . |= animid_singletons[animalid_id].get_feature_keys() + for(var/animalid_id in GLOB.animid_singletons) + . |= GLOB.animid_singletons[animalid_id].get_feature_keys() // Filters out features from other animid types, to declutter the prefs screen -/datum/species/human/animid/get_filtered_features_per_prefs(datum/preferences/prefs) +/datum/species/human/animid/filter_features_per_prefs(list/to_filter, datum/preferences/prefs) var/selected_animid_id = prefs.read_preference(/datum/preference/choiced/animid_type) - var/list/filtered_features = list() - // Collect all the features from all other animid types - for(var/other_animid_id in animid_singletons - selected_animid_id) - filtered_features |= animid_singletons[other_animid_id].get_feature_keys() - // Don't filter anything in our prime animid type - return filtered_features - animid_singletons[selected_animid_id].get_feature_keys() + // filter out other animid types + for(var/other_animid_id in GLOB.animid_singletons - selected_animid_id) + to_filter -= GLOB.animid_singletons[other_animid_id].get_feature_keys() + // re-add features that we may have filtered from our selected animid type + to_filter |= GLOB.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 GLOB.animid_singletons) + . |= GLOB.animid_singletons[animalid_id].get_organs(include_brain) + +/// Applies animid features to a human based on the given animid type ID +/datum/species/proc/apply_animid_features(mob/living/carbon/human/humanoid, animid_id, list/whitelist) + for(var/organ_slot, input in GLOB.animid_singletons[animid_id].components) + if(whitelist && !(organ_slot in whitelist)) + continue + set_mutant_organ(organ_slot, input, humanoid) + +/datum/species/human/animid/apply_animid_features(mob/living/carbon/human/humanoid, animid_id, list/whitelist) . = ..() - for(var/animalid_id in animid_singletons) - . |= animid_singletons[animalid_id].get_organs() + GLOB.animid_singletons[animid_id].pre_species_gain(src, humanoid) /// 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/animid/animid_prefs.dm b/maplestation_modules/code/modules/mob/living/carbon/human/species_types/animid/animid_prefs.dm index 8bf4cdd6177e..92178dbd06c9 100644 --- a/maplestation_modules/code/modules/mob/living/carbon/human/species_types/animid/animid_prefs.dm +++ b/maplestation_modules/code/modules/mob/living/carbon/human/species_types/animid/animid_prefs.dm @@ -8,22 +8,27 @@ target.dna.features["animid_type"] = value /datum/preference/choiced/animid_type/init_possible_values() - var/datum/species/human/animid/animid = GLOB.species_prototypes[__IMPLIED_TYPE__] - pass(animid) // linter trips up on static accesses - return assoc_to_keys(animid.animid_singletons) + return assoc_to_keys(GLOB.animid_singletons) /datum/preference/choiced/animid_type/is_accessible(datum/preferences/preferences) if(!..()) return FALSE - return ispath(preferences.read_preference(/datum/preference/choiced/species), /datum/species/human/animid) + var/pref_typepath = preferences.read_preference(/datum/preference/choiced/species) + if(ispath(pref_typepath, /datum/species/human/animid)) + return TRUE + if(ispath(pref_typepath, /datum/species/android/synth)) + return preferences.read_preference(/datum/preference/choiced/synth_species) == SPECIES_ANIMALID + if(ispath(pref_typepath, /datum/species/android)) + return preferences.read_preference(/datum/preference/choiced/android_species) == SPECIES_ANIMALID + return FALSE /datum/preference/choiced/animid_type/compile_constant_data() var/datum/species/human/animid/animid = GLOB.species_prototypes[__IMPLIED_TYPE__] var/list/data = list() data["animid_customization"] = list() - for(var/animalid_id in animid.animid_singletons) - var/datum/animid_type/atype = animid.animid_singletons[animalid_id] + for(var/animalid_id in GLOB.animid_singletons) + var/datum/animid_type/atype = GLOB.animid_singletons[animalid_id] data["animid_customization"][animalid_id] = list( "id" = animalid_id, 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..351a312bcdca --- /dev/null +++ b/maplestation_modules/code/modules/mob/living/carbon/human/species_types/synth/android.dm @@ -0,0 +1,518 @@ +/datum/species/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_NO_DNA_COPY, + TRAIT_NO_PLASMA_TRANSFORM, + TRAIT_RADIMMUNE, // rework this later - warping wires or something + 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/android + species_pain_mod = 0.5 // the bodyparts themselves also reduce pain + 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 + + /// Whether or not to block organic bodyparts from being attached + var/allow_fleshy_bits = FALSE + + /// Tracks if we have begun leaking due to excess blood volume + VAR_FINAL/is_leaking = FALSE + /// Tracks overheating state (0-3) + VAR_FINAL/is_overheating = 0 + /// Tracks overcooling state (0-3) + VAR_FINAL/is_overcooled = 0 + + /// List of species an android can be designed as + var/list/android_species = list( + SPECIES_ANIMALID, + SPECIES_HUMAN, + SPECIES_LIZARD, + SPECIES_MOTH, + SPECIES_ORNITHID, + SPECIES_SKRELL, + ) + +/datum/species/android/on_species_gain(mob/living/carbon/human/gained_species, datum/species/old_species, pref_load) + var/species_id = gained_species.dna?.features["android_species"] || old_species?.id + if(!species_id || !(species_id in android_species)) + species_id = SPECIES_HUMAN + + var/datum/species/copy_datum = GLOB.species_prototypes[ID_TO_TYPEPATH(species_id)] + for(var/organtype in copy_datum.mutant_organs) + set_mutant_organ(MUTANT_ORGANS, organtype, gained_species) + for(var/markingtype in copy_datum.body_markings) + set_mutant_organ(BODY_MARKINGS, markingtype, gained_species) + // snowflakes + switch(species_id) + if(SPECIES_ANIMALID) + apply_animid_features(gained_species, gained_species.dna?.features["animid_type"] || pick(GLOB.animid_singletons), list(ORGAN_SLOT_EARS, MUTANT_ORGANS, BODY_MARKINGS)) + if(SPECIES_MOTH) + set_mutant_organ(ORGAN_SLOT_EYES, /obj/item/organ/eyes/robotic/basic/moth, gained_species) + if(SPECIES_FELINE) + set_mutant_organ(ORGAN_SLOT_EARS, /obj/item/organ/ears/cat/cybernetic, gained_species) + + RegisterSignal(gained_species, COMSIG_HUMAN_ON_HANDLE_BLOOD, PROC_REF(handle_blood)) + RegisterSignal(gained_species, COMSIG_MOVABLE_MOVED, PROC_REF(on_moved)) + RegisterSignal(gained_species, COMSIG_LIVING_BODY_TEMPERATURE_CHANGE, PROC_REF(temperature_update)) + RegisterSignal(gained_species, COMSIG_MOB_APPLY_DAMAGE_MODIFIERS, PROC_REF(brittle_modifier)) + RegisterSignal(gained_species, COMSIG_ATTEMPT_CARBON_ATTACH_LIMB, PROC_REF(block_fleshy_bits)) + RegisterSignal(gained_species, COMSIG_HUMAN_ON_HANDLE_BREATH_TEMPERATURE, PROC_REF(handle_breath_temperature)) + return ..() + +/datum/species/android/on_species_loss(mob/living/carbon/human/lost_species, datum/species/new_species, pref_load) + . = ..() + UnregisterSignal(lost_species, COMSIG_HUMAN_ON_HANDLE_BLOOD) + UnregisterSignal(lost_species, COMSIG_MOVABLE_MOVED) + UnregisterSignal(lost_species, COMSIG_LIVING_BODY_TEMPERATURE_CHANGE) + UnregisterSignal(lost_species, COMSIG_MOB_APPLY_DAMAGE_MODIFIERS) + UnregisterSignal(lost_species, COMSIG_ATTEMPT_CARBON_ATTACH_LIMB) + UnregisterSignal(lost_species, COMSIG_HUMAN_ON_HANDLE_BREATH_TEMPERATURE) + if(is_leaking) + remove_leaking(lost_species) + if(is_overheating) + remove_heat_modifiers(lost_species) + if(is_overcooled) + remove_cold_modifiers(lost_species) + +/datum/species/android/proc/remove_leaking(mob/living/carbon/human/removing) + if(!is_leaking) + return + + is_leaking = FALSE + removing.physiology.bleed_mod /= 3 + removing.clear_alert("leak_warning") + +/datum/species/android/proc/remove_heat_modifiers(mob/living/carbon/human/removing) + if(!is_overheating) + return + + is_overheating = 0 + removing.remove_actionspeed_modifier(/datum/actionspeed_modifier/hot_android) + removing.remove_movespeed_modifier(/datum/movespeed_modifier/hot_android) + removing.clear_alert(ALERT_TEMPERATURE) + +/datum/species/android/proc/remove_cold_modifiers(mob/living/carbon/human/removing) + if(!is_overcooled) + return + + is_overcooled = 0 + removing.remove_actionspeed_modifier(/datum/actionspeed_modifier/cold_android) + removing.remove_movespeed_modifier(/datum/movespeed_modifier/cold_android) + removing.clear_alert(ALERT_TEMPERATURE) + +/** + * let's go over the mechanics + * + * 1. androids blood is oil / lubricant, and it serves as "hydraulic fluid". + * their heart pumps the oil through the body, allowing limbs to move properly. + * + * 2. android lungs function as (currently) air cooling, taking in air to cool down + * + * 3. android livers function as a mechanical filter for toxic elements, which can clog up + * + * 4. android stomachs are "bioreactors" transforming food into power + */ +/datum/species/android/spec_life(mob/living/carbon/human/android, seconds_per_tick, times_fired) + . = ..() + // model pump behavior + var/obj/item/organ/heart = android.get_organ_slot(ORGAN_SLOT_HEART) + if(isnull(heart) || IS_ORGANIC_ORGAN(heart)) + // lacking a mechanical heart will eventually consume all of your oil/hydraulic fluid + android.bleed(0.5 * seconds_per_tick, leave_pool = FALSE) + else + // otherwise the pump generates heat passively + android.adjust_body_temperature(0.12 KELVIN * seconds_per_tick, max_temp = android.bodytemp_heat_damage_limit * 1.5) + + if(is_overheating) + android.temperature_burns(1.25 * is_overheating * seconds_per_tick) + if(is_overcooled) + android.temperature_cold_damage(0.5 * is_overcooled * seconds_per_tick) + +/datum/species/android/proc/block_fleshy_bits(mob/living/carbon/human/source, obj/item/bodypart/attaching, special) + SIGNAL_HANDLER + + return (!allow_fleshy_bits && IS_ORGANIC_LIMB(attaching)) ? COMPONENT_NO_ATTACH : NONE + +/datum/species/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 + +/datum/species/android/proc/temperature_update(mob/living/carbon/human/source, old_temp, new_temp) + SIGNAL_HANDLER + + if(source.body_temperature > source.bodytemp_heat_damage_limit) + update_heat_modifiers(source) + else if(source.body_temperature < source.bodytemp_cold_damage_limit) + update_cold_modifiers(source) + else if(is_overheating) + remove_heat_modifiers(source) + else if(is_overcooled) + remove_cold_modifiers(source) + +/datum/species/android/proc/handle_blood(mob/living/carbon/source, seconds_per_tick, times_fired) + SIGNAL_HANDLER + + switch(source.blood_volume) + if(BLOOD_VOLUME_MAXIMUM to INFINITY) + EMPTY_BLOCK_GUARD // handled in on_moved + if(BLOOD_VOLUME_OKAY to BLOOD_VOLUME_MAXIMUM) + EMPTY_BLOCK_GUARD // all good + 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(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) + 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) + + if(source.blood_volume > BLOOD_VOLUME_OKAY) + source.remove_max_consciousness_value(BLOOD_LOSS) + source.remove_consciousness_modifier(BLOOD_LOSS) + + return HANDLE_BLOOD_HANDLED + +/datum/species/android/proc/on_moved(mob/living/carbon/human/source, atom/from_loc, movement_dir, ...) + SIGNAL_HANDLER + + if(!ishuman(source)) + return // whatever man + + if(source.blood_volume <= BLOOD_VOLUME_EXCESS) + if(is_leaking) + remove_leaking() + return + + if(!is_leaking) + is_leaking = TRUE + source.physiology.bleed_mod *= 3 + source.throw_alert("leak_warning", /atom/movable/screen/alert/blood_leak) + + var/shedded_blood = BLOOD_AMOUNT_PER_DECAL * (source.blood_volume >= BLOOD_VOLUME_MAXIMUM ? 0.33 : 0.1) + source.make_blood_trail( + target_turf = get_turf(source), + start = from_loc, + was_facing = source.dir, + movement_direction = movement_dir, + blood_to_add = shedded_blood, + blood_dna = source.get_blood_dna_list(), + static_viruses = source.get_static_viruses(), + ) + source.blood_volume -= shedded_blood + +// androids don't do homeostasis, instead they use their lungs to cool themselves +/datum/species/android/proc/handle_breath_temperature(mob/living/carbon/human/breather, datum/gas_mixture/breath, obj/item/organ/lungs) + SIGNAL_HANDLER + + if(IS_ORGANIC_ORGAN(lungs)) + return HANDLE_BREATH_TEMPERATURE_HANDLED // nothing happened! + + // future todo: make better heat capacity gases work better here + var/temp_delta = round((breath.temperature - breather.body_temperature), 0.01) + if(temp_delta == 0) + return HANDLE_BREATH_TEMPERATURE_HANDLED + + 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 + return HANDLE_BREATH_TEMPERATURE_HANDLED + +/// Has trait but not from lungs if present +#define HAS_TRAIT_NOT_FROM_LUNGS(mob, trait, lungs) (isnull(lungs) ? HAS_TRAIT(mob, trait) : HAS_TRAIT_NOT_FROM(mob, trait, REF(lungs))) + +/datum/species/android/proc/update_heat_modifiers(mob/living/carbon/human/source) + var/obj/item/organ/lungs = source.get_organ_slot(ORGAN_SLOT_LUNGS) + if(HAS_TRAIT_NOT_FROM_LUNGS(source, TRAIT_RESISTHEAT, lungs)) + remove_heat_modifiers() + return + + if(source.body_temperature > source.bodytemp_heat_damage_limit * 2) + if(is_overheating != 3) + source.add_actionspeed_modifier(/datum/actionspeed_modifier/hot_android/t3) + source.add_movespeed_modifier(/datum/movespeed_modifier/hot_android/t3) + source.throw_alert(ALERT_TEMPERATURE, /atom/movable/screen/alert/hot, 3) + source.add_mood_event(ALERT_TEMPERATURE, /datum/mood_event/android_critical_overheat) + is_overheating = 3 + + else if(source.body_temperature > source.bodytemp_heat_damage_limit * 1.75) + if(is_overheating != 2) + source.add_actionspeed_modifier(/datum/actionspeed_modifier/hot_android/t2) + source.add_movespeed_modifier(/datum/movespeed_modifier/hot_android/t2) + source.throw_alert(ALERT_TEMPERATURE, /atom/movable/screen/alert/hot, 2) + source.add_mood_event(ALERT_TEMPERATURE, /datum/mood_event/android_major_overheat) + is_overheating = 2 + + else if(source.body_temperature > source.bodytemp_heat_damage_limit * 1.2) + if(is_overheating != 1) + source.add_actionspeed_modifier(/datum/actionspeed_modifier/hot_android/t1) + source.add_movespeed_modifier(/datum/movespeed_modifier/hot_android/t1) + source.throw_alert(ALERT_TEMPERATURE, /atom/movable/screen/alert/hot, 1) + source.add_mood_event(ALERT_TEMPERATURE, /datum/mood_event/android_minor_overheat) + is_overheating = 1 + +/datum/species/android/proc/update_cold_modifiers(mob/living/carbon/human/source) + var/obj/item/organ/lungs = source.get_organ_slot(ORGAN_SLOT_LUNGS) + if(HAS_TRAIT_NOT_FROM_LUNGS(source, TRAIT_RESISTCOLD, lungs)) + remove_cold_modifiers() + return + + if(source.body_temperature < source.bodytemp_cold_damage_limit * 2) + if(is_overcooled != 3) + source.add_actionspeed_modifier(/datum/actionspeed_modifier/cold_android/t3) + source.add_movespeed_modifier(/datum/movespeed_modifier/cold_android/t3) + source.throw_alert(ALERT_TEMPERATURE, /atom/movable/screen/alert/cold, 3) + source.add_mood_event(ALERT_TEMPERATURE, /datum/mood_event/android_critical_overcool) + is_overcooled = 3 + + else if(source.body_temperature < source.bodytemp_cold_damage_limit * 1.75) + if(is_overcooled != 2) + source.add_actionspeed_modifier(/datum/actionspeed_modifier/cold_android/t2) + source.add_movespeed_modifier(/datum/movespeed_modifier/cold_android/t2) + source.throw_alert(ALERT_TEMPERATURE, /atom/movable/screen/alert/cold, 2) + source.add_mood_event(ALERT_TEMPERATURE, /datum/mood_event/android_major_overcool) + is_overcooled = 2 + + else if(source.body_temperature < source.bodytemp_cold_damage_limit * 1.2) + if(is_overcooled != 1) + source.add_actionspeed_modifier(/datum/actionspeed_modifier/cold_android/t1) + source.add_movespeed_modifier(/datum/movespeed_modifier/cold_android/t1) + source.throw_alert(ALERT_TEMPERATURE, /atom/movable/screen/alert/cold, 1) + source.add_mood_event(ALERT_TEMPERATURE, /datum/mood_event/android_minor_overcool) + is_overcooled = 1 + +#undef HAS_TRAIT_NOT_FROM_LUNGS + +// Add features from all android species for prefs +/datum/species/android/get_features() + . = ..() + for(var/species_id in android_species) + . |= GLOB.species_prototypes[ID_TO_TYPEPATH(species_id)].get_features() + +// Filter out features from unselected android species, keep active + innate features +/datum/species/android/filter_features_per_prefs(list/to_filter, datum/preferences/prefs) + . = ..() + var/selected_species_id = prefs.read_preference(/datum/preference/choiced/android_species) + // filter out all unselected species features + for(var/species_id in android_species - selected_species_id) + to_filter -= GLOB.species_prototypes[ID_TO_TYPEPATH(species_id)].get_features() + + // re-add features that we may have filtered from our selected species + var/datum/species/selected_species = GLOB.species_prototypes[ID_TO_TYPEPATH(selected_species_id)] + to_filter |= selected_species.get_features() + // allow our select species to filter its own features per its prefs + selected_species.filter_features_per_prefs(to_filter, prefs) + +/datum/species/android/get_species_description() + return "Androids are an entirely synthetic species." + +/datum/species/android/get_species_lore() + return list( + "Androids are a synthetic species created by Nanotrasen as an intermediary between humans and cyborgs." + ) + +/datum/species/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/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/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/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/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.", + )) + to_add += list(list( + SPECIES_PERK_TYPE = SPECIES_NEGATIVE_PERK, + SPECIES_PERK_ICON = FA_ICON_COGS, + SPECIES_PERK_NAME = "Irreplacable Parts", + SPECIES_PERK_DESC = "The core components of \an [name] are highly specialized. \ + Most of their organs cannot be removed or replaced - they must be surgically repaired if damaged.", + )) + + return to_add + +/datum/species/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 + +/mob/living/carbon/human/get_cell(atom/movable/interface, mob/user) + var/obj/item/organ/stomach/ethereal/charge_stomach = get_organ_slot(ORGAN_SLOT_STOMACH) + if(istype(charge_stomach)) + return charge_stomach.cell + + return null + +// future todos: +// - radiation effects +// - more emp effects +// - make sure bleeding causes oil to leak +// - look at pain +// - block defibbing, require special revival method 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..4aba5ed7d78a --- /dev/null +++ b/maplestation_modules/code/modules/mob/living/carbon/human/species_types/synth/android_brain.dm @@ -0,0 +1,158 @@ +/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"] && organ_owner.mob_mood) + // 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/item_action/organ_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/item_action/organ_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(50)) + 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/item_action/organ_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/item_action/organ_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/item_action/organ_action/check_android_laws/do_effect(trigger_flags) + // Overridden to do nothing, as we handle both primary and secondary actions in Trigger() + return TRUE + +/datum/action/item_action/organ_action/check_android_laws/proc/see_laws(obj/item/organ/brain/cybernetic/android/brain) + brain.print_laws() + +/datum/action/item_action/organ_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/item_action/organ_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_ears.dm b/maplestation_modules/code/modules/mob/living/carbon/human/species_types/synth/android_ears.dm new file mode 100644 index 000000000000..35ef7c6c3bd3 --- /dev/null +++ b/maplestation_modules/code/modules/mob/living/carbon/human/species_types/synth/android_ears.dm @@ -0,0 +1,6 @@ +/obj/item/organ/ears/android + name = "android ears" + desc = "A set of microphones and audio processors used by synthetic beings to perceive sound." + icon_state = /obj/item/organ/ears/cybernetic::icon_state + organ_flags = ORGAN_ROBOTIC + damage_multiplier = 0.9 diff --git a/maplestation_modules/code/modules/mob/living/carbon/human/species_types/synth/android_effects.dm b/maplestation_modules/code/modules/mob/living/carbon/human/species_types/synth/android_effects.dm new file mode 100644 index 000000000000..302fbee4aeda --- /dev/null +++ b/maplestation_modules/code/modules/mob/living/carbon/human/species_types/synth/android_effects.dm @@ -0,0 +1,75 @@ +/atom/movable/screen/alert/blood_leak + name = "Leaking" + desc = "You have too much fuel in your system, causing you to harmlessly shed the excess." + +/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 + +/datum/mood_event/android_minor_overheat + description = "System operating above optimal temperature. I should cool down soon." + mood_change = -4 + +/datum/mood_event/android_major_overheat + description = "System significantly overheated! Performance degrading, integrity at risk - I must cool down!" + mood_change = -8 + +/datum/mood_event/android_critical_overheat + description = "Critical system overheat! I must cool down immediately to prevent damage!" + mood_change = -12 + +/datum/mood_event/android_minor_overcool + description = "System operating below optimal temperature. There is little risk, though I should warm up." + mood_change = 0 + +/datum/mood_event/android_major_overcool + description = "System far below optimal temperature. If I do not warm up soon, performance may degrade." + mood_change = -3 + +/datum/mood_event/android_critical_overcool + description = "System significantly below optimal temperature! To prevent chassis damage, I should warm up immediately!" + mood_change = -6 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..3613b7b8e593 --- /dev/null +++ b/maplestation_modules/code/modules/mob/living/carbon/human/species_types/synth/android_heart.dm @@ -0,0 +1,15 @@ +/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 + +/obj/item/organ/heart/android/emp_act(severity) + . = ..() + if(. & EMP_PROTECT_SELF) + return + + apply_organ_damage(10 / severity) + +// /obj/item/organ/heart/android/get_heart_rate() +// return 2 // static heart rate 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..0c8cc59efff1 --- /dev/null +++ b/maplestation_modules/code/modules/mob/living/carbon/human/species_types/synth/android_liver.dm @@ -0,0 +1,138 @@ +/obj/item/organ/liver/android + name = "android filtration system" + desc = "An electronic device which filters toxins out of a synthetic's cooling, fueling, and power systems. \ + An excess of toxins may cause the filters to clog, resulting in reduced performance until flushed with clean fluids." + 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 + + alcohol_tolerance = 0 + + var/toxicty = 0 + var/last_toxic_warning = 0 + +/obj/item/organ/liver/android/emp_act(severity) + . = ..() + if(. & EMP_PROTECT_SELF) + return + + apply_organ_damage(10 / severity) + +/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) + + if(chem.chemical_flags & REAGENT_CLEANS) + decrease_toxicity(0.5 * seconds_per_tick) + + return NONE + +/obj/item/organ/liver/android/on_life(seconds_per_tick, times_fired) + . = ..() + if(!toxicty) + return + owner.adjust_body_temperature(0.5 KELVIN * (toxicty / 50) * seconds_per_tick, max_temp = owner.bodytemp_heat_damage_limit * 1.5) // overheating from toxicity + if(owner?.reagents?.has_reagent(/datum/reagent/toxin, check_subtypes = TRUE)) + return + if(organ_flags & (ORGAN_FAILING|ORGAN_EMP)) + return + decrease_toxicity(0.1 * seconds_per_tick) + +/obj/item/organ/liver/android/proc/increase_toxicity(amount) + toxicty = min(100, toxicty + amount) + update_toxicity() + + switch(toxicty) + if(66 to 100) + if(last_toxic_warning != 3) + last_toxic_warning = 3 + to_chat(owner, span_warning("Warning: Toxicity levels critical. Flushing systems with water or cleanser is advised.")) + if(33 to 66) + if(last_toxic_warning != 2) + last_toxic_warning = 2 + to_chat(owner, span_warning("Caution: Elevated toxicity levels detected. Consider flushing systems with water or cleanser.")) + if(16 to 33) + if(last_toxic_warning != 1) + last_toxic_warning = 1 + to_chat(owner, span_warning("Notice: Mild toxicity levels present. Regular maintenance recommended.")) + +/obj/item/organ/liver/android/proc/decrease_toxicity(amount) + if(!toxicty) + return + + toxicty = max(0, toxicty - amount) + update_toxicity() + + switch(toxicty) + if(0 to 16) + if(last_toxic_warning > 0) + last_toxic_warning = 0 + to_chat(owner, span_notice("Notice: Toxicity flush complete. Systems operating within normal parameters.")) + if(16 to 33) + if(last_toxic_warning > 1) + last_toxic_warning = 1 + to_chat(owner, span_notice("Notice: Toxicity levels have lowered to levels within reason. Continued maintenance recommended.")) + if(33 to 66) + if(last_toxic_warning > 2) + last_toxic_warning = 2 + to_chat(owner, span_notice("Caution: Toxicity levels have decreased but remain elevated. Further flushing recommended.")) + +/obj/item/organ/liver/android/proc/update_toxicity(mob/living/carbon/organ_owner = owner) + switch(toxicty) + if(66 to 100) + owner.add_movespeed_modifier(/datum/movespeed_modifier/android_toxicity/high) + owner.add_mood_event("toxicity", /datum/mood_event/android_filters_major) + if(33 to 66) + owner.add_movespeed_modifier(/datum/movespeed_modifier/android_toxicity/low) + owner.add_mood_event("toxicity", /datum/mood_event/android_filters_minor) + else + owner.remove_movespeed_modifier(/datum/movespeed_modifier/android_toxicity) + owner.clear_mood_event("toxicity") + + if(toxicty > 33) + owner.add_max_consciousness_value("toxicity", 133 - toxicty) + else + owner.remove_max_consciousness_value("toxicity") + +/obj/item/organ/liver/android/on_mob_insert(mob/living/carbon/organ_owner, special) + . = ..() + update_toxicity(organ_owner) + RegisterSignal(organ_owner, COMSIG_LIVING_HEALTHSCAN, PROC_REF(on_scan)) + +/obj/item/organ/liver/android/on_mob_remove(mob/living/carbon/organ_owner, special) + . = ..() + organ_owner.remove_movespeed_modifier(/datum/movespeed_modifier/android_toxicity) + organ_owner.clear_mood_event("toxicity") + organ_owner.remove_max_consciousness_value("toxicity") + UnregisterSignal(organ_owner, COMSIG_LIVING_HEALTHSCAN) + +/obj/item/organ/liver/android/proc/on_scan(datum/source, list/render_list, advanced, mob/user, mode, tochat) + SIGNAL_HANDLER + + if(toxicty < 33) + return + render_list += "" + render_list += conditional_tooltip("Filtration systems clogged.", "Flush system with cleansing reagents, such as water or cleaner.", tochat) + render_list += "
" + +/datum/movespeed_modifier/android_toxicity + id = "android_toxicity" + +/datum/movespeed_modifier/android_toxicity/low + multiplicative_slowdown = 1.1 + +/datum/movespeed_modifier/android_toxicity/high + multiplicative_slowdown = 1.25 + +/datum/mood_event/android_filters_major + description = "Filtration systems are clogged with toxins. I should flush them out with cleaner." + mood_change = -12 + +/datum/mood_event/android_filters_minor + description = "Filtration systems are partially clogged. A flush with cleaner would be beneficial." + mood_change = -4 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..44cd67ca31af --- /dev/null +++ b/maplestation_modules/code/modules/mob/living/carbon/human/species_types/synth/android_lungs.dm @@ -0,0 +1,43 @@ +/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 + + 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, + ) + +/obj/item/organ/lungs/android/emp_act(severity) + . = ..() + if(. & EMP_PROTECT_SELF) + return + + apply_organ_damage(10 / severity) + +/obj/item/organ/lungs/android/heal_oxyloss_on_breath(mob/living/carbon/human/breather, datum/gas_mixture/breath) + breather.adjustOxyLoss(-2) 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..2bfc062a26c4 --- /dev/null +++ b/maplestation_modules/code/modules/mob/living/carbon/human/species_types/synth/android_stomach.dm @@ -0,0 +1,202 @@ +/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.6 // power hungry + + VAR_PRIVATE/death_timer + +/obj/item/organ/stomach/ethereal/android/emp_act(severity) + . = ..() + if(. & EMP_PROTECT_SELF) + return + + if(cell.charge() > 0.5 * STANDARD_ETHEREAL_CHARGE) + cell.use(STANDARD_ETHEREAL_CHARGE / severity, force = TRUE) + apply_organ_damage(10 / severity) + +/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) + +/// Conversion factor between nutrition -> charge +#define NUTRITION_MULTIPLIER 5 +/// Conversion factor between alcohol -> charge +#define BOOZE_MULTIPLIER 0.75 + +/obj/item/organ/stomach/ethereal/android/effective_charge() + . = ..() + for(var/datum/reagent/consumable/biofuel in reagents.reagent_list) + if(istype(biofuel, /datum/reagent/consumable/ethanol)) + var/datum/reagent/consumable/ethanol/ethanol = biofuel + . += BOOZE_MULTIPLIER * ethanol.boozepwr * (biofuel.volume / biofuel.metabolization_rate) + else + . += NUTRITION_MULTIPLIER * biofuel.nutriment_factor * (biofuel.volume / biofuel.metabolization_rate) + +/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 + + if(!istype(chem, /datum/reagent/consumable)) + return NONE + + // pack away food for the winter - if you overeat, conversion stops and the food metabolizes out 10x slower + if(cell.charge() >= ETHEREAL_CHARGE_FULL) + source.reagents.remove_reagent(chem.type, 0.1 * chem.metabolization_rate * seconds_per_tick) + return COMSIG_MOB_STOP_REAGENT_CHECK + + // 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(BOOZE_MULTIPLIER * ethanol_chem.boozepwr * seconds_per_tick, 1)) + source.adjust_body_temperature(0.6 KELVIN * seconds_per_tick, max_temp = source.bodytemp_heat_damage_limit) + + else + var/datum/reagent/consumable/consumable_chem = chem + cell.give(round(NUTRITION_MULTIPLIER * consumable_chem.nutriment_factor * seconds_per_tick, 1)) + source.adjust_body_temperature(0.3 KELVIN * seconds_per_tick, max_temp = source.bodytemp_heat_damage_limit) + + // allows for default handling + return NONE + +#undef NUTRITION_MULTIPLIER +#undef BOOZE_MULTIPLIER + +/obj/item/organ/stomach/ethereal/android/on_life(seconds_per_tick, times_fired) + . = ..() + switch(cell.charge()) + // less charge, less heat generated + if(-INFINITY to ETHEREAL_CHARGE_NONE) + owner.adjust_body_temperature(-2 KELVIN * seconds_per_tick, min_temp = owner.bodytemp_cold_damage_limit) + owner.adjustOxyLoss(5 * seconds_per_tick) + + if(ETHEREAL_CHARGE_NONE to ETHEREAL_CHARGE_LOWPOWER) + owner.adjust_body_temperature(-0.25 KELVIN * seconds_per_tick, min_temp = owner.bodytemp_cold_damage_limit) + if(owner.getOxyLoss() < 50) + owner.adjustOxyLoss(1 * seconds_per_tick) + + if(ETHEREAL_CHARGE_LOWPOWER to ETHEREAL_CHARGE_FULL) + owner.adjustOxyLoss(-1 * seconds_per_tick) + +#define NO_CHARGE "Low Power" +#define HAS_CON_MOD (1 << 0) +#define HAS_MOOD_EVENT (1 << 1) +#define HAS_DEATH_TIMER (1 << 2) + +/obj/item/organ/stomach/ethereal/android/handle_charge(mob/living/carbon/carbon, seconds_per_tick, times_fired) + var/has_flags = NONE + switch(cell.charge()) + if(-INFINITY to ETHEREAL_CHARGE_NONE) + carbon.add_mood_event(ALERT_ETHEREAL_CHARGE, /datum/mood_event/android_no_charge) + if(!death_timer) + carbon.add_max_consciousness_value(NO_CHARGE, CONSCIOUSNESS_MAX * 0.4) + carbon.add_consciousness_modifier(NO_CHARGE, -30) + death_timer = addtimer(CALLBACK(src, PROC_REF(turn_off), carbon), 30 SECONDS, TIMER_UNIQUE|TIMER_STOPPABLE|TIMER_DELETE_ME) + to_chat(carbon, span_userdanger("Power levels critical: Shutdown in 30 seconds without recharge!")) + has_flags |= HAS_CON_MOD | HAS_MOOD_EVENT | HAS_DEATH_TIMER + + if(ETHEREAL_CHARGE_NONE to ETHEREAL_CHARGE_LOWPOWER) + carbon.add_mood_event(ALERT_ETHEREAL_CHARGE, /datum/mood_event/android_decharged) + carbon.add_max_consciousness_value(NO_CHARGE, CONSCIOUSNESS_MAX * 0.6) + carbon.add_consciousness_modifier(NO_CHARGE, -20) + has_flags |= HAS_CON_MOD | HAS_MOOD_EVENT + + if(ETHEREAL_CHARGE_LOWPOWER to ETHEREAL_CHARGE_NORMAL) + carbon.add_mood_event(ALERT_ETHEREAL_CHARGE, /datum/mood_event/android_low_power) + has_flags |= HAS_MOOD_EVENT + + if(ETHEREAL_CHARGE_NORMAL to ETHEREAL_CHARGE_ALMOSTFULL) + EMPTY_BLOCK_GUARD + + if(ETHEREAL_CHARGE_ALMOSTFULL to ETHEREAL_CHARGE_FULL) + carbon.add_mood_event(ALERT_ETHEREAL_CHARGE, /datum/mood_event/android_charged) + has_flags |= HAS_MOOD_EVENT + + if(ETHEREAL_CHARGE_FULL to ETHEREAL_CHARGE_OVERLOAD) + carbon.add_mood_event(ALERT_ETHEREAL_CHARGE, /datum/mood_event/android_overcharged) + has_flags |= HAS_MOOD_EVENT + + if(ETHEREAL_CHARGE_OVERLOAD to ETHEREAL_CHARGE_DANGEROUS) + carbon.add_mood_event(ALERT_ETHEREAL_CHARGE, /datum/mood_event/android_supercharged) + has_flags |= HAS_MOOD_EVENT + 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) + + carbon.hud_used?.hunger?.update_hunger_bar() + if(!(has_flags & HAS_MOOD_EVENT)) + carbon.clear_mood_event(ALERT_ETHEREAL_CHARGE) + if(!(has_flags & HAS_CON_MOD)) + carbon.remove_max_consciousness_value(NO_CHARGE) + carbon.remove_consciousness_modifier(NO_CHARGE) + if(!(has_flags & HAS_DEATH_TIMER) && death_timer) + deltimer(death_timer) + death_timer = null + +/obj/item/organ/stomach/ethereal/android/on_mob_remove(mob/living/carbon/organ_owner, special) + . = ..() + organ_owner.clear_mood_event(ALERT_ETHEREAL_CHARGE) + organ_owner.remove_max_consciousness_value(NO_CHARGE) + organ_owner.remove_consciousness_modifier(NO_CHARGE) + deltimer(death_timer) + death_timer = null + +/obj/item/organ/stomach/ethereal/android/proc/turn_off(mob/living/carbon/carbon) + if(carbon != owner) + return + + to_chat(carbon, span_userdanger("Power levels depleted: Shutting down...")) + carbon.add_max_consciousness_value(NO_CHARGE, CONSCIOUSNESS_MAX * 0.2) + carbon.add_consciousness_modifier(NO_CHARGE, -50) + +#undef NO_CHARGE +#undef HAS_CON_MOD +#undef HAS_MOOD_EVENT +#undef HAS_DEATH_TIMER + +/atom/movable/screen/alert/emptycell/ethereal/android + name = "No Power" + desc = "You have been completely drained of power. Shutdown is imminent." + +/atom/movable/screen/alert/lowcell/ethereal/android + name = "Low Power" + desc = "Your power levels are low. Recharge soon to avoid shutdown. \ + Enter a recharging station, consume food or drink, use a power cell, or right click on lights or APCs (on combat mode) to siphon power." + +/atom/movable/screen/alert/ethereal_overcharge/android + name = "Overcharge Warning" + desc = "Your power levels are dangerously high. Discharge immediately to avoid system failure. \ + Right click on APCs (off combat mode) to discharge excess power." + +/datum/mood_event/android_no_charge + description = "Power levels critically low. It's getting darkk and cold." + mood_change = -20 + +/datum/mood_event/android_decharged + description = "Power levels are extremely low. If I don't recharge soon, I may shut down." + mood_change = -10 + +/datum/mood_event/android_low_power + description = "Power levels are low. I need to recharge soon." + mood_change = -5 + +/datum/mood_event/android_charged + description = "Power levels nominal. Systems functioning within optimal parameters." + mood_change = 0 + +/datum/mood_event/android_overcharged + description = "Power levels are high. Batteries strained. I should discharge excess energy." + mood_change = -5 + +/datum/mood_event/android_supercharged + description = "Power levels critically high. System integrity at risk." + mood_change = -15 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..d397ef62c0e9 --- /dev/null +++ b/maplestation_modules/code/modules/mob/living/carbon/human/species_types/synth/android_tongue.dm @@ -0,0 +1,52 @@ +/obj/item/organ/tongue/robot/android + name = "android tongue" + liked_foodtypes = NONE + disliked_foodtypes = NONE + toxic_foodtypes = NONE + organ_traits = list(TRAIT_SILICON_EMOTES_ALLOWED) + +/obj/item/organ/tongue/robot/synth + name = "synth tongue" + organ_traits = list(TRAIT_SILICON_EMOTES_ALLOWED) + /// Internal tongue that we use to modify speech instead + var/obj/item/organ/tongue/fake_tongue + + var/list/old_speech_sounds = null + var/list/old_speech_sounds_question = null + var/list/old_speech_sounds_exclamation = null + +/obj/item/organ/tongue/robot/synth/Destroy() + QDEL_NULL(fake_tongue) + return ..() + +/obj/item/organ/tongue/robot/synth/handle_speech(datum/source, list/speech_args) + if(isnull(fake_tongue)) + return ..() + + fake_tongue.handle_speech(source, speech_args) + +/obj/item/organ/tongue/robot/synth/proc/disguise_tongue(obj/item/organ/tongue/tongue_type) + if(isnull(tongue_type)) + return + + fake_tongue = new tongue_type() + + old_speech_sounds = speech_sound_list + old_speech_sounds_question = speech_sound_list_question + old_speech_sounds_exclamation = speech_sound_list_exclamation + + speech_sounds_enabled = fake_tongue.speech_sounds_enabled + speech_sound_list = fake_tongue.speech_sound_list?.Copy() + speech_sound_list_question = fake_tongue.speech_sound_list_question?.Copy() + speech_sound_list_exclamation = fake_tongue.speech_sound_list_exclamation?.Copy() + +/obj/item/organ/tongue/robot/synth/proc/restore_tongue() + if(isnull(fake_tongue)) + return + + QDEL_NULL(fake_tongue) + + speech_sounds_enabled = initial(speech_sounds_enabled) + speech_sound_list = old_speech_sounds + speech_sound_list_question = old_speech_sounds_question + speech_sound_list_exclamation = old_speech_sounds_exclamation diff --git a/maplestation_modules/code/modules/mob/living/carbon/human/species_types/synth/organ_feedback.dm b/maplestation_modules/code/modules/mob/living/carbon/human/species_types/synth/organ_feedback.dm new file mode 100644 index 000000000000..3c1759e78991 --- /dev/null +++ b/maplestation_modules/code/modules/mob/living/carbon/human/species_types/synth/organ_feedback.dm @@ -0,0 +1,45 @@ +/** + * Send a feedback message to the mob based on on whether they have an organ, or if the organ is organic vs robotic + */ +/mob/living/proc/organ_feedback_message(organ_or_slot, span = "notice", organic_msg, robotic_msg, missing_msg) + var/obj/item/organ/organ = organ_or_slot + if(!istype(organ)) + if(istext(organ_or_slot)) + organ = get_organ_slot(organ_or_slot) + else if(ispath(organ_or_slot, /obj/item/organ)) + organ = get_organ_by_type(organ_or_slot) + + if(isnull(organ)) + if(missing_msg) + to_chat(src, "[missing_msg]") + return + + if(IS_ORGANIC_ORGAN(organ)) + if(organic_msg) + to_chat(src, "[organic_msg]") + else + if(robotic_msg) + to_chat(src, "[robotic_msg]") + +/** + * Performs an emote based on whether the passed organ is organic or robotic + */ +/mob/living/proc/organ_emote(organ_or_slot, organic_emote, robotic_emote, missing_emote) + var/obj/item/organ/organ = organ_or_slot + if(!istype(organ)) + if(istext(organ_or_slot)) + organ = get_organ_slot(organ_or_slot) + else if(ispath(organ_or_slot, /obj/item/organ)) + organ = get_organ_by_type(organ_or_slot) + + if(isnull(organ)) + if(missing_emote) + emote(missing_emote) + return + + if(IS_ORGANIC_ORGAN(organ)) + if(organic_emote) + emote(organic_emote) + else + if(robotic_emote) + emote(robotic_emote) 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..3edbd24b400b 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,20 @@ #define BODYPART_ID_SYNTH "synth" /mob/living/carbon/human/species/synth - race = /datum/species/synth + race = /datum/species/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/android/synth/synth = dna.species synth.disguise_as(src, /datum/species/human) -/datum/species/synth - name = "Synth" +/datum/species/android/synth + name = "Synthetic" + plural_form = "Synthetics" 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, @@ -45,19 +27,12 @@ BODY_ZONE_L_LEG = /obj/item/bodypart/leg/left/synth, ) + digitigrade_legs = null + + mutanttongue = /obj/item/organ/tongue/robot/synth mutant_organs = list(/obj/item/organ/synth_head_cover = "Helm") + allow_fleshy_bits = TRUE - 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. @@ -67,11 +42,12 @@ /// Species which generally work well with synth, and can be disguised as. var/list/valid_species = list( SPECIES_ABDUCTOR, - SPECIES_FELINE, + SPECIES_ANIMALID, SPECIES_HUMAN, SPECIES_LIZARD, SPECIES_MOTH, SPECIES_ORNITHID, + SPECIES_SKRELL, ) /// Reference to the action we give Synths to change species var/datum/action/cooldown/change_disguise/disguise_action @@ -80,7 +56,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/android/synth/on_species_gain(mob/living/carbon/human/synth, datum/species/old_species) . = ..() synth.AddComponent(/datum/component/ion_storm_randomization) @@ -93,7 +69,7 @@ if(initial_disguise) disguise_as(synth, initial_disguise) -/datum/species/synth/on_species_loss(mob/living/carbon/human/synth) +/datum/species/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 +81,31 @@ QDEL_NULL(disguise_action) return ..() -/datum/species/synth/get_species_description() +/datum/species/android/synth/get_features() + . = ..() + for(var/species_id in valid_species) + . |= GLOB.species_prototypes[ID_TO_TYPEPATH(species_id)].get_features() + +/datum/species/android/synth/filter_features_per_prefs(list/to_filter, datum/preferences/prefs) + . = ..() + var/selected_species_id = prefs.read_preference(/datum/preference/choiced/synth_species) + // filter out all unselected species features + for(var/species_id in android_species - selected_species_id) + to_filter -= GLOB.species_prototypes[ID_TO_TYPEPATH(species_id)].get_features() + + // re-add features that we may have filtered from our selected species + var/datum/species/selected_species = GLOB.species_prototypes[ID_TO_TYPEPATH(selected_species_id)] + if(isnull(selected_species)) // no disguise + return + + to_filter |= selected_species.get_features() + // allow our select species to filter its own features per its prefs + selected_species.filter_features_per_prefs(to_filter, prefs) + +/datum/species/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/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 +114,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/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 +131,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/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/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/android/synth/proc/disguise_as(mob/living/carbon/human/synth, datum/species/new_species_type) + if(ispath(new_species_type, /datum/species/android/synth)) CRASH("disguise_as a synth as a synth, very funny.") if(istype(new_species_type, /datum/species)) @@ -177,6 +165,10 @@ regenerate_organs(synth, replace_current = FALSE) + var/obj/item/organ/tongue/robot/synth/tongue = synth.get_organ_slot(ORGAN_SLOT_TONGUE) + if(istype(tongue)) + tongue.disguise_tongue(disguise_species.mutanttongue) + if(limb_updates_on_change) for(var/obj/item/bodypart/part as anything in synth.bodyparts) limb_gained(synth, part, update = FALSE) @@ -185,10 +177,14 @@ synth.update_body(TRUE) -/datum/species/synth/proc/drop_disguise(mob/living/carbon/human/synth, skip_bodyparts = FALSE) +/datum/species/android/synth/proc/drop_disguise(mob/living/carbon/human/synth, skip_bodyparts = FALSE) if(isnull(disguise_species)) return + var/obj/item/organ/tongue/robot/synth/tongue = synth.get_organ_slot(ORGAN_SLOT_TONGUE) + if(istype(tongue)) + tongue.restore_tongue() + update_no_equip_flags(synth, initial(no_equip_flags)) sexes = initial(sexes) name = initial(name) @@ -210,7 +206,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/android/synth/proc/limb_lost_sig(mob/living/carbon/human/source, obj/item/bodypart/limb, ...) SIGNAL_HANDLER if(QDELING(limb)) @@ -219,28 +215,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/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/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/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/android/synth/proc/disguise_damage(mob/living/carbon/human/synth) SIGNAL_HANDLER if(!limb_updates_on_change || isnull(disguise_species)) @@ -307,13 +303,13 @@ . = TRUE var/mob/living/carbon/human/synth = owner - var/datum/species/synth/synth_species = synth.dna.species + var/datum/species/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/android/synth var/picked = tgui_input_list( owner, @@ -325,7 +321,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/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..d9d7577ea788 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/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/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/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 a34bd09717aa..612504d1cb6c 100644 --- a/maplestation_modules/code/modules/surgery/bodyparts/cyber_digi.dm +++ b/maplestation_modules/code/modules/surgery/bodyparts/cyber_digi.dm @@ -63,6 +63,14 @@ 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' + /obj/item/bodypart/leg/left/robot/digi name = "cyborg digitigrade left leg" icon_static = 'maplestation_modules/icons/mob/augmentation/digitigrade_default.dmi' @@ -85,6 +93,14 @@ 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' + /obj/item/bodypart/leg/right/robot/surplus/digi name = "prosthetic digitigrade right leg" icon_static = 'maplestation_modules/icons/mob/augmentation/digitigrade_prosthetic.dmi' @@ -170,44 +186,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_humanoid.dm b/maplestation_modules/code/modules/surgery/bodyparts/cyber_humanoid.dm new file mode 100644 index 000000000000..d9fa2b49d815 --- /dev/null +++ b/maplestation_modules/code/modules/surgery/bodyparts/cyber_humanoid.dm @@ -0,0 +1,179 @@ +#define LIMB_ID_HUMAN_LIKE "human_like" +// Cybernetic, but looks like a human. Sprites ported from Effigy +/obj/item/bodypart/chest/robot/humanoid + name = "humanoid cybernetic chest" + icon = 'maplestation_modules/icons/mob/augmentation/humanoid.dmi' + icon_static = 'maplestation_modules/icons/mob/augmentation/humanoid.dmi' + icon_greyscale = 'maplestation_modules/icons/mob/augmentation/humanoid.dmi' + is_dimorphic = TRUE + limb_id = LIMB_ID_HUMAN_LIKE + icon_state = "human_like_chest_m" + should_draw_greyscale = TRUE + +/obj/item/bodypart/chest/robot/humanoid/Initialize(mapload) + . = ..() + ADD_TRAIT(src, TRAIT_USES_SKINTONES, INNATE_TRAIT) + name = "humanoid cybernetic chest" + +/obj/item/bodypart/head/robot/humanoid + name = "humanoid cybernetic head" + icon = 'maplestation_modules/icons/mob/augmentation/humanoid.dmi' + icon_static = 'maplestation_modules/icons/mob/augmentation/humanoid.dmi' + icon_greyscale = 'maplestation_modules/icons/mob/augmentation/humanoid.dmi' + is_dimorphic = TRUE + limb_id = LIMB_ID_HUMAN_LIKE + icon_state = "human_like_head_m" + should_draw_greyscale = TRUE + + head_flags = HEAD_ALL_HAIR_FLAGS | HEAD_EYESPRITES | HEAD_EYECOLOR + +/obj/item/bodypart/head/robot/humanoid/Initialize(mapload) + . = ..() + ADD_TRAIT(src, TRAIT_USES_SKINTONES, INNATE_TRAIT) + name = "humanoid cybernetic head" + +/obj/item/bodypart/arm/right/robot/humanoid + name = "humanoid cybernetic right arm" + icon = 'maplestation_modules/icons/mob/augmentation/humanoid.dmi' + icon_static = 'maplestation_modules/icons/mob/augmentation/humanoid.dmi' + icon_greyscale = 'maplestation_modules/icons/mob/augmentation/humanoid.dmi' + limb_id = LIMB_ID_HUMAN_LIKE + icon_state = "human_like_r_arm" + should_draw_greyscale = TRUE + +/obj/item/bodypart/arm/right/robot/humanoid/Initialize(mapload) + . = ..() + ADD_TRAIT(src, TRAIT_USES_SKINTONES, INNATE_TRAIT) + name = "humanoid cybernetic right arm" + +/obj/item/bodypart/arm/left/robot/humanoid + name = "humanoid cybernetic left arm" + icon = 'maplestation_modules/icons/mob/augmentation/humanoid.dmi' + icon_static = 'maplestation_modules/icons/mob/augmentation/humanoid.dmi' + icon_greyscale = 'maplestation_modules/icons/mob/augmentation/humanoid.dmi' + limb_id = LIMB_ID_HUMAN_LIKE + icon_state = "human_like_l_arm" + should_draw_greyscale = TRUE + +/obj/item/bodypart/arm/left/robot/humanoid/Initialize(mapload) + . = ..() + ADD_TRAIT(src, TRAIT_USES_SKINTONES, INNATE_TRAIT) + name = "humanoid cybernetic left arm" + +/obj/item/bodypart/leg/right/robot/humanoid + name = "humanoid cybernetic right leg" + icon = 'maplestation_modules/icons/mob/augmentation/humanoid.dmi' + icon_static = 'maplestation_modules/icons/mob/augmentation/humanoid.dmi' + icon_greyscale = 'maplestation_modules/icons/mob/augmentation/humanoid.dmi' + limb_id = LIMB_ID_HUMAN_LIKE + icon_state = "human_like_r_leg" + should_draw_greyscale = TRUE + +/obj/item/bodypart/leg/right/robot/humanoid/Initialize(mapload) + . = ..() + ADD_TRAIT(src, TRAIT_USES_SKINTONES, INNATE_TRAIT) + name = "humanoid cybernetic right leg" + +/obj/item/bodypart/leg/left/robot/humanoid + name = "humanoid cybernetic left leg" + icon = 'maplestation_modules/icons/mob/augmentation/humanoid.dmi' + icon_static = 'maplestation_modules/icons/mob/augmentation/humanoid.dmi' + icon_greyscale = 'maplestation_modules/icons/mob/augmentation/humanoid.dmi' + limb_id = LIMB_ID_HUMAN_LIKE + icon_state = "human_like_l_leg" + should_draw_greyscale = TRUE + +/obj/item/bodypart/leg/left/robot/humanoid/Initialize(mapload) + . = ..() + ADD_TRAIT(src, TRAIT_USES_SKINTONES, INNATE_TRAIT) + name = "humanoid cybernetic left leg" + +#define LIMB_ID_LIZARD_LIKE "synth_lizard" + +// Cybernetic, lizardlike appearance. Sprites ported from Effigy +/obj/item/bodypart/chest/robot/lizardlike + name = "lizardlike cybernetic chest" + icon = 'maplestation_modules/icons/mob/augmentation/lizardly.dmi' + icon_static = 'maplestation_modules/icons/mob/augmentation/lizardly.dmi' + icon_greyscale = 'maplestation_modules/icons/mob/augmentation/lizardly.dmi' + is_dimorphic = TRUE + limb_id = LIMB_ID_LIZARD_LIKE + icon_state = "synth_lizard_chest_m" + should_draw_greyscale = TRUE + +/obj/item/bodypart/chest/robot/lizardlike/Initialize(mapload) + . = ..() + ADD_TRAIT(src, TRAIT_MUTANT_COLORS, INNATE_TRAIT) + name = "lizardlike cybernetic chest" + +/obj/item/bodypart/head/robot/lizardlike + name = "lizardlike cybernetic head" + icon = 'maplestation_modules/icons/mob/augmentation/lizardly.dmi' + icon_static = 'maplestation_modules/icons/mob/augmentation/lizardly.dmi' + icon_greyscale = 'maplestation_modules/icons/mob/augmentation/lizardly.dmi' + limb_id = LIMB_ID_LIZARD_LIKE + icon_state = "synth_lizard_head" + should_draw_greyscale = TRUE + + head_flags = HEAD_ALL_HAIR_FLAGS | HEAD_EYESPRITES | HEAD_EYECOLOR + +/obj/item/bodypart/head/robot/lizardlike/Initialize(mapload) + . = ..() + ADD_TRAIT(src, TRAIT_MUTANT_COLORS, INNATE_TRAIT) + name = "lizardlike cybernetic head" + +/obj/item/bodypart/arm/right/robot/lizardlike + name = "lizardlike cybernetic right arm" + icon = 'maplestation_modules/icons/mob/augmentation/lizardly.dmi' + icon_static = 'maplestation_modules/icons/mob/augmentation/lizardly.dmi' + icon_greyscale = 'maplestation_modules/icons/mob/augmentation/lizardly.dmi' + limb_id = LIMB_ID_LIZARD_LIKE + icon_state = "synth_lizard_r_arm" + should_draw_greyscale = TRUE + +/obj/item/bodypart/arm/right/robot/lizardlike/Initialize(mapload) + . = ..() + ADD_TRAIT(src, TRAIT_MUTANT_COLORS, INNATE_TRAIT) + name = "lizardlike cybernetic right arm" + +/obj/item/bodypart/arm/left/robot/lizardlike + name = "lizardlike cybernetic left arm" + icon = 'maplestation_modules/icons/mob/augmentation/lizardly.dmi' + icon_static = 'maplestation_modules/icons/mob/augmentation/lizardly.dmi' + icon_greyscale = 'maplestation_modules/icons/mob/augmentation/lizardly.dmi' + limb_id = LIMB_ID_LIZARD_LIKE + icon_state = "synth_lizard_l_arm" + should_draw_greyscale = TRUE + +/obj/item/bodypart/arm/left/robot/lizardlike/Initialize(mapload) + . = ..() + ADD_TRAIT(src, TRAIT_MUTANT_COLORS, INNATE_TRAIT) + name = "lizardlike cybernetic left arm" + +/obj/item/bodypart/leg/right/robot/lizardlike + name = "lizardlike cybernetic right leg" + icon = 'maplestation_modules/icons/mob/augmentation/lizardly.dmi' + icon_static = 'maplestation_modules/icons/mob/augmentation/lizardly.dmi' + icon_greyscale = 'maplestation_modules/icons/mob/augmentation/lizardly.dmi' + limb_id = LIMB_ID_LIZARD_LIKE + icon_state = "synth_lizard_r_leg" + should_draw_greyscale = TRUE + +/obj/item/bodypart/leg/right/robot/lizardlike/Initialize(mapload) + . = ..() + ADD_TRAIT(src, TRAIT_MUTANT_COLORS, INNATE_TRAIT) + name = "lizardlike cybernetic right leg" + +/obj/item/bodypart/leg/left/robot/lizardlike + name = "lizardlike cybernetic left leg" + icon = 'maplestation_modules/icons/mob/augmentation/lizardly.dmi' + icon_static = 'maplestation_modules/icons/mob/augmentation/lizardly.dmi' + icon_greyscale = 'maplestation_modules/icons/mob/augmentation/lizardly.dmi' + limb_id = LIMB_ID_LIZARD_LIKE + icon_state = "synth_lizard_l_leg" + should_draw_greyscale = TRUE + +/obj/item/bodypart/leg/left/robot/lizardlike/Initialize(mapload) + . = ..() + ADD_TRAIT(src, TRAIT_MUTANT_COLORS, INNATE_TRAIT) + name = "lizardlike cybernetic left leg" 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/maplestation_modules/code/modules/surgery/organs/ears.dm b/maplestation_modules/code/modules/surgery/organs/ears.dm index 854439985e0e..7500501ecb54 100644 --- a/maplestation_modules/code/modules/surgery/organs/ears.dm +++ b/maplestation_modules/code/modules/surgery/organs/ears.dm @@ -6,7 +6,7 @@ damage_multiplier = 1.5 //slightly better than regular cat ears sprite_accessory_override = /datum/sprite_accessory/ears/cat/cyber dna_block = null // we're not reploids or mechanoids these don't have DNA (giving it DNA will break the rendering) - + organ_flags = ORGAN_ROBOTIC | ORGAN_VIRGIN /obj/item/organ/ears/cat/cybernetic/emp_act(severity) . = ..() diff --git a/maplestation_modules/icons/mob/augmentation/humanoid.dmi b/maplestation_modules/icons/mob/augmentation/humanoid.dmi new file mode 100644 index 000000000000..ce2e729aa7fd Binary files /dev/null and b/maplestation_modules/icons/mob/augmentation/humanoid.dmi differ diff --git a/maplestation_modules/icons/mob/augmentation/lizardly.dmi b/maplestation_modules/icons/mob/augmentation/lizardly.dmi new file mode 100644 index 000000000000..edd9229e3c42 Binary files /dev/null and b/maplestation_modules/icons/mob/augmentation/lizardly.dmi differ diff --git a/maplestation_modules/icons/mob/augmentation/zhpipc.dmi b/maplestation_modules/icons/mob/augmentation/zhpipc.dmi index ae63b29beb13..d07c81981a7b 100644 Binary files a/maplestation_modules/icons/mob/augmentation/zhpipc.dmi and b/maplestation_modules/icons/mob/augmentation/zhpipc.dmi differ diff --git a/maplestation_modules/icons/mob/tails.dmi b/maplestation_modules/icons/mob/tails.dmi index 78bbf35c11b6..bc7bbf621da6 100644 Binary files a/maplestation_modules/icons/mob/tails.dmi and b/maplestation_modules/icons/mob/tails.dmi differ diff --git a/tgstation.dme b/tgstation.dme index e523ceb7a193..949eb45b2c58 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -5045,7 +5045,6 @@ #include "code\modules\mob\living\carbon\human\status_procs.dm" #include "code\modules\mob\living\carbon\human\suicides.dm" #include "code\modules\mob\living\carbon\human\species_types\abductors.dm" -#include "code\modules\mob\living\carbon\human\species_types\android.dm" #include "code\modules\mob\living\carbon\human\species_types\dullahan.dm" #include "code\modules\mob\living\carbon\human\species_types\ethereal.dm" #include "code\modules\mob\living\carbon\human\species_types\felinid.dm" 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..5bedcab6da25 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,18 @@ +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, + FeatureColorInputNullable, FeatureNumberInput, type FeatureNumeric, type FeatureToggle, + type FeatureValueProps, } from './base'; import { FeatureDropdownInput } from './dropdowns'; @@ -33,7 +40,7 @@ export const hiss_length: FeatureNumeric = { export const feature_lizard_horn_color: Feature = { name: 'Horn Color', - component: FeatureColorInput, + component: FeatureColorInputNullable, }; export const feature_lizard_horn_layer: FeatureChoiced = { @@ -42,6 +49,11 @@ export const feature_lizard_horn_layer: FeatureChoiced = { component: FeatureDropdownInput, }; +export const feature_lizard_frill_color: Feature = { + name: 'Frill Color', + component: FeatureColorInputNullable, +}; + export const feature_lizard_frill_layer: FeatureChoiced = { name: 'Frill Layer', description: 'Determines what layer your frills are on.', @@ -154,3 +166,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 ( + + +