Shell May Cry: A comprehensive rework of synthetics. (#20721)

https://www.youtube.com/watch?v=9mPvZ96pHJI

A pull request commissioned by the Synthetic Lore Team to
comprehensively rework synthetics (read: IPCs) and how they work in
Aurora. The objective is to make IPCs as unique as possible from humans,
upgrading the robotic feel and atmosphere, while also preserving a good
sense of balance in-game.

Key features:

- A comprehensive expansion of synthetic organs, all of which now
fulfill a purpose: hydraulics, cooling units, power systems, actuators,
diagnostics units.
- Customizable organs with benefits and drawbacks, such as with cooling
units and power systems.
- Unique ways to repair the organs and more involved steps.
- Unique damage mechanics - every organ has wiring and electronics which
affect its functioning, and they are defended by plating which provides
natural armour.
- Improved and immersive diagnostics.
- Unique features, benefits, and drawbacks for every IPC frame.
- A rework of the positronic brain, which can be either destroyed or
shut down, alongside effects caused by low integrity.
- A rework of how EMPs affect IPC organs.
- Non-binary damage states for each organ.

To-do:

- [x] Finish the unique features for each frame.
- [x] Look into if mechanical synthskin is possible.
- [x] Power system.
- [x] Posibrain mechanics.
- [ ] Passive cooling expansion.
- [x] EMP mechanics.
- [x] Repair mechanics.
- [x] Mob weight mechanics.
- [ ] Gurney for heavy mobs.
- [x] New augments.
- [ ] IPC tag scanning and flashing.

---------

Signed-off-by: Werner <1331699+Arrow768@users.noreply.github.com>
Co-authored-by: Matt Atlas <liermattia@gmail.com>
Co-authored-by: Geeves <22774890+Geevies@users.noreply.github.com>
Co-authored-by: Werner <1331699+Arrow768@users.noreply.github.com>
This commit is contained in:
Matt Atlas
2025-12-21 17:26:23 +01:00
committed by GitHub
parent 1898ad3417
commit e0aa218843
201 changed files with 5962 additions and 896 deletions
@@ -109,6 +109,11 @@
key = RAD
return key
// Used for natural armor for species.
/datum/component/armor/natural
full_block_message = "Your natural armor blocks the blow!"
partial_block_message = "Your natural armor softens the blow!"
/datum/component/armor/toggle
var/active = TRUE
@@ -0,0 +1,61 @@
// An edit of ablative armor, taken from Baystation12.
/datum/component/armor/synthetic
full_block_message = "Your external plating absorbs the blow!"
partial_block_message = "Your external plating dulls the blow!"
/// Maximum armor values.
var/list/max_armor_values
/// How fast armor degrades with blocked damage, with armor value reduced by [coef * damage taken]
var/armor_degradation_coef = 0.8
/// Wearer feedback.
var/list/last_reported_damage
/datum/component/armor/synthetic/Initialize(list/armor, armor_type, _armor_degradation_speed)
. = ..()
max_armor_values = armor_values.Copy()
if(!isnull(_armor_degradation_speed))
armor_degradation_coef = _armor_degradation_speed
/datum/component/armor/synthetic/on_blocking(damage, damage_type, damage_flags, armor_pen, blocked)
if (!(damage_type == DAMAGE_BRUTE || damage_type == DAMAGE_BURN))
return
if(armor_degradation_coef)
var/key = get_armor_key(damage_type, damage_flags)
var/damage_taken
if(blocked)
damage_taken = round(damage * blocked)
else
damage_taken = damage
var/new_armor = max(0, get_value(key) - armor_degradation_coef * damage_taken)
set_value(key, new_armor)
var/mob/M = parent
if(istype(M))
var/list/visible = get_visible_damage()
for(var/k in visible)
if(LAZYACCESS(last_reported_damage, k) != visible[k])
LAZYSET(last_reported_damage, k, visible[k])
to_chat(M, SPAN_WARNING("Your external plating has suffered [visible[k]] damage!"))
playsound(M, 'sound/effects/synth_armor_break.ogg', 50)
/datum/component/armor/synthetic/proc/get_damage()
for(var/key in armor_values)
var/damage = max_armor_values[key] - armor_values[key]
if(damage > 0)
LAZYSET(., key, damage)
/datum/component/armor/synthetic/proc/get_visible_damage()
var/list/damages = get_damage()
if(!LAZYLEN(damages))
return
var/result = list()
for(var/key in damages)
switch(round(100 * damages[key]/max_armor_values[key]))
if(5 to 10)
result[key] = "minor"
if(11 to 25)
result[key] = "moderate"
if(26 to 50)
result[key] = "serious"
if(51 to 100)
result[key] = "catastrophic"
return result
@@ -0,0 +1,120 @@
/datum/component/synthetic_endoskeleton
/// The synthetic that owns this component. Equivalent to `parent`, we just use this for ease of use.
var/mob/living/carbon/human/owner
/**
* The synthetic endoskeleton is our answer to the eternal problem of IPCs being unable to be brainmed complaint because
* of a lack of mechanics to represent pain, organ breaking, or bleeding/respiration, which is how humans in brainmed are
* taken down nonlethally, or before they die.
*/
var/damage = 0
/// The maximum damage the endoskeleton can take before triggering the safety shutdown.
var/max_damage = 200
/// Time until next message.
var/message_cooldown = 10 SECONDS
/// World.time of last message sent.
var/last_sent_message = 0
/datum/component/synthetic_endoskeleton/Initialize(...)
. = ..()
if(isipc(parent))
owner = parent
else
log_debug("Synthetic endoskeleton component spawned on non-IPC. Deleting.")
qdel_self()
RegisterSignal(owner, COMSIG_EXTERNAL_ORGAN_DAMAGE, PROC_REF(receive_damage))
RegisterSignal(owner, COMSIG_SYNTH_ENDOSKELETON_REPAIR, PROC_REF(heal_damage))
RegisterSignal(owner, COMSIG_SYNTH_ENDOSKELETON_FULL_REPAIR, PROC_REF(full_repair))
/datum/component/synthetic_endoskeleton/Destroy(force)
owner = null
return ..()
/**
* Signal handler called when an external synthetic limb receives damage.
*/
/datum/component/synthetic_endoskeleton/proc/receive_damage(atom/source, amount)
SIGNAL_HANDLER
damage = min(damage + amount, max_damage)
handle_exoskeleton_damage()
/**
* Signal handler called when the endoskeleton is repaired.
*/
/datum/component/synthetic_endoskeleton/proc/heal_damage(atom/source, amount)
SIGNAL_HANDLER
damage = max(damage - amount, 0)
var/damage_ratio = damage / max_damage
switch(damage_ratio)
if(0 to 0.3)
owner.remove_movespeed_modifier(/datum/movespeed_modifier/endoskeleton)
if(0.3 to 0.5)
owner.add_or_update_variable_movespeed_modifier(/datum/movespeed_modifier/endoskeleton, multiplicative_slowdown = 1)
if(0.5 to 0.75)
owner.add_or_update_variable_movespeed_modifier(/datum/movespeed_modifier/endoskeleton, multiplicative_slowdown = 2)
if(0.75 to 1)
owner.add_or_update_variable_movespeed_modifier(/datum/movespeed_modifier/endoskeleton, multiplicative_slowdown = 3)
if(!damage)
STOP_PROCESSING(SSprocessing, src)
/**
* Does a full repair of the endoskeleton, also restoring max_damage to initial state.
*/
/datum/component/synthetic_endoskeleton/proc/full_repair(atom/source)
max_damage = initial(max_damage)
heal_damage(source, max_damage)
/**
* The function that handles exoskeleton damage effects.
*/
/datum/component/synthetic_endoskeleton/proc/handle_exoskeleton_damage()
if(damage)
START_PROCESSING(SSprocessing, src)
var/damage_ratio = damage / max_damage
switch(damage_ratio)
if(0.3 to 0.5)
notify_owner(owner, SPAN_WARNING("Your self-preservation warning system notifies you of moderate damage to your endoskeleton's supports!"))
owner.add_or_update_variable_movespeed_modifier(/datum/movespeed_modifier/endoskeleton, multiplicative_slowdown = 1)
if(0.5 to 0.75)
notify_owner(owner, SPAN_WARNING("Your self-preservation warning system notifies you of major damage to your endoskeleton!"))
spark(owner, 2, GLOB.alldirs)
owner.add_or_update_variable_movespeed_modifier(/datum/movespeed_modifier/endoskeleton, multiplicative_slowdown = 2)
if(0.75 to 0.99)
notify_owner(owner, SPAN_DANGER("Your self-preservation routines are starting to kick in! Your endoskeleton is falling apart!"))
spark(owner, 3, GLOB.alldirs)
owner.add_or_update_variable_movespeed_modifier(/datum/movespeed_modifier/endoskeleton, multiplicative_slowdown = 3)
if(1 to INFINITY)
SEND_SIGNAL(owner, COMSIG_SYNTH_SET_SELF_PRESERVATION, TRUE)
/datum/component/synthetic_endoskeleton/process()
if(owner.stat)
return
var/damage_ratio = damage / max_damage
switch(damage_ratio)
if(0.3 to 0.5)
if(prob(5))
notify_owner(owner, SPAN_MACHINE_WARNING("Warning: Endoskeleton integrity at [100 - (damage_ratio * 100)]%."))
if(0.5 to 0.75)
if(prob(10))
notify_owner(owner, SPAN_MACHINE_WARNING("WARNING: Endoskeleton integrity at [100 - (damage_ratio * 100)]%!"))
notify_owner(owner, SPAN_WARNING("The mangled and exposed wires in your endoskeleton spark!"))
spark(owner, 3, GLOB.alldirs)
owner.bodytemperature += 10
if(0.75 to 1)
if(prob(15))
notify_owner(owner, SPAN_MACHINE_DANGER(FONT_LARGE("ENDOSKELETON INTEGRITY CRITICAL. Your self preservation blares at you to return to safety!")))
notify_owner(owner, SPAN_DANGER("Your frame creaks and groans, threatening to break apart at the metallic seams!"))
spark(owner, 4, GLOB.alldirs)
owner.bodytemperature += 20
/**
* Wrapper to_chat proc with a stat check.
*/
/datum/component/synthetic_endoskeleton/proc/notify_owner(mob/living/carbon/human/user, message)
if(user.is_asystole() || (last_sent_message + message_cooldown > world.time))
return
to_chat(user, message)
last_sent_message = world.time