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]: