mirror of
https://github.com/Aurorastation/Aurora.3.git
synced 2026-08-21 20:11:04 +01:00
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:
co-authored by
Matt Atlas
Geeves
Werner
parent
1898ad3417
commit
e0aa218843
@@ -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
|
||||
@@ -118,3 +118,14 @@
|
||||
mid_sounds = list('sound/machines/clanking.ogg' = 1)
|
||||
mid_length = 5 SECONDS
|
||||
volume = 75
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/datum/looping_sound/synth_fab
|
||||
start_sound = 'sound/machines/synthfab/synthfab_start.ogg'
|
||||
start_length = 1 SECOND
|
||||
start_volume = 100
|
||||
mid_sounds = list('sound/machines/synthfab/synthfab_running.ogg' = 1)
|
||||
mid_length = 3 SECONDS
|
||||
end_sound = 'sound/machines/synthfab/synthfab_end.ogg'
|
||||
volume = 75
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
/datum/looping_sound/ipc_sizzling
|
||||
mid_sounds = list('sound/species/synthetic/sizzling_mid.ogg' = 1)
|
||||
end_sound = 'sound/species/synthetic/sizzling_end.ogg'
|
||||
mid_length = 30
|
||||
volume = 50
|
||||
extra_range = SHORT_RANGE_SOUND_EXTRARANGE
|
||||
@@ -42,7 +42,7 @@
|
||||
U.hidden_uplink.bluecrystals = round(uplink_uses / 2)
|
||||
U.hidden_uplink.tgui_menu = 1
|
||||
if(isipc(H))
|
||||
var/obj/item/organ/internal/ipc_tag/tag = H.internal_organs_by_name[BP_IPCTAG]
|
||||
var/obj/item/organ/internal/machine/ipc_tag/tag = H.internal_organs_by_name[BP_IPCTAG]
|
||||
if(istype(tag))
|
||||
tag.modify_tag_data(TRUE)
|
||||
|
||||
|
||||
@@ -59,7 +59,7 @@ GLOBAL_DATUM_INIT(crew_repository, /datum/repository/crew, new())
|
||||
crewmemberData["pulse"] = H.get_pulse(GETPULSE_TOOL)
|
||||
else
|
||||
if(isipc(H) && H.internal_organs_by_name[BP_IPCTAG]) // Don't make untagged IPCs obvious
|
||||
var/obj/item/organ/internal/cell/cell = H.internal_organs_by_name[BP_CELL]
|
||||
var/obj/item/organ/internal/machine/power_core/cell = H.internal_organs_by_name[BP_CELL]
|
||||
if(cell)
|
||||
crewmemberData["cellCharge"] = cell.percent()
|
||||
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
// Synthetic internals represent things that are not physically modelled as an organ, but are rather part of the organ.
|
||||
// That means wiring, plating, and electronics. Damaging organs has adverse effects on these components.
|
||||
// The goal is to emulate damaging effects similar to arterial bleeding, infections, etc. on organics.
|
||||
|
||||
// The specific effects of each synthetic internal can vary organ by organ. They all have different health measurements as well.
|
||||
// These measurements can change depending on the organ they are attached to.
|
||||
|
||||
/datum/synthetic_internal
|
||||
/// The name of the internal component. Shows up in analysis tools.
|
||||
var/name = "default synthetic internal"
|
||||
/// The description. Shows up in analysis tools.
|
||||
var/desc = "A default synthetic internal component."
|
||||
/// The organ this synthetic internal belongs to.
|
||||
var/obj/item/organ/internal/machine/organ
|
||||
|
||||
/datum/synthetic_internal/New(obj/item/organ/internal/machine/attached_organ)
|
||||
. = ..()
|
||||
if(istype(attached_organ))
|
||||
organ = attached_organ
|
||||
else
|
||||
LOG_DEBUG("Synthetic internal [type] generated on invalid organ [attached_organ]. Aborting.")
|
||||
qdel(src)
|
||||
|
||||
/datum/synthetic_internal/Destroy(force)
|
||||
organ = null
|
||||
return ..()
|
||||
|
||||
/**
|
||||
* The proc that handles taking damage. As some components take damage differently,
|
||||
* this is overridden by them.
|
||||
*/
|
||||
/datum/synthetic_internal/proc/take_damage(amount)
|
||||
return
|
||||
|
||||
/**
|
||||
* The proc that handles restoring damage. As some components restore damage differently,
|
||||
* this is overridden by them.
|
||||
*/
|
||||
/datum/synthetic_internal/proc/heal_damage(amount)
|
||||
return
|
||||
|
||||
/**
|
||||
* This proc returns the status of this synthetic internal as a percentage.
|
||||
* It will return 100 if the component is 100% healthy, a value between 0 and 100 if not, and 0 if broken.
|
||||
* This logic can be overridden for custom behaviour.
|
||||
*/
|
||||
/datum/synthetic_internal/proc/get_status()
|
||||
return
|
||||
|
||||
/**
|
||||
* This proc returns the status of this internal component, but imprecisely.
|
||||
* Basically what could be obvious at an eye-glance.
|
||||
*/
|
||||
/datum/synthetic_internal/proc/analyse_integrity_imprecise()
|
||||
return
|
||||
|
||||
/**
|
||||
* This proc returns the status of this internal component, but precisely.
|
||||
* Basically the results of an in-depth analysis with an appropriate tool.
|
||||
*/
|
||||
/datum/synthetic_internal/proc/analyze_integrity_precise()
|
||||
return
|
||||
|
||||
/**
|
||||
* Replaces the health by setting a new max health and setting the health to the new max_health value.
|
||||
* Needs to be overridden on every subtype.
|
||||
*/
|
||||
/datum/synthetic_internal/proc/replace_health(new_max_health)
|
||||
SHOULD_CALL_PARENT(FALSE)
|
||||
CRASH("Not implemented!")
|
||||
@@ -0,0 +1,19 @@
|
||||
// The synthetic's electronics. Represents both the hardware and software component.
|
||||
// The integrity of electronics only goes from 0% to 100%. It does not vary, unlike other components.
|
||||
|
||||
/datum/synthetic_internal/electronics
|
||||
name = "internal electronics"
|
||||
desc = "The electronics and the code responsible for many internal functions."
|
||||
/// The maximum integrity percentage of the internals. Represents code coherence and physical damage.
|
||||
var/max_integrity = 30
|
||||
/// The integrity of the internals in percentage. Represents code coherence and physical damage.
|
||||
var/integrity = 30
|
||||
|
||||
/datum/synthetic_internal/electronics/get_status()
|
||||
return (integrity / max_integrity) * 100
|
||||
|
||||
/datum/synthetic_internal/electronics/take_damage(amount)
|
||||
integrity = max(integrity - amount, 0)
|
||||
|
||||
/datum/synthetic_internal/electronics/heal_damage(amount)
|
||||
integrity = min(integrity + amount, max_integrity)
|
||||
@@ -0,0 +1,39 @@
|
||||
// The synthetic organ's plating. Represents its natural armour, which will degrade as it's damaged.
|
||||
|
||||
/datum/synthetic_internal/plating
|
||||
name = "internal steel plating"
|
||||
desc = "The steel plating that keeps internal components safe and sound from the exterior."
|
||||
/// The maximum health of the organ's plating. Represents... you know, the plating.
|
||||
var/max_health = 50
|
||||
/// The health of the organ's plating. Represents... you know, the plating.
|
||||
var/health = 50
|
||||
|
||||
/datum/synthetic_internal/plating/take_damage(amount)
|
||||
var/old_health = health
|
||||
health = max(health - amount, 0)
|
||||
return max(amount - old_health, 0)
|
||||
|
||||
/datum/synthetic_internal/plating/heal_damage(amount)
|
||||
health = min(health + amount, max_health)
|
||||
|
||||
/datum/synthetic_internal/plating/get_status()
|
||||
return (health / max_health) * 100
|
||||
|
||||
/datum/synthetic_internal/plating/replace_health(new_max_health)
|
||||
max_health = new_max_health
|
||||
health = new_max_health
|
||||
|
||||
/obj/item/synth_plating
|
||||
name = "synthetic steel plating"
|
||||
desc = "These steel plates are replacements for the steel plating lining a synthetic's internal components. They are welded to the chassis in order to safeguard the component in question."
|
||||
icon = 'icons/obj/ipc_utilities.dmi'
|
||||
icon_state = "ipc_repair_plates"
|
||||
matter = list(MATERIAL_STEEL = 40000)
|
||||
surgerysound = 'sound/weapons/saw/drillhit1.ogg'
|
||||
item_flags = ITEM_FLAG_SURGERY
|
||||
|
||||
/obj/item/synth_plating/mechanics_hints()
|
||||
. = ..()
|
||||
. += list("These plates are used to replace broken internal plating around a synthetic's organ. \
|
||||
While normally you can repair it with normal steel plates, in the case that it is broken, \
|
||||
you'll need to substitute the plating entirely with this item.")
|
||||
@@ -0,0 +1,18 @@
|
||||
// The synthetic's wiring. Represents the fragile connections between organs and limbs.
|
||||
|
||||
/datum/synthetic_internal/wiring
|
||||
name = "internal wiring"
|
||||
desc = "The wiring that connects components together and allows them to talk to eachother."
|
||||
/// The maximum wires on this organ. Wires represent physical connections between components. They are less plentiful than plating, but they are worse to lose.
|
||||
var/max_wires = 15
|
||||
/// The wires left on this organ. Wires represent physical connections between components. They are less plentiful than plating, but they are worse to lose.
|
||||
var/wires = 15
|
||||
|
||||
/datum/synthetic_internal/wiring/get_status()
|
||||
return (wires / max_wires) * 100
|
||||
|
||||
/datum/synthetic_internal/wiring/take_damage(amount)
|
||||
wires = max(wires - round(amount, 1), 0)
|
||||
|
||||
/datum/synthetic_internal/wiring/heal_damage(amount)
|
||||
wires = min(wires + amount, max_wires)
|
||||
Reference in New Issue
Block a user