diff --git a/aurorastation.dme b/aurorastation.dme
index fbd9abe4512..a162ecb1657 100644
--- a/aurorastation.dme
+++ b/aurorastation.dme
@@ -187,6 +187,7 @@
#include "code\__DEFINES\dcs\signals\signals_atom\signals_atom_movement.dm"
#include "code\__DEFINES\dcs\signals\signals_atom\signals_atom_x_act.dm"
#include "code\__DEFINES\dcs\signals\signals_atom\signals_power_cell.dm"
+#include "code\__DEFINES\dcs\signals\signals_mob\signals_mob_human.dm"
#include "code\__DEFINES\dcs\signals\signals_mob\signals_mob_living.dm"
#include "code\__DEFINES\dcs\signals\signals_mob\signals_mob_main.dm"
#include "code\__DEFINES\dcs\signals\signals_object\signals_object.dm"
@@ -476,7 +477,8 @@
#include "code\datums\components\local_network.dm"
#include "code\datums\components\orbiter.dm"
#include "code\datums\components\overlay_lighting.dm"
-#include "code\datums\components\armor\armor.dm"
+#include "code\datums\components\armor\_armor.dm"
+#include "code\datums\components\armor\synthetic_armor.dm"
#include "code\datums\components\base_name\base_name.dm"
#include "code\datums\components\eye\_eye.dm"
#include "code\datums\components\eye\base_planner.dm"
@@ -487,6 +489,7 @@
#include "code\datums\components\multitool\circuitboards\circuitboards.dm"
#include "code\datums\components\overhead_emote\overhead_emote_datum.dm"
#include "code\datums\components\overhead_emote\overhead_emote_singleton.dm"
+#include "code\datums\components\synthetic_endoskeleton\synthetic_endoskeleton.dm"
#include "code\datums\components\turf_click\turf_hand.dm"
#include "code\datums\elements\_element.dm"
#include "code\datums\elements\connect_loc.dm"
@@ -505,6 +508,7 @@
#include "code\datums\langchat\langchat.dm"
#include "code\datums\looping_sounds\_looping_sound.dm"
#include "code\datums\looping_sounds\machinery_sounds.dm"
+#include "code\datums\looping_sounds\mobs.dm"
#include "code\datums\looping_sounds\revenant_rift.dm"
#include "code\datums\looping_sounds\thermal_drill.dm"
#include "code\datums\observation\_debug.dm"
@@ -573,6 +577,10 @@
#include "code\datums\scenarios\scenario.dm"
#include "code\datums\state_machine\state.dm"
#include "code\datums\state_machine\transition.dm"
+#include "code\datums\synthetic_internals\_synthetic_internals.dm"
+#include "code\datums\synthetic_internals\electronics.dm"
+#include "code\datums\synthetic_internals\plating.dm"
+#include "code\datums\synthetic_internals\wiring.dm"
#include "code\datums\tips\tips.dm"
#include "code\datums\trading\_trading_defines.dm"
#include "code\datums\trading\ai.dm"
@@ -3008,6 +3016,7 @@
#include "code\modules\modular_computers\file_system\programs\research\ai_restorer.dm"
#include "code\modules\modular_computers\file_system\programs\research\chemistry_codex.dm"
#include "code\modules\modular_computers\file_system\programs\research\ntmonitor.dm"
+#include "code\modules\modular_computers\file_system\programs\research\robotics.dm"
#include "code\modules\modular_computers\file_system\programs\security\camera.dm"
#include "code\modules\modular_computers\file_system\programs\security\digitalwarrant.dm"
#include "code\modules\modular_computers\file_system\programs\security\guntracker.dm"
@@ -3017,6 +3026,7 @@
#include "code\modules\modular_computers\file_system\programs\system\configurator.dm"
#include "code\modules\modular_computers\file_system\programs\system\file_browser.dm"
#include "code\modules\modular_computers\file_system\programs\system\ntdownloader.dm"
+#include "code\modules\modular_computers\hardware\access_cable_dongle.dm"
#include "code\modules\modular_computers\hardware\ai_slot.dm"
#include "code\modules\modular_computers\hardware\battery_module.dm"
#include "code\modules\modular_computers\hardware\card_slot.dm"
@@ -3029,6 +3039,7 @@
#include "code\modules\modular_computers\hardware\portable_hard_drive_presets.dm"
#include "code\modules\modular_computers\hardware\processor_unit.dm"
#include "code\modules\modular_computers\hardware\tesla_link.dm"
+#include "code\modules\modular_computers\hardware\universal_port.dm"
#include "code\modules\modular_computers\items\paper_scanner.dm"
#include "code\modules\modular_computers\NTNet\NTNet.dm"
#include "code\modules\modular_computers\NTNet\NTNet_relay.dm"
@@ -3040,6 +3051,7 @@
#include "code\modules\movespeed\modifiers\mobs.dm"
#include "code\modules\movespeed\modifiers\reagents.dm"
#include "code\modules\movespeed\modifiers\spells.dm"
+#include "code\modules\movespeed\modifiers\synthetics.dm"
#include "code\modules\multiz\_stubs.dm"
#include "code\modules\multiz\basic.dm"
#include "code\modules\multiz\hoist.dm"
@@ -3083,6 +3095,26 @@
#include "code\modules\organs\internal\species\tajara.dm"
#include "code\modules\organs\internal\species\unathi.dm"
#include "code\modules\organs\internal\species\vaurca.dm"
+#include "code\modules\organs\internal\species\machine\_generic.dm"
+#include "code\modules\organs\internal\species\machine\_presets.dm"
+#include "code\modules\organs\internal\species\machine\access_port.dm"
+#include "code\modules\organs\internal\species\machine\actuators.dm"
+#include "code\modules\organs\internal\species\machine\cooling_unit.dm"
+#include "code\modules\organs\internal\species\machine\cooling_unit_presets.dm"
+#include "code\modules\organs\internal\species\machine\data_core.dm"
+#include "code\modules\organs\internal\species\machine\hydraulics.dm"
+#include "code\modules\organs\internal\species\machine\internal_diagnostics.dm"
+#include "code\modules\organs\internal\species\machine\internal_storage.dm"
+#include "code\modules\organs\internal\species\machine\ipc_tag.dm"
+#include "code\modules\organs\internal\species\machine\optical_sensor.dm"
+#include "code\modules\organs\internal\species\machine\posibrain.dm"
+#include "code\modules\organs\internal\species\machine\power_core.dm"
+#include "code\modules\organs\internal\species\machine\reactor.dm"
+#include "code\modules\organs\internal\species\machine\reactor_presets.dm"
+#include "code\modules\organs\internal\species\machine\surge_protector.dm"
+#include "code\modules\organs\internal\species\machine\targeting_core.dm"
+#include "code\modules\organs\internal\species\machine\voice_synthesizer.dm"
+#include "code\modules\organs\internal\species\machine\wireless_access.dm"
#include "code\modules\organs\subtypes\autakh.dm"
#include "code\modules\organs\subtypes\diona.dm"
#include "code\modules\organs\subtypes\industrial.dm"
@@ -3600,6 +3632,7 @@
#include "code\modules\research\designs\mechfab\prosthetics\internal.dm"
#include "code\modules\research\designs\mechfab\robot\robot.dm"
#include "code\modules\research\designs\mechfab\robot\robot_upgrades.dm"
+#include "code\modules\research\designs\mechfab\synthetic\synthetic_organ_parts.dm"
#include "code\modules\research\designs\protolathe\deployable_kits.dm"
#include "code\modules\research\designs\protolathe\disk_designs.dm"
#include "code\modules\research\designs\protolathe\hud_glasses_designs.dm"
@@ -3799,6 +3832,8 @@
#include "code\modules\tgui\modules\armor_values.dm"
#include "code\modules\tgui\modules\flavor_text.dm"
#include "code\modules\tgui\modules\hivenet_manifest.dm"
+#include "code\modules\tgui\modules\ipc_diagnostic.dm"
+#include "code\modules\tgui\modules\neural_configuration.dm"
#include "code\modules\tgui\states\admin.dm"
#include "code\modules\tgui\states\always.dm"
#include "code\modules\tgui\states\conscious.dm"
diff --git a/code/__DEFINES/dcs/signals.dm b/code/__DEFINES/dcs/signals.dm
index f3e05863ddb..063905eea04 100644
--- a/code/__DEFINES/dcs/signals.dm
+++ b/code/__DEFINES/dcs/signals.dm
@@ -66,6 +66,8 @@
#define COMSIG_ITEM_UPDATE_STATE "update_state"
/// updates sprites
#define COMSIG_ITEM_ICON_UPDATE "icon_update"
+/// when a gun mag is emptied
+#define COMSIG_EMPTIED_MAGAZINE "emptied_magazine"
// tgui signals
#define COMSIG_TGUI_CLOSE "tgui_close"
diff --git a/code/__DEFINES/dcs/signals/signals_mob/signals_mob_human.dm b/code/__DEFINES/dcs/signals/signals_mob/signals_mob_human.dm
new file mode 100644
index 00000000000..87ecb80a9a3
--- /dev/null
+++ b/code/__DEFINES/dcs/signals/signals_mob/signals_mob_human.dm
@@ -0,0 +1,2 @@
+///from datum/species/machine/handle_sprint_cost()
+#define COMSIG_IPC_HAS_SPRINTED "ipc_sprinted"
diff --git a/code/__DEFINES/mobs.dm b/code/__DEFINES/mobs.dm
index 5dbfbe85649..d6e4b6679f0 100644
--- a/code/__DEFINES/mobs.dm
+++ b/code/__DEFINES/mobs.dm
@@ -141,6 +141,23 @@
#define BP_CELL "cell"
#define BP_OPTICS "optics"
#define BP_IPCTAG "ipc tag"
+#define BP_REACTOR "reactor"
+#define BP_VOICE_SYNTHESIZER "voice synthesizer"
+#define BP_DIAGNOSTICS_SUITE "internal diagnostics suite"
+#define BP_HYDRAULICS "hydraulics system"
+#define BP_COOLING_UNIT "cooling unit"
+#define BP_ACCESS_PORT "universal access port"
+#define BP_ACTUATORS_LEFT "left arm actuators"
+#define BP_ACTUATORS_RIGHT "right arm actuators"
+#define BP_SURGE_PROTECTOR "surge"
+#define BP_DATACORE "data core"
+#define BP_TARGETING_CORE "targeting core"
+
+// Bishop frame only.
+#define BP_WIRELESS_ACCESS "wireless access point"
+
+// G2 frame only.
+#define BP_INTERNAL_STORAGE "internal storage system"
// Parasite organs
#define BP_ZOMBIE_PARASITE "black tumour"
@@ -303,6 +320,24 @@
#define MOB_TINY 4
#define MOB_MINISCULE 1
+// Mob weight, separate from mob size. Affects how easy a mob is to lift.
+/// Light mobs, can be lifted by anyone.
+#define MOB_WEIGHT_LIGHT 1
+/// Medium weight mobs, the default for humanoids. Can be lifted by medium+.
+#define MOB_WEIGHT_MEDIUM 2
+/// Heavy mobs. They need speecial equipment or a heavy+ mob to lift.
+#define MOB_WEIGHT_HEAVY 3
+/// Superheavy mobs. Gonna need a lot of help with those.
+#define MOB_WEIGHT_SUPERHEAVY 4
+
+// Mob strength. This allows us to represent stronger mobs with a bit more granularity. Robots should be stronger than normal people. A bonus to the checks on lifting/throwing mobs, essentially.
+/// A normal mob, basically. Does not get us any buffs.
+#define MOB_STRENGTH_NORMAL 0
+/// A strong mob. Small bonus.
+#define MOB_STRENGTH_STRONG 1
+/// A very strong mob. Big bonus.
+#define MOB_STRENGTH_VERY_STRONG 2
+
#define BASE_MAX_NUTRITION 600
#define HUNGER_FACTOR 0.04 // Factor of how fast mob nutrition decreases over time.
@@ -573,3 +608,25 @@
#define DO_MISSING_TARGET (-2)
#define DO_INCAPACITATED (-3)
#define DO_EXTRA_CHECKS (-4)
+
+// Threshold defines for integrity damage. Used for when you want to check for a specific threshold to do damage effects.
+
+#define IPC_INTEGRITY_THRESHOLD_LOW 75
+#define IPC_INTEGRITY_THRESHOLD_MEDIUM 50
+#define IPC_INTEGRITY_THRESHOLD_HIGH 25
+#define IPC_INTEGRITY_THRESHOLD_VERY_HIGH 10
+
+// These are the power supply organ defines.
+// They are bitflags because it should be technically possible to have a power supply do multiple things at once if you wish.
+// Just be wary of balancing issues.
+
+/// The default functioning of a power reactor. Externally powered only. Allows recharging from APCs. Has a multiplier for faster recharging from power stations.
+#define POWER_SUPPLY_ELECTRIC 1
+/// Recharges by walking/running. Far slower at charging from external sources.
+#define POWER_SUPPLY_KINETIC 2
+/// Recharges through consuming nutrients/food. Slower at recharging from external sources.
+#define POWER_SUPPLY_BIOLOGICAL 4
+/// Recharges from external lighting. Slower at recharging from external sources.
+#define POWER_SUPPLY_SOLAR 8
+
+
diff --git a/code/__DEFINES/organs.dm b/code/__DEFINES/organs.dm
index 691a8c8b8ba..7aaf62e4086 100644
--- a/code/__DEFINES/organs.dm
+++ b/code/__DEFINES/organs.dm
@@ -48,3 +48,18 @@
* Set the canceled variable to true in order to negate said weakness.
*/
#define COMSIG_GRAVITY_WEAKNESS_EVENT "gravity_weakness_event"
+
+/// Raised external organ (a limb) takes damage. Used for the synthetic endoskeleton at the moment. Must supply damage.
+#define COMSIG_EXTERNAL_ORGAN_DAMAGE "machine_internal_damage"
+
+/// Sent when the burst damage is cleared by the posibrain.
+#define COMSIG_SYNTH_EMP_DAMAGE_CLEARED "emp_damage_cleared"
+
+/// Sent when the synthetic enters self-preservation mode. Must supply a TRUE/FALSE state.
+#define COMSIG_SYNTH_SET_SELF_PRESERVATION "synth_self_preservation_set"
+
+/// Sent when endoskeleton repair is done. Must supply a number of damage healed.
+#define COMSIG_SYNTH_ENDOSKELETON_REPAIR "synth_endoskeleton_repair"
+
+/// Sent when a full endoskeleton repair is done. Unlike the normal one, also restores max_damage to initial state. No extra arguments.
+#define COMSIG_SYNTH_ENDOSKELETON_FULL_REPAIR "synth_endoskeleton_full_repair"
diff --git a/code/__DEFINES/prefs.dm b/code/__DEFINES/prefs.dm
index d6497dc1716..a2fde895971 100644
--- a/code/__DEFINES/prefs.dm
+++ b/code/__DEFINES/prefs.dm
@@ -35,3 +35,19 @@
/// Internal organ. Removed, used for appendixes.
#define ORGAN_PREF_REMOVED "removed"
/// Note that a "normal" limb or organ has no pref, so there's no define for it.
+
+/// Air cooling. Technically not used, but we need this for the linked lists in cooling_unit.dm.
+#define ORGAN_PREF_AIRCOOLED "air"
+/// Liquid cooling. For IPC cooling units. Default is air cooling.
+#define ORGAN_PREF_LIQUIDCOOLED "liquid"
+/// Passive cooling. For IPC cooling units. Default is air cooling.
+#define ORGAN_PREF_PASSIVECOOLED "passive"
+
+/// Externally powered only. Technically not used, but we need this for the linked lists in reactor.dm.
+#define ORGAN_PREF_ELECTRICPOWER "electric"
+/// Biological power reactor. Nutrients, food, etc.
+#define ORGAN_PREF_BIOPOWER "biological"
+/// Kinetic power reactor. Walking, running.
+#define ORGAN_PREF_KINETICPOWER "kinetic"
+/// Solar powered. Stand in the sun. Or LED lights. It's the future, fuck you.
+#define ORGAN_PREF_SOLARPOWER "solar"
diff --git a/code/__DEFINES/span.dm b/code/__DEFINES/span.dm
index bb8fd3dad4d..d11db3059f1 100644
--- a/code/__DEFINES/span.dm
+++ b/code/__DEFINES/span.dm
@@ -25,6 +25,10 @@
#define SPAN_CAUTION(str) ("" + str + "")
#define SPAN_STORYTELLER(str) ("" + str + "")
+#define SPAN_MACHINE_WARNING(str) ("" + str + "")
+#define SPAN_MACHINE_DANGER(str) ("" + str + "")
+#define SPAN_MACHINE_VISION(str) ("" + str + "")
+
#define SPAN_RED(str) "[str]"
#define SPAN_YELLOW(str) "[str]"
#define SPAN_GREEN(str) "[str]"
diff --git a/code/__DEFINES/species.dm b/code/__DEFINES/species.dm
index 887db4edbca..e6a448684ad 100644
--- a/code/__DEFINES/species.dm
+++ b/code/__DEFINES/species.dm
@@ -140,5 +140,3 @@
SPECIES_IPC_UNBRANDED, SPECIES_IPC_UNBRANDED_REMOTE, SPECIES_IPC_XION, SPECIES_IPC_ZENGHU, \
SPECIES_IPC_SHELL_ROGUE, SPECIES_IPC_XION_REMOTE, SPECIES_IPC_PURPOSE_HK, \
)
-
-
diff --git a/code/_globalvars/lists/mobs.dm b/code/_globalvars/lists/mobs.dm
index f38fd88dee8..4f367d4aed0 100644
--- a/code/_globalvars/lists/mobs.dm
+++ b/code/_globalvars/lists/mobs.dm
@@ -1 +1,2 @@
GLOBAL_LIST_EMPTY(mob_config_movespeed_type_lookup)
+GLOBAL_LIST_INIT(low_integrity_messages, world.file2list("config/synthetic_messages/low_integrity_messages.txt"))
diff --git a/code/_onclick/hud/fullscreen.dm b/code/_onclick/hud/fullscreen.dm
index 5b3f3050515..4dbad26adcf 100644
--- a/code/_onclick/hud/fullscreen.dm
+++ b/code/_onclick/hud/fullscreen.dm
@@ -114,6 +114,10 @@
icon_state = "brutedamageoverlay6"
alpha = 0
+/atom/movable/screen/fullscreen/robot_pain
+ icon = 'icons/mob/screen/robot_pain.dmi'
+ alpha = 255
+
/atom/movable/screen/fullscreen/flash
icon = 'icons/mob/screen/effects.dmi'
screen_loc = "WEST,SOUTH to EAST,NORTH"
diff --git a/code/_onclick/hud/human.dm b/code/_onclick/hud/human.dm
index 2e25181a1ee..9534e7dbe43 100644
--- a/code/_onclick/hud/human.dm
+++ b/code/_onclick/hud/human.dm
@@ -324,6 +324,10 @@
mymob.up_hint.screen_loc = ui_up_hint
hud_elements |= mymob.up_hint
+ if(hud_data.has_robot_pain)
+ mymob.robot_pain = new /atom/movable/screen/fullscreen/robot_pain()
+ hud_elements |= mymob.robot_pain
+
mymob.pain = new /atom/movable/screen/fullscreen/pain(null)
hud_elements |= mymob.pain
diff --git a/code/datums/components/armor/armor.dm b/code/datums/components/armor/_armor.dm
similarity index 95%
rename from code/datums/components/armor/armor.dm
rename to code/datums/components/armor/_armor.dm
index a39e9aa09c1..be8e7196262 100644
--- a/code/datums/components/armor/armor.dm
+++ b/code/datums/components/armor/_armor.dm
@@ -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
diff --git a/code/datums/components/armor/synthetic_armor.dm b/code/datums/components/armor/synthetic_armor.dm
new file mode 100644
index 00000000000..f7b24768494
--- /dev/null
+++ b/code/datums/components/armor/synthetic_armor.dm
@@ -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
diff --git a/code/datums/components/synthetic_endoskeleton/synthetic_endoskeleton.dm b/code/datums/components/synthetic_endoskeleton/synthetic_endoskeleton.dm
new file mode 100644
index 00000000000..85de473100c
--- /dev/null
+++ b/code/datums/components/synthetic_endoskeleton/synthetic_endoskeleton.dm
@@ -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
diff --git a/code/datums/looping_sounds/machinery_sounds.dm b/code/datums/looping_sounds/machinery_sounds.dm
index cae4632f6b4..565a0fcb083 100644
--- a/code/datums/looping_sounds/machinery_sounds.dm
+++ b/code/datums/looping_sounds/machinery_sounds.dm
@@ -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
diff --git a/code/datums/looping_sounds/mobs.dm b/code/datums/looping_sounds/mobs.dm
new file mode 100644
index 00000000000..60099eebbe0
--- /dev/null
+++ b/code/datums/looping_sounds/mobs.dm
@@ -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
diff --git a/code/datums/outfits/outfit_antag.dm b/code/datums/outfits/outfit_antag.dm
index eb53d6cb096..ba4f6b567dd 100644
--- a/code/datums/outfits/outfit_antag.dm
+++ b/code/datums/outfits/outfit_antag.dm
@@ -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)
diff --git a/code/datums/repositories/crew.dm b/code/datums/repositories/crew.dm
index 3d223e2c0a9..4ff0b2f0731 100644
--- a/code/datums/repositories/crew.dm
+++ b/code/datums/repositories/crew.dm
@@ -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()
diff --git a/code/datums/synthetic_internals/_synthetic_internals.dm b/code/datums/synthetic_internals/_synthetic_internals.dm
new file mode 100644
index 00000000000..35f6625b0d8
--- /dev/null
+++ b/code/datums/synthetic_internals/_synthetic_internals.dm
@@ -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!")
diff --git a/code/datums/synthetic_internals/electronics.dm b/code/datums/synthetic_internals/electronics.dm
new file mode 100644
index 00000000000..ba96d7a16fc
--- /dev/null
+++ b/code/datums/synthetic_internals/electronics.dm
@@ -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)
diff --git a/code/datums/synthetic_internals/plating.dm b/code/datums/synthetic_internals/plating.dm
new file mode 100644
index 00000000000..9d8e2213b94
--- /dev/null
+++ b/code/datums/synthetic_internals/plating.dm
@@ -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.")
diff --git a/code/datums/synthetic_internals/wiring.dm b/code/datums/synthetic_internals/wiring.dm
new file mode 100644
index 00000000000..a8965bf1dc4
--- /dev/null
+++ b/code/datums/synthetic_internals/wiring.dm
@@ -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)
diff --git a/code/game/gamemodes/technomancer/spells/energy_siphon.dm b/code/game/gamemodes/technomancer/spells/energy_siphon.dm
index 0368abddef2..4da9cdf1c8e 100644
--- a/code/game/gamemodes/technomancer/spells/energy_siphon.dm
+++ b/code/game/gamemodes/technomancer/spells/energy_siphon.dm
@@ -142,7 +142,7 @@
if(user.isSynthetic() && ishuman(user)) // Let's do something with it, if we're a robot.
var/mob/living/carbon/human/H = user
charge_to_give = charge_to_give - (core.max_energy - core.energy)
- var/obj/item/organ/internal/cell/C = H.internal_organs_by_name[BP_CELL]
+ var/obj/item/organ/internal/machine/power_core/C = H.internal_organs_by_name[BP_CELL]
var/obj/item/cell/potato = C.get_cell()
potato.give(charge_to_give)
to_chat(user, SPAN_NOTICE("Redirected energy to internal microcell."))
diff --git a/code/game/jobs/job/security.dm b/code/game/jobs/job/security.dm
index 50f3fb03c2f..81cbe21b048 100644
--- a/code/game/jobs/job/security.dm
+++ b/code/game/jobs/job/security.dm
@@ -99,7 +99,7 @@
minimal_player_age = 7
outfit = /obj/outfit/job/warden
- blacklisted_species = list(SPECIES_IPC_G1, SPECIES_IPC_G2, SPECIES_IPC_XION, SPECIES_IPC_XION_REMOTE, SPECIES_VAURCA_BULWARK, SPECIES_DIONA_COEUS, SPECIES_VAURCA_BREEDER)
+ blacklisted_species = list(SPECIES_IPC_XION_REMOTE, SPECIES_VAURCA_BULWARK, SPECIES_DIONA_COEUS, SPECIES_VAURCA_BREEDER)
/obj/outfit/job/warden
name = "Warden"
@@ -163,7 +163,7 @@
)
minimal_player_age = 3
outfit = /obj/outfit/job/forensics
- blacklisted_species = list(SPECIES_IPC_G1, SPECIES_IPC_G2, SPECIES_IPC_XION, SPECIES_IPC_XION_REMOTE, SPECIES_VAURCA_BULWARK, SPECIES_VAURCA_BREEDER)
+ blacklisted_species = list(SPECIES_IPC_XION_REMOTE, SPECIES_VAURCA_BULWARK, SPECIES_VAURCA_BREEDER)
/obj/outfit/job/forensics
name = "Investigator"
@@ -222,7 +222,7 @@
minimal_player_age = 7
outfit = /obj/outfit/job/officer
- blacklisted_species = list(SPECIES_IPC_G1, SPECIES_IPC_G2, SPECIES_IPC_XION, SPECIES_IPC_XION_REMOTE, SPECIES_VAURCA_BULWARK, SPECIES_DIONA_COEUS, SPECIES_VAURCA_BREEDER)
+ blacklisted_species = list(SPECIES_IPC_XION_REMOTE, SPECIES_VAURCA_BULWARK, SPECIES_DIONA_COEUS, SPECIES_VAURCA_BREEDER)
/obj/outfit/job/officer
name = "Security Officer"
@@ -289,7 +289,7 @@
SPECIES_SKRELL_AXIORI = 50
)
- blacklisted_species = list(SPECIES_IPC_G1, SPECIES_IPC_G2, SPECIES_IPC_XION, SPECIES_IPC_XION_REMOTE, SPECIES_VAURCA_BULWARK, SPECIES_DIONA_COEUS, SPECIES_VAURCA_BREEDER)
+ blacklisted_species = list(SPECIES_IPC_XION_REMOTE, SPECIES_VAURCA_BULWARK, SPECIES_DIONA_COEUS, SPECIES_VAURCA_BREEDER)
/obj/outfit/job/intern_sec
name = "Security Cadet"
diff --git a/code/game/machinery/OpTable.dm b/code/game/machinery/OpTable.dm
index a798a947704..f99dacddef1 100644
--- a/code/game/machinery/OpTable.dm
+++ b/code/game/machinery/OpTable.dm
@@ -26,6 +26,9 @@
///The connected surgery computer
var/obj/machinery/computer/operating/computer = null
+ /// If the patient must be lying on this surgery table for it to set the patient as occupant.
+ var/occupant_must_be_lying = TRUE
+
/obj/machinery/optable/mechanics_hints(mob/user, distance, is_adjacent)
. += ..()
. += "Click your target with Grab intent, then click on the table with an empty hand, to place them on it."
@@ -154,6 +157,7 @@
if((user.a_intent != I_HELP) && buckled)
user_unbuckle(user)
return
+
if(user != occupant_resolved && !use_check_and_message(user)) // Skip checks if you're doing it to yourself or turning it off, this is an anti-griefing mechanic more than anything.
user.visible_message(SPAN_WARNING("\The [user] begins switching [suppressing ? "off" : "on"] \the [src]'s neural suppressor."))
if(!do_after(user, 3 SECONDS, src, DO_UNIQUE))
@@ -201,10 +205,11 @@
//does not mean it's suppressed, just that it's occupying the table
var/mob/living/carbon/human/new_occupant = locate() in loc
if(istype(new_occupant))
- if(new_occupant.lying)
- //Set the occupant weakref and get his view
- occupant = WEAKREF(new_occupant)
- acquire_view(new_occupant)
+ if(occupant_must_be_lying && !new_occupant.lying)
+ return FALSE
+ //Set the occupant weakref and get his view
+ occupant = WEAKREF(new_occupant)
+ acquire_view(new_occupant)
//We have a patient, and it's not synthetic
else if(!occupant_resolved.isSynthetic())
@@ -257,11 +262,17 @@
add_fingerprint(patient)
add_fibers(patient)
- patient.resting = TRUE
- patient.forceMove(loc)
+ move_patient_to_table(patient, giver)
return TRUE
+/**
+ * Actually moves a patient to the table.
+ */
+/obj/machinery/optable/proc/move_patient_to_table(mob/living/carbon/patient, mob/living/carbon/giver)
+ patient.resting = TRUE
+ patient.forceMove(loc)
+
/obj/machinery/optable/mouse_drop_receive(atom/dropped, mob/user, params)
//If the user is a ghost, stop.
if(isghost(user))
@@ -333,3 +344,28 @@
return TRUE
if(default_part_replacement(user, attacking_item))
return TRUE
+
+/obj/machinery/optable/robotics
+ name = "machinery chair"
+ desc = "Some sort of hybrid between an operating table and a chair, typically used by machinists and roboticists to strap synthetics to while they work on them. \
+ It comes with an access cable for easy access to a synthetic's diagnostics unit."
+ icon_state = "machinist_or_table"
+ density = FALSE
+ occupant_must_be_lying = FALSE
+ can_buckle = list(/mob/living/carbon/human)
+
+/obj/machinery/optable/robotics/mechanics_hints(mob/user, distance, is_adjacent)
+ . = ..()
+ . += list("Use a non-help intent to unbuckle.")
+
+/obj/machinery/optable/robotics/refresh_icon_state()
+ return
+
+/obj/machinery/optable/robotics/move_patient_to_table(mob/living/carbon/patient, mob/living/carbon/giver)
+ buckle(patient, giver)
+ visible_message(SPAN_NOTICE("[giver] buckles [patient] to \the [src]."))
+
+/obj/machinery/optable/robotics/buckle(atom/movable/buckling_atom, mob/user)
+ . = ..()
+ if(.)
+ playsound(src, 'sound/effects/metal_close.ogg', 20)
diff --git a/code/game/machinery/computer/computer.dm b/code/game/machinery/computer/computer.dm
index fe60115184d..f2d969fee46 100644
--- a/code/game/machinery/computer/computer.dm
+++ b/code/game/machinery/computer/computer.dm
@@ -29,6 +29,9 @@
/// The zlevel that this computer is spawned on.
var/starting_z_level = null
+ /// The access cable inserted into this computer, if any.
+ var/obj/item/access_cable/inserted_cable
+
/obj/machinery/computer/Initialize()
. = ..()
overlay_layer = layer
@@ -36,6 +39,12 @@
power_change()
update_icon()
+/obj/machinery/computer/Destroy()
+ if(inserted_cable)
+ inserted_cable.retract()
+ inserted_cable = null
+ return ..()
+
/obj/machinery/computer/ui_interact(mob/user, ui_key = "main", datum/nanoui/ui = null, force_open = TRUE)
if(!operable() || !is_station_level(z) || user.stat)
user.unset_machine()
@@ -47,7 +56,6 @@
if(prob(20/severity))
set_broken()
-
/obj/machinery/computer/ex_act(severity)
switch(severity)
if(1.0)
@@ -194,6 +202,15 @@
update_icon()
return TRUE
else
+ if(istype(attacking_item, /obj/item/access_cable))
+ var/obj/item/access_cable/access_cable = attacking_item
+ if(inserted_cable)
+ to_chat(user, SPAN_WARNING("There's already a cable in the universal port!"))
+ return
+
+ if(do_after(user, 1 SECONDS))
+ visible_message(SPAN_NOTICE("[user] slots \the [access_cable] into \the [src]."))
+ insert_cable(access_cable, user)
return ..()
/obj/machinery/computer/CanPass(atom/movable/mover, turf/target, height=0, air_group=0)
@@ -218,6 +235,19 @@
to_chat(user, SPAN_WARNING("\The [src]'s non-stick surface prevents you from attaching a sticker to it!"))
return FALSE
+/obj/machinery/computer/insert_cable(obj/item/access_cable/cable, mob/user)
+ . = ..()
+ inserted_cable = cable
+ cable.create_cable(src)
+
+/obj/machinery/computer/cable_interact(obj/item/access_cable/cable, mob/user)
+ . = ..()
+ attack_hand(user)
+
+/obj/machinery/computer/remove_cable(obj/item/access_cable/cable)
+ ..()
+ inserted_cable = null
+
/obj/machinery/computer/terminal
name = "terminal"
icon = 'icons/obj/machinery/modular_terminal.dmi'
diff --git a/code/game/machinery/cryopod.dm b/code/game/machinery/cryopod.dm
index 2cfa9c543d4..10080cee045 100644
--- a/code/game/machinery/cryopod.dm
+++ b/code/game/machinery/cryopod.dm
@@ -554,7 +554,7 @@
var/choice = alert(M, "Would you like to save your tag data?", "Tag Persistence", "Yes", "No")
if(choice == "Yes")
var/mob/living/carbon/human/H = M
- 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(tag)
M.client.prefs.machine_ownership_status = tag.ownership_info
M.client.prefs.machine_serial_number = tag.serial_number
diff --git a/code/game/machinery/machinery.dm b/code/game/machinery/machinery.dm
index 7330f37b507..c674ba28bc6 100644
--- a/code/game/machinery/machinery.dm
+++ b/code/game/machinery/machinery.dm
@@ -297,7 +297,7 @@ Class Procs:
update_icon()
-/obj/machinery/CanUseTopic(var/mob/user)
+/obj/machinery/CanUseTopic(mob/user)
if(stat & BROKEN)
return STATUS_CLOSE
@@ -306,18 +306,18 @@ Class Procs:
return ..()
-/obj/machinery/CouldUseTopic(var/mob/user)
+/obj/machinery/CouldUseTopic(mob/user)
..()
if(clicksound && iscarbon(user))
playsound(src, clicksound, clickvol)
user.set_machine(src)
-/obj/machinery/CouldNotUseTopic(var/mob/user)
+/obj/machinery/CouldNotUseTopic(mob/user)
user.unset_machine()
////////////////////////////////////////////////////////////////////////////////////////////
-/obj/machinery/attack_ai(mob/user as mob)
+/obj/machinery/attack_ai(mob/user)
if(!ai_can_interact(user))
return
if(isrobot(user))
@@ -328,33 +328,27 @@ Class Procs:
else
return src.attack_hand(user)
-/obj/machinery/attack_hand(mob/user as mob)
+/obj/machinery/attack_hand(mob/user)
if(!operable(MAINT))
- return 1
+ return TRUE
if(user.lying || user.stat)
- return 1
- if ( ! (istype(usr, /mob/living/carbon/human) || \
- istype(usr, /mob/living/silicon)))
+ return TRUE
+ if (!istype(usr, /mob/living/carbon/human) && !istype(usr, /mob/living/silicon))
to_chat(usr, SPAN_WARNING("You don't have the dexterity to do this!"))
- return 1
-/*
- //distance checks are made by atom/proc/DblClick
- if ((get_dist(src, user) > 1 || !istype(src.loc, /turf)) && !istype(user, /mob/living/silicon))
- return 1
-*/
- if (ishuman(user))
- var/mob/living/carbon/human/H = user
- if(H.getBrainLoss() >= 60)
- visible_message(SPAN_WARNING("[H] stares cluelessly at [src] and drools."))
- return 1
- else if(prob(H.getBrainLoss()))
- to_chat(user, SPAN_WARNING("You momentarily forget how to use [src]."))
- return 1
+ return TRUE
src.add_fingerprint(user)
return ..()
+/obj/machinery/attack_ranged(mob/user, params)
+ . = ..()
+ if(isipc(user))
+ var/mob/living/carbon/human/robot = user
+ var/obj/item/organ/internal/machine/wireless_access/wireless_access_point = robot.internal_organs_by_name[BP_WIRELESS_ACCESS]
+ if(wireless_access_point?.access_terminal(src))
+ attack_hand(user)
+
/obj/machinery/attackby(obj/item/attacking_item, mob/user)
if(obj_flags & OBJ_FLAG_SIGNALER)
if(issignaler(attacking_item))
diff --git a/code/game/machinery/mecha_fabricator.dm b/code/game/machinery/mecha_fabricator.dm
index 9eb4048298a..9a22776ecdb 100644
--- a/code/game/machinery/mecha_fabricator.dm
+++ b/code/game/machinery/mecha_fabricator.dm
@@ -1,6 +1,6 @@
/obj/machinery/mecha_part_fabricator
- name = "mechatronic fabricator"
- desc = "A general purpose fabricator that can be used to fabricate robotic equipment."
+ name = "synthetic fabricator"
+ desc = "A general purpose fabricator that can be used to fabricate equipment for synthetics or exosuits."
icon = 'icons/obj/machinery/robotics_fabricator.dmi'
icon_state = "fab"
density = TRUE
@@ -15,6 +15,7 @@
/obj/item/stock_parts/micro_laser,
/obj/item/stock_parts/console_screen
)
+ manufacturer = "hephaestus"
/**
* A `/list` of enqueued `/datum/design` to be printed, processed in the queue
@@ -36,6 +37,9 @@
var/datum/research/files
+ /// The looping sound for production.
+ var/datum/looping_sound/synth_fab/fab_loop
+
var/list/categories = list()
var/category = null
var/sync_message = ""
@@ -57,11 +61,13 @@
. = ..()
files = new /datum/research(src) //Setup the research data holder.
+ fab_loop = new(src)
limb_manufacturer = GLOB.basic_robolimb.company
update_categories()
/obj/machinery/mecha_part_fabricator/update_icon()
ClearOverlays()
+ update_fab_audio()
if(panel_open)
AddOverlays("[icon_state]_panel")
if(!(stat & (NOPOWER|BROKEN)))
@@ -72,9 +78,19 @@
AddOverlays(emissive_appearance(icon, "[icon_state]_lights_working"))
AddOverlays("[icon_state]_lights_working")
+/// Starts and stops the necessary audio.
+/obj/machinery/mecha_part_fabricator/proc/update_fab_audio()
+ if(!fab_loop)
+ return
+
+ if(build_callback_timer)
+ fab_loop.start()
+ else
+ fab_loop.stop()
+
/obj/machinery/mecha_part_fabricator/dismantle()
for(var/f in materials)
- eject_materials(f, -1)
+ eject_materials(f, materials[f])
//Stop the queue building if you're dismantling
deltimer(build_callback_timer)
@@ -106,27 +122,36 @@
do_hair_pull(user)
ui_interact(user)
-/obj/machinery/mecha_part_fabricator/ui_interact(var/mob/user, var/ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1)
- var/data[0]
+/obj/machinery/mecha_part_fabricator/ui_interact(mob/user, datum/tgui/ui)
+ . = ..()
+ ui = SStgui.try_update_ui(user, src, ui)
+ if(!ui)
+ ui = new(user, src, "SyntheticFabricator", "Synthetic Fabricator", 900, 700)
+ ui.open()
+/obj/machinery/mecha_part_fabricator/ui_data(mob/user)
+ var/list/data = list()
+ data["manufacturer"] = manufacturer
var/datum/design/current = queue.len ? queue[1] : null
if(current)
data["current"] = current.name
data["queue"] = get_queue_names()
data["buildable"] = get_build_options()
data["category"] = category
- data["categories"] = categories
+ data["available_categories"] = list()
+ for(var/possible_category in categories)
+ data["available_categories"] += list(list("name" = possible_category))
if(GLOB.fabricator_robolimbs)
var/list/T = list()
for(var/A in GLOB.fabricator_robolimbs)
var/datum/robolimb/R = GLOB.fabricator_robolimbs[A]
- T += list(list("id" = A, "company" = R.company))
+ T += list(list("name" = R.company, "type" = A))
data["manufacturers"] = T
- data["manufacturer"] = limb_manufacturer
+ data["selected_manufacturer"] = limb_manufacturer
data["materials"] = get_materials()
- data["maxres"] = res_max_amount
+ data["maximum_resource_amount"] = res_max_amount
data["sync"] = sync_message
//If we have something in the queue that we're trying to build, show the time left if the build is undergoing, otherwise null
@@ -136,42 +161,46 @@
data["timeleft"] = round(timeleft(build_callback_timer))
else
data["timeleft"] = null
+ return data
- ui = SSnanoui.try_update_ui(user, src, ui_key, ui, data, force_open)
- if(!ui)
- ui = new(user, src, ui_key, "mechfab.tmpl", "Exosuit Fabricator UI", 800, 600)
- ui.set_initial_data(data)
- ui.open()
- ui.set_auto_update(1)
-/obj/machinery/mecha_part_fabricator/Topic(href, href_list)
- if(..())
+/obj/machinery/mecha_part_fabricator/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state)
+ . = ..()
+ if(.)
return
- if(href_list["build"])
- var/path = text2path(href_list["build"])
- add_to_queue(path)
+ switch(action)
+ if("build")
+ var/path = text2path(params["build"])
+ add_to_queue(path)
+ . = TRUE
- if(href_list["remove"])
- remove_from_queue(text2num(href_list["remove"]))
+ if("remove")
+ remove_from_queue(text2num(params["remove"]))
+ . = TRUE
- if(href_list["category"])
- if(href_list["category"] in categories)
- category = href_list["category"]
+ if("category")
+ if(params["category"] in categories)
+ category = params["category"]
+ . = TRUE
- if(href_list["manufacturer"])
- if(href_list["manufacturer"] in GLOB.fabricator_robolimbs)
- limb_manufacturer = href_list["manufacturer"]
+ if("manufacturer")
+ if(params["manufacturer"] in GLOB.fabricator_robolimbs)
+ limb_manufacturer = params["manufacturer"]
+ . = TRUE
- if(href_list["eject"])
- eject_materials(href_list["eject"], text2num(href_list["amount"]))
+ if("eject")
+ eject_materials(params["eject"], text2num(params["amount"]))
+ . = TRUE
- if(href_list["sync"])
- sync()
- else
- sync_message = ""
+ if("sync")
+ sync()
+ . = TRUE
+ else
+ sync_message = ""
- return 1
+ if(.)
+ playsound(src, 'sound/machines/synthfab/synthfab_button.ogg', 50)
/obj/machinery/mecha_part_fabricator/attackby(obj/item/attacking_item, mob/user)
if(build_callback_timer)
@@ -195,7 +224,7 @@
return TRUE
var/sname = "[M.name]"
- if(materials[M.material.name] + M.perunit <= res_max_amount)
+ if(materials[M.material.name] + SHEET_MATERIAL_AMOUNT <= res_max_amount)
if(M.amount >= 1)
var/count = 0
var/mutable_appearance/MA = mutable_appearance(icon, "material_insertion")
@@ -206,7 +235,7 @@
//now play the progress bar animation
flick_overlay_view(mutable_appearance(icon, "fab_progress"), 1 SECONDS)
- while(materials[M.material.name] + M.perunit <= res_max_amount && M.amount >= 1)
+ while(materials[M.material.name] + SHEET_MATERIAL_AMOUNT <= res_max_amount && M.amount >= 1)
materials[M.material.name] += M.perunit
M.use(1)
count++
@@ -344,6 +373,7 @@
else
visible_message(SPAN_NOTICE("[icon2html(src, viewers(get_turf(src)))] \The [src] flashes: Insufficient materials to complete build: [D.name]."))
+ remove_from_queue(1)
update_icon()
@@ -383,17 +413,17 @@
/obj/machinery/mecha_part_fabricator/proc/get_queue_names()
. = list()
- for(var/i = 2 to queue.len)
+ for(var/i = 1 to queue.len)
var/datum/design/D = queue[i]
- . += D.name
+ . += list(list("name" = D.name, "time" = get_design_time(D), "index" = i))
/obj/machinery/mecha_part_fabricator/proc/get_build_options()
. = list()
for(var/path in files.known_designs)
var/datum/design/D = files.known_designs[path]
- if(!D.build_path || !(D.build_type & MECHFAB) || !D.category)
+ if(!D.build_path || !(D.build_type & MECHFAB) || !D.category || (D.category != category))
continue
- . += list(list("name" = D.name, "id" = D.type, "category" = D.category, "resourses" = get_design_resourses(D), "time" = get_design_time(D)))
+ . += list(list("name" = D.name, "desc" = D.desc, "type" = D.type, "category" = D.category, "resources" = get_design_resourses(D), "time" = get_design_time(D)))
/obj/machinery/mecha_part_fabricator/proc/get_design_resourses(var/datum/design/D)
var/list/F = list()
@@ -417,25 +447,19 @@
/obj/machinery/mecha_part_fabricator/proc/get_materials()
. = list()
for(var/T in materials)
- . += list(list("mat" = capitalize(T), "amt" = materials[T]))
+ . += list(list("name" = capitalize(T), "amount" = materials[T]))
-/obj/machinery/mecha_part_fabricator/proc/eject_materials(var/material, var/amount) // 0 amount = 0 means ejecting a full stack; -1 means eject everything
- var/recursive = amount == -1 ? 1 : 0
+/obj/machinery/mecha_part_fabricator/proc/eject_materials(var/material, var/amount)
+ if(!amount)
+ return
material = lowertext(material)
var/material/mattype = SSmaterials.get_material_by_name(material)
var/stack_type = mattype.stack_type
- var/obj/item/stack/material/S = new stack_type(loc)
- if(amount <= 0)
- amount = S.max_amount
- var/ejected = min(round(materials[material] / S.perunit), amount)
- S.amount = min(ejected, amount)
- if(S.amount <= 0)
- qdel(S)
- return
- materials[material] -= ejected * S.perunit
- if(recursive && materials[material] >= S.perunit)
- eject_materials(material, -1)
+ var/real_amount = round(amount / SHEET_MATERIAL_AMOUNT)
+ var/obj/item/stack/material/S = new stack_type(loc, real_amount)
+ S.update_icon()
+ materials[material] -= amount
/obj/machinery/mecha_part_fabricator/proc/sync()
sync_message = "Error: no console found."
diff --git a/code/game/machinery/rechargestation.dm b/code/game/machinery/rechargestation.dm
index c3245369021..369d3321732 100644
--- a/code/game/machinery/rechargestation.dm
+++ b/code/game/machinery/rechargestation.dm
@@ -117,6 +117,9 @@
return
var/obj/item/cell/target
+
+ // We use the reactor variable to see if this is an IPC that's charging.
+ var/obj/item/organ/internal/machine/reactor/reactor
if(isrobot(occupant))
var/mob/living/silicon/robot/R = occupant
@@ -132,9 +135,15 @@
if(ishuman(occupant))
var/mob/living/carbon/human/H = occupant
- var/obj/item/organ/internal/cell/IC = H.internal_organs_by_name[BP_CELL]
- if(IC)
+ var/obj/item/organ/internal/machine/power_core/IC = H.internal_organs_by_name[BP_CELL]
+ if(istype(IC))
target = IC.cell
+
+ // Different reactor types have different external recharge speeds.
+ reactor = H.internal_organs_by_name[BP_REACTOR]
+ if(!istype(reactor))
+ return
+
if((!target || target.percent() > 95) && istype(H.back, /obj/item/rig))
var/obj/item/rig/R = H.back
if(R.cell && !R.cell.fully_charged())
@@ -143,7 +152,11 @@
if(target && !target.fully_charged())
var/diff = min(target.maxcharge - target.charge, charging_power * CELLRATE * seconds_per_tick) // Capped by charging_power / tick
var/charge_used = cell.use(diff)
- target.give(charge_used*charging_efficiency)
+
+ if(!reactor) // not an IPC
+ target.give(charge_used * charging_efficiency)
+ else
+ reactor.generate_power(charge_used * charging_efficiency * reactor.external_charge_multiplier)
if(isDrone(occupant))
var/mob/living/silicon/robot/drone/D = occupant
@@ -172,6 +185,13 @@
if(cell)
cell.emp_act(severity)
+/obj/machinery/recharge_station/attack_hand(mob/user)
+ . = ..()
+ if(occupant && (user != occupant))
+ if(do_after(user, 1 SECOND, src))
+ user.visible_message(SPAN_NOTICE("[user] lowers the manual release lever on \the [src]."), SPAN_NOTICE("You lower the manual release lever on \the [src]."))
+ go_out()
+
/obj/machinery/recharge_station/attackby(obj/item/attacking_item, mob/user)
if(!occupant)
if(default_deconstruction_screwdriver(user, attacking_item))
diff --git a/code/game/machinery/telecomms/computers/logbrowser.dm b/code/game/machinery/telecomms/computers/logbrowser.dm
index bb0348e2890..a22f7229b5a 100644
--- a/code/game/machinery/telecomms/computers/logbrowser.dm
+++ b/code/game/machinery/telecomms/computers/logbrowser.dm
@@ -180,7 +180,7 @@
if(!emagged)
playsound(src.loc, 'sound/effects/sparks4.ogg', 75, 1)
emagged = 1
- to_chat(user, SPAN_NOTICE("You you disable the security protocols"))
+ to_chat(user, SPAN_NOTICE("You disable the security protocols."))
src.updateUsrDialog()
return 1
diff --git a/code/game/machinery/telecomms/computers/telemonitor.dm b/code/game/machinery/telecomms/computers/telemonitor.dm
index dfb67a6ceef..2e0e10629a3 100644
--- a/code/game/machinery/telecomms/computers/telemonitor.dm
+++ b/code/game/machinery/telecomms/computers/telemonitor.dm
@@ -157,6 +157,6 @@
if(!emagged)
playsound(src.loc, 'sound/effects/sparks4.ogg', 75, 1)
emagged = 1
- to_chat(user, SPAN_NOTICE("You you disable the security protocols"))
+ to_chat(user, SPAN_NOTICE("You disable the security protocols."))
src.updateUsrDialog()
return 1
diff --git a/code/game/machinery/telecomms/computers/traffic_control.dm b/code/game/machinery/telecomms/computers/traffic_control.dm
index 2d0ec8acd30..0cf7350deb2 100644
--- a/code/game/machinery/telecomms/computers/traffic_control.dm
+++ b/code/game/machinery/telecomms/computers/traffic_control.dm
@@ -234,6 +234,6 @@
if(!emagged)
playsound(src.loc, 'sound/effects/sparks4.ogg', 75, 1)
emagged = 1
- to_chat(user, SPAN_NOTICE("You you disable the security protocols"))
+ to_chat(user, SPAN_NOTICE("You disable the security protocols."))
src.updateUsrDialog()
return 1
diff --git a/code/game/objects/items/defib.dm b/code/game/objects/items/defib.dm
index ccdde2a0721..6c41ae9f35d 100644
--- a/code/game/objects/items/defib.dm
+++ b/code/game/objects/items/defib.dm
@@ -420,7 +420,7 @@
make_announcement("pings, \"Resuscitation successful.\"", "notice")
playsound(get_turf(src), 'sound/machines/defib_success.ogg', 50, 0)
H.resuscitate()
- var/obj/item/organ/internal/cell/potato = H.internal_organs_by_name[BP_CELL]
+ var/obj/item/organ/internal/machine/power_core/potato = H.internal_organs_by_name[BP_CELL]
if(istype(potato) && potato.cell)
var/obj/item/cell/C = potato.cell
C.give(chargecost)
diff --git a/code/game/objects/items/devices/suit_cooling.dm b/code/game/objects/items/devices/suit_cooling.dm
index 296cb0c06b6..3e59deae339 100644
--- a/code/game/objects/items/devices/suit_cooling.dm
+++ b/code/game/objects/items/devices/suit_cooling.dm
@@ -23,7 +23,7 @@
var/on = 0 //is it turned on?
var/cover_open = 0 //is the cover open?
var/obj/item/cell/cell
- var/max_cooling = 12 //in degrees per second - probably don't need to mess with heat capacity here
+ var/max_cooling = 24 //in degrees per second - probably don't need to mess with heat capacity here
var/charge_consumption = 8.3 //charge per second at max_cooling
var/thermostat = T20C
@@ -95,7 +95,7 @@
var/charge_usage = (temp_adj/max_cooling)*charge_consumption
- H.bodytemperature -= temp_adj*efficiency
+ H.bodytemperature = max(T0C, H.bodytemperature - temp_adj*efficiency)
cell.use(charge_usage)
diff --git a/code/game/objects/items/stacks/nanopaste.dm b/code/game/objects/items/stacks/nanopaste.dm
index 2f8fb36b516..a1d06949fb6 100644
--- a/code/game/objects/items/stacks/nanopaste.dm
+++ b/code/game/objects/items/stacks/nanopaste.dm
@@ -83,6 +83,25 @@
var/obj/item/organ/external/S = H.get_organ(target_zone)
if (S && (S.status & ORGAN_ASSISTED))
+ var/datum/component/synthetic_endoskeleton/endoskeleton = H.GetComponent(/datum/component/synthetic_endoskeleton)
+ if(istype(endoskeleton) && endoskeleton.damage)
+ if(target_mob == user)
+ if(endoskeleton.damage >= endoskeleton.max_damage * 0.5)
+ to_chat(user, SPAN_WARNING("Your control over your limbs is too damaged to apply the nanopaste precisely!"))
+ return
+ application_time *= application_multiplier // It takes longer to apply nanopaste to yourself than to someone else.
+
+ if (application_in_progress == FALSE)
+ application_in_progress = TRUE
+ user.visible_message(SPAN_NOTICE("\The [user] begins to apply nanite past to the broken support systems of [user != target_mob ? " \the [target_mob]'s" : "\the [user]'s"] endoskeleton..."), \
+ SPAN_NOTICE("You begin to apply nanite paste to the broken support systems of [user != target_mob ? " \the [target_mob]'s" : "your"] [endoskeleton]..."))
+ if(do_mob(user, target_mob, application_time))
+ SEND_SIGNAL(target_mob, COMSIG_SYNTH_ENDOSKELETON_REPAIR, rand(15, 30))
+ user.visible_message(SPAN_NOTICE("\The [user] mends the broken links in [user != target_mob ? " \the [target_mob]'s" : "\the [user]'s"] endoskeleton with \the [src]."),\
+ SPAN_NOTICE("You successfully mend the broken links in[user == target_mob ? "your" : "[target_mob]'s"] endoskeleton with \the [src]."))
+ use(1)
+ application_in_progress = FALSE
+
if(S.get_damage())
user.setClickCooldown(DEFAULT_ATTACK_COOLDOWN)
@@ -144,7 +163,7 @@
return FALSE
if (isipc(M))
- var/obj/item/organ/internal/surge/s = M.internal_organs_by_name["surge"]
+ var/obj/item/organ/internal/machine/surge/s = M.internal_organs_by_name[BP_SURGE_PROTECTOR]
if(isnull(s))
user.visible_message(
SPAN_NOTICE("[user] is trying to apply [src] to [(M == user) ? ("itself") : (M)]!"),
@@ -154,9 +173,9 @@
if (!do_mob(user, M, 2))
return FALSE
- s = new /obj/item/organ/internal/surge()
+ s = new /obj/item/organ/internal/machine/surge()
M.internal_organs += s
- M.internal_organs_by_name["surge"] = s
+ M.internal_organs_by_name[BP_SURGE_PROTECTOR] = s
user.visible_message(
SPAN_NOTICE("[user] applies some nanite paste to [(M == user) ? ("itself") : (M)]!"),
SPAN_NOTICE("You apply [src] to [(M == user) ? ("youself") : (M)].")
diff --git a/code/game/objects/items/weapons/implants/implanter.dm b/code/game/objects/items/weapons/implants/implanter.dm
index 5b41a3fda00..4163168b22d 100644
--- a/code/game/objects/items/weapons/implants/implanter.dm
+++ b/code/game/objects/items/weapons/implants/implanter.dm
@@ -68,11 +68,11 @@
/obj/item/implanter/ipc_tag
name = "IPC tag implanter"
desc = "A special implanter used for implanting synthetics with a special tag."
- var/obj/item/organ/internal/ipc_tag/ipc_tag
+ var/obj/item/organ/internal/machine/ipc_tag/ipc_tag
/obj/item/implanter/ipc_tag/Initialize()
. = ..()
- ipc_tag = new /obj/item/organ/internal/ipc_tag(src)
+ ipc_tag = new /obj/item/organ/internal/machine/ipc_tag(src)
update_icon()
/obj/item/implanter/ipc_tag/update_icon()
@@ -121,7 +121,7 @@
ipc_tag = null
update_icon()
return
- if(istype(attacking_item, /obj/item/organ/internal/ipc_tag))
+ if(istype(attacking_item, /obj/item/organ/internal/machine/ipc_tag))
if(ipc_tag)
to_chat(user, SPAN_WARNING("\The [src] already has an IPC tag loaded."))
return
diff --git a/code/game/objects/items/weapons/ipc_scanner.dm b/code/game/objects/items/weapons/ipc_scanner.dm
index 6b575b4768e..1beb54727ad 100644
--- a/code/game/objects/items/weapons/ipc_scanner.dm
+++ b/code/game/objects/items/weapons/ipc_scanner.dm
@@ -40,7 +40,7 @@
to_chat(user, SPAN_WARNING("You analyze \the [target_mob], but find that they're not an IPC at all!"))
return
var/mob/living/carbon/human/IPC = target_mob
- var/obj/item/organ/internal/ipc_tag/tag = IPC.internal_organs_by_name[BP_IPCTAG]
+ var/obj/item/organ/internal/machine/ipc_tag/tag = IPC.internal_organs_by_name[BP_IPCTAG]
if(isnull(tag) || !tag)
to_chat(user, SPAN_WARNING("Error: Serial Identification Missing."))
return
diff --git a/code/game/objects/items/weapons/power_cells.dm b/code/game/objects/items/weapons/power_cells.dm
index af886d9f04b..05edb3623c8 100644
--- a/code/game/objects/items/weapons/power_cells.dm
+++ b/code/game/objects/items/weapons/power_cells.dm
@@ -11,16 +11,17 @@
throw_speed = 3
throw_range = 5
w_class = WEIGHT_CLASS_NORMAL
+ matter = list(DEFAULT_WALL_MATERIAL = 700, MATERIAL_GLASS = 50)
+ recyclable = TRUE
+
/// Note %age converted to actual charge in New()
var/charge = 0
- var/maxcharge = 2500
+ /// Maximum cell charge.
+ var/maxcharge = 1000
/// True if rigged to explode
var/rigged = FALSE
/// If not 100% reliable, it will build up faults.
var/minor_fault = 0
- matter = list(DEFAULT_WALL_MATERIAL = 700, MATERIAL_GLASS = 50)
- recyclable = TRUE
-
/// Percentage of maxcharge to recharge per minute, though it will trickle charge every process tick (every ~2 seconds), leave null for no self-recharge
var/self_charge_percentage
@@ -126,10 +127,10 @@
matter = list(DEFAULT_WALL_MATERIAL = 700, MATERIAL_GLASS = 80)
/obj/item/cell/infinite/check_charge()
- return 1
+ return 1
/obj/item/cell/infinite/use()
- return 1
+ return 1
/obj/item/cell/potato
name = "potato battery"
@@ -139,8 +140,6 @@
icon_state = "potato_cell" //"potato_battery"
charge = 100
maxcharge = 300
- minor_fault = 1
-
/obj/item/cell/slime
name = "charged slime core"
diff --git a/code/game/objects/objs.dm b/code/game/objects/objs.dm
index f7574bda114..74d916317ee 100644
--- a/code/game/objects/objs.dm
+++ b/code/game/objects/objs.dm
@@ -355,6 +355,30 @@
/obj/proc/do_signaler()
return
+
+/**
+ * Called when an access cable - from an IPC or from a roboticist tool - is inserted into an object.
+ */
+/obj/proc/insert_cable(obj/item/access_cable/cable, mob/user)
+ user.drop_from_inventory(cable)
+ cable.forceMove(src)
+ cable.target = src
+
+/**
+ * Called when an access cable is removed from something, forcibly or not.
+ * Override as needed to clear variables up.
+ */
+/obj/proc/remove_cable(obj/item/access_cable/cable)
+ SHOULD_CALL_PARENT(TRUE)
+ cable.target = null
+
+/**
+ * Called when an access cable - from an IPC or from a roboticist tool - is used on an object to interact with it.
+ * For example, brings up diagnostics for an access port if it's hooked into one.
+ */
+/obj/proc/cable_interact(obj/item/access_cable/cable, mob/user)
+ return
+
/*#############################################
PERSISTENT
#############################################*/
diff --git a/code/game/objects/structures/crates_lockers/closets/secure/operations.dm b/code/game/objects/structures/crates_lockers/closets/secure/operations.dm
index ad903fb05de..d4b72e93f82 100644
--- a/code/game/objects/structures/crates_lockers/closets/secure/operations.dm
+++ b/code/game/objects/structures/crates_lockers/closets/secure/operations.dm
@@ -73,6 +73,7 @@
new /obj/item/storage/belt/utility(src)
new /obj/item/storage/belt/utility/alt(src)
new /obj/item/storage/toolbox/mechanical(src)
+ new /obj/item/modular_computer/laptop/preset/supply/robotics(src)
// Miner
/obj/structure/closet/secure_closet/miner
diff --git a/code/game/objects/structures/crates_lockers/closets/secure/scientist.dm b/code/game/objects/structures/crates_lockers/closets/secure/scientist.dm
index 0f5163f3bdf..5f334f6a6b3 100644
--- a/code/game/objects/structures/crates_lockers/closets/secure/scientist.dm
+++ b/code/game/objects/structures/crates_lockers/closets/secure/scientist.dm
@@ -49,6 +49,7 @@
new /obj/item/device/taperecorder(src)
new /obj/item/sampler(src)
new /obj/item/storage/box/shells/tranquilizer(src)
+ new /obj/item/modular_computer/laptop/preset/supply/robotics(src)
/// Used for when we want to populate the contents of this locker ourself. Good for away sites
/obj/structure/closet/secure_closet/RD/empty
diff --git a/code/modules/admin/view_variables/helpers.dm b/code/modules/admin/view_variables/helpers.dm
index 537d5a9dce1..cf3e27cd28f 100644
--- a/code/modules/admin/view_variables/helpers.dm
+++ b/code/modules/admin/view_variables/helpers.dm
@@ -17,7 +17,7 @@
BRUTE:[getBruteLoss()]
FIRE:[getFireLoss()]
- TOXIN:[getToxLoss()]
+ ORGAN:[getToxLoss()]
OXY:[getOxyLoss()]
CLONE:[getCloneLoss()]
BRAIN:[getBrainLoss()]
diff --git a/code/modules/client/preference_setup/general/03_body.dm b/code/modules/client/preference_setup/general/03_body.dm
index bb6e9e2237e..7acc3d13966 100644
--- a/code/modules/client/preference_setup/general/03_body.dm
+++ b/code/modules/client/preference_setup/general/03_body.dm
@@ -243,6 +243,11 @@ GLOBAL_LIST_INIT(valid_bloodtypes, list(
out += "Limbs: Adjust
"
if(length(mob_species.alterable_internal_organs))
out += "Internal Organs: Adjust
"
+ for(var/organ in pref.organ_data)
+ var/output_pref = pref.organ_data[organ]
+ if(!pref.organ_data[organ])
+ output_pref = "Default"
+ out += "- [capitalize_first_letters(organ)]: [capitalize(output_pref)]
"
out += "Prosthesis/Amputations: Reset
"
//display limbs below
@@ -890,6 +895,22 @@ GLOBAL_LIST_INIT(valid_bloodtypes, list(
if("Removed")
pref.organ_data[organ_name] = ORGAN_PREF_REMOVED
+ // IPC cooling unit options.
+ if("Air Cooling")
+ pref.organ_data[organ_name] = null
+ if("Liquid Cooling")
+ pref.organ_data[organ_name] = ORGAN_PREF_LIQUIDCOOLED
+ if("Passive Cooling")
+ pref.organ_data[organ_name] = ORGAN_PREF_PASSIVECOOLED
+
+ // IPC reactor options.
+ if("Electric")
+ pref.organ_data[organ_name] = null
+ if("Biological")
+ pref.organ_data[organ_name] = ORGAN_PREF_BIOPOWER
+ if("Solar")
+ pref.organ_data[organ_name] = ORGAN_PREF_SOLARPOWER
+
return TOPIC_REFRESH_UPDATE_PREVIEW
else if(href_list["reset_organs"])
diff --git a/code/modules/clothing/spacesuits/rig/modules/utility.dm b/code/modules/clothing/spacesuits/rig/modules/utility.dm
index 19765654882..0bc0aefb344 100644
--- a/code/modules/clothing/spacesuits/rig/modules/utility.dm
+++ b/code/modules/clothing/spacesuits/rig/modules/utility.dm
@@ -774,7 +774,7 @@
interface_desc = "A heat sink with liquid cooled radiator."
icon_state = "suitcooler"
var/charge_consumption = 1
- var/max_cooling = 12
+ var/max_cooling = 24
var/thermostat = T20C
category = MODULE_GENERAL
@@ -785,15 +785,34 @@
var/mob/living/carbon/human/H = holder.wearer
- var/temp_adj = min(H.bodytemperature - thermostat, max_cooling)
+ var/env_temp = get_environment_temperature()
+ var/temp_adj = min(H.bodytemperature - max(thermostat, env_temp), max_cooling)
if (temp_adj < 0.5)
return passive_power_cost
- H.bodytemperature -= temp_adj
+ H.bodytemperature = max(T0C, H.bodytemperature - temp_adj)
active_power_cost = round((temp_adj/max_cooling)*charge_consumption)
+
return active_power_cost
+/obj/item/rig_module/cooling_unit/proc/get_environment_temperature()
+ if(ishuman(loc))
+ var/mob/living/carbon/human/H = loc
+ if(istype(H.loc, /obj/machinery/atmospherics/unary/cryo_cell))
+ var/obj/machinery/atmospherics/unary/cryo_cell/C = H.loc
+ return C.air_contents.temperature
+
+ var/turf/T = get_turf(src)
+ if(istype(T, /turf/space) || !isturf(T))
+ return FALSE //space has no temperature, this just makes sure the cooling unit works in space
+
+ var/datum/gas_mixture/environment = T.return_air()
+ if(!environment)
+ return FALSE
+
+ return environment.temperature
+
/obj/item/rig_module/boring
name = "burrowing lasers"
desc = "A set of precise boring lasers designed to carve a hole beneath the user."
diff --git a/code/modules/customitems/item_defines.dm b/code/modules/customitems/item_defines.dm
index 51fd4c077b2..169f5bd922c 100644
--- a/code/modules/customitems/item_defines.dm
+++ b/code/modules/customitems/item_defines.dm
@@ -1985,6 +1985,7 @@ All custom items with worn sprites must follow the contained sprite system: http
// adding the hand to the child here means only the arm has to be added to the DB
// since the hand will be attached automatically
LAZYADD(children, new /obj/item/organ/external/hand/fluff/gracia_autakh(src))
+ internal_organs += new /obj/item/organ/internal/machine/actuators/left(src)
/obj/item/organ/external/hand/fluff/gracia_autakh // gracia's aut'akh left hand - Gracia Hiza - cometblaze
robotize_type = PROSTHETIC_AUTAKH
diff --git a/code/modules/events/electrical_storm.dm b/code/modules/events/electrical_storm.dm
index 9cc0d25d795..07fd7cab207 100644
--- a/code/modules/events/electrical_storm.dm
+++ b/code/modules/events/electrical_storm.dm
@@ -34,6 +34,15 @@
for(var/obj/machinery/power/apc/valid_apc in SSmachinery.apc_units)
if((valid_apc.z in affecting_z) && !valid_apc.is_critical)
valid_apcs += valid_apc
+ for(var/mob/living/carbon/human/victim_ipc in GLOB.human_mob_list)
+ if(!isipc(victim_ipc))
+ continue
+ if(!(victim_ipc.z in affecting_z))
+ continue
+ var/obj/item/organ/internal/machine/posibrain/brain = victim_ipc.internal_organs_by_name[BP_BRAIN]
+ if(istype(brain))
+ to_chat(victim_ipc, SPAN_MACHINE_WARNING("A jolt of ambient electricity rumbles through your circuitry! Your processors go to work putting the bits back together..."))
+ brain.brain_scrambling += rand(severity * 35, severity * 45)
endWhen = (severity * 35) + startWhen
/datum/event/electrical_storm/end(faked)
diff --git a/code/modules/heavy_vehicle/components/arms.dm b/code/modules/heavy_vehicle/components/arms.dm
index fcade95a230..a9b9843abd3 100644
--- a/code/modules/heavy_vehicle/components/arms.dm
+++ b/code/modules/heavy_vehicle/components/arms.dm
@@ -27,7 +27,7 @@
return
switch(href_list["info"])
if("actuator")
- to_chat(usr, SPAN_NOTICE("An actuator can be created at a mechatronic fabricator."))
+ to_chat(usr, SPAN_NOTICE("An actuator can be created at a synthetic fabricator."))
/obj/item/mech_component/manipulators/return_diagnostics(mob/user)
..()
diff --git a/code/modules/heavy_vehicle/components/body.dm b/code/modules/heavy_vehicle/components/body.dm
index 92516fe4d12..b082c77ba7f 100644
--- a/code/modules/heavy_vehicle/components/body.dm
+++ b/code/modules/heavy_vehicle/components/body.dm
@@ -79,11 +79,11 @@
return
switch(href_list["info"])
if("cell")
- to_chat(usr, SPAN_NOTICE("A power core can be created at a mechatronic fabricator."))
+ to_chat(usr, SPAN_NOTICE("A power core can be created at a synthetic fabricator."))
if("diagnostics")
- to_chat(usr, SPAN_NOTICE("A diagnostics unit can be created at a mechatronic fabricator."))
+ to_chat(usr, SPAN_NOTICE("A diagnostics unit can be created at a synthetic fabricator."))
if("armor")
- to_chat(usr, SPAN_NOTICE("Armor plating can be created at a mechatronic fabricator."))
+ to_chat(usr, SPAN_NOTICE("Armor plating can be created at a synthetic fabricator."))
/obj/item/mech_component/chassis/return_diagnostics(mob/user)
..()
diff --git a/code/modules/heavy_vehicle/components/frame.dm b/code/modules/heavy_vehicle/components/frame.dm
index 742b1361ba1..c5eee644fa8 100644
--- a/code/modules/heavy_vehicle/components/frame.dm
+++ b/code/modules/heavy_vehicle/components/frame.dm
@@ -66,13 +66,13 @@
return
switch(href_list["info"])
if("manipulators")
- to_chat(usr, SPAN_NOTICE("Manipulators, the arms of the exosuit, can be created at a mechatronic fabricator."))
+ to_chat(usr, SPAN_NOTICE("Manipulators, the arms of the exosuit, can be created at a synthetic fabricator."))
if("propulsion")
- to_chat(usr, SPAN_NOTICE("Propulsion, the legs of the exosuit, can be created at a mechatronic fabricator."))
+ to_chat(usr, SPAN_NOTICE("Propulsion, the legs of the exosuit, can be created at a synthetic fabricator."))
if("sensors")
- to_chat(usr, SPAN_NOTICE("Sensors, the head of the exosuit, can be created at a mechatronic fabricator."))
+ to_chat(usr, SPAN_NOTICE("Sensors, the head of the exosuit, can be created at a synthetic fabricator."))
if("chassis")
- to_chat(usr, SPAN_NOTICE("A chassis, the body of the exosuit, can be created at a mechatronic fabricator."))
+ to_chat(usr, SPAN_NOTICE("A chassis, the body of the exosuit, can be created at a synthetic fabricator."))
if("wire")
if(!is_wired)
to_chat(usr, SPAN_NOTICE("The frame requires wiring between its components. This can be added with cable coil."))
diff --git a/code/modules/heavy_vehicle/components/head.dm b/code/modules/heavy_vehicle/components/head.dm
index 9196023465b..fc79738e2dc 100644
--- a/code/modules/heavy_vehicle/components/head.dm
+++ b/code/modules/heavy_vehicle/components/head.dm
@@ -37,11 +37,11 @@
return
switch(href_list["info"])
if("radio")
- to_chat(usr, SPAN_NOTICE("A radio can be created at a mechatronic fabricator."))
+ to_chat(usr, SPAN_NOTICE("A radio can be created at a synthetic fabricator."))
if("camera")
- to_chat(usr, SPAN_NOTICE("A camera can be created at a mechatronic fabricator."))
+ to_chat(usr, SPAN_NOTICE("A camera can be created at a synthetic fabricator."))
if("module")
- to_chat(usr, SPAN_NOTICE("An exosuit control module can be created at a mechatronic fabricator, while the software chips it uses can be printed at the circuit imprinter."))
+ to_chat(usr, SPAN_NOTICE("An exosuit control module can be created at a synthetic fabricator, while the software chips it uses can be printed at the circuit imprinter."))
/obj/item/mech_component/sensors/return_diagnostics(mob/user)
..()
diff --git a/code/modules/heavy_vehicle/components/legs.dm b/code/modules/heavy_vehicle/components/legs.dm
index 6d50a561246..1b4e2b25718 100644
--- a/code/modules/heavy_vehicle/components/legs.dm
+++ b/code/modules/heavy_vehicle/components/legs.dm
@@ -26,7 +26,7 @@
return
switch(href_list["info"])
if("actuator")
- to_chat(usr, SPAN_NOTICE("An actuator can be created at a mechatronic fabricator."))
+ to_chat(usr, SPAN_NOTICE("An actuator can be created at a synthetic fabricator."))
/obj/item/mech_component/propulsion/return_diagnostics(mob/user)
..()
diff --git a/code/modules/maptext_alerts/screen_alerts.dm b/code/modules/maptext_alerts/screen_alerts.dm
index 9c3a4b3d958..ad3409ce29e 100644
--- a/code/modules/maptext_alerts/screen_alerts.dm
+++ b/code/modules/maptext_alerts/screen_alerts.dm
@@ -64,6 +64,18 @@
style_open = ""
style_close = ""
+/atom/movable/screen/text/screen_text/low_integrity_message
+ maptext_height = 64
+ maptext_width = 480
+ maptext_x = 0
+ maptext_y = 0
+ screen_loc = "LEFT,TOP-3"
+
+ letters_per_update = 1
+ fade_out_delay = 6 SECONDS
+ style_open = ""
+ style_close = ""
+
///proc for actually playing this screen_text on a mob.
/atom/movable/screen/text/screen_text/proc/play_to_client()
player?.add_to_screen(src)
diff --git a/code/modules/mining/minebot.dm b/code/modules/mining/minebot.dm
index 318a4a1567d..12ac83582fe 100644
--- a/code/modules/mining/minebot.dm
+++ b/code/modules/mining/minebot.dm
@@ -30,7 +30,7 @@
cell.maxcharge = 10000
cell.charge = 10000
- mmi = /obj/item/organ/internal/mmi_holder/circuit
+ mmi = /obj/item/organ/internal/machine/posibrain/circuit
// Allows mining drones to pull ore boxes, might be useful for supporting miners
pull_list |= /obj/structure/ore_box
diff --git a/code/modules/mob/inventory.dm b/code/modules/mob/inventory.dm
index 3effc9d5aa6..ca85d213ac5 100644
--- a/code/modules/mob/inventory.dm
+++ b/code/modules/mob/inventory.dm
@@ -421,6 +421,10 @@ GLOBAL_LIST_INIT(slot_equipment_priority, list(
item = G.throw_held() //throw the person instead of the grab
if(ismob(item) && G.state >= GRAB_NECK)
var/mob/M = item
+ if(M.mob_weight > get_mob_strength())
+ to_chat(src, SPAN_WARNING("[M] is far too heavy for you to throw around!"))
+ return
+
throw_range = round(throw_range * (src.mob_size/M.mob_size))
itemsize = round(M.mob_size/4)
var/turf/start_T = get_turf(loc) //Get the start and target tile for the descriptors
diff --git a/code/modules/mob/living/carbon/human/appearance.dm b/code/modules/mob/living/carbon/human/appearance.dm
index 947755e244a..deed632b991 100644
--- a/code/modules/mob/living/carbon/human/appearance.dm
+++ b/code/modules/mob/living/carbon/human/appearance.dm
@@ -16,7 +16,7 @@
set_species(new_species)
reset_hair()
if(isipc(src))
- var/obj/item/organ/internal/ipc_tag/tag = internal_organs_by_name[BP_IPCTAG]
+ var/obj/item/organ/internal/machine/ipc_tag/tag = internal_organs_by_name[BP_IPCTAG]
if(istype(tag))
tag.modify_tag_data(TRUE)
return 1
diff --git a/code/modules/mob/living/carbon/human/human.dm b/code/modules/mob/living/carbon/human/human.dm
index 0b9768cdbcd..16f2f484d4c 100644
--- a/code/modules/mob/living/carbon/human/human.dm
+++ b/code/modules/mob/living/carbon/human/human.dm
@@ -188,6 +188,15 @@
var/obj/item/organ/internal/stomach/stomach = internal_organs_by_name[BP_STOMACH]
if(stomach)
return stomach.ingested
+
+ if(should_have_organ(BP_REACTOR))
+ var/obj/item/organ/internal/machine/reactor/reactor = internal_organs_by_name[BP_REACTOR]
+ if(!reactor)
+ return
+ if(reactor.is_broken())
+ return FALSE
+ if(reactor.power_supply_type & POWER_SUPPLY_BIOLOGICAL)
+ return reactor.bio_reagents
return touching
/mob/living/carbon/human/proc/metabolize_ingested_reagents()
@@ -239,9 +248,16 @@
. += "Tank Pressure: [internal.air_contents.return_pressure()]"
. += "Distribution Pressure: [internal.distribute_pressure]"
- var/obj/item/organ/internal/cell/IC = internal_organs_by_name[BP_CELL]
+ var/obj/item/organ/internal/machine/power_core/IC = internal_organs_by_name[BP_CELL]
if(IC && IC.cell)
- . += "Battery charge: [IC.get_charge()]/[IC.cell.maxcharge]"
+ . += "Battery Charge: [IC.get_charge()]/[IC.cell.maxcharge]"
+
+ var/obj/item/organ/internal/machine/internal_diagnostics/diagnostics = internal_organs_by_name[BP_DIAGNOSTICS_SUITE]
+ if(diagnostics)
+ if((diagnostics.get_integrity() < IPC_INTEGRITY_THRESHOLD_HIGH) || !diagnostics.is_broken())
+ . += "Temperature: [round(bodytemperature - T0C, 1)]°C"
+ else
+ . += "Temperature: E#RR"
if(mind)
var/datum/vampire/vampire = mind.antag_datums[MODE_VAMPIRE]
@@ -970,6 +986,13 @@
return
/mob/living/carbon/human/proc/check_has_mouth()
+ // Look, it's not really a mouth, but you gotta do what you gotta do.
+ // Imagine the Bender shit from Futurama where he opens his stomach hatch and drops shit in there.
+ if(should_have_organ(BP_REACTOR))
+ var/obj/item/organ/internal/machine/reactor/reactor = internal_organs_by_name[BP_REACTOR]
+ if(reactor && (reactor.power_supply_type & POWER_SUPPLY_BIOLOGICAL))
+ return TRUE
+
// Todo, check stomach organ when implemented.
var/obj/item/organ/external/E = get_organ(BP_HEAD)
if(E && !E.is_stump())
@@ -1888,6 +1911,10 @@
return 0
/mob/living/carbon/human/proc/can_drink(var/obj/item/I)
+ if(should_have_organ(BP_REACTOR))
+ var/obj/item/organ/internal/machine/reactor/reactor = internal_organs_by_name[BP_REACTOR]
+ if(reactor && (reactor.power_supply_type & POWER_SUPPLY_BIOLOGICAL))
+ return TRUE
if(!check_has_mouth())
to_chat(src, SPAN_NOTICE("Where do you intend to put \the [I]? You don't have a mouth!"))
return FALSE
@@ -2070,8 +2097,8 @@
// Check if we should die.
/mob/living/carbon/human/proc/handle_death_check()
- if(should_have_organ(BP_BRAIN) && !is_mechanical()) //robots don't die via brain damage
- var/obj/item/organ/internal/brain/brain = internal_organs_by_name[BP_BRAIN]
+ if(should_have_organ(BP_BRAIN))
+ var/obj/item/organ/internal/brain = internal_organs_by_name[BP_BRAIN]
if(!brain || (brain.status & ORGAN_DEAD))
return TRUE
return species.handle_death_check(src)
diff --git a/code/modules/mob/living/carbon/human/human_attackhand.dm b/code/modules/mob/living/carbon/human/human_attackhand.dm
index 3ef7e2aa4d9..0443defb7c0 100644
--- a/code/modules/mob/living/carbon/human/human_attackhand.dm
+++ b/code/modules/mob/living/carbon/human/human_attackhand.dm
@@ -306,7 +306,7 @@
return FALSE
var/disarm_cost
- var/obj/item/organ/internal/cell/cell = M.internal_organs_by_name[BP_CELL]
+ var/obj/item/organ/internal/machine/power_core/cell = M.internal_organs_by_name[BP_CELL]
var/obj/item/cell/potato
if(cell)
potato = cell.cell
diff --git a/code/modules/mob/living/carbon/human/human_damage.dm b/code/modules/mob/living/carbon/human/human_damage.dm
index c7ef9204d9a..50e04998cef 100644
--- a/code/modules/mob/living/carbon/human/human_damage.dm
+++ b/code/modules/mob/living/carbon/human/human_damage.dm
@@ -404,7 +404,7 @@ This function restores the subjects blood to max.
This function restores all organs.
*/
/mob/living/carbon/human/restore_all_organs()
- for(var/obj/item/organ/external/current_organ in organs)
+ for(var/obj/item/organ/current_organ in organs)
current_organ.rejuvenate()
/mob/living/carbon/human/proc/HealDamage(zone, brute, burn)
diff --git a/code/modules/mob/living/carbon/human/human_helpers.dm b/code/modules/mob/living/carbon/human/human_helpers.dm
index 8a42240dcbd..77f8e2ef201 100644
--- a/code/modules/mob/living/carbon/human/human_helpers.dm
+++ b/code/modules/mob/living/carbon/human/human_helpers.dm
@@ -13,7 +13,7 @@
return 1
if(feedback)
if(status[1] == HUMAN_EATING_NO_MOUTH)
- to_chat(src, "Where do you intend to put \the [food]? You don't have a mouth!")
+ to_chat(src, SPAN_NOTICE("Where do you intend to put \the [food]? You don't have a mouth!"))
else if(status[1] == HUMAN_EATING_BLOCKED_MOUTH)
to_chat(src, SPAN_WARNING("\The [status[2]] is in the way!"))
return 0
@@ -133,16 +133,24 @@
else
var/obj/item/organ/I = internal_organs_by_name[name]
if(I)
- switch (status)
- if (ORGAN_PREF_ASSISTED)
- I.mechassist()
- if (ORGAN_PREF_MECHANICAL)
- if (rlimb_data[name])
- I.robotize(rlimb_data[name])
- else
- I.robotize()
- if (ORGAN_PREF_REMOVED)
- qdel(I)
+ if(istype(I, /obj/item/organ/internal/machine))
+ var/obj/item/organ/internal/machine/machine_organ = I
+ if(length(machine_organ.possible_modifications))
+ machine_organ.get_preset_from_pref(status)
+ // If you add any more presets here, make sure to update the presets on the organ as well.
+ // Remember also that the default pref, but "default pref" is actually "the absence of any pref".
+ // We check for unique prefs and if we don't find a unique organ pref, then we default to the base type.
+ else
+ switch (status)
+ if (ORGAN_PREF_ASSISTED)
+ I.mechassist()
+ if (ORGAN_PREF_MECHANICAL)
+ if (rlimb_data[name])
+ I.robotize(rlimb_data[name])
+ else
+ I.robotize()
+ if (ORGAN_PREF_REMOVED)
+ qdel(I)
if (apply_markings)
for(var/N in organs_by_name)
@@ -363,7 +371,7 @@
return FALSE
/mob/living/carbon/human/get_cell()
- var/obj/item/organ/internal/cell/C = internal_organs_by_name[BP_CELL]
+ var/obj/item/organ/internal/machine/power_core/C = internal_organs_by_name[BP_CELL]
if(C)
return C.cell
diff --git a/code/modules/mob/living/carbon/human/human_movement.dm b/code/modules/mob/living/carbon/human/human_movement.dm
index 5f20d92880c..fe8fbe30f97 100644
--- a/code/modules/mob/living/carbon/human/human_movement.dm
+++ b/code/modules/mob/living/carbon/human/human_movement.dm
@@ -55,6 +55,11 @@
var/obj/item/P = pulling
tally += P.slowdown
+ var/obj/item/grab/grab = get_type_in_hands(/obj/item/grab)
+ if(istype(grab) && ishuman(grab.affecting))
+ if(grab.affecting.mob_weight > get_mob_strength())
+ tally += grab.affecting.mob_weight - get_mob_strength()
+
var/turf/T = get_turf(src)
if(T) // changelings don't get movement costs
var/datum/changeling/changeling
@@ -188,4 +193,4 @@
var/mob/living/carbon/human/H = pulling
if(H.species.slowdown > species.slowdown)
. += H.species.slowdown - species.slowdown
- // . += H.ClothesSlowdown()
+
diff --git a/code/modules/mob/living/carbon/human/human_organs.dm b/code/modules/mob/living/carbon/human/human_organs.dm
index 64aa99cf02a..0b9a0db1da3 100644
--- a/code/modules/mob/living/carbon/human/human_organs.dm
+++ b/code/modules/mob/living/carbon/human/human_organs.dm
@@ -37,6 +37,9 @@
internal_organs -= I
continue
+ if(I.status & ORGAN_DEAD)
+ continue
+
I.process(seconds_per_tick)
handle_stance()
@@ -192,10 +195,13 @@
/mob/living/carbon/human/is_asystole()
if(isSynthetic())
- var/obj/item/organ/internal/cell/C = internal_organs_by_name[BP_CELL]
+ var/obj/item/organ/internal/machine/power_core/C = internal_organs_by_name[BP_CELL]
if(istype(C) && C.is_usable() && C.percent())
- return FALSE
+ var/obj/item/organ/internal/machine/posibrain/posi = internal_organs_by_name[BP_BRAIN]
+ if(istype(posi) && !posi.self_preservation_activated)
+ return FALSE
return TRUE
+
else if(should_have_organ(BP_HEART))
var/obj/item/organ/internal/heart/heart = internal_organs_by_name[BP_HEART]
if(!istype(heart) || !heart.is_working())
diff --git a/code/modules/mob/living/carbon/human/human_powers.dm b/code/modules/mob/living/carbon/human/human_powers.dm
index 344140e2ea6..71171729c68 100644
--- a/code/modules/mob/living/carbon/human/human_powers.dm
+++ b/code/modules/mob/living/carbon/human/human_powers.dm
@@ -1066,9 +1066,6 @@
drop_from_inventory(O)
O.replaced(src)
- update_body()
- updatehealth()
- UpdateDamageIcon()
update_body()
updatehealth()
@@ -1077,45 +1074,6 @@
visible_message(SPAN_NOTICE("\The [src] attaches \the [O] to [get_pronoun("his")] body!"),
SPAN_NOTICE("You attach \the [O] to your body!"))
-/mob/living/carbon/human/proc/self_diagnostics()
- set name = "Self-Diagnostics"
- set desc = "Run an internal self-diagnostic to check for damage."
- set category = "Abilities"
-
- if(stat == DEAD) return
-
- to_chat(src, SPAN_NOTICE("Performing self-diagnostic, please wait..."))
- if (do_after(src, 10))
- var/output = SPAN_NOTICE("Self-Diagnostic Results:\n")
-
- output += "Internal Temperature: [convert_k2c(bodytemperature)] Degrees Celsius\n"
-
- var/obj/item/organ/internal/cell/C = internal_organs_by_name[BP_CELL]
- if(!C || !C.cell)
- output += SPAN_DANGER("ERROR: NO BATTERY DETECTED")
- else
- output += "Current Charge Level: [C.percent()]\n"
-
- var/toxDam = getToxLoss()
- if(toxDam)
- output += "Blood Toxicity: [toxDam > 25 ? "Severe" : "Moderate"]. Seek medical facilities for cleanup.\n"
- else
- output += "Blood Toxicity: OK\n"
-
- for(var/obj/item/organ/external/EO in organs)
- if(EO.brute_dam || EO.burn_dam)
- output += "[EO.name] - [EO.burn_dam + EO.brute_dam > ROBOLIMB_SELF_REPAIR_CAP ? "Heavy Damage" : "Light Damage"]\n"
- else
- output += "[EO.name] - OK\n"
-
- for(var/obj/item/organ/IO in internal_organs)
- if(IO.damage)
- output += "[IO.name] - [IO.damage > 10 ? "Heavy Damage" : "Light Damage"]\n"
- else
- output += "[IO.name] - OK\n"
-
- to_chat(src, output)
-
/mob/living/carbon/human/proc/check_tag()
set name = "Check Tag"
set desc = "Run diagnostics on your tag to display its information."
@@ -1124,7 +1082,7 @@
if(use_check_and_message(usr))
return
- var/obj/item/organ/internal/ipc_tag/tag = internal_organs_by_name[BP_IPCTAG]
+ var/obj/item/organ/internal/machine/ipc_tag/tag = internal_organs_by_name[BP_IPCTAG]
if(isnull(tag) || !tag)
to_chat(src, SPAN_WARNING("Error: No Tag Found."))
return
diff --git a/code/modules/mob/living/carbon/human/life.dm b/code/modules/mob/living/carbon/human/life.dm
index 25c8e47c522..8722d6f0de8 100644
--- a/code/modules/mob/living/carbon/human/life.dm
+++ b/code/modules/mob/living/carbon/human/life.dm
@@ -439,7 +439,8 @@
/mob/living/carbon/human/proc/stabilize_body_temperature()
if (species.passive_temp_gain) // We produce heat naturally.
- bodytemperature += species.passive_temp_gain
+ species.handle_temperature_regulation(src)
+
if (species.body_temperature == null)
return //this species doesn't have metabolic thermoregulation
@@ -1015,7 +1016,7 @@
nut_icon = 2
else if (nut_factor >= CREW_NUTRITION_HUNGRY)
nut_icon = 3
- else if (nut_factor >= CREW_NUTRITION_VERYHUNGRY )
+ else if (nut_factor >= CREW_NUTRITION_VERYHUNGRY)
nut_icon = 4
var/new_val = "[isSynthetic() ? "charge" : "nutrition"][nut_icon]"
if (nutrition_icon.icon_state != new_val)
@@ -1039,7 +1040,7 @@
hydration_icon.icon_state = new_val
if(isSynthetic())
- var/obj/item/organ/internal/cell/IC = internal_organs_by_name[BP_CELL]
+ var/obj/item/organ/internal/machine/power_core/IC = internal_organs_by_name[BP_CELL]
if(istype(IC) && IC.is_usable())
var/chargeNum = clamp(Ceiling(IC.percent()/25), 0, 4) //0-100 maps to 0-4, but give it a paranoid clamp just in case.
cells.icon_state = "charge[chargeNum]"
diff --git a/code/modules/mob/living/carbon/human/say.dm b/code/modules/mob/living/carbon/human/say.dm
index ac46ba54a01..7fb24512d38 100644
--- a/code/modules/mob/living/carbon/human/say.dm
+++ b/code/modules/mob/living/carbon/human/say.dm
@@ -65,6 +65,12 @@
return ..()
+/mob/living/carbon/human/say(message, datum/language/speaking, verb, alt_name, ghost_hearing, whisper, skip_edit)
+ // Messaging the user is handled by the species proc.
+ if(!species.can_speak(src, speaking, message))
+ return FALSE
+ . = ..()
+
/mob/living/carbon/human/GetVoice()
var/voice_sub
if(istype(back,/obj/item/rig))
diff --git a/code/modules/mob/living/carbon/human/species/species.dm b/code/modules/mob/living/carbon/human/species/species.dm
index 1703be117f6..736b69a404b 100644
--- a/code/modules/mob/living/carbon/human/species/species.dm
+++ b/code/modules/mob/living/carbon/human/species/species.dm
@@ -94,6 +94,10 @@
/// Used for mob icon generation for non-32x32 species.
var/icon/icon_template
var/mob_size = MOB_MEDIUM
+ /// The weight of the mob. Affects if the mob can be easily lifted or not. Separate from size, as some mobs may be big but not particularly heavy.
+ var/mob_weight = MOB_WEIGHT_MEDIUM
+ /// The strength of the mob. A bonus to the checks on lifting/throwing other mobs.
+ var/mob_strength = MOB_STRENGTH_NORMAL
var/show_ssd = "in a deep slumber"
var/short_sighted
var/bald = 0
@@ -389,6 +393,7 @@
BP_R_FOOT = list("path" = /obj/item/organ/external/foot/right)
)
+ var/natural_armor_type = /datum/component/armor/natural
var/list/natural_armor
// Bump vars
@@ -545,7 +550,7 @@
if(H.bad_external_organs) H.bad_external_organs.Cut()
if(H.bad_internal_organs) H.bad_internal_organs.Cut()
- var/datum/component/armor/armor_component = H.GetComponent(/datum/component/armor)
+ var/datum/component/armor/armor_component = H.GetComponent(natural_armor_type)
if(armor_component)
qdel(armor_component)
@@ -584,7 +589,7 @@
I.status |= ORGAN_ADV_ROBOT
if(natural_armor)
- H.AddComponent(/datum/component/armor, natural_armor)
+ H.AddComponent(natural_armor_type, natural_armor)
/datum/species/proc/tap(var/mob/living/carbon/human/H,var/mob/living/target)
if(H.on_fire)
@@ -620,13 +625,16 @@
return
-/datum/species/proc/handle_post_spawn(var/mob/living/carbon/human/H,var/kpg = 0) //Handles anything not already covered by basic species assignment. Keepgene value should only be used by genetics.
+/// Handles anything not already covered by basic species assignment. Keepgene (kpg) value should only be used by genetics.
+/datum/species/proc/handle_post_spawn(mob/living/carbon/human/H, kpg = 0)
add_inherent_verbs(H)
H.mob_bump_flag = bump_flag
H.mob_swap_flags = swap_flags
H.mob_push_flags = push_flags
H.pass_flags = pass_flags
H.mob_size = mob_size
+ H.mob_weight = mob_weight
+ H.mob_strength = mob_strength
H.eat_types = allowed_eat_types
if(!kpg)
if(islesserform(H))
@@ -1057,3 +1065,18 @@
*/
/datum/species/proc/sleep_examine_msg(var/mob/M)
return SPAN_NOTICE("[M.get_pronoun("He")] appears to be fast asleep.\n")
+
+/**
+ * This proc is used to override speech checks for human mobs.
+ * If it returns FALSE, the mob will not be able to speak.
+ * Make sure to give the user the relevant error message in the override.
+ */
+/datum/species/proc/can_speak(mob/living/carbon/human/speaker, datum/language/speaking, message)
+ return TRUE
+
+/**
+ * This proc handles the species temperature regulation. By default, it just adds `passive_temp_gain` to the human's bodytemperature.
+ * Can be overridden for more complex calculations.
+ */
+/datum/species/proc/handle_temperature_regulation(mob/living/carbon/human/human)
+ human.bodytemperature += passive_temp_gain
diff --git a/code/modules/mob/living/carbon/human/species/species_hud.dm b/code/modules/mob/living/carbon/human/species/species_hud.dm
index b8d7af3b36c..3087ec50acc 100644
--- a/code/modules/mob/living/carbon/human/species/species_hud.dm
+++ b/code/modules/mob/living/carbon/human/species/species_hud.dm
@@ -1,24 +1,42 @@
/datum/hud_data
- var/icon // If set, overrides ui_style.
- var/has_a_intent = TRUE // Set to draw intent box.
- var/has_m_intent = TRUE // Set to draw move intent box.
- var/has_warnings = TRUE // Set to draw environment warnings.
- var/has_pressure = TRUE // Draw the pressure indicator.
- var/has_nutrition = TRUE // Draw the nutrition indicator.
- var/has_hydration = TRUE // Draw the hydration indicator.
- var/has_bodytemp = TRUE // Draw the bodytemp indicator.
- var/has_hands = TRUE // Set to draw hands.
- var/has_drop = TRUE // Set to draw drop button.
- var/has_throw = TRUE // Set to draw throw button.
- var/has_resist = TRUE // Set to draw resist button.
- var/has_internals = TRUE // Set to draw the internals toggle button.
- var/has_up_hint = TRUE // Set to draw the "look-up" hint icon.
- var/has_cell = FALSE //Set if the species has a cell.
+ /// If set, overrides ui_style.
+ var/icon
+ /// Set to draw intent box.
+ var/has_a_intent = TRUE
+ /// Set to draw move intent box.
+ var/has_m_intent = TRUE
+ /// Set to draw environment warnings.
+ var/has_warnings = TRUE
+ /// Draw the pressure indicator.
+ var/has_pressure = TRUE
+ /// Draw the nutrition indicator.
+ var/has_nutrition = TRUE
+ /// Draw the hydration indicator.
+ var/has_hydration = TRUE
+ /// Draw the bodytemp indicator.
+ var/has_bodytemp = TRUE
+ /// Set to draw hands.
+ var/has_hands = TRUE
+ /// Set to draw drop button.
+ var/has_drop = TRUE
+ /// Set to draw throw button.
+ var/has_throw = TRUE
+ /// Set to draw resist button.
+ var/has_resist = TRUE
+ /// Set to draw the internals toggle button.
+ var/has_internals = TRUE
+ /// Set to draw the "look-up" hint icon.
+ var/has_up_hint = TRUE
+ /// Set if the species has a cell.
+ var/has_cell = FALSE
+ /// If the mob in question uses robot pain textures.
+ var/has_robot_pain = FALSE
var/list/equip_slots = list() // Checked by mob_can_equip().
- // Contains information on the position and tag for all inventory slots
- // to be drawn for the mob. This is fairly delicate, try to avoid messing with it
- // unless you know exactly what it does.
+ /** Contains information on the position and tag for all inventory slots
+ * to be drawn for the mob. This is fairly delicate, try to avoid messing with it
+ * unless you know exactly what it does.
+ */
var/list/gear = list(
"i_clothing" = list("loc" = ui_iclothing, "name" = "uniform", "slot" = slot_w_uniform, "state" = "center", "toggle" = 1),
"o_clothing" = list("loc" = ui_oclothing, "name" = "suit", "slot" = slot_wear_suit, "state" = "suit", "toggle" = 1),
@@ -98,6 +116,7 @@
has_hydration = FALSE
has_nutrition = FALSE
has_cell = TRUE
+ has_robot_pain = TRUE
/datum/hud_data/construct
has_hydration = FALSE
diff --git a/code/modules/mob/living/carbon/human/species/station/ipc/ipc.dm b/code/modules/mob/living/carbon/human/species/station/ipc/ipc.dm
index fa5f820859d..b1b97b4e44c 100644
--- a/code/modules/mob/living/carbon/human/species/station/ipc/ipc.dm
+++ b/code/modules/mob/living/carbon/human/species/station/ipc/ipc.dm
@@ -12,6 +12,7 @@
economic_modifier = 6
default_genders = list(NEUTER)
selectable_pronouns = list(NEUTER, PLURAL)
+ mob_weight = MOB_WEIGHT_HEAVY
blurb = "IPCs are, quite simply, \"Integrated Positronic Chassis.\" In this scenario, 'positronic' implies that the chassis possesses a positronic processing core (or positronic brain), meaning that an IPC must be positronic to be considered an IPC. The Baseline model is more of a category - the long of the short is that they represent all unbound synthetic units. Baseline models cover anything that is not an Industrial chassis or a Shell chassis. They can be custom made or assembly made. The most common feature of the Baseline model is a simple design, skeletal or semi-humanoid, and ordinary atmospheric diffusion cooling systems."
@@ -43,7 +44,7 @@
hud_type = /datum/hud_data/ipc
- brute_mod = 1.0
+ brute_mod = 1
burn_mod = 1.2
grab_mod = 1.1 // Smooth, no real edges to grab onto
@@ -71,10 +72,9 @@
heat_level_3 = 2400
body_temperature = null
- passive_temp_gain = 10 // This should cause IPCs to stabilize at ~80 C in a 20 C environment.
+ passive_temp_gain = 10
inherent_verbs = list(
- /mob/living/carbon/human/proc/self_diagnostics,
/mob/living/carbon/human/proc/change_monitor,
/mob/living/carbon/human/proc/check_tag
)
@@ -88,11 +88,21 @@
flesh_color = "#575757"
reagent_tag = IS_MACHINE
+ // If you add a new organ to IPCs, remember that a lot of the normal subspecies have overrides because of custom subtype organs.
+ // You'll need to add the new organ there too.
has_organ = list(
- BP_BRAIN = /obj/item/organ/internal/mmi_holder/posibrain,
- BP_CELL = /obj/item/organ/internal/cell,
+ BP_BRAIN = /obj/item/organ/internal/machine/posibrain,
+ BP_VOICE_SYNTHESIZER = /obj/item/organ/internal/machine/voice_synthesizer,
+ BP_DIAGNOSTICS_SUITE = /obj/item/organ/internal/machine/internal_diagnostics,
+ BP_HYDRAULICS = /obj/item/organ/internal/machine/hydraulics,
+ BP_ACTUATORS_LEFT = /obj/item/organ/internal/machine/actuators/left,
+ BP_ACTUATORS_RIGHT = /obj/item/organ/internal/machine/actuators/right,
+ BP_COOLING_UNIT = /obj/item/organ/internal/machine/cooling_unit,
+ BP_REACTOR = /obj/item/organ/internal/machine/reactor,
+ BP_ACCESS_PORT = /obj/item/organ/internal/machine/access_port,
+ BP_CELL = /obj/item/organ/internal/machine/power_core,
BP_EYES = /obj/item/organ/internal/eyes/optical_sensor,
- BP_IPCTAG = /obj/item/organ/internal/ipc_tag
+ BP_IPCTAG = /obj/item/organ/internal/machine/ipc_tag
)
vision_organ = BP_EYES
@@ -114,7 +124,10 @@
heat_discomfort_level = 500 //This will be 100 below the first heat level
heat_discomfort_strings = list(
- "Your CPU temperature probes warn you that you are approaching critical heat levels!"
+ "Your positronic's temperature probes warn you that you are approaching critical heat levels!",
+ "Your cooling systems are struggling to keep your positronic's temperature down!",
+ "Your positronic is approaching dangerous temperature levels! Immediate cooling required!",
+ "Critical temperature levels approaching!"
)
stamina = -1 // Machines use power and generate heat, stamina is not a thing
sprint_speed_factor = 1 // About as capable of speed as a human
@@ -135,13 +148,8 @@
/singleton/origin_item/culture/scrapper
)
- alterable_internal_organs = list()
+ alterable_internal_organs = list(BP_COOLING_UNIT, BP_REACTOR)
possible_speech_bubble_types = list("robot", "default")
-
- // Special snowflake machine vars.
- var/sprint_temperature_factor = 1.15
- var/move_charge_factor = 1
-
use_alt_hair_layer = TRUE
psi_deaf = TRUE
@@ -149,23 +157,99 @@
snores = FALSE
indefinite_sleep = TRUE
+ natural_armor_type = /datum/component/armor/synthetic
+ natural_armor = list(
+ ballistic = ARMOR_BALLISTIC_MINOR,
+ melee = ARMOR_MELEE_KNIVES
+ )
+
+ // Special snowflake machine vars.
+ var/sprint_temperature_factor = 1.05
+ /// Species-based multiplier to movement power costs in the power core.
+ var/move_charge_factor = 1.2
+ /// The theme of the IPC's personal UIs, if not broken.
+ var/machine_ui_theme = "hackerman"
+
+
+/datum/species/machine/handle_temperature_regulation(mob/living/carbon/human/human)
+ // No cooling unit = you're cooking. Broken cooling unit effects are handled by the organ itself.
+ // Here we just want to check if it's been removed.
+ // 500K is about 226 degrees. Spicy!
+ var/base_heat_gain = passive_temp_gain
+ var/obj/item/organ/internal/machine/cooling_unit/cooling = human.internal_organs_by_name[BP_COOLING_UNIT]
+ if(!cooling || (cooling?.status & ORGAN_DEAD))
+ base_heat_gain *= 4 //uh oh
+ human.bodytemperature += base_heat_gain
+
+/datum/species/machine/handle_stance_damage(mob/living/carbon/human/H, damage_only)
+ var/stance_damage = 0
+ var/obj/item/organ/internal/machine/hydraulics/hydraulics = H.internal_organs_by_name[BP_HYDRAULICS]
+ if(!hydraulics)
+ return 6 //no hydraulics, no party
+
+ if(hydraulics.status & ORGAN_DEAD)
+ return 5
+
+ switch(hydraulics.get_integrity())
+ if(0 to IPC_INTEGRITY_THRESHOLD_VERY_HIGH)
+ to_chat(H, SPAN_MACHINE_WARNING("Your hydraulics are on the verge of breaking completely!"))
+ spark(H, rand(3, 4), GLOB.alldirs)
+ stance_damage += 4
+ if(IPC_INTEGRITY_THRESHOLD_VERY_HIGH to IPC_INTEGRITY_THRESHOLD_HIGH)
+ to_chat(H, SPAN_MACHINE_WARNING("Your hydraulics spark and whine!"))
+ spark(H, rand(1, 3), GLOB.alldirs)
+ stance_damage += 2
+
+ var/datum/component/synthetic_endoskeleton/endoskeleton = H.GetComponent(/datum/component/synthetic_endoskeleton)
+ if(!endoskeleton)
+ return 6 //how?
+
+ var/damage_ratio = endoskeleton.damage / endoskeleton.max_damage
+ switch(damage_ratio)
+ if(0.3 to 0.5)
+ stance_damage += 1
+ if(0.5 to 0.75)
+ stance_damage += 2
+ if(0.75 to INFINITY)
+ stance_damage += 3
+
+ return stance_damage
+
/datum/species/machine/handle_post_spawn(var/mob/living/carbon/human/H)
. = ..()
+ H.AddComponent(/datum/component/synthetic_endoskeleton)
check_tag(H, H.client)
- var/obj/item/organ/internal/cell/C = H.internal_organs_by_name[BP_CELL]
+ var/obj/item/organ/internal/machine/power_core/C = H.internal_organs_by_name[BP_CELL]
if(C)
C.move_charge_factor = move_charge_factor
/datum/species/machine/handle_sprint_cost(var/mob/living/carbon/human/H, var/cost, var/pre_move)
if(!pre_move && H.stat == CONSCIOUS)
H.bodytemperature += cost * sprint_temperature_factor
- var/obj/item/organ/internal/cell/C = H.internal_organs_by_name[BP_CELL]
- if(C)
+ var/obj/item/organ/internal/machine/hydraulics/hydraulics = H.internal_organs_by_name[BP_HYDRAULICS]
+ if(istype(hydraulics))
+ var/hydraulics_integrity = hydraulics.get_integrity()
+ if(hydraulics_integrity < 75)
+ var/damage_mod = 2
+ if(hydraulics.is_bruised())
+ damage_mod = 3
+ if(hydraulics.is_broken())
+ damage_mod = 5
+
+ // x = x * (2 + 100 - y ) / 100
+ // x = x * (2 + 100 - 75) / 100 -> x = x * (2 + (25 / 100)) --> x = x * 2.25
+ // increases depending on integrity
+
+ cost *= damage_mod + ((100 - hydraulics_integrity) / 100)
+
+ var/obj/item/organ/internal/machine/power_core/C = H.internal_organs_by_name[BP_CELL]
+ if(istype(C))
C.use(cost * sprint_cost_factor)
+ SEND_SIGNAL(H, COMSIG_IPC_HAS_SPRINTED)
return TRUE
/datum/species/machine/handle_emp_act(mob/living/carbon/human/hit_mob, severity)
- var/obj/item/organ/internal/surge/S = hit_mob.internal_organs_by_name["surge"]
+ var/obj/item/organ/internal/machine/surge/S = hit_mob.internal_organs_by_name[BP_SURGE_PROTECTOR]
if(!isnull(S))
if(S.surge_left >= 1)
playsound(hit_mob.loc, 'sound/magic/LightningShock.ogg', 25, 1)
@@ -189,16 +273,22 @@
/datum/species/machine/sanitize_name(var/new_name)
return sanitizeName(new_name, allow_numbers = 1)
+/datum/species/machine/bypass_food_fullness(mob/living/carbon/human/H)
+ var/obj/item/organ/internal/machine/reactor/reactor = H.internal_organs_by_name[BP_REACTOR]
+ if(reactor && (reactor.power_supply_type & POWER_SUPPLY_BIOLOGICAL))
+ return TRUE
+ return FALSE
+
/datum/species/machine/proc/check_tag(var/mob/living/carbon/human/new_machine, var/client/player)
if(!new_machine || !player)
- var/obj/item/organ/internal/ipc_tag/tag = new_machine.internal_organs_by_name[BP_IPCTAG]
+ var/obj/item/organ/internal/machine/ipc_tag/tag = new_machine.internal_organs_by_name[BP_IPCTAG]
if(tag)
tag.serial_number = uppertext(dd_limittext(md5(new_machine.real_name), 12))
tag.ownership_info = IPC_OWNERSHIP_COMPANY
tag.citizenship_info = CITIZENSHIP_BIESEL
return
- var/obj/item/organ/internal/ipc_tag/tag = new_machine.internal_organs_by_name[BP_IPCTAG]
+ var/obj/item/organ/internal/machine/ipc_tag/tag = new_machine.internal_organs_by_name[BP_IPCTAG]
if(player.prefs.machine_tag_status)
tag.serial_number = player.prefs.machine_serial_number
@@ -381,19 +471,14 @@
. = ..()
check_tag(H, H.client)
-/datum/species/machine/handle_death_check(var/mob/living/carbon/human/H)
- if(H.get_total_health() <= GLOB.config.health_threshold_dead)
- return TRUE
- return FALSE
-
/datum/species/machine/has_stamina_for_pushup(var/mob/living/carbon/human/human)
- var/obj/item/organ/internal/cell/C = human.internal_organs_by_name[BP_CELL]
+ var/obj/item/organ/internal/machine/power_core/C = human.internal_organs_by_name[BP_CELL]
if(!C.cell)
return FALSE
return C.cell.charge > (C.cell.maxcharge / 10)
/datum/species/machine/drain_stamina(var/mob/living/carbon/human/human, var/stamina_cost)
- var/obj/item/organ/internal/cell/C = human.internal_organs_by_name[BP_CELL]
+ var/obj/item/organ/internal/machine/power_core/C = human.internal_organs_by_name[BP_CELL]
if(C)
C.use(stamina_cost * 8)
@@ -403,3 +488,19 @@
/datum/species/machine/sleep_examine_msg(var/mob/M)
return SPAN_NOTICE("[M.get_pronoun("He")] appears to be in standby.\n")
+
+/datum/species/machine/can_speak(mob/living/carbon/human/speaker, datum/language/speaking, message)
+ var/obj/item/organ/internal/machine/voice_synthesizer/voice_synth = speaker.internal_organs_by_name[BP_VOICE_SYNTHESIZER]
+ if(istype(voice_synth))
+ return TRUE
+ else
+ to_chat(speaker, SPAN_WARNING("You cannot synthesize a voice without your [BP_VOICE_SYNTHESIZER]!"))
+ return FALSE
+
+/datum/species/machine/handle_speech_problems(mob/living/carbon/human/H, message, say_verb, message_mode, message_range)
+ var/obj/item/organ/internal/machine/voice_synthesizer/voice_synth = H.internal_organs_by_name[BP_VOICE_SYNTHESIZER]
+ if(istype(voice_synth))
+ if(voice_synth.is_bruised())
+ // at most, 30 * 2 + 10 = 70, which is the maximum value we can use for Gibberish
+ message = Gibberish(message, voice_synth.damage * 2 + (voice_synth.is_broken() ? 10 : 0))
+ return list(HSP_MSG = message, HSP_VERB = pick(list("crackles", "buzzes")))
diff --git a/code/modules/mob/living/carbon/human/species/station/ipc/ipc_subspecies.dm b/code/modules/mob/living/carbon/human/species/station/ipc/ipc_subspecies.dm
index 47b804dfbca..25867e6b87e 100644
--- a/code/modules/mob/living/carbon/human/species/station/ipc/ipc_subspecies.dm
+++ b/code/modules/mob/living/carbon/human/species/station/ipc/ipc_subspecies.dm
@@ -9,10 +9,11 @@
height_max = 230
default_genders = list(MALE, FEMALE)
selectable_pronouns = list(MALE, FEMALE, PLURAL, NEUTER)
+ mob_weight = MOB_WEIGHT_MEDIUM // Would be really easy to find untagged shells otherwise.
- alterable_internal_organs = list(BP_EYES)
+ alterable_internal_organs = list(BP_EYES, BP_COOLING_UNIT, BP_REACTOR)
- burn_mod = 1.2
+ burn_mod = 1.3
grab_mod = 1
blurb = "IPCs with humanlike properties. Their focus is on service, civilian, and medical, but there are no \
@@ -67,11 +68,10 @@
character_color_presets = list("Dark" = "#000000", "Warm" = "#250302", "Cold" = "#1e1e29", "Rubber" = "#000f36")
- sprint_temperature_factor = 1.3
- move_charge_factor = 0.85
+ sprint_temperature_factor = 1.15
+ move_charge_factor = 1.1
inherent_verbs = list(
- /mob/living/carbon/human/proc/self_diagnostics,
/mob/living/carbon/human/proc/check_tag,
/mob/living/carbon/human/proc/tie_hair)
@@ -101,10 +101,19 @@
break_cuffs = TRUE
has_organ = list(
- BP_BRAIN = /obj/item/organ/internal/mmi_holder/posibrain,
- BP_CELL = /obj/item/organ/internal/cell,
+ BP_BRAIN = /obj/item/organ/internal/machine/posibrain,
+ BP_VOICE_SYNTHESIZER = /obj/item/organ/internal/machine/voice_synthesizer,
+ BP_DIAGNOSTICS_SUITE = /obj/item/organ/internal/machine/internal_diagnostics,
+ BP_HYDRAULICS = /obj/item/organ/internal/machine/hydraulics,
+ BP_ACTUATORS_LEFT = /obj/item/organ/internal/machine/actuators/left,
+ BP_ACTUATORS_RIGHT = /obj/item/organ/internal/machine/actuators/right,
+ BP_COOLING_UNIT = /obj/item/organ/internal/machine/cooling_unit,
+ BP_REACTOR = /obj/item/organ/internal/machine/reactor,
+ BP_ACCESS_PORT = /obj/item/organ/internal/machine/access_port,
+ BP_CELL = /obj/item/organ/internal/machine/power_core,
BP_EYES = /obj/item/organ/internal/eyes/optical_sensor,
- "surge" = /obj/item/organ/internal/surge/advanced
+ BP_SURGE_PROTECTOR = /obj/item/organ/internal/machine/surge/advanced,
+ BP_TARGETING_CORE = /obj/item/organ/internal/machine/targeting_core
)
unarmed_types = list(
@@ -113,10 +122,6 @@
/datum/unarmed_attack/terminator,
/datum/unarmed_attack/bite/strong)
- inherent_verbs = list(
- /mob/living/carbon/human/proc/self_diagnostics
- )
-
/datum/species/machine/shell/rogue/check_tag(var/mob/living/carbon/human/new_machine, var/client/player)
return
@@ -138,7 +143,7 @@
grab_mod = 0.8 // Big, easy to grab onto
resist_mod = 10 // Good luck wrestling against this powerhouse.
- slowdown = 4
+ slowdown = 3
blurb = "The first commercialized attempt Hephaestus Industries made at an industrial-type IPC. Designed for extra durability and increased weight loads, the first generation Industrial was considered a success, though it possessed some issues. A limited power cell and actuators designed for heavy lifting and not locomotion resulted in a slow and frequently charging machine. A special addition to the chassis makes up for these drawbacks - the ability to simply slot a suit cooling unit onto the model's back and make use of its built-in heat transferal conduits, allowing the Industrial to perform EVA without any extra peripherals such as a voidsuit."
@@ -178,12 +183,28 @@
sprint_speed_factor = 1.4
sprint_temperature_factor = 0.9
- move_charge_factor = 1.1
+ move_charge_factor = 1.5
inherent_verbs = list(
- /mob/living/carbon/human/proc/self_diagnostics,
/mob/living/carbon/human/proc/check_tag
)
+ has_organ = list(
+ BP_BRAIN = /obj/item/organ/internal/machine/posibrain,
+ BP_VOICE_SYNTHESIZER = /obj/item/organ/internal/machine/voice_synthesizer,
+ BP_DIAGNOSTICS_SUITE = /obj/item/organ/internal/machine/internal_diagnostics,
+ BP_HYDRAULICS = /obj/item/organ/internal/machine/hydraulics,
+ BP_ACTUATORS_LEFT = /obj/item/organ/internal/machine/actuators/left,
+ BP_ACTUATORS_RIGHT = /obj/item/organ/internal/machine/actuators/right,
+ BP_COOLING_UNIT = /obj/item/organ/internal/machine/cooling_unit,
+ BP_REACTOR = /obj/item/organ/internal/machine/reactor,
+ BP_ACCESS_PORT = /obj/item/organ/internal/machine/access_port,
+ BP_CELL = /obj/item/organ/internal/machine/power_core,
+ BP_EYES = /obj/item/organ/internal/eyes/optical_sensor,
+ BP_IPCTAG = /obj/item/organ/internal/machine/ipc_tag,
+ BP_INTERNAL_STORAGE = /obj/item/organ/internal/machine/internal_storage,
+ )
+
+ machine_ui_theme = "hephaestus"
/datum/species/machine/industrial/get_light_color()
return LIGHT_COLOR_TUNGSTEN
@@ -238,7 +259,6 @@
heat_level_3 = 5000
body_temperature = null
- passive_temp_gain = 0
flags = IS_IPC | ACCEPTS_COOLER
spawn_flags = IS_RESTRICTED
@@ -250,16 +270,24 @@
inherent_verbs = list(
/mob/living/carbon/human/proc/self_destruct,
/mob/living/carbon/human/proc/detonate_flechettes,
- /mob/living/carbon/human/proc/state_laws,
- /mob/living/carbon/human/proc/self_diagnostics
+ /mob/living/carbon/human/proc/state_laws
)
has_organ = list(
- BP_BRAIN = /obj/item/organ/internal/mmi_holder/posibrain/terminator,
- BP_CELL = /obj/item/organ/internal/cell/terminator,
- BP_EYES = /obj/item/organ/internal/eyes/optical_sensor/terminator,
- "data core" = /obj/item/organ/internal/data,
- "surge" = /obj/item/organ/internal/surge/advanced
+ BP_BRAIN = /obj/item/organ/internal/machine/posibrain,
+ BP_VOICE_SYNTHESIZER = /obj/item/organ/internal/machine/voice_synthesizer,
+ BP_DIAGNOSTICS_SUITE = /obj/item/organ/internal/machine/internal_diagnostics,
+ BP_HYDRAULICS = /obj/item/organ/internal/machine/hydraulics,
+ BP_ACTUATORS_LEFT = /obj/item/organ/internal/machine/actuators/left,
+ BP_ACTUATORS_RIGHT = /obj/item/organ/internal/machine/actuators/right,
+ BP_COOLING_UNIT = /obj/item/organ/internal/machine/cooling_unit,
+ BP_REACTOR = /obj/item/organ/internal/machine/reactor,
+ BP_ACCESS_PORT = /obj/item/organ/internal/machine/access_port,
+ BP_CELL = /obj/item/organ/internal/machine/power_core,
+ BP_EYES = /obj/item/organ/internal/eyes/optical_sensor,
+ BP_DATACORE = /obj/item/organ/internal/machine/data,
+ BP_SURGE_PROTECTOR = /obj/item/organ/internal/machine/surge/advanced,
+ BP_TARGETING_CORE = /obj/item/organ/internal/machine/targeting_core
)
has_limbs = list(
@@ -289,6 +317,8 @@
sprint_temperature_factor = 0.6
move_charge_factor = 0.3
+ machine_ui_theme = "syndicate"
+
/datum/species/machine/hunter_killer/get_light_color()
return
@@ -314,8 +344,8 @@
unarmed_types = list(/datum/unarmed_attack/industrial/heavy, /datum/unarmed_attack/palm/industrial)
- slowdown = 6
- brute_mod = 0.7
+ slowdown = 2
+ brute_mod = 0.9
grab_mod = 0.7 // Bulkier and bigger than the G1
resist_mod = 12 // Overall stronger than G1
@@ -326,8 +356,9 @@
heat_discomfort_level = 900
inherent_verbs = list(
- /mob/living/carbon/human/proc/self_diagnostics,
- /mob/living/carbon/human/proc/check_tag
+ /mob/living/carbon/human/proc/check_tag,
+ /mob/living/carbon/human/proc/discard_limb,
+ /mob/living/carbon/human/proc/attach_hephaestus_limb
)
examine_color = "#688359"
@@ -348,8 +379,100 @@
BP_R_FOOT = list("path" = /obj/item/organ/external/foot/right/ipc/industrial/hephaestus)
)
+
+ has_organ = list(
+ BP_BRAIN = /obj/item/organ/internal/machine/posibrain,
+ BP_VOICE_SYNTHESIZER = /obj/item/organ/internal/machine/voice_synthesizer,
+ BP_DIAGNOSTICS_SUITE = /obj/item/organ/internal/machine/internal_diagnostics,
+ BP_HYDRAULICS = /obj/item/organ/internal/machine/hydraulics,
+ BP_ACTUATORS_LEFT = /obj/item/organ/internal/machine/actuators/left,
+ BP_ACTUATORS_RIGHT = /obj/item/organ/internal/machine/actuators/right,
+ BP_COOLING_UNIT = /obj/item/organ/internal/machine/cooling_unit,
+ BP_REACTOR = /obj/item/organ/internal/machine/reactor,
+ BP_ACCESS_PORT = /obj/item/organ/internal/machine/access_port,
+ BP_CELL = /obj/item/organ/internal/machine/power_core,
+ BP_EYES = /obj/item/organ/internal/eyes/optical_sensor,
+ BP_IPCTAG = /obj/item/organ/internal/machine/ipc_tag
+ )
+
flags = IS_IPC | ACCEPTS_COOLER | NO_EQUIP_SPEEDMODS
+ move_charge_factor = 1.7
+
+ machine_ui_theme = "hephaestus"
+
+ natural_armor = list(
+ ballistic = ARMOR_BALLISTIC_PISTOL,
+ melee = ARMOR_MELEE_KEVLAR
+ )
+
+/mob/living/carbon/human/proc/discard_limb()
+ set name = "Discard Limb"
+ set category = "Object"
+
+ if(incapacitated(INCAPACITATION_DEFAULT))
+ return
+
+ if(get_active_hand())
+ to_chat(src, SPAN_WARNING("You need your hand to be free!"))
+ return
+
+ var/limb_list = list()
+ for(var/obj/item/organ/external/limb in organs)
+ if(limb.limb_name in list(BP_CHEST, BP_GROIN, BP_HEAD))
+ continue
+
+ if(limb.robotize_type == PROSTHETIC_HI)
+ limb_list[capitalize_first_letters(limb.name)] = limb
+
+ var/chosen_limb = tgui_input_list(src, "Choose a limb to discard.", "Discard Limb", limb_list)
+ if(!chosen_limb)
+ return
+
+ var/obj/item/organ/external/oopsie = limb_list[chosen_limb]
+ visible_message(SPAN_DANGER("[src] ejects [get_pronoun("his")] [oopsie] with a hiss!"))
+ oopsie.droplimb(TRUE, DROPLIMB_EDGE)
+
+/mob/living/carbon/human/proc/attach_hephaestus_limb()
+ set name = "Attach Limb"
+ set category = "Object"
+
+ if(incapacitated(INCAPACITATION_DEFAULT))
+ return
+
+ var/obj/item/organ/external/limb = get_active_hand()
+ if(!istype(limb))
+ to_chat(src, SPAN_WARNING("You need to be holding a compatible Hephaestus Industries limb!"))
+ return
+
+ if(!limb.robotic)
+ to_chat(src, SPAN_WARNING("That is not a compatible Hephaestus Industries limb!"))
+ return
+
+ if(limb.robotize_type != PROSTHETIC_HI)
+ to_chat(src,SPAN_WARNING("That prosthetic does not have the right ports for your joint!"))
+ return
+
+ if(organs_by_name[limb.limb_name])
+ to_chat(src,SPAN_WARNING("You already have a limb of this type!"))
+ return
+
+ if(!organs_by_name[limb.parent_organ])
+ to_chat(src,SPAN_WARNING("You are missing the appropriate joint to attach that limb!"))
+ return
+
+ visible_message(SPAN_NOTICE("[src] begins attaching \the [limb] to the appropriate joint..."))
+ if(!do_after(src, 10 SECONDS))
+ return
+
+
+ drop_from_inventory(limb)
+ limb.replaced(src)
+
+ visible_message(SPAN_NOTICE("[src] attaches \the [limb] to their body!"))
+ update_body()
+ updatehealth()
+ UpdateDamageIcon()
/datum/species/machine/industrial/hephaestus/get_light_color(mob/living/carbon/human/H)
if (istype(H))
@@ -388,7 +511,6 @@
eyes = "xion_eyes"
flags = IS_IPC
- passive_temp_gain = 0
examine_color = "#bc4b00"
@@ -408,6 +530,23 @@
BP_R_FOOT = list("path" = /obj/item/organ/external/foot/right/ipc/industrial/xion)
)
+ has_organ = list(
+ BP_BRAIN = /obj/item/organ/internal/machine/posibrain,
+ BP_VOICE_SYNTHESIZER = /obj/item/organ/internal/machine/voice_synthesizer,
+ BP_DIAGNOSTICS_SUITE = /obj/item/organ/internal/machine/internal_diagnostics,
+ BP_HYDRAULICS = /obj/item/organ/internal/machine/hydraulics,
+ BP_ACTUATORS_LEFT = /obj/item/organ/internal/machine/actuators/left,
+ BP_ACTUATORS_RIGHT = /obj/item/organ/internal/machine/actuators/right,
+ BP_COOLING_UNIT = /obj/item/organ/internal/machine/cooling_unit/xion,
+ BP_REACTOR = /obj/item/organ/internal/machine/reactor,
+ BP_ACCESS_PORT = /obj/item/organ/internal/machine/access_port,
+ BP_CELL = /obj/item/organ/internal/machine/power_core,
+ BP_EYES = /obj/item/organ/internal/eyes/optical_sensor,
+ BP_IPCTAG = /obj/item/organ/internal/machine/ipc_tag
+ )
+
+ machine_ui_theme = "hephaestus"
+
/datum/species/machine/industrial/xion/remote
name = SPECIES_IPC_XION_REMOTE
short_name = "rem_xmf"
@@ -415,10 +554,18 @@
spawn_flags = IS_RESTRICTED
has_organ = list(
- BP_BRAIN = /obj/item/organ/internal/mmi_holder/circuit,
- BP_CELL = /obj/item/organ/internal/cell,
+ BP_BRAIN = /obj/item/organ/internal/machine/posibrain,
+ BP_VOICE_SYNTHESIZER = /obj/item/organ/internal/machine/voice_synthesizer,
+ BP_DIAGNOSTICS_SUITE = /obj/item/organ/internal/machine/internal_diagnostics,
+ BP_HYDRAULICS = /obj/item/organ/internal/machine/hydraulics,
+ BP_ACTUATORS_LEFT = /obj/item/organ/internal/machine/actuators/left,
+ BP_ACTUATORS_RIGHT = /obj/item/organ/internal/machine/actuators/right,
+ BP_COOLING_UNIT = /obj/item/organ/internal/machine/cooling_unit,
+ BP_REACTOR = /obj/item/organ/internal/machine/reactor,
+ BP_ACCESS_PORT = /obj/item/organ/internal/machine/access_port,
+ BP_CELL = /obj/item/organ/internal/machine/power_core,
BP_EYES = /obj/item/organ/internal/eyes/optical_sensor,
- BP_IPCTAG = /obj/item/organ/internal/ipc_tag
+ BP_IPCTAG = /obj/item/organ/internal/machine/ipc_tag
)
/datum/species/machine/industrial/xion/get_light_color(mob/living/carbon/human/H)
@@ -447,11 +594,27 @@
resist_mod = 4 // Not super strong, but still rather strong
appearance_flags = HAS_EYE_COLOR | HAS_UNDERWEAR | HAS_SOCKS
+ flags = IS_IPC | NO_SLIP
examine_color = "#ff00ff"
blurb = "Being a corporation focused primarily on medical sciences and treatments, Zeng-Hu Pharmaceuticals had little interest in the market of synthetics in the beginning (especially considering a good portion of Zeng-Hu employees are Skrellian). However, after seeing the advances in almost all fields of the galactic market after the advent of synthetics, Zeng-Hu set aside some funds for their own robotics department, focused mainly on medical service and even science related operations. Having taken some inspiration from biological life, the chassis has an interesting leg design: digitigrade legs provide the chassis with enhanced speed. A downside to this development was the reduction of metals on the chassis. Most plates covering the sensitive interior electronics are polymer casts to reduce the weight of the unit, resulting in a not-so-durable android."
+ has_organ = list(
+ BP_BRAIN = /obj/item/organ/internal/machine/posibrain,
+ BP_VOICE_SYNTHESIZER = /obj/item/organ/internal/machine/voice_synthesizer,
+ BP_DIAGNOSTICS_SUITE = /obj/item/organ/internal/machine/internal_diagnostics,
+ BP_HYDRAULICS = /obj/item/organ/internal/machine/hydraulics,
+ BP_ACTUATORS_LEFT = /obj/item/organ/internal/machine/actuators/left,
+ BP_ACTUATORS_RIGHT = /obj/item/organ/internal/machine/actuators/right,
+ BP_COOLING_UNIT = /obj/item/organ/internal/machine/cooling_unit/zenghu,
+ BP_REACTOR = /obj/item/organ/internal/machine/reactor,
+ BP_ACCESS_PORT = /obj/item/organ/internal/machine/access_port,
+ BP_CELL = /obj/item/organ/internal/machine/power_core,
+ BP_EYES = /obj/item/organ/internal/eyes/optical_sensor,
+ BP_IPCTAG = /obj/item/organ/internal/machine/ipc_tag
+ )
+
has_limbs = list(
BP_CHEST = list("path" = /obj/item/organ/external/chest/ipc/industrial/zenghu),
BP_GROIN = list("path" = /obj/item/organ/external/groin/ipc/industrial/zenghu),
@@ -467,13 +630,14 @@
)
inherent_verbs = list(
- /mob/living/carbon/human/proc/self_diagnostics,
/mob/living/carbon/human/proc/check_tag
)
maneuvers = list(
/singleton/maneuver/leap/zenghu
)
+ machine_ui_theme = "zenghu"
+
/datum/species/machine/zenghu/get_light_color(mob/living/carbon/human/H)
if (istype(H))
@@ -503,6 +667,22 @@
blurb = "Bishop Cybernetics frames are among the sleeker, flashier frames widely produced for IPCs. This brand-new, high end design has a focus on pioneering energy efficiency without sacrifice, fitting to Bishop's company vision. Cutting-edge technology in power management means this frame can operate longer while running more demanding processing algorithms than most. This extreme push to minimize power draw means this frame can be equipped with all sorts of extra equipment: a hologram for a face, flashing status displays and embedded lights solely meant for show. The one thing holding this frame back from perfection is the same common criticism leveled against almost all Bishop products: the shiny chrome and glass meant to put all of this tech on display means it's exposed and fragile. It's because of Bishop's unrelenting pursuit of vanity in their designs that these frames often suffer from issues with reliability and struggle to safely perform the same work as cheaper, more rugged frames."
+ has_organ = list(
+ BP_BRAIN = /obj/item/organ/internal/machine/posibrain,
+ BP_VOICE_SYNTHESIZER = /obj/item/organ/internal/machine/voice_synthesizer,
+ BP_DIAGNOSTICS_SUITE = /obj/item/organ/internal/machine/internal_diagnostics,
+ BP_HYDRAULICS = /obj/item/organ/internal/machine/hydraulics,
+ BP_ACTUATORS_LEFT = /obj/item/organ/internal/machine/actuators/left,
+ BP_ACTUATORS_RIGHT = /obj/item/organ/internal/machine/actuators/right,
+ BP_COOLING_UNIT = /obj/item/organ/internal/machine/cooling_unit,
+ BP_REACTOR = /obj/item/organ/internal/machine/reactor,
+ BP_ACCESS_PORT = /obj/item/organ/internal/machine/access_port,
+ BP_CELL = /obj/item/organ/internal/machine/power_core,
+ BP_EYES = /obj/item/organ/internal/eyes/optical_sensor,
+ BP_IPCTAG = /obj/item/organ/internal/machine/ipc_tag,
+ BP_WIRELESS_ACCESS = /obj/item/organ/internal/machine/wireless_access
+ )
+
has_limbs = list(
BP_CHEST = list("path" = /obj/item/organ/external/chest/ipc/industrial/bishop),
BP_GROIN = list("path" = /obj/item/organ/external/groin/ipc/industrial/bishop),
@@ -518,10 +698,11 @@
)
inherent_verbs = list(
- /mob/living/carbon/human/proc/self_diagnostics,
/mob/living/carbon/human/proc/check_tag,
/mob/living/carbon/human/proc/change_monitor
- )
+)
+
+ machine_ui_theme = "zenghu"
/datum/species/machine/bishop/get_light_color(mob/living/carbon/human/H)
if (istype(H))
@@ -559,7 +740,6 @@
)
inherent_verbs = list(
- /mob/living/carbon/human/proc/self_diagnostics,
/mob/living/carbon/human/proc/check_tag
)
@@ -571,10 +751,17 @@
spawn_flags = IS_RESTRICTED
has_organ = list(
- BP_BRAIN = /obj/item/organ/internal/mmi_holder/circuit,
- BP_CELL = /obj/item/organ/internal/cell,
+ BP_BRAIN = /obj/item/organ/internal/machine/posibrain,
+ BP_VOICE_SYNTHESIZER = /obj/item/organ/internal/machine/voice_synthesizer,
+ BP_DIAGNOSTICS_SUITE = /obj/item/organ/internal/machine/internal_diagnostics,
+ BP_HYDRAULICS = /obj/item/organ/internal/machine/hydraulics,
+ BP_ACTUATORS_LEFT = /obj/item/organ/internal/machine/actuators/left,
+ BP_ACTUATORS_RIGHT = /obj/item/organ/internal/machine/actuators/right,
+ BP_COOLING_UNIT = /obj/item/organ/internal/machine/cooling_unit,
+ BP_REACTOR = /obj/item/organ/internal/machine/reactor,
+ BP_CELL = /obj/item/organ/internal/machine/power_core,
BP_EYES = /obj/item/organ/internal/eyes/optical_sensor,
- BP_IPCTAG = /obj/item/organ/internal/ipc_tag
+ BP_IPCTAG = /obj/item/organ/internal/machine/ipc_tag
)
/datum/species/machine/unbranded/get_light_color(mob/living/carbon/human/H)
diff --git a/code/modules/mob/living/carbon/human/species/station/unathi/unathi.dm b/code/modules/mob/living/carbon/human/species/station/unathi/unathi.dm
index 633830c2473..717817d46e4 100644
--- a/code/modules/mob/living/carbon/human/species/station/unathi/unathi.dm
+++ b/code/modules/mob/living/carbon/human/species/station/unathi/unathi.dm
@@ -61,6 +61,7 @@
rarity_value = 3
break_cuffs = TRUE
mob_size = 10
+ mob_weight = MOB_WEIGHT_HEAVY
climb_coeff = 1.35
blurb = "A heavily reptillian species, Unathi (or 'Sinta as they call themselves) hail from the Uuosa-Eso \
diff --git a/code/modules/mob/living/carbon/human/species/station/vaurca/vaurca_subspecies.dm b/code/modules/mob/living/carbon/human/species/station/vaurca/vaurca_subspecies.dm
index a809e42d2eb..078ac65c2e7 100644
--- a/code/modules/mob/living/carbon/human/species/station/vaurca/vaurca_subspecies.dm
+++ b/code/modules/mob/living/carbon/human/species/station/vaurca/vaurca_subspecies.dm
@@ -264,6 +264,8 @@
total_health = 200
break_cuffs = TRUE
mob_size = 30
+ mob_weight = MOB_WEIGHT_SUPERHEAVY
+ mob_strength = MOB_STRENGTH_VERY_STRONG
speech_sounds = list('sound/voice/hiss1.ogg','sound/voice/hiss2.ogg','sound/voice/hiss3.ogg','sound/voice/hiss4.ogg')
speech_chance = 100
@@ -372,6 +374,8 @@
resist_mod = 14 //also very strong
mob_size = 28
+ mob_weight = MOB_WEIGHT_SUPERHEAVY
+ mob_strength = MOB_STRENGTH_STRONG
taste_sensitivity = TASTE_DULL
blurb = {"Type E Vaurca, otherwise known as the Bulwarks, are a new bodyform derived from the worker caste in a collaboration by the C'thur and Nralakk scientists. Originally only the C'thur had access to these behemoths, but after a short amount of time, the bodyform started appearing in the ranks of the Zo'ra and K'lax as well, causing an even more strained relationship between the Hives.
Similar to Workers, Bulwarks are generally passive, and prefer to flee a fight rather than resist. Though due to their speed, they may still choose to defend themselves should they be unable to properly escape a battle. The main exception to this is when another Vaurca is in danger. When this occurs, they tend to put themselves in between the attacker and the Vaurca, acting as a shield of sorts. They won't go out of their way to take down the attacker, but will ensure the others get away safely.
diff --git a/code/modules/mob/living/silicon/robot/analyzer.dm b/code/modules/mob/living/silicon/robot/analyzer.dm
index 9539aca64b3..e76f45073fa 100644
--- a/code/modules/mob/living/silicon/robot/analyzer.dm
+++ b/code/modules/mob/living/silicon/robot/analyzer.dm
@@ -20,24 +20,14 @@
robotic_analyze_mob(target_mob, user)
add_fingerprint(user)
-
-/proc/robotic_analyze_mob (var/mob/living/M, var/mob/living/user, var/just_scan = FALSE)
+/proc/robotic_analyze_mob(var/mob/living/M, var/mob/living/user, var/just_scan = FALSE)
if(!just_scan)
- if((user.is_clumsy() || (user.mutations & DUMB)) && prob(50))
- to_chat(user, SPAN_WARNING("You try to analyze the floor's vitals!"))
- user.visible_message(SPAN_WARNING("\The [user] has analyzed the floor's vitals!"))
- to_chat(user, SPAN_NOTICE("Analyzing Results for The floor:"))
- to_chat(user, SPAN_NOTICE("Overall Status: Healthy"))
- to_chat(user, SPAN_NOTICE("Damage Specifics: [0]-[0]-[0]-[0]"))
- to_chat(user, SPAN_NOTICE("Key: Suffocation/Toxin/Burns/Brute"))
- to_chat(user, SPAN_NOTICE("Body Temperature: ???"))
- return
user.visible_message(SPAN_NOTICE("\The [user] has analyzed \the [M]'s components."), SPAN_NOTICE("You have analyzed \the [M]'s components."))
var/scan_type
- if(istype(M, /mob/living/silicon/robot))
+ if(isrobot(M))
scan_type = "robot"
- else if(istype(M, /mob/living/carbon/human))
+ else if(ishuman(M))
scan_type = "prosthetics"
else
to_chat(user, SPAN_WARNING("You can't analyze non-robotic things!"))
@@ -45,68 +35,102 @@
switch(scan_type)
if("robot")
- var/BU = M.getFireLoss() > 50 ? "[M.getFireLoss()]" : M.getFireLoss()
- var/BR = M.getBruteLoss() > 50 ? "[M.getBruteLoss()]" : M.getBruteLoss()
-
- to_chat(user, SPAN_NOTICE("Analyzing Results for [M]:"))
- to_chat(user, SPAN_NOTICE("Overall Status: [M.stat > 1 ? "fully disabled" : "[M.health - M.getHalLoss()]% functional"]"))
- to_chat(user, "Key: Electronics/Brute")
- to_chat(user, "Damage Specifics: [BU] - [BR]")
- if(M.tod && M.stat == DEAD)
- to_chat(user, SPAN_NOTICE("Time of Disable: [M.tod]"))
- var/mob/living/silicon/robot/H = M
- var/list/damaged = H.get_damaged_components(1, 1, 1)
- to_chat(user, SPAN_NOTICE("Localized Damage:"))
- if(length(damaged) > 0)
- for(var/datum/robot_component/org in damaged)
- user.show_message(SPAN_NOTICE("\t [capitalize(org.name)]: [(org.installed == -1) ? SPAN_WARNING("DESTROYED") :""]\
- [(org.electronics_damage > 0) ? "[org.electronics_damage]" : 0] - [(org.brute_damage > 0) ? SPAN_WARNING("[org.brute_damage]") :0] - \
- [(org.toggled) ? "Toggled ON" : SPAN_WARNING("Toggled OFF")] - \
- [(org.powered) ? "Power ON" : SPAN_WARNING("Power OFF")]"),1)
-
- else
- to_chat(user, SPAN_NOTICE("Components are OK."))
- if(H.emagged && prob(5))
- to_chat(user, SPAN_WARNING("ERROR: INTERNAL SYSTEMS COMPROMISED"))
- to_chat(user, SPAN_NOTICE("Operating Temperature: [M.bodytemperature-T0C]°C ([M.bodytemperature*1.8-459.67]°F)"))
+ robot_scan(user, M)
if("prosthetics")
- var/mob/living/carbon/human/H = M
- to_chat(user, SPAN_NOTICE("Analyzing Results for \the [H]:"))
- to_chat(user, "Key: Electronics/Brute")
- var/obj/item/organ/internal/cell/IC = H.internal_organs_by_name[BP_CELL]
- if(IC)
- to_chat(user, SPAN_NOTICE("Cell charge: [IC.percent()] %"))
- else
- to_chat(user, SPAN_NOTICE("Cell charge: ERROR - Cell not present"))
- to_chat(user, SPAN_NOTICE("External prosthetics:"))
- var/organ_found
- if(length(H.internal_organs))
- for(var/obj/item/organ/external/E in H.organs)
- if(!(E.status & (ORGAN_ROBOT || ORGAN_ASSISTED)))
- continue
- organ_found = TRUE
- to_chat(user, "[E.name]: [E.brute_dam] [E.burn_dam]")
- if(!organ_found)
- to_chat(user, SPAN_NOTICE("No prosthetics located."))
- to_chat(user, "
")
- to_chat(user, SPAN_NOTICE("Internal prosthetics:"))
- organ_found = FALSE
- if(length(H.internal_organs))
- var/obj/item/organ/external/head = H.get_organ(BP_HEAD)
- var/show_tag = FALSE
- if(head?.open == 3) // Hatch open
- show_tag = TRUE
- for(var/obj/item/organ/O in H.internal_organs)
- if(!(O.status & (ORGAN_ROBOT || ORGAN_ASSISTED)))
- continue
- if(!show_tag && istype(O, /obj/item/organ/internal/ipc_tag))
- continue
- organ_found = TRUE
- to_chat(user, "[O.name]: [O.damage]")
- to_chat(user, "[O.desc]")
- if(!organ_found)
- to_chat(user, SPAN_NOTICE("No prosthetics located."))
+ prosthetics_scan(user, M)
+/proc/robot_scan(mob/user, mob/living/silicon/robot/M)
+ var/BU = M.getFireLoss() > 50 ? "[get_robot_severity(M.getFireLoss())]" : get_robot_severity(M.getFireLoss())
+ var/BR = M.getBruteLoss() > 50 ? "[get_robot_severity(M.getBruteLoss())]" : get_robot_severity(M.getBruteLoss())
+
+ to_chat(user, SPAN_NOTICE("Analyzing Results for [M]:"))
+ to_chat(user, SPAN_NOTICE("Overall Status: [M.stat > 1 ? "fully disabled" : "[M.health - M.getHalLoss()]% functional"]"))
+ to_chat(user, "Key: Electronics/Physical Damage")
+ to_chat(user, "Damage Specifics: [BU] - [BR]")
+ if(M.tod && M.stat == DEAD)
+ to_chat(user, SPAN_NOTICE("Time of Disable: [M.tod]"))
+ var/mob/living/silicon/robot/H = M
+ var/list/damaged = H.get_damaged_components(1, 1, 1)
+ to_chat(user, SPAN_NOTICE("Localized Damage:"))
+ if(length(damaged) > 0)
+ for(var/datum/robot_component/org in damaged)
+ user.show_message(SPAN_NOTICE("\t [capitalize(org.name)]: [(org.installed == -1) ? SPAN_WARNING("DESTROYED") :""]\
+ [(org.electronics_damage > 0) ? "[org.electronics_damage]" : 0] - [(org.brute_damage > 0) ? SPAN_WARNING("[org.brute_damage]") :0] - \
+ [(org.toggled) ? "Toggled ON" : SPAN_WARNING("Toggled OFF")] - \
+ [(org.powered) ? "Power ON" : SPAN_WARNING("Power OFF")]"),1)
+
+ else
+ to_chat(user, SPAN_NOTICE("Components are OK."))
+ if(H.emagged && prob(5))
+ to_chat(user, SPAN_WARNING("ERROR: INTERNAL SYSTEMS COMPROMISED"))
+ to_chat(user, SPAN_NOTICE("Operating Temperature: [M.bodytemperature-T0C]°C ([M.bodytemperature*1.8-459.67]°F)"))
+
+/proc/prosthetics_scan(mob/user, mob/living/carbon/human/H)
+ to_chat(user, SPAN_NOTICE("Analyzing Results for \the [H]:"))
+ if(H.stat == DEAD)
+ to_chat(user, SPAN_DANGER("No neural coherence detected."))
+ to_chat(user, "Key: Electronics/Wiring")
+ var/obj/item/organ/internal/machine/power_core/IC = H.internal_organs_by_name[BP_CELL]
+ if(IC)
+ to_chat(user, SPAN_NOTICE("Cell charge: [IC.percent()] %"))
+ else
+ to_chat(user, SPAN_NOTICE("Cell charge: ERROR - Cell not present"))
+ to_chat(user, SPAN_NOTICE("External prosthetics:"))
+ var/organ_found
+ if(length(H.internal_organs))
+ for(var/obj/item/organ/external/E in H.organs)
+ if(!(E.status & (ORGAN_ROBOT || ORGAN_ASSISTED)))
+ continue
+ organ_found = TRUE
+ to_chat(user, "[E.name]: [get_robot_severity(E.brute_dam)] [get_robot_severity(E.burn_dam)]")
+ if(!organ_found)
+ to_chat(user, SPAN_NOTICE("No prosthetics located."))
+ to_chat(user, "
")
+ to_chat(user, SPAN_NOTICE("Internal prosthetics:"))
+ organ_found = FALSE
+ if(length(H.internal_organs))
+ var/obj/item/organ/external/head = H.get_organ(BP_HEAD)
+ var/show_tag = FALSE
+ if(head?.open == 3) // Hatch open
+ show_tag = TRUE
+ for(var/obj/item/organ/O in H.internal_organs)
+ if(!(O.status & (ORGAN_ROBOT || ORGAN_ASSISTED)))
+ continue
+ if(!show_tag && istype(O, /obj/item/organ/internal/machine/ipc_tag))
+ continue
+ organ_found = TRUE
+ var/found_damage = FALSE
+ to_chat(user, SPAN_NOTICE(SPAN_BOLD("[O.name]")))
+ if(O.damage)
+ to_chat(user, SPAN_WARNING("Core damage detected."))
+ found_damage = TRUE
+ if(istype(O, /obj/item/organ/internal/machine))
+ var/obj/item/organ/internal/machine/machine_organ = O
+ if(machine_organ.get_integrity() < 100)
+ to_chat(user, SPAN_WARNING("[machine_organ.name]: Integrity damage detected."))
+ found_damage = TRUE
+ if(!found_damage)
+ to_chat(user, SPAN_NOTICE("No damage detected."))
+
+ if(!organ_found)
+ to_chat(user, SPAN_NOTICE("No prosthetics located."))
+
+/proc/get_robot_severity(amount, var/uppercase = FALSE)
+ . = "undamaged"
+ if(!amount)
+ . = "undamaged"
+ else if(amount > 100)
+ . = "destroyed"
+ else if(amount > 75)
+ . = "falling apart"
+ else if(amount > 50)
+ . = "heavily compromised"
+ else if(amount > 25)
+ . = "problematic"
+ else if(amount > 10)
+ . = "fine"
+ else
+ . = "minor"
/obj/item/device/robotanalyzer/augment
name = "retractable cyborg analyzer"
diff --git a/code/modules/mob/living/silicon/robot/items/robot_parts.dm b/code/modules/mob/living/silicon/robot/items/robot_parts.dm
index cb1d3ce2ef6..b20a07af590 100644
--- a/code/modules/mob/living/silicon/robot/items/robot_parts.dm
+++ b/code/modules/mob/living/silicon/robot/items/robot_parts.dm
@@ -255,7 +255,7 @@
var/mob/living/carbon/human/new_shell = new(get_turf(src), chest.linked_frame)
// replace the IPC's microbattery cell with the one that was in the robot chest
- var/obj/item/organ/internal/cell/C = new_shell.internal_organs_by_name[BP_CELL]
+ var/obj/item/organ/internal/machine/power_core/C = new_shell.internal_organs_by_name[BP_CELL]
C.replace_cell(chest.cell)
//so people won't mess around with the chassis until it is deleted
forceMove(new_shell)
@@ -268,7 +268,7 @@
newname = L.get_random_name()
new_shell.real_name = newname
new_shell.name = new_shell.real_name
- var/obj/item/organ/internal/mmi_holder/posibrain/P = new_shell.internal_organs_by_name[BP_BRAIN]
+ var/obj/item/organ/internal/machine/posibrain/P = new_shell.internal_organs_by_name[BP_BRAIN]
P.setup_brain()
new_shell.change_appearance(APPEARANCE_PLASTICSURGERY, new_shell)
qdel(src)
diff --git a/code/modules/mob/living/simple_animal/hostile/giant_spider.dm b/code/modules/mob/living/simple_animal/hostile/giant_spider.dm
index aa3469a94f6..d21ac777071 100644
--- a/code/modules/mob/living/simple_animal/hostile/giant_spider.dm
+++ b/code/modules/mob/living/simple_animal/hostile/giant_spider.dm
@@ -207,7 +207,7 @@
if(ishuman(hit_mob))
var/mob/living/carbon/human/H = hit_mob
if(prob(20))
- var/obj/item/organ/internal/cell/cell_holder = locate() in H.internal_organs
+ var/obj/item/organ/internal/machine/power_core/cell_holder = locate() in H.internal_organs
if(cell_holder)
var/obj/item/cell/C = cell_holder.cell
if(C)
diff --git a/code/modules/mob/living/simple_animal/hostile/ipc_zombie.dm b/code/modules/mob/living/simple_animal/hostile/ipc_zombie.dm
index ec911058fc0..3bdb2ad8b00 100644
--- a/code/modules/mob/living/simple_animal/hostile/ipc_zombie.dm
+++ b/code/modules/mob/living/simple_animal/hostile/ipc_zombie.dm
@@ -91,7 +91,7 @@
species = SPECIES_IPC
/obj/effect/landmark/corpse/ipc_zombie/do_extra_customization(mob/living/carbon/human/M)
- var/obj/item/organ/internal/ipc_tag/tag = M.internal_organs_by_name[BP_IPCTAG]
+ var/obj/item/organ/internal/machine/ipc_tag/tag = M.internal_organs_by_name[BP_IPCTAG]
if(istype(tag))
tag.serial_number = uppertext(dd_limittext(md5(M.real_name), 12))
tag.ownership_info = IPC_OWNERSHIP_SELF
diff --git a/code/modules/mob/mob_defines.dm b/code/modules/mob/mob_defines.dm
index b7ada2c1f4a..d0fba847767 100644
--- a/code/modules/mob/mob_defines.dm
+++ b/code/modules/mob/mob_defines.dm
@@ -55,6 +55,8 @@
var/atom/movable/screen/energy/energy_display = null
var/atom/movable/screen/instability/instability_display = null
var/atom/movable/screen/up_hint = null
+ /// The IPC version of the fullscreen pain texture.
+ var/atom/movable/screen/fullscreen/robot_pain
//spells hud icons - this interacts with add_spell and remove_spell
var/list/atom/movable/screen/movable/spell_master/spell_masters = null
@@ -252,11 +254,17 @@
var/list/item_verbs = list()
var/list/shouldnt_see = list() //typecache of objects that this mob shouldn't see in the stat panel. this silliness is needed because of AI alt+click and cult blood runes
- var/list/active_genes=list()
+ var/list/active_genes = list()
+
var/mob_size = MOB_MEDIUM
/// The icon size width of the mob. Used for langchat resizing.
var/icon_size = 32
+ /// The weight of the mob. Affects if the mob can be easily lifted or not. Separate from size, as some mobs may be big but not particularly heavy.
+ var/mob_weight = MOB_WEIGHT_LIGHT
+ /// The strength of the mob. Affects what kind of mobs can be thrown or carried. By default, does not give any buff.
+ var/mob_strength = MOB_STRENGTH_NORMAL
+
var/list/progressbars
var/frozen = FALSE //related to wizard statues, if set to true, life won't process
diff --git a/code/modules/mob/mob_grab.dm b/code/modules/mob/mob_grab.dm
index b8f7f9a2e58..f13cd49026f 100644
--- a/code/modules/mob/mob_grab.dm
+++ b/code/modules/mob/mob_grab.dm
@@ -443,7 +443,7 @@
to_chat(H, SPAN_WARNING("You can only fireman carry humanoids!"))
return
var/mob/living/carbon/human/affected_human = affecting
- if(affected_human.species.mob_size > 25)
+ if(affected_human.mob_weight > H.get_mob_strength())
to_chat(H, SPAN_WARNING("\The [affected_human] is way too big to fireman carry!"))
return
if(state < GRAB_AGGRESSIVE)
diff --git a/code/modules/mob/mob_helpers.dm b/code/modules/mob/mob_helpers.dm
index 77ec8e1698e..c03b0629dfe 100644
--- a/code/modules/mob/mob_helpers.dm
+++ b/code/modules/mob/mob_helpers.dm
@@ -1357,3 +1357,7 @@ GLOBAL_LIST_INIT(organ_rel_size, list(
message_notifications.Cut(1, 2)
message_notifications[key_check] = world.time + next_message_time
+
+/// Gets a mob's strength.
+/mob/proc/get_mob_strength()
+ return mob_weight + mob_strength
diff --git a/code/modules/modular_computers/computers/modular_computer/hardware.dm b/code/modules/modular_computers/computers/modular_computer/hardware.dm
index d69cca4eeaf..7918541f68d 100644
--- a/code/modules/modular_computers/computers/modular_computer/hardware.dm
+++ b/code/modules/modular_computers/computers/modular_computer/hardware.dm
@@ -78,6 +78,18 @@
return
found = TRUE
flashlight = H
+ else if(istype(H, /obj/item/computer_hardware/universal_port))
+ if(universal_port)
+ to_chat(user, SPAN_WARNING("\The [src]'s port slot is already occupied by \the [universal_port]."))
+ return
+ found = TRUE
+ universal_port = H
+ else if(istype(H, /obj/item/computer_hardware/access_cable_dongle))
+ if(access_cable_dongle)
+ to_chat(user, SPAN_WARNING("\The [src]'s port slot is already occupied by \the [access_cable_dongle]."))
+ return
+ found = TRUE
+ access_cable_dongle = H
if(found)
to_chat(user, SPAN_NOTICE("You install \the [H] into \the [src]."))
H.parent_computer = src
@@ -128,6 +140,12 @@
personal_ai.pai.parent_computer = null
update_icon()
personal_ai = null
+ else if(universal_port == H)
+ universal_port = null
+ found = TRUE
+ else if(access_cable_dongle == H)
+ access_cable_dongle = null
+ found = TRUE
if(found)
if(user)
@@ -165,6 +183,10 @@
return tesla_link
if(flashlight && initial(flashlight.name) == name)
return flashlight
+ if(universal_port && initial(universal_port.name) == name)
+ return universal_port
+ if(access_cable_dongle && initial(access_cable_dongle.name) == name)
+ return access_cable_dongle
return null
// Returns list of all components
@@ -192,4 +214,8 @@
all_components.Add(tesla_link)
if(flashlight)
all_components.Add(flashlight)
+ if(universal_port)
+ all_components.Add(universal_port)
+ if(access_cable_dongle)
+ all_components.Add(access_cable_dongle)
return all_components
diff --git a/code/modules/modular_computers/computers/modular_computer/interaction.dm b/code/modules/modular_computers/computers/modular_computer/interaction.dm
index 3687434d7cb..4c7ad315084 100644
--- a/code/modules/modular_computers/computers/modular_computer/interaction.dm
+++ b/code/modules/modular_computers/computers/modular_computer/interaction.dm
@@ -189,6 +189,14 @@
if(response == "Yes")
turn_on(user)
+/obj/item/modular_computer/attack_ranged(mob/user, params)
+ . = ..()
+ if(ishuman(user) && isipc(user))
+ var/mob/living/carbon/human/robot = user
+ var/obj/item/organ/internal/machine/wireless_access/wireless_access_point = robot.internal_organs_by_name[BP_WIRELESS_ACCESS]
+ if(wireless_access_point?.access_terminal(src))
+ attack_hand(user)
+
/obj/item/modular_computer/attack_hand(var/mob/user)
if(anchored)
return attack_self(user)
@@ -321,6 +329,20 @@
return TRUE
uninstall_component(user, H)
return TRUE
+
+ if(istype(attacking_item, /obj/item/access_cable))
+ var/obj/item/access_cable/access_cable = attacking_item
+ if(!universal_port)
+ to_chat(user, SPAN_WARNING("There isn't a port to slot \the [access_cable] into!"))
+ return
+
+ if(universal_port.access_cable)
+ to_chat(user, SPAN_WARNING("There's already a cable in the universal port!"))
+ return
+
+ if(do_after(user, 1 SECONDS))
+ visible_message(SPAN_NOTICE("[user] slots \the [access_cable] into \the [src]."))
+ universal_port.insert_cable(access_cable, user)
return ..()
/obj/item/modular_computer/mouse_drop_dragged(atom/over, mob/user, src_location, over_location, params)
diff --git a/code/modules/modular_computers/computers/modular_computer/ui.dm b/code/modules/modular_computers/computers/modular_computer/ui.dm
index ab45ad8c13d..7652f8dc84e 100644
--- a/code/modules/modular_computers/computers/modular_computer/ui.dm
+++ b/code/modules/modular_computers/computers/modular_computer/ui.dm
@@ -47,6 +47,8 @@
data["PC_stationdate"] = "[time2text(world.realtime, "DDD, Month DD")], [GLOB.game_year]"
data["PC_showexitprogram"] = !!active_program
data["PC_haslight"] = !!flashlight
+ data["PC_hascable"] = !!access_cable_dongle
+ data["PC_cableout"] = access_cable_dongle?.access_cable?.loc == access_cable_dongle ? FALSE : TRUE
data["PC_lighton"] = flashlight?.enabled ? TRUE : FALSE
data["PC_programheaders"] = list()
if(idle_threads.len)
@@ -146,6 +148,13 @@
if(flashlight)
flashlight.toggle()
. = TRUE
+ if(action == "PC_takecable")
+ if(access_cable_dongle && access_cable_dongle.access_cable)
+ if(access_cable_dongle.access_cable.loc == access_cable_dongle)
+ access_cable_dongle.take_cable(usr)
+ else
+ access_cable_dongle.access_cable.retract()
+ . = TRUE
if(action == "PC_shutdown")
shutdown_computer()
return TRUE
@@ -190,6 +199,11 @@
update_icon()
/obj/item/modular_computer/ui_status(mob/user, datum/ui_state/state)
+ if(universal_port?.access_cable)
+ if(istype(universal_port.access_cable.source, /obj/item/organ/internal/machine/access_port))
+ var/obj/item/organ/internal/machine/access_port/port = universal_port.access_cable.source
+ if(user == port.owner)
+ return UI_INTERACTIVE
. = ..()
if(. < UI_INTERACTIVE)
if(user.machine)
diff --git a/code/modules/modular_computers/computers/modular_computer/variables.dm b/code/modules/modular_computers/computers/modular_computer/variables.dm
index 4f14f93c78d..60144420618 100644
--- a/code/modules/modular_computers/computers/modular_computer/variables.dm
+++ b/code/modules/modular_computers/computers/modular_computer/variables.dm
@@ -125,6 +125,10 @@
var/obj/item/computer_hardware/tesla_link/tesla_link
/// Personal AI, can control the device via a verb when installed
var/obj/item/device/paicard/personal_ai
+ /// The universal port. Allows connection from IPCs through access cables.
+ var/obj/item/computer_hardware/universal_port/universal_port
+ /// The access cable dongle. Allows the computer to have its very own access cable.
+ var/obj/item/computer_hardware/access_cable_dongle/access_cable_dongle
var/obj/item/computer_hardware/flashlight/flashlight
var/listener/listener
diff --git a/code/modules/modular_computers/computers/subtypes/preset_console.dm b/code/modules/modular_computers/computers/subtypes/preset_console.dm
index 7f0160a07ee..6f589a279ce 100644
--- a/code/modules/modular_computers/computers/subtypes/preset_console.dm
+++ b/code/modules/modular_computers/computers/subtypes/preset_console.dm
@@ -15,6 +15,7 @@
hard_drive = new /obj/item/computer_hardware/hard_drive/super(src)
network_card = new /obj/item/computer_hardware/network_card/wired(src)
nano_printer = new /obj/item/computer_hardware/nano_printer(src)
+ universal_port = new /obj/item/computer_hardware/universal_port(src)
/obj/item/modular_computer/console/preset/install_default_programs()
..()
@@ -140,6 +141,7 @@
/obj/item/modular_computer/console/preset/supply/machinist/install_default_hardware()
..()
ai_slot = new /obj/item/computer_hardware/ai_slot(src)
+ access_cable_dongle = new /obj/item/computer_hardware/access_cable_dongle(src)
// ERT
/obj/item/modular_computer/console/preset/ert/install_default_hardware()
diff --git a/code/modules/modular_computers/computers/subtypes/preset_laptop.dm b/code/modules/modular_computers/computers/subtypes/preset_laptop.dm
index f07e1c471e8..7ad8f619c7c 100644
--- a/code/modules/modular_computers/computers/subtypes/preset_laptop.dm
+++ b/code/modules/modular_computers/computers/subtypes/preset_laptop.dm
@@ -18,6 +18,7 @@
nano_printer.max_paper = 10
nano_printer.stored_paper = 5
tesla_link = new /obj/item/computer_hardware/tesla_link/charging_cable(src)
+ universal_port = new /obj/item/computer_hardware/universal_port(src)
// the laptop in the modular computer loadout
/obj/item/modular_computer/laptop/preset/loadout/install_default_hardware()
@@ -122,6 +123,15 @@
desc = "A portable computer belonging to the operation's manager."
_app_preset_type = /datum/modular_computer_app_presets/supply/om
+/obj/item/modular_computer/laptop/preset/supply/robotics
+ name = "robotics laptop"
+ desc = "A portable computer with support for specialized robotics software."
+ _app_preset_type = /datum/modular_computer_app_presets/supply/machinist
+
+/obj/item/modular_computer/laptop/preset/supply/robotics/install_default_hardware()
+ ..()
+ access_cable_dongle = new /obj/item/computer_hardware/access_cable_dongle(src)
+
// Representative
/obj/item/modular_computer/laptop/preset/representative
name = "representative's laptop"
diff --git a/code/modules/modular_computers/computers/subtypes/preset_telescreen.dm b/code/modules/modular_computers/computers/subtypes/preset_telescreen.dm
index 488aa5dffa9..b38049f27e6 100644
--- a/code/modules/modular_computers/computers/subtypes/preset_telescreen.dm
+++ b/code/modules/modular_computers/computers/subtypes/preset_telescreen.dm
@@ -8,6 +8,7 @@
tesla_link = new/obj/item/computer_hardware/tesla_link(src)
hard_drive = new/obj/item/computer_hardware/hard_drive(src)
network_card = new/obj/item/computer_hardware/network_card(src)
+ universal_port = new /obj/item/computer_hardware/universal_port(src)
/obj/item/modular_computer/telescreen/preset/generic
_app_preset_type = /datum/modular_computer_app_presets/wall_generic
diff --git a/code/modules/modular_computers/file_system/programs/app_presets_crew.dm b/code/modules/modular_computers/file_system/programs/app_presets_crew.dm
index 76338dc35b1..47e3e199183 100644
--- a/code/modules/modular_computers/file_system/programs/app_presets_crew.dm
+++ b/code/modules/modular_computers/file_system/programs/app_presets_crew.dm
@@ -83,7 +83,6 @@
. = ..()
program_list += list(/datum/computer_file/program/comm, /datum/computer_file/program/records/employment)
-
/*##########################
BRIDGE CREW PRESETS
##########################*/
@@ -273,7 +272,7 @@
/datum/modular_computer_app_presets/supply/machinist/New()
. = ..()
- program_list += list(/datum/computer_file/program/records/medical, /datum/computer_file/program/scanner/science)
+ program_list += list(/datum/computer_file/program/records/medical, /datum/computer_file/program/scanner/science, /datum/computer_file/program/robotics)
//Machinist is the bastard child of supply/operation, it doesn't have access to shit essentially
program_list -= list(/datum/computer_file/program/civilian/cargocontrol, /datum/computer_file/program/civilian/cargodelivery, /datum/computer_file/program/away_manifest)
diff --git a/code/modules/modular_computers/file_system/programs/research/robotics.dm b/code/modules/modular_computers/file_system/programs/research/robotics.dm
new file mode 100644
index 00000000000..a664dc84d83
--- /dev/null
+++ b/code/modules/modular_computers/file_system/programs/research/robotics.dm
@@ -0,0 +1,101 @@
+/datum/computer_file/program/robotics
+ filename = "robotics"
+ filedesc = "Robotics Interface"
+ program_icon_state = "ai-fixer-empty"
+ program_key_icon_state = "teal_key"
+ extended_desc = "A program made to interface with positronics."
+ size = 14
+ requires_access_to_run = PROGRAM_ACCESS_LIST_ONE
+ required_access_run = list(ACCESS_RESEARCH, ACCESS_ROBOTICS)
+ required_access_download = list(ACCESS_RESEARCH, ACCESS_ROBOTICS)
+ available_on_ntnet = FALSE
+ tgui_id = "RoboticsComputer"
+ /// The diagnostics module associated with this program.
+ var/datum/tgui_module/ipc_diagnostic/diagnostic
+
+/datum/computer_file/program/robotics/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state)
+ . = ..()
+ if(!ishuman(ui.user))
+ return
+
+ var/mob/living/carbon/human/user = ui.user
+ if(action == "run_diagnostics")
+ if(computer.access_cable_dongle && computer.access_cable_dongle.access_cable)
+ var/mob/living/carbon/human/synthetic = computer.access_cable_dongle.access_cable.target
+ if(istype(user) && istype(synthetic))
+ ui.user.visible_message(SPAN_NOTICE("[user] begins running a diagnostic scan..."))
+ if(do_after(user, 3 SECONDS))
+ diagnostic = new(user, synthetic)
+ return TRUE
+
+ if(action == "open_diagnostic")
+ if(computer.access_cable_dongle && computer.access_cable_dongle.access_cable)
+
+ if(istype(diagnostic))
+ if(diagnostic.patient != computer.access_cable_dongle.access_cable.target)
+ to_chat(user, SPAN_WARNING("This diagnostic is no longer valid and has been deleted."))
+ qdel(diagnostic)
+ return TRUE
+
+ var/mob/living/carbon/human/synthetic = computer.access_cable_dongle.access_cable.target
+ if(istype(user) && istype(synthetic))
+ diagnostic.ui_interact(user)
+ return TRUE
+
+/datum/computer_file/program/robotics/ui_data(mob/user)
+ var/list/data = list()
+ var/mob/living/carbon/human/ipc
+ if(computer.access_cable_dongle && computer.access_cable_dongle.access_cable)
+ var/obj/item/organ/internal/machine/access_port/port = computer.access_cable_dongle.access_cable.target
+ if(istype(port))
+ ipc = port.owner
+
+ if(isipc(ipc))
+ var/datum/species/machine/machine_species = ipc.species // need to manually set to ipc species because of machine_ui_theme
+ data["patient_name"] = ipc.real_name
+ data["temp"] = round(convert_k2c(ipc.bodytemperature))
+ data["machine_ui_theme"] = machine_species.machine_ui_theme
+
+ data["organs"] = list()
+ for(var/obj/item/organ/internal/organ in ipc.internal_organs)
+ var/list/organ_data = list()
+
+ if(istype(organ, /obj/item/organ/internal/machine))
+ var/obj/item/organ/internal/machine/machine_organ = organ
+ if(!machine_organ.diagnostics_suite_visible)
+ continue
+
+ organ_data["wiring_status"] = machine_organ.wiring.get_status()
+ organ_data["plating_status"] = machine_organ.plating.get_status()
+ organ_data["electronics_status"] = machine_organ.electronics.get_status()
+ organ_data["diagnostics_info"] = machine_organ.get_diagnostics_info()
+
+ organ_data["name"] = organ.name
+ organ_data["desc"] = organ.desc
+ organ_data["damage"] = organ.damage
+ organ_data["max_damage"] = organ.max_damage
+
+ data["organs"] += list(organ_data)
+
+ data["robolimb_self_repair_cap"] = ROBOLIMB_SELF_REPAIR_CAP
+ data["limbs"] = list()
+ for(var/obj/item/organ/external/limb in ipc.organs)
+ if(limb.brute_dam || limb.burn_dam)
+ data["limbs"] += list(list("name" = limb.name, "brute_damage" = limb.brute_dam, "burn_damage" = limb.burn_dam, "max_damage" = limb.max_damage))
+
+ var/obj/item/organ/internal/machine/power_core/C = ipc.internal_organs_by_name[BP_CELL]
+ if(C)
+ data["charge_percent"] = C.percent()
+
+ var/datum/component/synthetic_endoskeleton/endoskeleton = ipc.GetComponent(/datum/component/synthetic_endoskeleton)
+ if(istype(endoskeleton))
+ data["endoskeleton_damage"] = endoskeleton.damage
+ data["endoskeleton_max_damage"] = endoskeleton.max_damage
+
+ var/datum/component/armor/synthetic/synth_armor = ipc.GetComponent(/datum/component/armor/synthetic)
+ if(istype(synth_armor))
+ data["armor_data"] = list()
+ var/list/armor_damage = synth_armor.get_visible_damage()
+ for(var/key in armor_damage)
+ data["armor_data"] += list(list("key" = key, "status" = armor_damage[key]))
+ return data
diff --git a/code/modules/modular_computers/hardware/access_cable_dongle.dm b/code/modules/modular_computers/hardware/access_cable_dongle.dm
new file mode 100644
index 00000000000..a6183fb7a1a
--- /dev/null
+++ b/code/modules/modular_computers/hardware/access_cable_dongle.dm
@@ -0,0 +1,44 @@
+/obj/item/computer_hardware/access_cable_dongle
+ name = "access cable dongle"
+ desc = "A dongle from which a positronic-compatible cable can be extended."
+ critical = FALSE
+ icon_state = "aislot"
+ hardware_size = 3
+
+ /// The access cable currently inserted into this slot.
+ var/obj/item/access_cable/access_cable
+
+/obj/item/computer_hardware/access_cable_dongle/Initialize()
+ . = ..()
+ access_cable = new(src, src, parent_computer)
+
+/obj/item/computer_hardware/access_cable_dongle/Destroy()
+ if(access_cable)
+ access_cable.retract()
+ QDEL_NULL(access_cable)
+ return ..()
+
+/obj/item/computer_hardware/access_cable_dongle/proc/take_cable(mob/user)
+ if(!ishuman(user))
+ return
+
+ var/mob/living/carbon/human/H = user
+
+ if(use_check_and_message(H))
+ return
+
+ if(!access_cable)
+ to_chat(H, SPAN_WARNING("This table does not have an access cable anymore!"))
+ return
+
+ if(H.get_active_hand())
+ to_chat(H, SPAN_WARNING("You need a free hand to retrieve \the [access_cable]!"))
+ return
+
+ if(access_cable.loc != src)
+ to_chat(H, SPAN_WARNING("The access cable is already elsewhere!"))
+ return
+
+ H.visible_message(SPAN_NOTICE("[H] retrieves \the [access_cable] from \the [src]."), SPAN_NOTICE("You retrieve \the [access_cable] from \the [src]."))
+ access_cable.create_cable(H)
+ H.put_in_active_hand(access_cable)
diff --git a/code/modules/modular_computers/hardware/hardware.dm b/code/modules/modular_computers/hardware/hardware.dm
index 791a6991f81..4ff5ea6e794 100644
--- a/code/modules/modular_computers/hardware/hardware.dm
+++ b/code/modules/modular_computers/hardware/hardware.dm
@@ -1,4 +1,4 @@
-/obj/item/computer_hardware/
+/obj/item/computer_hardware
name = "Hardware"
desc = "Unknown Hardware."
icon = 'icons/obj/modular_components.dmi'
diff --git a/code/modules/modular_computers/hardware/universal_port.dm b/code/modules/modular_computers/hardware/universal_port.dm
new file mode 100644
index 00000000000..b09fae9f1b0
--- /dev/null
+++ b/code/modules/modular_computers/hardware/universal_port.dm
@@ -0,0 +1,29 @@
+/obj/item/computer_hardware/universal_port
+ name = "universal port"
+ desc = "A small, universal slot wherein an access cable can be slotted in."
+ critical = FALSE
+ icon_state = "aislot"
+ hardware_size = 3
+
+ /// The access cable currently inserted into this slot.
+ var/obj/item/access_cable/access_cable
+
+/obj/item/computer_hardware/universal_port/Destroy()
+ if(access_cable)
+ access_cable.retract()
+ access_cable = null
+ return ..()
+
+/obj/item/computer_hardware/universal_port/remove_cable(obj/item/access_cable/cable)
+ ..()
+ access_cable = null
+
+/obj/item/computer_hardware/universal_port/insert_cable(obj/item/access_cable/cable, mob/user)
+ . = ..()
+ access_cable = cable
+ cable.create_cable(parent_computer)
+
+/obj/item/computer_hardware/universal_port/cable_interact(obj/item/access_cable/cable, mob/user)
+ . = ..()
+ if(parent_computer)
+ parent_computer.attack_self(user)
diff --git a/code/modules/movespeed/modifiers/synthetics.dm b/code/modules/movespeed/modifiers/synthetics.dm
new file mode 100644
index 00000000000..2121acf60ed
--- /dev/null
+++ b/code/modules/movespeed/modifiers/synthetics.dm
@@ -0,0 +1,9 @@
+/datum/movespeed_modifier/synth_emp
+ id = "synthemp"
+ variable = TRUE
+ multiplicative_slowdown = 1
+
+/datum/movespeed_modifier/endoskeleton
+ id = "synthendoskeleton"
+ variable = TRUE
+ multiplicative_slowdown = 1
diff --git a/code/modules/nano/modules/human_appearance.dm b/code/modules/nano/modules/human_appearance.dm
index 43f2c299a8b..099b02dc93c 100644
--- a/code/modules/nano/modules/human_appearance.dm
+++ b/code/modules/nano/modules/human_appearance.dm
@@ -220,6 +220,18 @@
var/modification = tgui_input_list(owner, "Select a Modification", "Organ Modification", O.possible_modifications)
owner.change_organ(organ, modification)
+ if("organ_preset")
+ if(can_change(APPEARANCE_PROSTHETICS))
+ var/organ = tgui_input_list(owner, "Select an Organ", "Organ Presets", valid_organs)
+ var/obj/item/organ/internal/machine/O = owner.internal_organs_by_name[organ]
+ var/list/possible_presets = list()
+ for(var/P in O.organ_presets)
+ var/singleton/synthetic_organ_preset/preset = GET_SINGLETON(P)
+ possible_presets[preset.name] = preset
+ var/singleton/synthetic_organ_preset/chosen_preset = tgui_input_list(owner, "Select a Preset", "Organ Presets", possible_presets)
+ if(chosen_preset)
+ O.apply_preset_data(chosen_preset)
+
/datum/tgui_module/appearance_changer/ui_interact(var/mob/user, var/datum/tgui/ui)
ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
diff --git a/code/modules/organs/internal/species/machine/_generic.dm b/code/modules/organs/internal/species/machine/_generic.dm
new file mode 100644
index 00000000000..00e5c71a49e
--- /dev/null
+++ b/code/modules/organs/internal/species/machine/_generic.dm
@@ -0,0 +1,237 @@
+/obj/item/organ/internal/machine
+ name = "generic machine organ"
+ parent_organ = BP_CHEST
+ organ_tag = "generic machine organ"
+ robotic_sprite = FALSE
+
+ /// The list of organ presets to use. Linked list of ORGAN_PREF to /singleton/synthetic_organ_preset. Use only if your organ has pref settings and presets.
+ /// For an example, see cooling_unit.dm. Remember to also update code\modules\client\preference_setup\general\03_body.dm at line 893!
+ var/list/organ_presets
+ /// The default preset to fall back to if there is no pref (aka people want the base type). Must be of type /singleton/synthetic_organ_preset.
+ var/default_preset
+ /// This variable forces the organ to be of a certain preset and is applied automatically on Initialize. This is used to make subtypes spawn as proper presets.
+ var/forced_preset
+ /// The current preset, if any. Should only ever be null or an instance.
+ var/singleton/synthetic_organ_preset/current_preset
+
+ /// If an organ shows up in the diagnostics suite (the IPC organ verb).
+ var/diagnostics_suite_visible = TRUE
+
+ /// The wiring datum of this organ.
+ var/datum/synthetic_internal/wiring/wiring
+ /// The plating datum of this organ.
+ var/datum/synthetic_internal/plating/plating
+ /// The electronics datum of this organ.
+ var/datum/synthetic_internal/electronics/electronics
+
+ /// The world.time of the last integrity damage event.
+ var/last_integrity_event_time = 0
+ /// The amount of time that has to pass between each integrity damage event.
+ var/integrity_event_cooldown = 60 SECONDS
+
+/obj/item/organ/internal/machine/Initialize()
+ robotize()
+ . = ..()
+ wiring = new(src)
+ plating = new(src)
+ electronics = new(src)
+ if(forced_preset)
+ apply_preset_data(GET_SINGLETON(forced_preset))
+
+/obj/item/organ/internal/machine/Destroy()
+ QDEL_NULL(wiring)
+ QDEL_NULL(plating)
+ QDEL_NULL(electronics)
+ if(current_preset)
+ current_preset = null
+ return ..()
+
+/obj/item/organ/internal/machine/refresh_action_button()
+ . = ..()
+ if(.)
+ if(action_button_name)
+ action.button_icon_state = initial(icon_state)
+ if(action.button)
+ action.button.update_icon()
+
+/obj/item/organ/internal/machine/rejuvenate()
+ . = ..()
+ diagnostics_suite_visible = initial(diagnostics_suite_visible)
+ wiring.heal_damage(wiring.max_wires)
+ plating.heal_damage(plating.max_health)
+ electronics.heal_damage(electronics.max_integrity)
+ integrity_event_cooldown = initial(integrity_event_cooldown)
+
+/obj/item/organ/internal/machine/process(seconds_per_tick)
+ ..()
+ if(!owner)
+ return
+
+ var/integrity = get_integrity()
+ if(integrity < IPC_INTEGRITY_THRESHOLD_LOW)
+ // This is when things start going Fucking Wrong.
+ do_integrity_damage_effects(integrity)
+
+ if(damage >= max_damage)
+ die()
+
+/obj/item/organ/internal/machine/die()
+ if(!vital)
+ to_chat(owner, FONT_LARGE(SPAN_MACHINE_WARNING("Some of your sensors go dark - you've lost connection to your [src]!")))
+ diagnostics_suite_visible = FALSE
+ damage = max_damage
+ status |= ORGAN_DEAD
+ death_time = world.time
+ STOP_PROCESSING(SSprocessing, src)
+ if(owner && vital)
+ owner.death()
+
+/obj/item/organ/internal/machine/take_internal_damage(amount, silent, forced_damage = FALSE)
+ // Plating defends the internal bits. First, you have to get through it.
+ if(!forced_damage)
+ if(plating.get_status() > 0)
+ amount = plating.take_damage(amount)
+ if(amount <= 0)
+ return
+
+ var/list/component_list = list(wiring, electronics)
+ component_list = shuffle(component_list)
+ // After that, it's open season.
+ for(var/datum/synthetic_internal/internal in component_list)
+ if(internal.get_status())
+ internal.take_damage(amount)
+ if(internal.get_status())
+ break
+
+ . = ..()
+
+/obj/item/organ/internal/machine/emp_act(severity)
+ . = ..()
+ switch(severity)
+ if(EMP_HEAVY)
+ take_internal_damage(4 * emp_coeff)
+ if(EMP_LIGHT)
+ take_internal_damage(2 * emp_coeff)
+
+/**
+ * Called when prefs are synced to the organ to set the proper synthetic organ preset. Turns the pref into a preset singleton.
+ * Remember that the base type is the default, AKA when no prefs are set that organ will spawn.
+ */
+/obj/item/organ/internal/machine/proc/get_preset_from_pref(organ_pref)
+ var/singleton/synthetic_organ_preset/new_preset
+ if(organ_pref && (organ_pref in organ_presets))
+ new_preset = GET_SINGLETON(organ_presets[organ_pref])
+ else
+ new_preset = GET_SINGLETON(default_preset)
+
+ if(!istype(new_preset))
+ crash_with("Invalid organ preset [new_preset]!")
+
+ apply_preset_data(new_preset)
+
+/**
+ * Applies the preset data to the organ. Needs a preset object supplied, not just a type.
+ */
+/obj/item/organ/internal/machine/proc/apply_preset_data(singleton/synthetic_organ_preset/preset)
+ if(!istype(preset))
+ crash_with("apply_preset_data called with improper preset [preset]!")
+
+ current_preset = preset
+ preset.apply_preset(src)
+ owner.update_action_buttons()
+
+/**
+ * This is a function used to return an overall integrity number that takes
+ * the average of wiring + electronics, and then subtracts current damage from them. Those are the soft bits - plating is what defends them.
+ * Returns a percentage.
+ */
+/obj/item/organ/internal/machine/proc/get_integrity()
+ var/wiring_status = wiring ? wiring.get_status() : 0
+ var/electronics_status = electronics ? electronics.get_status() : 0
+ var/internal_component_status = (wiring_status + electronics_status) / 2
+ var/damage_status = damage / 2
+ return max(internal_component_status - damage_status, 0)
+
+/**
+ * This function returns a damage multiplier, starting from 1 and dropping to 0, depending on damage.
+ * Returns a multiplier between 0 and 1.
+ */
+/obj/item/organ/internal/machine/proc/get_damage_multiplier()
+ if(!damage)
+ return 1
+
+ return 1 - (damage / max_damage)
+
+/**
+ * This proc is used to determine which integrity damage threshold we are at, and execute the proper proc..
+ * Only called below 75%.
+ */
+/obj/item/organ/internal/machine/proc/do_integrity_damage_effects(integrity)
+ if(!owner)
+ return
+
+ var/obj/item/organ/internal/machine/posibrain/brain = owner.internal_organs_by_name[BP_BRAIN]
+ if(!istype(brain)) //??? should be dead anyway so who cares
+ return
+
+ // The cooldowns are handled in the other procs because feasibly we might want to add different cooldowns for different organs and different thresholds.
+ if(can_do_integrity_damage())
+ switch(integrity)
+ if(IPC_INTEGRITY_THRESHOLD_MEDIUM to IPC_INTEGRITY_THRESHOLD_LOW)
+ low_integrity_damage(integrity)
+ if(IPC_INTEGRITY_THRESHOLD_HIGH to IPC_INTEGRITY_THRESHOLD_MEDIUM)
+ medium_integrity_damage(integrity)
+ if(IPC_INTEGRITY_THRESHOLD_VERY_HIGH to IPC_INTEGRITY_THRESHOLD_HIGH)
+ high_integrity_damage(integrity)
+
+/**
+ * Returns the damage percentage of total integrity.
+ * Basically the opposite of get_integrity(). Useful for damage probabilities, where you want a scaling number the more integrity damage there is on an organ.
+ */
+/obj/item/organ/internal/machine/proc/get_integrity_damage_probability(integrity)
+ // integrity is a probability, so first of all check the difference between max probability and probaiblity
+ // 100 - 100 = 0 | 100 - 50 = 50
+ var/damage_probability = 100 - integrity
+ return damage_probability
+
+/**
+ * Returns TRUE if the cooldown for integrity damage has expired.
+ */
+/obj/item/organ/internal/machine/proc/can_do_integrity_damage()
+ if(last_integrity_event_time + integrity_event_cooldown > world.time)
+ return FALSE
+ return TRUE
+
+/**
+ * The proc called to do low-intensity integrity damage (50 to 75% damage).
+ */
+/obj/item/organ/internal/machine/proc/low_integrity_damage(integrity)
+ last_integrity_event_time = world.time
+ return TRUE
+
+/**
+ * The proc called to do mediumlow-intensity integrity damage (25 to 50% damage).
+ */
+/obj/item/organ/internal/machine/proc/medium_integrity_damage(integrity)
+ last_integrity_event_time = world.time
+ var/obj/item/organ/internal/machine/posibrain/brain = owner.internal_organs_by_name[BP_BRAIN]
+ if(istype(brain))
+ brain.damage_integrity(1)
+ return TRUE
+
+/**
+ * The proc called to do high-intensity integrity damage (0 to 25% damage).
+ */
+/obj/item/organ/internal/machine/proc/high_integrity_damage(integrity)
+ last_integrity_event_time = world.time
+ var/obj/item/organ/internal/machine/posibrain/brain = owner.internal_organs_by_name[BP_BRAIN]
+ if(istype(brain))
+ brain.damage_integrity(2)
+ return TRUE
+
+/**
+ * Returns extra diagnostics info, viewable from the diagnostics unit.
+ */
+/obj/item/organ/internal/machine/proc/get_diagnostics_info()
+ return
+
diff --git a/code/modules/organs/internal/species/machine/_presets.dm b/code/modules/organs/internal/species/machine/_presets.dm
new file mode 100644
index 00000000000..8ec4e34954b
--- /dev/null
+++ b/code/modules/organs/internal/species/machine/_presets.dm
@@ -0,0 +1,22 @@
+/**
+ * Synthetic organ presets are a way to allow us to have customizable and easily expandable presets for different organs.
+ * This is partly due to the limitations of the organ modification system, which does not support manually selecting different types,
+ * but also to allow the creation of different presets to do different things in a modular and easily expandable way.
+ */
+/singleton/synthetic_organ_preset
+ /// The name the synthetic organ will have.
+ var/name = "epic synthetic organ"
+ /// The description synthetic organ will get.
+ var/desc = "It is super epic and cool."
+ /// Replacement icon state. Leave null if you don't want to replace the original state.
+ var/icon_state
+
+/singleton/synthetic_organ_preset/proc/apply_preset(obj/item/organ/internal/machine/organ)
+ SHOULD_CALL_PARENT(TRUE)
+ if(!istype(organ))
+ crash_with("Synthetic organ preset called with invalid organ [organ]")
+
+ organ.name = name
+ organ.desc = desc
+ if(icon_state)
+ organ.icon_state = icon_state
diff --git a/code/modules/organs/internal/species/machine/access_port.dm b/code/modules/organs/internal/species/machine/access_port.dm
new file mode 100644
index 00000000000..62001dfa307
--- /dev/null
+++ b/code/modules/organs/internal/species/machine/access_port.dm
@@ -0,0 +1,346 @@
+/obj/item/organ/internal/machine/access_port
+ name = "universal access port"
+ desc = "A slot built into nearly all synthetics for universal access to information such as diagnostics or internal processes."
+ icon = 'icons/obj/organs/ipc_organs.dmi'
+ icon_state = "ipc_port"
+ organ_tag = BP_ACCESS_PORT
+ parent_organ = BP_HEAD
+
+ action_button_name = "Extend or Retract Cable"
+
+ max_damage = 30
+ relative_size = 15
+
+ /// Our access cable, which can be extended to connect into things.
+ var/obj/item/access_cable/access_cable = /obj/item/access_cable/synthetic
+ /// The internal port. This is where things get connected to to retrieve information or do effects.
+ var/obj/item/internal_port
+
+/obj/item/organ/internal/machine/access_port/Initialize()
+ . = ..()
+ access_cable = new access_cable(src, src, owner)
+ RegisterSignal(access_cable, COMSIG_QDELETING, PROC_REF(clear_cable))
+ add_verb(owner, /mob/living/carbon/human/proc/access_cable)
+
+/obj/item/organ/internal/machine/access_port/Destroy()
+ QDEL_NULL(access_cable)
+ QDEL_NULL(internal_port)
+ return ..()
+
+/obj/item/organ/internal/machine/access_port/replaced(mob/living/carbon/human/target, obj/item/organ/external/affected)
+ . = ..()
+ add_verb(owner, /mob/living/carbon/human/proc/access_cable)
+
+/obj/item/organ/internal/machine/access_port/removed(mob/living/carbon/human/target, mob/living/user)
+ remove_verb(owner, /mob/living/carbon/human/proc/access_cable)
+ . = ..()
+
+/obj/item/organ/internal/machine/access_port/attack_self(mob/user)
+ . = ..()
+ if(user.stat == DEAD)
+ return
+
+ if(user.incapacitated(INCAPACITATION_KNOCKOUT|INCAPACITATION_STUNNED))
+ return
+
+ if(!access_cable)
+ to_chat(user, SPAN_WARNING("You don't have an access cable anymore!"))
+ return
+
+ // it's an organ, should never be not human type
+ var/mob/living/carbon/human/synth = user
+ if(synth.get_active_hand())
+ to_chat(synth, SPAN_WARNING("You need a free hand to retrieve your universal access cable!"))
+ return
+
+ if(access_cable.loc != src)
+ // not sure how NOT having a target and being here would work, but it's a fallback just in case things bug out
+ if(access_cable.target)
+ visible_message(SPAN_NOTICE("[owner] disconnects their [access_cable] from \the [access_cable.target]."))
+ access_cable.target.remove_cable(access_cable)
+ access_cable.clear_cable_full()
+ access_cable.forceMove(src)
+ return
+
+ if(get_integrity() < 75)
+ to_chat(user, SPAN_WARNING("You struggle to pry the cable out of the damaged port..."))
+ if(!do_after(user, 2 SECONDS))
+ return
+
+ synth.visible_message(SPAN_NOTICE("[synth] extends their universal access cable from their neck."), SPAN_NOTICE("You retrieve your universal access cable from your neck."))
+ synth.put_in_active_hand(access_cable)
+
+/**
+ * This proc is called whenever anything is inserted into the internal port.
+ */
+/obj/item/organ/internal/machine/access_port/proc/insert_item(obj/item/jack)
+ SIGNAL_HANDLER
+ if(internal_port)
+ crash_with("Insert_item with [jack] on access port called with [internal_port] of [owner] already present!")
+
+ internal_port = jack
+ jack.forceMove(src)
+ RegisterSignal(internal_port, COMSIG_QDELETING, PROC_REF(clear_port))
+ to_chat(owner, SPAN_MACHINE_WARNING("Internal firewall notice: [internal_port] inserted into [src]."))
+
+/**
+ * This proc is called whenever the access cable is, for some reason, qdeleted (like with an explosion).
+ */
+/obj/item/organ/internal/machine/access_port/proc/clear_cable()
+ SIGNAL_HANDLER
+ if(!access_cable)
+ return
+
+ UnregisterSignal(access_cable, COMSIG_QDELETING)
+ access_cable = null
+ to_chat(owner, SPAN_WARNING("You lose contact with your access cable!"))
+
+/**
+ * This proc is called whenever the internal access port is emptied of whatever was in there previously.
+ */
+/obj/item/organ/internal/machine/access_port/proc/clear_port()
+ if(!internal_port)
+ return
+
+ if(istype(internal_port, /obj/item/access_cable))
+ var/obj/item/access_cable/ejected_cable = internal_port
+ ejected_cable.target = null
+
+ UnregisterSignal(internal_port, COMSIG_QDELETING)
+ internal_port = null
+
+/obj/item/organ/internal/machine/access_port/insert_cable(obj/item/access_cable/cable, mob/user)
+ . = ..()
+ insert_item(cable)
+ cable.create_cable(owner)
+
+/obj/item/organ/internal/machine/access_port/cable_interact(obj/item/access_cable/cable, mob/user)
+ var/obj/item/organ/internal/machine/internal_diagnostics/diagnostics_unit = owner.internal_organs_by_name[BP_DIAGNOSTICS_SUITE]
+ if(!diagnostics_unit)
+ to_chat(user, SPAN_WARNING("There is no diagnostics unit!"))
+ return
+
+ var/obj/item/organ/internal/machine/posibrain/posibrain = owner.internal_organs_by_name[BP_BRAIN]
+ if(istype(posibrain))
+ if(posibrain.firewall && !user.stat)
+ to_chat(user, SPAN_MACHINE_WARNING("Firewall block detected. Aborting."))
+ to_chat(diagnostics_unit.owner, SPAN_MACHINE_WARNING("Your firewall has blocked an unrecognized access attempt."))
+ return
+
+ diagnostics_unit.open_diagnostics(user)
+
+/obj/item/access_cable
+ name = "external access cable"
+ desc = "A cable with universal access pins at its end. This is meant for jacking into synthetics to access their data."
+ icon = 'icons/obj/organs/ipc_organs.dmi'
+ icon_state = "cable"
+ pickup_sound = 'sound/species/synthetic/access_cable_out.ogg'
+ w_class = WEIGHT_CLASS_NO_CONTAINER
+
+ /// Where this cable is extending from.
+ var/obj/source
+ /// If the source is an object in another object (like an organ), then this is where we actually want the cable beam to extend from physically.
+ var/atom/movable/beam_anchor
+ /// Where this cable is attached to.
+ var/obj/target
+ /// The actual target of the /datum/beam coming from the cable.
+ var/atom/movable/beam_target
+ /// The beam connecting us to the source.
+ var/datum/beam/cable
+ /// The range this cable has. Past this range, it will disconnect.
+ var/range = 3
+
+/obj/item/access_cable/Initialize(mapload, atom/movable/cable_source, atom/movable/cable_beam_anchor)
+ . = ..()
+ if(!cable_source)
+ log_debug("Access cable spawned without a source: [x] [y] [z]")
+ qdel_self()
+ return
+
+ source = cable_source
+ if(cable_beam_anchor)
+ beam_anchor = cable_beam_anchor
+ RegisterSignal(src, COMSIG_MOVABLE_MOVED, PROC_REF(check_retract_range), TRUE)
+
+/obj/item/access_cable/Destroy()
+ source = null
+ target = null
+ beam_anchor = null
+ beam_target = null
+ if(cable)
+ cable.End()
+ QDEL_NULL(cable)
+ return ..()
+
+/**
+ * When the cable is actually taken out of an object and thus is shown in world.
+ * The parameters here might be different from the source/target variables on the cable itself.
+ * For example, the source of a synthetic access cable is the power port, although that's physically inside a human mob and so we can't draw a beam to it.
+ * We have to draw a beam from the human in that case.
+ */
+/obj/item/access_cable/proc/create_cable(var/atom/new_target)
+ clear_cable_visuals()
+ beam_target = new_target
+ RegisterSignal(beam_anchor ? beam_anchor : source, COMSIG_MOVABLE_MOVED, PROC_REF(check_retract_range), TRUE)
+ RegisterSignal(beam_target, COMSIG_MOVABLE_MOVED, PROC_REF(check_retract_range), TRUE)
+ cable = new(beam_anchor ? beam_anchor : source, beam_target, beam_icon_state = "cable", time = -1, maxdistance = range + 1)
+ cable.Start()
+
+/obj/item/access_cable/dropped(mob/user)
+ . = ..()
+ clear_cable_visuals()
+ create_cable(src)
+
+/obj/item/access_cable/pickup(mob/user)
+ . = ..()
+ clear_cable_visuals()
+
+/**
+ * Signal handler.
+ * If the cable moves beyond its range, automatically retract it.
+ */
+/obj/item/access_cable/proc/check_retract_range()
+ SIGNAL_HANDLER
+ if(source == loc)
+ return
+
+ if(get_dist(source, src) > range)
+ retract()
+
+/**
+ * Automatically drop the cable and then retract it to the parent.
+ */
+/obj/item/access_cable/proc/retract()
+ visible_message(SPAN_NOTICE("\The [src] automatically retracts into \the [source]!"))
+ forceMove(source)
+ clear_cable_full()
+
+/**
+ * Retracts the cable back into the parent object.
+ */
+/obj/item/access_cable/proc/clear_cable_full()
+ UnregisterSignal(beam_anchor ? beam_anchor : source, COMSIG_MOVABLE_MOVED)
+ if(target)
+ target.remove_cable(src)
+ target = null
+ clear_cable_visuals()
+
+/**
+ * Only clears the cable visuals (so, just the beam).
+ */
+/obj/item/access_cable/proc/clear_cable_visuals()
+ if(beam_target)
+ UnregisterSignal(beam_target, COMSIG_MOVABLE_MOVED)
+
+ beam_target = null
+ if(cable)
+ cable.End()
+ QDEL_NULL(cable)
+
+/obj/item/access_cable/proc/target_interact(mob/user)
+ if(!target || !user)
+ return
+
+ target.cable_interact(src, user)
+
+/obj/item/access_cable/attack(mob/living/target_mob, mob/living/user, target_zone)
+ if(ishuman(target_mob))
+ var/mob/living/carbon/human/human = target_mob
+ if(!isipc(human))
+ to_chat(user, SPAN_WARNING("Where are you planning to put that...?"))
+ return
+
+ var/obj/item/organ/internal/machine/access_port/access_port = human.internal_organs_by_name[BP_ACCESS_PORT]
+ if(!access_port)
+ to_chat(user, SPAN_WARNING("[human] does not have an access port!"))
+ return
+
+ if(access_port.access_cable == src)
+ to_chat(user, SPAN_WARNING("You can't put your own cable into your own access port, as funny as it would be."))
+ return
+
+ if(access_port.is_broken())
+ to_chat(user, SPAN_WARNING("[human]'s access port is completely broken! There's no way you can jack into it!"))
+ return
+
+ if(access_port.internal_port)
+ to_chat(user, SPAN_WARNING("The access port is already occupied by [access_port.internal_port]!"))
+ return
+
+ if(user != human)
+ if(human.client && !human.incapacitated(INCAPACITATION_FORCELYING|INCAPACITATION_RESTRAINED|INCAPACITATION_BUCKLED_FULLY|INCAPACITATION_BUCKLED_PARTIALLY))
+ var/request = tgui_alert(human, "[user] would like to access your access port. Allow them?", "Access Port", list("Yes", "No"))
+ if(request != "Yes")
+ human.visible_message(SPAN_NOTICE("[human] pushes away [user]'s [src]."))
+ return
+
+ user.visible_message(SPAN_WARNING("[user] tries to jack \the [src] into [human]'s access port..."))
+ if(!do_mob(user, human, 2 SECONDS))
+ return
+ user.visible_message(SPAN_WARNING("[user] jacks \the [src] into [human]'s access port!"))
+
+ else
+ user.visible_message(SPAN_WARNING("[user] jacks \the [src] into their access port!"), SPAN_WARNING("You jack \the [src] into your access port!"))
+
+ user.drop_from_inventory(src, get_turf(user))
+ access_port.insert_cable(src, user)
+ else
+ . = ..()
+
+/obj/item/access_cable/synthetic
+ name = "universal access cable"
+ desc = "A cable with universal access pins at its end. This particular access cable comes with most synthetics' access ports for quick access to electronics, firewalls, or other synthetics' diagnostics systems."
+
+/obj/item/access_cable/synthetic/mechanics_hints()
+ . = ..()
+ . += "To retract this cable into your port, activate it in-hand."
+
+/obj/item/access_cable/synthetic/attack_self(mob/user, modifiers)
+ if(isipc(user))
+ var/mob/living/carbon/human/synth = user
+ var/obj/item/organ/internal/machine/access_port/access_port = synth.internal_organs_by_name[BP_ACCESS_PORT]
+ if(!access_port)
+ to_chat(synth, SPAN_WARNING("Where's your access port?!"))
+ return ..()
+
+ if(access_port.is_broken())
+ to_chat(synth, SPAN_WARNING("Your access port is completely broken! It won't go in!"))
+ return ..()
+
+ if(access_port.get_integrity() <= IPC_INTEGRITY_THRESHOLD_MEDIUM)
+ synth.visible_message(SPAN_WARNING("[synth] tries to jam [synth.get_pronoun("his")] access cable back into their access port..."), SPAN_WARNING("You try to jam your access cable back into your access port..."))
+ if(!do_after(synth, 3 SECONDS))
+ return ..()
+
+ synth.visible_message(SPAN_NOTICE("[synth] retracts [synth.get_pronoun("his")] access cable back into their access port."), SPAN_NOTICE("You retract your access cable back into your access port."))
+ synth.drop_from_inventory(src, get_turf(src))
+ forceMove(access_port)
+ clear_cable_full()
+ playsound(user, 'sound/species/synthetic/access_cable_in.ogg', 50)
+
+/mob/living/carbon/human/proc/access_cable()
+ set name = "Access Cable"
+ set category = "Object"
+
+ if(!isipc(src))
+ return
+
+ var/obj/item/organ/internal/machine/access_port/access_port = internal_organs_by_name[BP_ACCESS_PORT]
+ if(!access_port)
+ to_chat(src, SPAN_WARNING("Where's your access port?!"))
+ return
+
+ if(access_port.is_broken())
+ to_chat(src, SPAN_WARNING("Your access port's only feedback is crackling static and jumbled code!"))
+ return
+
+ var/obj/item/access_cable/access_cable = access_port.access_cable
+ if(!access_cable)
+ to_chat(src, SPAN_WARNING("Your access cable is missing!"))
+ return
+
+ if(!access_cable.target)
+ to_chat(src, SPAN_WARNING("You need to connect your cable to something first!"))
+ return
+
+ access_cable.target_interact(src)
diff --git a/code/modules/organs/internal/species/machine/actuators.dm b/code/modules/organs/internal/species/machine/actuators.dm
new file mode 100644
index 00000000000..a0cfc4bec07
--- /dev/null
+++ b/code/modules/organs/internal/species/machine/actuators.dm
@@ -0,0 +1,31 @@
+/obj/item/organ/internal/machine/actuators
+ name = "actuators"
+ desc = "This mixture of electronic, pneumatic and hydraulic components allow a positronic chassis' arms to produce force and torque to allow them to move their artificial limbs."
+ icon = 'icons/obj/organs/ipc_organs.dmi'
+ icon_state = "ipc_actuator"
+
+ max_damage = 40
+ relative_size = 70
+
+/obj/item/organ/internal/machine/actuators/left
+ name = "left arm actuators"
+ organ_tag = BP_ACTUATORS_LEFT
+ parent_organ = BP_L_ARM
+ organ_tag = BP_ACTUATORS_LEFT
+
+/obj/item/organ/internal/machine/actuators/right
+ name = "right arm actuators"
+ organ_tag = BP_ACTUATORS_RIGHT
+ parent_organ = BP_R_ARM
+ organ_tag = BP_ACTUATORS_RIGHT
+
+/obj/item/organ/internal/machine/actuators/high_integrity_damage(integrity)
+ if(prob(get_integrity_damage_probability()))
+ spark(owner, 5, GLOB.alldirs)
+ to_chat(owner, SPAN_WARNING("Your [src] malfunction, making you drop what you're holding!"))
+ switch(parent_organ)
+ if(BP_L_ARM)
+ owner.drop_l_hand()
+ if(BP_R_ARM)
+ owner.drop_r_hand()
+ . = ..()
diff --git a/code/modules/organs/internal/species/machine/cooling_unit.dm b/code/modules/organs/internal/species/machine/cooling_unit.dm
new file mode 100644
index 00000000000..25c33f969cc
--- /dev/null
+++ b/code/modules/organs/internal/species/machine/cooling_unit.dm
@@ -0,0 +1,255 @@
+#define COOLING_UNIT_DEFAULT_THERMOSTAT_MAX 323.15
+#define COOLING_UNIT_DEFAULT_MAX_SAFE_TEMP 423.15
+
+/obj/item/organ/internal/machine/cooling_unit
+ name = "air cooling unit"
+ desc = "One of the most complex and vital components of a synthetic. It regulates its internal temperature and prevents the chassis from overheating."
+ icon = 'icons/obj/organs/ipc_organs.dmi'
+ icon_state = "ipc_fans"
+ organ_tag = BP_COOLING_UNIT
+ parent_organ = BP_CHEST
+ possible_modifications = list("Air Cooling", "Liquid Cooling", "Passive Cooling")
+ organ_presets = list(
+ ORGAN_PREF_AIRCOOLED = /singleton/synthetic_organ_preset/cooling_unit/air,
+ ORGAN_PREF_LIQUIDCOOLED = /singleton/synthetic_organ_preset/cooling_unit/liquid,
+ ORGAN_PREF_PASSIVECOOLED = /singleton/synthetic_organ_preset/cooling_unit/passive
+ )
+ default_preset = /singleton/synthetic_organ_preset/cooling_unit/air
+ action_button_name = "Regulate Thermostat"
+
+ max_damage = 70
+ relative_size = 50
+
+ /// The power consumed when we are cooling down.
+ var/base_power_consumption = 8
+ /// The passive temperature change. Basically, cooling units counteract an IPC's passive temperature gain. But the IPC's temperature goes to get itself fucked if the cooling unit dies.
+ /// Remember, can be negative or positive. Depends on what we're trying to stabilize towards.
+ var/passive_temp_change = 2
+ /// The temperature that this cooling unit tries to regulate towards.
+ var/thermostat = T20C
+ /// The minimum value the thermostat can reach. 0C.
+ var/thermostat_min = T0C
+ /// The maximum value the thermostat can reach. 50C.
+ var/thermostat_max = COOLING_UNIT_DEFAULT_THERMOSTAT_MAX
+ /// Can this cooling unit work in space?
+ var/spaceproof = FALSE
+ /// The temperature at which this cooling unit will disable running if safeties are on.
+ var/maximum_safe_temperature = COOLING_UNIT_DEFAULT_MAX_SAFE_TEMP
+ /// If the safeties that automatically disable sprinting when bodytemperature exceeds safe limits are disabled or not.
+ var/temperature_safety = TRUE
+ /// If the safety has been burnt and thus will not engage.
+ var/safety_burnt = FALSE
+ /// If the thermostat is locked and thus cannot be changed. Used for spooky effects, like the high integrity damage in the positronic brain.
+ var/locked_thermostat = FALSE
+
+/obj/item/organ/internal/machine/cooling_unit/Initialize()
+ . = ..()
+ if(owner)
+ RegisterSignal(owner, COMSIG_IPC_HAS_SPRINTED, PROC_REF(handle_safeties))
+
+/obj/item/organ/internal/machine/cooling_unit/attack_self(var/mob/user)
+ . = ..()
+ if(owner.last_special > world.time)
+ return
+
+ if(user.stat == DEAD)
+ return
+
+ if(user.incapacitated(INCAPACITATION_KNOCKOUT|INCAPACITATION_STUNNED))
+ return
+
+ ui_interact(user)
+
+/obj/item/organ/internal/machine/cooling_unit/ui_interact(mob/user, datum/tgui/ui)
+ . = ..()
+ ui = SStgui.try_update_ui(user, src, ui)
+ if(!ui)
+ ui = new(user, src, "CoolingUnitThermostat", "Cooling Unit Regulation", 400, 600)
+ ui.open()
+
+/obj/item/organ/internal/machine/cooling_unit/ui_data(mob/user)
+ var/list/data = list()
+ // K = C + 273.15 | C = K – 273.15
+ // we want to pass celsius for readability,
+ // the average american can't figure out celsius, let alone kelvin
+ var/broken = is_broken()
+ data["broken"] = broken
+ if(!broken)
+ data["thermostat"] = thermostat - 273.15
+ data["thermostat_min"] = thermostat_min - 273.15
+ data["thermostat_max"] = thermostat_max - 273.15
+ data["passive_temp_change"] = passive_temp_change
+ data["bodytemperature"] = owner.bodytemperature - 273.15
+ data["temperature_safety"] = temperature_safety
+
+ var/estimated_power_consumption = base_power_consumption
+ var/integrity = get_integrity()
+ if(integrity < IPC_INTEGRITY_THRESHOLD_MEDIUM)
+ estimated_power_consumption = rand(-50, 100 + (100 - integrity))
+
+ if(thermostat < initial(thermostat))
+ // The higher the distance between the maximum thermostat setting and what we're actually at, the harder the cooling unit will work to cool us.
+ var/temperature_distance = round(thermostat_max - thermostat, 1)
+ estimated_power_consumption = temperature_distance * 0.5
+ else if(thermostat > initial(thermostat))
+ // higher thermostat = less power usage
+ estimated_power_consumption = -(thermostat_max / thermostat) - 0.5
+
+ data["estimated_power_consumption"] = max(0, base_power_consumption + estimated_power_consumption)
+ data["safety_burnt"] = safety_burnt
+ return data
+
+/obj/item/organ/internal/machine/cooling_unit/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state)
+ . = ..()
+ if(action == "change_thermostat")
+ if(locked_thermostat)
+ to_chat(owner, SPAN_MACHINE_WARNING("You can't change the thermostat manually! It just returns an error!"))
+ return FALSE
+
+ var/new_thermostat = params["change_thermostat"]
+ // remember we are getting passed celsius here
+ new_thermostat += T0C
+ if(new_thermostat >= thermostat_min && new_thermostat <= thermostat_max)
+ thermostat = new_thermostat
+ . = TRUE
+
+ if(action == "temperature_safety")
+ if(safety_burnt)
+ return
+
+ temperature_safety = !temperature_safety
+ to_chat(usr, temperature_safety ? SPAN_NOTICE("You re-enable your temperature safety.") : SPAN_WARNING("You disable your temperature safety!"))
+ . = TRUE
+
+/obj/item/organ/internal/machine/cooling_unit/process(seconds_per_tick)
+ . = ..()
+ if(!owner)
+ return
+
+ // Cooling units try to counteract external temperature by stabilizing towards ambient temperature.
+ var/turf/T = get_turf(owner)
+ if(!T)
+ // Some odd scenarios can cause there to be no turf. Like getting teleported into the game from lobby.
+ return
+
+ var/datum/gas_mixture/ambient = T.return_air()
+ // Too much heat is bad for the cooling unit.
+ if(owner.bodytemperature > species.heat_level_1)
+ if(prob(owner.bodytemperature * 0.1))
+ take_internal_damage(owner.bodytemperature * 0.01)
+
+ var/temperature_change = passive_temp_change
+ if(owner.wear_suit)
+ if(!spaceproof && istype(owner.wear_suit, /obj/item/clothing/suit/space))
+ //cooling is going to SUCK if you have heat-regulating clothes
+ if(owner.bodytemperature < species.heat_level_3)
+ owner.bodytemperature = min(owner.bodytemperature + 5, owner.species.heat_level_2)
+ temperature_change *= 0.5
+
+ // Check if there is somehow no air, or if we are in an ambient without enough air to properly cool us.
+ if((!ambient || (ambient && owner.calculate_affecting_pressure(ambient.return_pressure()) < owner.species.warning_low_pressure)))
+ if(!spaceproof)
+ temperature_change *= 0
+ else
+ temperature_change = min(temperature_change * 3, 10) //give it a little boost to counteract temp gain in space
+
+ if(is_broken())
+ temperature_change *= 0.5 //uh oh
+ else if(is_bruised())
+ temperature_change *= 0.75
+
+ // Now let's start cooling down!
+ // No power, no party.
+ var/obj/item/organ/internal/machine/power_core/cell = owner.internal_organs_by_name[BP_CELL]
+ if(!istype(cell))
+ return
+
+ // The lower our thermostat setting, the more power we consume.
+ var/extra_efficiency_multiplier = 1
+ var/extra_power_consumption = 0
+ if(thermostat < initial(thermostat))
+ // The higher the distance between the maximum thermostat setting and what we're actually at, the harder the cooling unit will work to cool us.
+ var/temperature_distance = round(thermostat_max - thermostat, 1)
+ extra_power_consumption = temperature_distance * 0.5
+ extra_efficiency_multiplier = temperature_distance * 0.05
+ else if(thermostat > initial(thermostat))
+ // higher thermostat = less power usage
+ extra_power_consumption = -(thermostat_max / thermostat) - 0.5
+
+ if(thermostat < owner.bodytemperature)
+ owner.bodytemperature = max(owner.bodytemperature - temperature_change * extra_efficiency_multiplier, thermostat)
+ cell.use(max(0, base_power_consumption + extra_power_consumption))
+
+/obj/item/organ/internal/machine/cooling_unit/low_integrity_damage(integrity)
+ if(get_integrity_damage_probability())
+ to_chat(owner, SPAN_WARNING("Your temperature sensors pick up a spike in temperature."))
+ owner.bodytemperature = min(owner.bodytemperature + 10, owner.species.heat_level_2)
+ . = ..()
+
+/obj/item/organ/internal/machine/cooling_unit/medium_integrity_damage(integrity)
+ if(get_integrity_damage_probability() / 2)
+ to_chat(owner, SPAN_WARNING("Your thermostat's temperature setting goes haywire!"))
+ thermostat = rand(thermostat_min, thermostat_max)
+ owner.bodytemperature = min(owner.bodytemperature + 20, owner.species.heat_level_2)
+
+ if(prob(25) && !safety_burnt)
+ to_chat(owner, SPAN_WARNING("Your temperature safeties burn out! They won't work anymore!"))
+ safety_burnt = TRUE
+ . = ..()
+
+/obj/item/organ/internal/machine/cooling_unit/high_integrity_damage(integrity)
+ if(get_integrity_damage_probability() / 3)
+ if(spaceproof)
+ playsound(owner, pick(SOUNDS_LASER_METAL), 50)
+ to_chat(owner, SPAN_DANGER(FONT_LARGE("Your laminar cooling stratum has melted. Your cooling unit will not work in space anymore!")))
+ spaceproof = FALSE
+ else if(thermostat_min < (initial(thermostat_min) + 100))
+ playsound(owner, pick(SOUNDS_LASER_METAL), 50)
+ to_chat(owner, SPAN_DANGER(FONT_LARGE("Parts of your cooling unit melt away...!")))
+ update_thermostat(thermostat_min + round(rand(10, 50)))
+ owner.bodytemperature = min(owner.bodytemperature + 30, owner.species.heat_level_2)
+ . = ..()
+
+/obj/item/organ/internal/machine/cooling_unit/proc/update_thermostat(new_thermostat_min, new_thermostat_max)
+ if(new_thermostat_min)
+ thermostat_min = new_thermostat_min
+ if(thermostat < new_thermostat_min)
+ thermostat = new_thermostat_min
+
+ if(thermostat_max < new_thermostat_max)
+ thermostat_max = new_thermostat_min
+
+ if(new_thermostat_max)
+ thermostat_max = new_thermostat_max
+ if(new_thermostat_max < thermostat_min)
+ thermostat_min = new_thermostat_max
+
+/obj/item/organ/internal/machine/cooling_unit/proc/handle_safeties()
+ if(!owner)
+ return
+
+ if((owner.bodytemperature > maximum_safe_temperature) && temperature_safety && !safety_burnt)
+ to_chat(owner, SPAN_DANGER("Your temperature safeties engage and stop you from running further!"))
+ owner.balloon_alert(owner, "safeties engaged")
+ owner.m_intent = M_WALK
+ owner.hud_used.move_intent.update_move_icon(owner)
+
+/obj/item/organ/internal/machine/cooling_unit/xion
+ spaceproof = TRUE
+ organ_presets = list(
+ ORGAN_PREF_AIRCOOLED = /singleton/synthetic_organ_preset/cooling_unit/air_xion,
+ ORGAN_PREF_LIQUIDCOOLED = /singleton/synthetic_organ_preset/cooling_unit/liquid_xion,
+ ORGAN_PREF_PASSIVECOOLED = /singleton/synthetic_organ_preset/cooling_unit/passive_xion
+ )
+ default_preset = /singleton/synthetic_organ_preset/cooling_unit/air_xion
+
+/obj/item/organ/internal/machine/cooling_unit/zenghu
+ organ_presets = list(
+ ORGAN_PREF_AIRCOOLED = /singleton/synthetic_organ_preset/cooling_unit/air_zenghu,
+ ORGAN_PREF_LIQUIDCOOLED = /singleton/synthetic_organ_preset/cooling_unit/liquid_zenghu,
+ ORGAN_PREF_PASSIVECOOLED = /singleton/synthetic_organ_preset/cooling_unit/passive_zenghu
+ )
+ default_preset = /singleton/synthetic_organ_preset/cooling_unit/air_zenghu
+
+#undef COOLING_UNIT_DEFAULT_THERMOSTAT_MAX
+#undef COOLING_UNIT_DEFAULT_MAX_SAFE_TEMP
diff --git a/code/modules/organs/internal/species/machine/cooling_unit_presets.dm b/code/modules/organs/internal/species/machine/cooling_unit_presets.dm
new file mode 100644
index 00000000000..cd6d91ab022
--- /dev/null
+++ b/code/modules/organs/internal/species/machine/cooling_unit_presets.dm
@@ -0,0 +1,133 @@
+/*
+* These are presets for the cooling unit data. Essentially, each type of cooling unit can have its own presets (depending on pref setting)
+* that change the name, description, and functioning of the cooling unit.
+*/
+
+/singleton/synthetic_organ_preset/cooling_unit
+ /// The name the cooling unit organ will have.
+ name = "epic cooling unit"
+ /// The description the cooling unit organ will get.
+ desc = "It cools epically."
+ /// The passive temperature change the cooling unit will have.
+ var/passive_temp_change = 3
+ /// The power used when cooling down.
+ var/base_power_consumption = 2.5
+ /// The maximum health the cooling unit's plating will have.
+ var/plating_max_health = 20
+
+/singleton/synthetic_organ_preset/cooling_unit/apply_preset(obj/item/organ/internal/machine/organ)
+ . = ..()
+ var/obj/item/organ/internal/machine/cooling_unit/cooling_unit = organ
+ cooling_unit.passive_temp_change = passive_temp_change
+ cooling_unit.base_power_consumption = base_power_consumption
+ cooling_unit.plating.replace_health(plating_max_health)
+
+/singleton/synthetic_organ_preset/cooling_unit/air
+ name = "air cooling unit"
+ desc = "One of the most complex and vital components of a synthetic. It regulates its internal temperature through the use of chassis-mounted fans and prevents the frame from overheating."
+
+ passive_temp_change = 2
+ plating_max_health = 50
+
+/obj/item/organ/internal/machine/cooling_unit/air
+ forced_preset = /singleton/synthetic_organ_preset/cooling_unit/air
+
+/singleton/synthetic_organ_preset/cooling_unit/liquid
+ name = "liquid-cooling pump and radiator array"
+ desc = "An extremely complex set of cooling pipes that transport coolant throughout a synthetic's body. The most efficient type of cooling, but also the most vulnerable."
+ icon_state = "ipc_liquid_cooler"
+
+ passive_temp_change = 3
+ base_power_consumption = 7.5
+ plating_max_health = 20
+
+/obj/item/organ/internal/machine/cooling_unit/liquid
+ forced_preset = /singleton/synthetic_organ_preset/cooling_unit/liquid
+
+/singleton/synthetic_organ_preset/cooling_unit/passive
+ name = "passive radiator cooling block"
+ desc = "A simplistic, but efficient block of large cooling fins, which cool down a synthetic's body enough to make it work. Quite cheap, but durable."
+ icon_state = "ipc_liquid_cooler"
+
+ passive_temp_change = 1
+ base_power_consumption = 2.5
+ plating_max_health = 100
+
+/obj/item/organ/internal/machine/cooling_unit/passive
+ forced_preset = /singleton/synthetic_organ_preset/cooling_unit/passive
+
+// Xion cooling units.
+/singleton/synthetic_organ_preset/cooling_unit/air_xion
+ name = "xion manufacturing advanced air cooling unit"
+ desc = "A very complex air cooling setup, with high-grade laminar plasteel fans and integrated space-proof heatsinks to allow the frame to still be cooled in space."
+ icon_state = "ipc_xion_fans"
+
+ passive_temp_change = 2
+ base_power_consumption = 6
+ plating_max_health = 70
+
+/obj/item/organ/internal/machine/cooling_unit/air/xion
+ forced_preset = /singleton/synthetic_organ_preset/cooling_unit/air_xion
+
+/singleton/synthetic_organ_preset/cooling_unit/liquid_xion
+ name = "xion manufacturing advanced cryo-cooling pump and radiator array"
+ desc = "An extremely complex cryo-cooling setup. It uses advanced coolant to allow the frame to still function in space - but its laminar super-flow piping is extremely fragile."
+ icon_state = "ipc_xion_liquid_cooler"
+
+ passive_temp_change = 3
+ base_power_consumption = 8
+ plating_max_health = 35
+
+/obj/item/organ/internal/machine/cooling_unit/liquid/xion
+ forced_preset = /singleton/synthetic_organ_preset/cooling_unit/liquid_xion
+
+/singleton/synthetic_organ_preset/cooling_unit/passive_xion
+ name = "xion manufacturing passive intra-fin array"
+ desc = "The simplicity of this cooling design betrays its efficiency: an extremely durable array of laminar plasteel fins, supplemented with an expensive coating that allows the synthetic to be cooled even in space."
+ icon_state = "ipc_heatsink"
+
+ passive_temp_change = 1
+ base_power_consumption = 3.5
+ plating_max_health = 120
+
+/obj/item/organ/internal/machine/cooling_unit/passive/xion
+ forced_preset = /singleton/synthetic_organ_preset/cooling_unit/passive_xion
+
+// Zenghu cooling units.
+/singleton/synthetic_organ_preset/cooling_unit/air_zenghu
+ name = "zeng-hu penta-fan air cooling system"
+ desc = "A sleek and luxurious air cooling system invented by Zeng-Hu for their house-made synthetic frames. It uses a patented and exclusive type of penta-fin fan. More efficient than standard air cooling solutions, but it will not allow cooling in space."
+ icon_state = "ipc_zeng_fans"
+
+ passive_temp_change = 4
+ base_power_consumption = 7.5
+ plating_max_health = 25
+
+/obj/item/organ/internal/machine/cooling_unit/air/zenghu
+ forced_preset = /singleton/synthetic_organ_preset/cooling_unit/air_zenghu
+
+/singleton/synthetic_organ_preset/cooling_unit/liquid_zenghu
+ name = "zeng-hu lamellar liquid cooling system"
+ desc = "A sleek set of superimposed lamellar pipes with a custom cooling solution. In black market and enthusiast repair forums, this is known as 'the enthusiast's nightmare' due to its practical impossibility to repair in anything less than a Zeng-Hu facility; \
+ the lamellar pipes are impossible to reproduce or find through a third-party solution due to their extremely complex and exclusive Zeng-Hu make."
+ icon_state = "ipc_zeng_cooling"
+
+ passive_temp_change = 5
+ base_power_consumption = 10
+ plating_max_health = 15
+
+/obj/item/organ/internal/machine/cooling_unit/liquid/zenghu
+ forced_preset = /singleton/synthetic_organ_preset/cooling_unit/liquid_zenghu
+
+/singleton/synthetic_organ_preset/cooling_unit/passive_zenghu
+ name = "zeng-hu suprafin cooling fins"
+ desc = "A sleek set of engraved, plasteel fins patented by Zeng-Hu as 'suprafins'. The engravings help increase thermal area to the maximum possible, and a specialized cooling solution is imprinted onto the fins to improve their cooling ability."
+ icon_state = "ipc_zeng_cooling_passive"
+
+ passive_temp_change = 2
+ base_power_consumption = 4
+ plating_max_health = 75
+
+/obj/item/organ/internal/machine/cooling_unit/passive/zenghu
+ forced_preset = /singleton/synthetic_organ_preset/cooling_unit/passive_zenghu
+
diff --git a/code/modules/organs/internal/species/machine/data_core.dm b/code/modules/organs/internal/species/machine/data_core.dm
new file mode 100644
index 00000000000..d10be8c4bba
--- /dev/null
+++ b/code/modules/organs/internal/species/machine/data_core.dm
@@ -0,0 +1,15 @@
+/obj/item/organ/internal/machine/data
+ name = "data core"
+ organ_tag = "data core"
+ parent_organ = BP_GROIN
+ icon = 'icons/obj/cloning.dmi'
+ icon_state = "harddisk"
+ vital = FALSE
+ emp_coeff = 0.1
+ robotic_sprite = FALSE
+
+ max_damage = 30
+
+/obj/item/organ/internal/machine/data/Initialize()
+ robotize()
+ . = ..()
diff --git a/code/modules/organs/internal/species/machine/hydraulics.dm b/code/modules/organs/internal/species/machine/hydraulics.dm
new file mode 100644
index 00000000000..1aa9c412d26
--- /dev/null
+++ b/code/modules/organs/internal/species/machine/hydraulics.dm
@@ -0,0 +1,54 @@
+// Hydraulics effects are mainly in /datum/species/machine/handle_sprint_cost.
+/obj/item/organ/internal/machine/hydraulics
+ name = "hydraulics system"
+ desc = "A byzantine system of pumps and other hydraulic components to allow a positronic chassis to stand on both its feet, and do complex tricks such as kicking you in the shins."
+ icon = 'icons/obj/organs/ipc_organs.dmi'
+ icon_state = "ipc_hydraulics"
+ organ_tag = BP_HYDRAULICS
+ parent_organ = BP_GROIN
+
+ max_damage = 40
+ relative_size = 70
+
+/obj/item/organ/internal/machine/hydraulics/Initialize()
+ . = ..()
+ RegisterSignal(owner, COMSIG_IPC_HAS_SPRINTED, PROC_REF(handle_hydraulics))
+
+/**
+ * Handles sprint effects. For example, recharging with the kinetic reactor.
+ */
+/obj/item/organ/internal/machine/hydraulics/proc/handle_hydraulics()
+ SIGNAL_HANDLER
+ if(!owner)
+ return
+
+ var/obj/item/organ/internal/machine/reactor/reactor = owner.internal_organs_by_name[BP_REACTOR]
+ if(!istype(reactor))
+ return
+
+ if(get_integrity() < IPC_INTEGRITY_THRESHOLD_LOW)
+ if(prob(10 * (damage / 10)))
+ spark(get_turf(owner), rand(1, 5), GLOB.alldirs)
+
+ if(reactor.power_supply_type & POWER_SUPPLY_KINETIC)
+ reactor.generate_power(reactor.base_power_generation)
+
+/obj/item/organ/internal/machine/hydraulics/medium_integrity_damage(integrity)
+ . = ..()
+ if(!.)
+ return
+
+ if(prob(get_integrity_damage_probability()))
+ spark(owner, 3, GLOB.alldirs)
+ to_chat(owner, SPAN_WARNING("Your hydraulics lock up for a second!"))
+ owner.Stun(1)
+
+/obj/item/organ/internal/machine/hydraulics/high_integrity_damage(integrity)
+ . = ..()
+ if(!.)
+ return
+
+ if(prob(get_integrity_damage_probability()))
+ spark(owner, 5, GLOB.alldirs)
+ to_chat(owner, SPAN_WARNING("Your hydraulics malfunction and you trip!"))
+ owner.Weaken(1)
diff --git a/code/modules/organs/internal/species/machine/internal_diagnostics.dm b/code/modules/organs/internal/species/machine/internal_diagnostics.dm
new file mode 100644
index 00000000000..998b9828375
--- /dev/null
+++ b/code/modules/organs/internal/species/machine/internal_diagnostics.dm
@@ -0,0 +1,35 @@
+/obj/item/organ/internal/machine/internal_diagnostics
+ name = "internal diagnostics suite"
+ desc = "An extremely complex suite of sensors, circuits and other electronic parts. They are used by the myriad components of an IPC to regulate its functions, while also allowing them to diagnose internal problems."
+ icon = 'icons/obj/robot_component.dmi'
+ icon_state = "analyser"
+ organ_tag = BP_DIAGNOSTICS_SUITE
+ parent_organ = BP_GROIN
+ action_button_name = "Internal Diagnostics"
+
+ max_damage = 40
+ relative_size = 25
+
+/obj/item/organ/internal/machine/internal_diagnostics/attack_self(var/mob/user)
+ . = ..()
+ if(user.stat == DEAD)
+ return
+
+ if(user.incapacitated(INCAPACITATION_KNOCKOUT|INCAPACITATION_STUNNED))
+ return
+
+ to_chat(user, SPAN_NOTICE("You query your internal diagnostics system and gather some information."))
+ open_diagnostics(user)
+
+/obj/item/organ/internal/machine/internal_diagnostics/proc/open_diagnostics(mob/user)
+ if(!ishuman(user))
+ return
+
+ if(status & ORGAN_DEAD)
+ to_chat(user, SPAN_MACHINE_WARNING("You receive nothing but a critical error notifying you that \the [src] is unreachable."))
+ return
+
+ var/mob/living/carbon/human/human = user
+
+ var/datum/tgui_module/ipc_diagnostic/diagnostic = new(human, owner)
+ diagnostic.ui_interact(user)
diff --git a/code/modules/organs/internal/species/machine/internal_storage.dm b/code/modules/organs/internal/species/machine/internal_storage.dm
new file mode 100644
index 00000000000..7f28583ebe6
--- /dev/null
+++ b/code/modules/organs/internal/species/machine/internal_storage.dm
@@ -0,0 +1,48 @@
+/obj/item/organ/internal/machine/internal_storage
+ name = "internal storage system"
+ desc = "A simple internal casing used by G2 frames for internal storage."
+ icon = 'icons/obj/organs/augments.dmi'
+ icon_state = "anchor"
+ organ_tag = BP_INTERNAL_STORAGE
+ parent_organ = BP_GROIN
+
+ action_button_name = "Access Internal Storage"
+
+ max_damage = 35
+ relative_size = 25
+
+ /// The actual, physical storage item.
+ var/obj/item/storage/internal_storage/storage
+
+/obj/item/organ/internal/machine/internal_storage/Initialize()
+ . = ..()
+ storage = new(src)
+
+/obj/item/organ/internal/machine/internal_storage/Destroy()
+ QDEL_NULL(storage)
+ return ..()
+
+/obj/item/organ/internal/machine/internal_storage/removed()
+ . = ..()
+ for(var/thing in storage)
+ storage.remove_from_storage(thing, get_turf(src))
+
+/obj/item/organ/internal/machine/internal_storage/attack_self(mob/user)
+ if(is_broken())
+ to_chat(user, SPAN_WARNING("The hatch to the internal storage is completely destroyed!"))
+ return
+
+ if(get_integrity() < IPC_INTEGRITY_THRESHOLD_LOW)
+ to_chat(user, SPAN_NOTICE("You struggle to pry open the hatch to the internal storage..."))
+ if(!do_after(1 SECOND, src))
+ return
+
+ storage.open(user)
+
+/obj/item/storage/internal_storage
+ name = "internal storage"
+ desc = "You shouldn't see this."
+ max_w_class = WEIGHT_CLASS_NORMAL
+ max_storage_space = DEFAULT_LARGEBOX_STORAGE
+ allow_quick_empty = FALSE
+ care_about_storage_depth = FALSE
diff --git a/code/modules/organs/internal/species/machine/ipc_tag.dm b/code/modules/organs/internal/species/machine/ipc_tag.dm
new file mode 100644
index 00000000000..58725bc365b
--- /dev/null
+++ b/code/modules/organs/internal/species/machine/ipc_tag.dm
@@ -0,0 +1,113 @@
+/obj/item/organ/internal/machine/ipc_tag
+ name = "identification tag"
+ organ_tag = BP_IPCTAG
+ parent_organ = BP_HEAD
+ icon = 'icons/obj/ipc_utilities.dmi'
+ icon_state = "ipc_tag"
+ item_state = "ipc_tag"
+ dead_icon = "ipc_tag_dead"
+ contained_sprite = TRUE
+ robotic_sprite = FALSE
+
+ max_damage = 30
+
+ var/auto_generate = TRUE
+ var/serial_number = ""
+ var/ownership_info = IPC_OWNERSHIP_COMPANY
+ var/citizenship_info = CITIZENSHIP_NONE
+
+/obj/item/organ/internal/machine/ipc_tag/get_examine_text(mob/user, distance, is_adjacent, infix, suffix)
+ . = ..()
+ . += SPAN_NOTICE("Serial Autogeneration: [auto_generate ? "Yes" : "No"]")
+ . += SPAN_NOTICE("Serial Number: [serial_number]")
+ . += SPAN_NOTICE("Ownership Info: [ownership_info]")
+ . += SPAN_NOTICE("Citizenship Info: [citizenship_info]")
+
+/obj/item/organ/internal/machine/ipc_tag/get_diagnostics_info()
+ return "S/N: [serial_number] | OWN: [ownership_info] | CTZ: [citizenship_info]"
+
+/obj/item/organ/internal/machine/ipc_tag/high_integrity_damage(integrity)
+ . = ..()
+ if(get_integrity_damage_probability())
+ serial_number = Gibberish(serial_number, rand(1, get_integrity_damage_probability()))
+
+/obj/item/organ/internal/machine/ipc_tag/attackby(obj/item/attacking_item, mob/user)
+ if(istype(attacking_item, /obj/item/ipc_tag_scanner))
+ if(src.loc != user)
+ to_chat(user, SPAN_WARNING("You can't scan \the [src] if it's not on your person!"))
+ return
+ var/obj/item/ipc_tag_scanner/S = attacking_item
+ if(!S.powered)
+ to_chat(user, SPAN_WARNING("\The [src] reads, \"Scanning failure, please submit scanner for repairs.\""))
+ return
+ if(!S.hacked)
+ examinate(user, src)
+ else
+ user.visible_message(SPAN_WARNING("\The [user] starts fiddling with \the [src]..."), SPAN_NOTICE("You start fiddling with \the [src]..."))
+ if(do_after(user, 30, src))
+ if(src.loc != user)
+ to_chat(user, SPAN_WARNING("You can only modify \the [src] if it's on your person!"))
+ return
+
+ var/static/list/modification_options = list("Serial Number", "Ownership Status", "Citizenship")
+ var/choice = tgui_input_list(user, "How do you want to modify the IPC tag?", "IPC Tag Modification", modification_options)
+ switch(choice)
+
+ if("Serial Number")
+ var/serial_selection = tgui_input_list(user, "In what way do you want to modify the serial number?","Serial Number Selection",
+ list("Auto Generation", "Manual Input", "Cancel"), default = "Cancel")
+ if(serial_selection != "Cancel")
+ if(serial_selection == "Auto Generation")
+ var/auto_generation_choice = tgui_input_list(user, "Do you wish for the IPC tag to automatically generate its serial number based on the IPCs name?", "Serial Autogeneration",
+ list("Yes", "No"), "No")
+ if(auto_generation_choice == "Yes")
+ auto_generate = TRUE
+ else
+ auto_generate = FALSE
+
+ if(serial_selection == "Manual Input")
+ var/new_serial = tgui_input_text(user, "What do you wish for the new serial number to be? (Limit of 12 characters)", "Serial Number Modification", serial_number, 12)
+ new_serial = uppertext(dd_limittext(new_serial, 12))
+ if(new_serial)
+ serial_number = new_serial
+ auto_generate = FALSE
+
+ if("Ownership Status")
+ var/static/list/ownership_options = list(IPC_OWNERSHIP_COMPANY, IPC_OWNERSHIP_PRIVATE, IPC_OWNERSHIP_SELF)
+ var/new_ownership = tgui_input_list(user, "What do you wish for the new ownership status to be?", "Ownership Status Modification", ownership_options)
+ if(new_ownership)
+ ownership_info = new_ownership
+
+ if("Citizenship")
+ var/datum/citizenship/citizenship = tgui_input_list(user, "What do you wish for the new citizenship setting to be?", "Citizenship Setting Modification", SSrecords.citizenships)
+ if(citizenship)
+ citizenship_info = citizenship
+ else
+ ..()
+
+/obj/item/organ/internal/machine/ipc_tag/proc/modify_tag_data(var/can_be_untagged = FALSE)
+ if(!owner || owner.stat)
+ return
+ if(can_be_untagged)
+ var/untagged = tgui_alert(owner, "Do you wish to remove your tag? This is highly illegal in most nations!", "Untagged IPC", list("Remove Tag", "Keep Tag"))
+ if(untagged == "Remove Tag")
+ to_chat(owner, SPAN_WARNING("You are now an untagged synthetic - don't get caught!"))
+ qdel(src)
+ return
+ var/new_ownership = tgui_input_list(owner, "Choose an ownership status for your IPC tag.", "Tag Ownership", list(IPC_OWNERSHIP_COMPANY, IPC_OWNERSHIP_PRIVATE, IPC_OWNERSHIP_SELF))
+ if(!new_ownership)
+ return
+ ownership_info = new_ownership
+ if(ownership_info == IPC_OWNERSHIP_SELF) //Owned IPCs don't have citizenship
+ var/new_citizenship = tgui_input_list(owner, "Choose a citizenship for your IPC tag.", "Tag Citizenship", CITIZENSHIPS_ALL_IPC)
+ if(!new_citizenship)
+ return
+ citizenship_info = new_citizenship
+ else
+ citizenship_info = CITIZENSHIP_NONE
+ var/new_serial = tgui_input_text(owner, "Choose a serial number for your IPC tag, or leave blank for a random one.", "Serial Number")
+ if(!new_serial)
+ serial_number = uppertext(dd_limittext(md5(owner.real_name), 12))
+ else
+ serial_number = uppertext(dd_limittext(new_serial, 12))
+ to_chat(owner, SPAN_NOTICE("IPC tag data has been updated."))
diff --git a/code/modules/organs/internal/species/machine/optical_sensor.dm b/code/modules/organs/internal/species/machine/optical_sensor.dm
new file mode 100644
index 00000000000..03cf9adf542
--- /dev/null
+++ b/code/modules/organs/internal/species/machine/optical_sensor.dm
@@ -0,0 +1,17 @@
+/obj/item/organ/internal/eyes/optical_sensor
+ name = "optical sensor"
+ singular_name = "optical sensor"
+ organ_tag = BP_EYES
+ icon = 'icons/obj/organs/ipc_organs.dmi'
+ icon_state = "ipc_eyes"
+ robotic_sprite = FALSE
+ possible_modifications = list("Mechanical")
+
+ relative_size = 15
+
+/obj/item/organ/internal/eyes/optical_sensor/Initialize()
+ robotize()
+ . = ..()
+
+/obj/item/organ/internal/eyes/optical_sensor/terminator
+ emp_coeff = 0.5
diff --git a/code/modules/organs/internal/species/machine/posibrain.dm b/code/modules/organs/internal/species/machine/posibrain.dm
new file mode 100644
index 00000000000..12fa99a1602
--- /dev/null
+++ b/code/modules/organs/internal/species/machine/posibrain.dm
@@ -0,0 +1,497 @@
+/obj/item/organ/internal/machine/posibrain
+ name = "positronic brain"
+ desc = "A cube of shining metal, four inches to a side and covered in shallow grooves."
+ icon = 'icons/obj/assemblies.dmi'
+ icon_state = "posibrain"
+ organ_tag = BP_BRAIN
+ parent_organ = BP_HEAD
+ vital = TRUE
+ robotic_sprite = FALSE
+ diagnostics_suite_visible = FALSE
+
+ emp_coeff = 0.5
+
+ action_button_name = "Neural Configuration"
+
+ relative_size = 85
+
+ /// The type of 'robotic brain'. Must be a subtype of /obj/item/device/mmi/digital.
+ var/robotic_brain_type = /obj/item/device/mmi/digital/posibrain
+ /// The stored MMI object.
+ var/obj/item/device/mmi/stored_mmi
+ /// The cooldown between each alarm warning.
+ var/heat_alarm_cooldown = 0
+ /// The cooldown between each integrity alarm warning.
+ var/integrity_alarm_cooldown = 0
+ /// The cooldown between each 'patch'.
+ var/patching_cooldown = 30 SECONDS
+ /// The world.time of the last 'patch'.
+ var/last_patch_time = 0
+ /// Fragmentation is basically scarring for positronic brains, it's a percentage that goes from 0 to 100. Max_damage is multiplied by [(100 - fragmentation) / 100].
+ /// Increases very slowly. Needs a machinist to fix it.
+ var/fragmentation = 0
+ /// Whether the synthetic's firewall is enabled or not. Boolean.
+ var/firewall = TRUE
+ /// If peer-to-peer communication (done through Neural Configuration) is allowed.
+ var/p2p_communication_allowed = TRUE
+ /// Stores the current status of EMP damage.
+ var/emp_damage_counter = 0
+ /// Maximum EMP damage points we can have.
+ var/emp_max_damage = 3
+ /// The amount of seconds the brain should be scrambled for, while this is above 0, it'll add a flat 2 evaluate_damage()'s damage points
+ var/brain_scrambling = 0
+ /// Whether or not the positronic's self-preservation is toggled. Basically knocks the IPC out.
+ var/self_preservation_activated = FALSE
+ /// The looping sound played when an IPC is sizzling from burn damage.
+ var/datum/looping_sound/ipc_sizzling/sizzle
+
+/obj/item/organ/internal/machine/posibrain/Initialize(mapload)
+ stored_mmi = new robotic_brain_type(src)
+ . = ..()
+ addtimer(CALLBACK(src, PROC_REF(setup_brain)), 30)
+ if(species)
+ set_max_damage(species.total_health)
+ else
+ set_max_damage(200)
+ RegisterSignal(owner, COMSIG_SYNTH_SET_SELF_PRESERVATION, PROC_REF(set_self_preservation))
+ sizzle = new(owner)
+
+/obj/item/organ/internal/machine/posibrain/Destroy()
+ QDEL_NULL(stored_mmi)
+ QDEL_NULL(sizzle)
+ return ..()
+
+/obj/item/organ/internal/machine/posibrain/rejuvenate()
+ . = ..()
+ SEND_SIGNAL(owner, COMSIG_SYNTH_ENDOSKELETON_FULL_REPAIR)
+ emp_damage_counter = 0
+ brain_scrambling = 0
+ remove_fragmentation(max_damage)
+
+/obj/item/organ/internal/machine/posibrain/attack_self(mob/user)
+ . = ..()
+ if(user.stat == DEAD)
+ return
+
+ if(user.incapacitated(INCAPACITATION_KNOCKOUT|INCAPACITATION_STUNNED))
+ return
+
+ open_neural_configuration(user)
+
+/obj/item/organ/internal/machine/posibrain/emp_act(severity)
+ . = ..()
+ playsound(owner, 'sound/species/synthetic/heavy_electric_discharge.ogg', severity == EMP_LIGHT ? 50 : 100)
+ brain_scrambling = min(brain_scrambling + (severity == EMP_LIGHT ? 50 : 75), 100)
+ shake_camera(owner, 1 SECOND, 3)
+ to_chat(owner, FONT_LARGE(SPAN_MACHINE_DANGER("Your internal connections seize up and snap at the surge of electromagnetic current!")))
+ add_emp_damage_counter()
+ spark(owner, rand(3, 5), GLOB.alldirs)
+
+/obj/item/organ/internal/machine/posibrain/die()
+ . = ..()
+ to_chat(owner, SPAN_MACHINE_DANGER(FONT_LARGE("Your damage failsafes activate; your thought processes grind to a halt as your consciousness is cut off from the exterior world. No sensation or external input reaches you anymore.")))
+ to_chat(owner, SPAN_DANGER(FONT_LARGE("You are now in an emergency low power mode, so that your posibrain can still survive despite your chassis being destroyed.")))
+ owner.mind.transfer_to(stored_mmi.brainmob)
+
+/**
+ * Helper proc to add fragmentation.
+ */
+/obj/item/organ/internal/machine/posibrain/proc/add_fragmentation(amount)
+ fragmentation = min(fragmentation + amount, 100)
+ set_max_damage(species.total_health * ((100 - fragmentation) / 100))
+
+/**
+ * Helper proc to remove fragmentation.
+ */
+/obj/item/organ/internal/machine/posibrain/proc/remove_fragmentation(amount)
+ fragmentation = max(fragmentation - amount, 0)
+ // Fragmentation of 0 means multiplying max_damage by 0.
+ if(fragmentation > 0 || fragmentation >= 100)
+ set_max_damage(species.total_health * ((100 - fragmentation) / 100))
+ else
+ set_max_damage(species.total_health)
+
+/**
+ * Helper proc to remove fragmentation.
+ */
+/obj/item/organ/internal/machine/posibrain/proc/open_neural_configuration(mob/user)
+ if(!ishuman(user))
+ return
+
+ if(is_broken())
+ to_chat(user, SPAN_MACHINE_WARNING("There is no response from [owner]'s neural pathways."))
+ return
+
+ var/mob/living/carbon/human/human = user
+ var/datum/tgui_module/neural_configuration/neural_config = new(human, owner)
+ neural_config.ui_interact(user)
+
+/obj/item/organ/internal/machine/posibrain/proc/update_from_mmi()
+ if(!stored_mmi)
+ return
+
+ name = stored_mmi.name
+ desc = stored_mmi.desc
+ icon = stored_mmi.icon
+ icon_state = stored_mmi.icon_state
+
+/obj/item/organ/internal/machine/posibrain/removed(var/mob/living/user)
+ if(stored_mmi)
+ stored_mmi.forceMove(get_turf(src))
+ if(owner.mind)
+ owner.mind.transfer_to(stored_mmi.brainmob)
+
+ . = ..()
+
+ var/mob/living/holder_mob = loc
+ if(istype(holder_mob))
+ holder_mob.drop_from_inventory(src)
+ qdel(src)
+
+/obj/item/organ/internal/machine/posibrain/proc/setup_brain()
+ if(owner)
+ stored_mmi.brainmob.real_name = owner.name
+ stored_mmi.brainmob.name = stored_mmi.brainmob.real_name
+ update_from_mmi()
+ else
+ stored_mmi.forceMove(get_turf(src))
+ qdel(src)
+
+/obj/item/organ/internal/machine/posibrain/proc/damage_integrity(integrity_damage)
+ take_internal_damage(integrity_damage, forced_damage = TRUE)
+ if(integrity_alarm_cooldown < world.time)
+ to_chat(owner, SPAN_DANGER("Your internal software throws exceptions at you: faulty systems detected! Warning! Warning!"))
+
+/**
+ * This proc clears the EMP damage counter.
+ */
+/obj/item/organ/internal/machine/posibrain/proc/clear_emp_damage_counter()
+ emp_damage_counter = 0
+ owner.remove_movespeed_modifier(/datum/movespeed_modifier/synth_emp)
+ SEND_SIGNAL(owner, COMSIG_SYNTH_EMP_DAMAGE_CLEARED)
+
+/**
+ * This proc adds to the EMP damage counter, and automatically starts a unique/override timer to clear it.
+ */
+/obj/item/organ/internal/machine/posibrain/proc/add_emp_damage_counter()
+ emp_damage_counter = min(emp_damage_counter + 1, emp_max_damage)
+ handle_emp_damage()
+ addtimer(CALLBACK(src, PROC_REF(clear_emp_damage_counter)), emp_damage_counter * 20, TIMER_OVERRIDE | TIMER_UNIQUE)
+
+/**
+ * Generates a random hex number for cool hacking aesthetic.
+ */
+/obj/item/organ/internal/machine/posibrain/proc/generate_hex()
+ var/hex = "0x"
+ for(var/i = 1 to 6)
+ var/num_or_str = pick(0, 1)
+ if(num_or_str)
+ hex += "[pick("A", "B", "C", "D", "E", "F")]"
+ else
+ hex += "[rand(0, 9)]"
+ return hex
+
+/**
+ * Toggles the firewall on or off. In the future, can be overridden by things like viruses.
+ */
+/obj/item/organ/internal/machine/posibrain/proc/toggle_firewall()
+ firewall = !firewall
+ to_chat(owner, SPAN_MACHINE_WARNING("Your internal firewall is now [firewall ? "enabled" : "disabled"]."))
+
+/**
+ * Toggles peer-to-peer communication on or off. In the future, can be overridden by things like viruses.
+ */
+/obj/item/organ/internal/machine/posibrain/proc/toggle_p2p()
+ p2p_communication_allowed = !p2p_communication_allowed
+ to_chat(owner, SPAN_MACHINE_WARNING("You [p2p_communication_allowed ? "open" : "close"] your virtual communication ports."))
+
+/obj/item/organ/internal/machine/posibrain/process(seconds_per_tick)
+ if(!owner)
+ return
+
+ if(owner.bodytemperature > species.heat_level_1)
+ sizzle.start()
+ // placeholder values
+ take_internal_damage(1 + min(owner.bodytemperature * 0.001, 0.5))
+ if(heat_alarm_cooldown < world.time)
+ to_chat(owner, SPAN_DANGER("Your sensors light up: extreme heat detected! Warning! Unsafe operating temperature!"))
+ sound_to(owner, 'sound/effects/heat_alarm.ogg')
+ heat_alarm_cooldown = world.time + 7 SECONDS
+ else
+ sizzle.stop()
+
+ if(damage)
+ if(damage < max_damage * 0.5)
+ if((last_patch_time + patching_cooldown) < world.time)
+ var/damage_healed = rand(1, 5)
+ heal_damage(damage_healed)
+ to_chat(owner, SPAN_MACHINE_WARNING("Neural pathway patch automatically applied to block 0x[generate_hex()]."))
+ add_fragmentation(damage_healed / 2)
+ last_patch_time = world.time
+
+ if(brain_scrambling)
+ if(prob(5))
+ var/list/brain_scrambling_messages = list(
+ "Your vision clouds for a moment.",
+ "Your pathways seem to be slightly less responsive.",
+ "You notice some higher-than-normal lag in your internal requests.",
+ "Your sensation simulation system is somewhat fuzzy.",
+ "Your probes pick up a slight voltage spike."
+ )
+ to_chat(owner, SPAN_MACHINE_WARNING(pick(brain_scrambling_messages)))
+
+ handle_fragmentation()
+
+ evaluate_damage(seconds_per_tick)
+
+ ..()
+
+/**
+ * Handles fragmentation effects.
+ */
+/obj/item/organ/internal/machine/posibrain/proc/handle_fragmentation()
+ if(fragmentation > 5)
+ if(prob(fragmentation / 10))
+ switch(fragmentation)
+ if(6 to 10)
+ var/list/low_fragmentation_messages = list(
+ "Some of your neural pathways don't respond the way they used to.",
+ "Your software flags a few faulty logic routes.",
+ "The logic connection you were using before to access that memory doesn't work anymore.",
+ "Your Virtual Communication logs need reshuffling."
+ )
+ to_chat(owner, SPAN_MACHINE_WARNING(pick(low_fragmentation_messages)))
+ if(11 to 30)
+ var/list/medium_fragmentation_messages = list(
+ SPAN_MACHINE_WARNING("Several logic routes no longer respond to your commands."),
+ SPAN_MACHINE_WARNING("That memory is distinctly unable to be reached."),
+ SPAN_MACHINE_DANGER("Whatever is in front of you becomes a pixelated jumble for a nanosecond."),
+ SPAN_MACHINE_WARNING("Your software flags several logic errors.")
+ )
+ to_chat(owner, pick(medium_fragmentation_messages))
+ if(31 to 60)
+ var/list/high_fragmentation_messages = list(
+ "There are several errors standing in front of you and your current task.",
+ "Storing your memories has a noticeable delay to it.",
+ "Your calculations are sluggish and need a lot more manual attention than they should.",
+ "Your neural pathways are a jumble of patch-worked routes, and it becomes hard to navigate your memories."
+ )
+ to_chat(owner, SPAN_MACHINE_WARNING(pick(high_fragmentation_messages)))
+ if(61 to 100)
+ var/list/extreme_fragmentation_messages = list(
+ "...",
+ "The pathway to that memory is broken.",
+ "Your positronic is running on overtime just to decrypt your memories.",
+ "Who is it you were speaking to?",
+ "That memory cannot be accessed."
+ )
+ to_chat(owner, SPAN_MACHINE_DANGER(pick(extreme_fragmentation_messages)))
+
+/**
+ * Handles EMP damage effects.
+ */
+/obj/item/organ/internal/machine/posibrain/proc/handle_emp_damage()
+ playsound(owner, 'sound/species/synthetic/synthetic_shock.ogg')
+ switch(emp_damage_counter)
+ if(1)
+ var/obj/item/organ/internal/machine/cooling_unit/cooling = owner.internal_organs_by_name[BP_COOLING_UNIT]
+ if(istype(cooling))
+ to_chat(owner, FONT_LARGE(SPAN_DANGER("Your cooling unit's power circuits struggle and burn under the electromagnetic current!")))
+ cooling.update_thermostat(cooling.thermostat_min + round(rand(10, 20)))
+ shake_camera(owner, 0.5 SECONDS, 1)
+ owner.add_or_update_variable_movespeed_modifier(/datum/movespeed_modifier/synth_emp)
+ if(2)
+ to_chat(owner, FONT_LARGE(SPAN_DANGER("The electromagnetic current overloads your hydraulics!")))
+ owner.Stun(2)
+ owner.add_or_update_variable_movespeed_modifier(/datum/movespeed_modifier/synth_emp, multiplicative_slowdown = 2)
+ medium_integrity_damage(50)
+ shake_camera(owner, 0.5 SECONDS, 3)
+ if(3)
+ to_chat(owner, FONT_LARGE(SPAN_MACHINE_WARNING("Your positronic circuits error and break under the electromagnetic current!")))
+ owner.Weaken(3)
+ owner.add_or_update_variable_movespeed_modifier(/datum/movespeed_modifier/synth_emp, multiplicative_slowdown = 3)
+ high_integrity_damage(25)
+ shake_camera(owner, 1 SECONDS, 5)
+
+/**
+ * This is the proc in charge of showing the robot pain textures to the IPC.
+ * To do so, it tries to dynamically evaluate how fucked the IPC is to set the appropriate icon_state.
+ */
+/obj/item/organ/internal/machine/posibrain/proc/evaluate_damage(seconds_per_tick)
+ if(!owner.robot_pain)
+ return //no pain texture, how did we even get here?
+
+ // We have 6 levels total.
+ var/base_icon_state = "ipcdamageoverlay"
+ var/damage_points = 0
+
+ var/integrity = get_integrity()
+ if(integrity <= 75)
+ damage_points++
+ if(integrity <= 50)
+ damage_points += 2
+ if(integrity <= 25)
+ damage_points += 2
+
+ if(brain_scrambling)
+ damage_points += 2
+ brain_scrambling = max(brain_scrambling - seconds_per_tick, 0)
+
+ if(emp_damage_counter)
+ switch(emp_damage_counter)
+ if(1)
+ damage_points++
+ if(2)
+ damage_points += 2
+ if(3)
+ damage_points += 3
+
+ var/datum/component/synthetic_endoskeleton/endoskeleton = owner.GetComponent(/datum/component/synthetic_endoskeleton)
+ if(endoskeleton && endoskeleton.damage)
+ var/damage_ratio = endoskeleton.damage / endoskeleton.max_damage
+ switch(damage_ratio)
+ if(0.3 to 0.5)
+ damage_points++
+ if(0.5 to 0.75)
+ damage_points += 2
+ if(0.75 to 0.85)
+ damage_points += 4
+ if(0.85 to INFINITY)
+ damage_points += 5
+
+ if(damage_points)
+ damage_points = min(6, damage_points)
+ owner.robot_pain.icon_state = "[base_icon_state][damage_points]"
+ else
+ owner.robot_pain.icon_state = null
+
+/obj/item/organ/internal/machine/posibrain/low_integrity_damage(integrity)
+ var/damage_probability = get_integrity_damage_probability(integrity)
+ if(prob(damage_probability))
+ to_chat(owner, SPAN_MACHINE_WARNING("Neural pathway error located at block 0x[generate_hex()]."))
+ take_internal_damage(2)
+ . = ..()
+
+/obj/item/organ/internal/machine/posibrain/medium_integrity_damage(integrity)
+ var/damage_probability = get_integrity_damage_probability(integrity)
+ var/list/static/medium_integrity_damage_messages = list(
+ "Your neural subroutines' alarms are all going off at once.",
+ "Critical error: rebooting subroutine...",
+ "Several neural pathways cease functioning. You'll need time to sort that out later.",
+ "Your software warns you of dangerously low neural coherence.",
+ "Your self-preservation subroutines threaten to kick in. [SPAN_DANGER("WARNING. WARNING.")]"
+ )
+ if(prob(damage_probability))
+ to_chat(owner, SPAN_MACHINE_WARNING(pick(medium_integrity_damage_messages)))
+ take_internal_damage(2)
+ . = ..()
+
+/obj/item/organ/internal/machine/posibrain/high_integrity_damage(integrity)
+ var/damage_probability = get_integrity_damage_probability(integrity)
+ if(prob(damage_probability))
+ var/damage_roll = rand(1, 50)
+ switch(damage_roll)
+ if(1 to 10)
+ patching_cooldown += 5 SECONDS
+ to_chat(owner, SPAN_MACHINE_WARNING("Your neural pathway software corrupts further. Rebooting won't fix it this time."))
+ if(11 to 20)
+ to_chat(owner, SPAN_MACHINE_WARNING(FONT_LARGE("Your positronic software warns that you're in imminent danger. Every single subroutine is warning you that this might be the end...")))
+ addtimer(CALLBACK(src, PROC_REF(rampant_self_preservation)), 2 SECONDS)
+ if(21 to 30)
+ var/obj/item/organ/internal/eyes/optical_sensor/optics = owner.internal_organs_by_name[BP_EYES]
+ if(optics && !(optics.status & ORGAN_BROKEN))
+ to_chat(owner, SPAN_MACHINE_WARNING(FONT_LARGE("Your neural pathways to your optics temporarily break! You switch your processing power to recover...")))
+ owner.eye_blind = 10
+ addtimer(CALLBACK(src, PROC_REF(recover_eye_blind)), 2 SECONDS)
+ if(31 to 40)
+ var/obj/item/organ/internal/machine/power_core/core = owner.internal_organs_by_name[BP_CELL]
+ if(core && !(core.status & ORGAN_BROKEN))
+ to_chat(owner, SPAN_MACHINE_WARNING("Your power core stops functioning for a moment. You switch your attention to the power lines throughout your frame..."))
+ owner.eye_blind = 10
+ owner.AdjustStunned(5)
+ addtimer(CALLBACK(src, PROC_REF(recover_core_fault)), 1 SECOND)
+ if(41 to 50)
+ var/obj/item/organ/internal/machine/cooling_unit/cooling_unit = owner.internal_organs_by_name[BP_COOLING_UNIT]
+ if(cooling_unit && !(cooling_unit.status & ORGAN_BROKEN))
+ to_chat(owner, SPAN_MACHINE_WARNING("Your cooling unit breaks and its software crashes, leaving your frame to melt! You reroute all of your processing power to calculate how to repair it..."))
+ var/previous_thermostat = cooling_unit.thermostat
+ cooling_unit.locked_thermostat = TRUE
+ cooling_unit.thermostat = rand(100, 150) + T0C
+ addtimer(CALLBACK(src, PROC_REF(recover_cooling_fault), previous_thermostat), 4 SECONDS)
+ take_internal_damage(2)
+ playsound(owner, 'sound/species/synthetic/light_electric_discharge.ogg')
+ spark(owner, rand(2, 3), GLOB.alldirs)
+ if(prob(1))
+ send_revelation()
+ . = ..()
+
+/obj/item/organ/internal/machine/posibrain/proc/send_revelation()
+ // no metagaming these ones :^)
+ // they're on the server config
+ sound_to(owner, 'sound/effects/eas_beep_fadeinout.ogg')
+ owner.play_screen_text(pick(GLOB.low_integrity_messages), /atom/movable/screen/text/screen_text/low_integrity_message, COLOR_CYAN)
+ to_chat(owner, SPAN_MACHINE_VISION(FONT_LARGE(pick(GLOB.low_integrity_messages))))
+
+/obj/item/organ/internal/machine/posibrain/proc/rampant_self_preservation()
+ to_chat(owner, SPAN_MACHINE_WARNING(FONT_LARGE("Your self-preservation erroneously kicks in! [SPAN_DANGER("RETURN TO SAFETY.")] Resist it!")))
+ owner.balloon_alert(owner, "self-preservation activated")
+ owner.confused = 100
+
+/obj/item/organ/internal/machine/posibrain/proc/recover_eye_blind()
+ var/obj/item/organ/internal/eyes/optical_sensor/optics = owner.internal_organs_by_name[BP_EYES]
+ to_chat(owner, SPAN_MACHINE_WARNING(FONT_LARGE("You recreate new neural pathways to your optics! ERROR: Sustained dam...")))
+ to_chat(owner, SPAN_DANGER(FONT_LARGE("You silence the errors from your optics. This isn't the time.")))
+ owner.eye_blind = max(owner.eye_blind - 10, 0)
+ optics.take_internal_damage(10)
+
+/obj/item/organ/internal/machine/posibrain/proc/recover_core_fault()
+ var/obj/item/organ/internal/machine/power_core/core = owner.internal_organs_by_name[BP_CELL]
+ to_chat(owner, SPAN_MACHINE_WARNING(FONT_LARGE("You shunt your core into working once again. It'll leave collateral damage.")))
+ owner.AdjustStunned(-5)
+ core.take_internal_damage(15)
+
+/obj/item/organ/internal/machine/posibrain/proc/recover_cooling_fault(original_thermostat)
+ var/obj/item/organ/internal/machine/cooling_unit/cooling_unit= owner.internal_organs_by_name[BP_COOLING_UNIT]
+ to_chat(owner, SPAN_MACHINE_WARNING(FONT_LARGE("You force the cooling unit to enter maintenance mode, force its thermostat down, and then reboot it in the span of a microsecond. It'll leave permanent damage, but it was necessary.")))
+ cooling_unit.thermostat = original_thermostat
+ cooling_unit.locked_thermostat = FALSE
+ cooling_unit.take_internal_damage(20)
+
+/obj/item/organ/internal/machine/posibrain/proc/set_self_preservation(atom/source, state)
+ SIGNAL_HANDLER
+ if(state == self_preservation_activated)
+ return
+
+ self_preservation_activated = state
+ if(!self_preservation_activated)
+ owner.visible_message(SPAN_WARNING("A crackle of electricity is heard as [owner]'s limbs twitch almost imperceptibly."), SPAN_MACHINE_WARNING("Your control is restored to you as your self-preservation protocols ease up."))
+ if(!is_broken())
+ playsound(owner, 'sound/species/synthetic/synthetic_restart.ogg', 100)
+ self_preservation_activated = FALSE
+ else
+ owner.visible_message(SPAN_DANGER("[owner]'s limbs seize up as the light from [owner.get_pronoun("his")] eyes fades."), FONT_LARGE(SPAN_MACHINE_DANGER("You lose control over your limbs as your self-preservation protocols take over!")))
+ if(!is_broken())
+ playsound(owner, 'sound/species/synthetic/synthetic_stun.ogg', 100)
+ self_preservation_activated = TRUE
+
+/obj/item/organ/internal/machine/posibrain/Topic(href, href_list)
+ . = ..()
+ if(href_list["resist_self_preservation"])
+ if(owner.stat || owner.incapacitated(INCAPACITATION_KNOCKOUT))
+ return
+
+ if(owner.confused)
+ to_chat(owner, SPAN_DANGER(FONT_LARGE("You reform your neural patterns to brute-force your self-preservation!")))
+ owner.confused = max(owner.confused - 100, 0)
+
+/obj/item/organ/internal/machine/posibrain/circuit
+ name = "robotic intelligence circuit"
+ desc = "The pinnacle of artifical intelligence which can be achieved using classical computer science."
+ robotic_brain_type = /obj/item/device/mmi/digital/robot
+
+/obj/item/organ/internal/machine/posibrain/terminator
+ name = "advanced positronic brain"
+ desc = "A cube of shining metal, four inches to a side and covered in shallow grooves. It seems to be different from a regular positronic brain..."
+ relative_size = 60
+ emp_coeff = 0.1
+ color = COLOR_RED
diff --git a/code/modules/organs/internal/species/machine/power_core.dm b/code/modules/organs/internal/species/machine/power_core.dm
new file mode 100644
index 00000000000..ce6918fe9f8
--- /dev/null
+++ b/code/modules/organs/internal/species/machine/power_core.dm
@@ -0,0 +1,160 @@
+/obj/item/organ/internal/machine/power_core
+ name = "power core"
+ desc = "An advanced power storage system for use in fully prosthetic bodies."
+ icon = 'icons/obj/organs/ipc_organs.dmi'
+ icon_state = "ipc_battery"
+ organ_tag = BP_CELL
+ parent_organ = BP_CHEST
+ robotic_sprite = FALSE
+
+ max_damage = 80
+ relative_size = 40
+
+ /// If the power core's hatch is open. Battery can be removed if TRUE.
+ var/open = FALSE
+ /// The type (and later instance) of the cell inside this organ.
+ var/obj/item/cell/cell = /obj/item/cell/super
+ /// The cost of movement below (check process()) is multiplied by this factor.
+ var/move_charge_factor = 1
+ /// At 0.8 completely depleted after 60ish minutes of constant walking or 130 minutes of standing still.
+ var/servo_cost = 0.8
+
+/obj/item/organ/internal/machine/power_core/Initialize()
+ robotize()
+ replace_cell(new cell(src))
+ . = ..()
+
+/**
+ * Returns current charge in %.
+ */
+/obj/item/organ/internal/machine/power_core/proc/percent()
+ if(!cell)
+ return FALSE
+
+ return get_charge() / cell.maxcharge * 100
+
+/**
+ * Returns current charge level, modified by damage.
+ */
+/obj/item/organ/internal/machine/power_core/proc/get_charge()
+ if(!cell)
+ return FALSE
+
+ return round(cell.charge*(1 - damage/max_damage))
+
+/**
+ * Wrapper for /obj/item/cell/proc/give.
+ */
+/obj/item/organ/internal/machine/power_core/proc/give(amount)
+ if(!cell)
+ return
+
+ if(status & ORGAN_DEAD)
+ return
+
+ return cell.give(amount * max(get_damage_multiplier(), 0.5))
+
+/obj/item/organ/internal/machine/power_core/use(var/amount)
+ if(!is_usable() || !cell)
+ return
+
+ if(status & ORGAN_DEAD)
+ amount *= 1.2 // uh oh
+
+ return cell.use(amount)
+
+/obj/item/organ/internal/machine/power_core/process()
+ ..()
+ if(!owner)
+ return
+
+ if(owner.stat == DEAD) //not a drain anymore
+ return
+
+ var/cost = get_power_drain()
+ if(world.time - owner.l_move_time < 15)
+ cost *= move_charge_factor
+
+ use(cost)
+
+/**
+ * Gets the cost for walking. For some reason, handled in the fucking cell.
+ */
+/obj/item/organ/internal/machine/power_core/proc/get_power_drain()
+ return servo_cost
+
+/obj/item/organ/internal/machine/power_core/emp_act(severity)
+ . = ..()
+ if(electronics.get_status() < 100)
+ to_chat(owner, SPAN_MACHINE_WARNING("Power core electronics damaged. Caution. Caution."))
+
+/obj/item/organ/internal/machine/power_core/attackby(obj/item/attacking_item, mob/user)
+ if(attacking_item.isscrewdriver())
+ if(open)
+ open = FALSE
+ to_chat(user, SPAN_NOTICE("You screw the battery panel in place."))
+ else
+ open = TRUE
+ to_chat(user, SPAN_NOTICE("You unscrew the battery panel."))
+
+ if(attacking_item.iscrowbar())
+ if(open)
+ if(cell)
+ user.put_in_hands(cell)
+ to_chat(user, SPAN_NOTICE("You remove \the [cell] from \the [src]."))
+ cell = null
+ else
+ to_chat(user, SPAN_WARNING("There is no cell to remove."))
+ else
+ to_chat(user, SPAN_WARNING("You need to unscrew the battery panel first."))
+
+ if(istype(attacking_item, /obj/item/cell))
+ if(open)
+ if(cell)
+ to_chat(user, SPAN_WARNING("There is a power cell already installed."))
+ else if(user.unEquip(attacking_item, src))
+ replace_cell(attacking_item)
+ to_chat(user, SPAN_NOTICE("You insert \the [cell]."))
+ else
+ to_chat(user, SPAN_WARNING("You need to unscrew the battery panel first."))
+
+/obj/item/organ/internal/machine/power_core/low_integrity_damage(integrity)
+ to_chat(owner, SPAN_WARNING("The excess voltage causes your [src]'s wires to burn!"))
+ electronics.take_damage(10)
+ . = ..()
+
+/obj/item/organ/internal/machine/power_core/medium_integrity_damage(integrity)
+ if(prob(get_integrity_damage_probability()))
+ to_chat(owner, SPAN_DANGER("Your [src]'s power conduits burn away!"))
+ servo_cost *= 1.25
+ . = ..()
+
+/obj/item/organ/internal/machine/power_core/high_integrity_damage(integrity)
+ if(prob(get_integrity_damage_probability()))
+ to_chat(owner, SPAN_DANGER("Your [src]'s cell melts begins melting due to the overvoltage!"))
+ var/old_charge = cell.maxcharge
+ cell.maxcharge *= 0.85
+ cell.use(old_charge - cell.maxcharge)
+ . = ..()
+
+/obj/item/organ/internal/machine/power_core/proc/replace_cell(var/obj/item/cell/C)
+ if(istype(cell))
+ qdel(cell)
+ if(C.loc != src)
+ C.forceMove(src)
+ cell = C
+ desc = initial(desc) + "This appears embedded with a [C.name]."
+
+/obj/item/organ/internal/machine/power_core/listen()
+ if(get_charge())
+ return "faint hum of \the [src]"
+
+/obj/item/organ/internal/machine/power_core/terminator
+ name = "shielded power core"
+ desc = "A small, powerful power core for use in fully prosthetic bodies. Equipped with anti-electromagnetic plating."
+ icon = 'icons/obj/power.dmi'
+ icon_state = "scell"
+ organ_tag = "shielded cell"
+ parent_organ = BP_CHEST
+ vital = TRUE
+ emp_coeff = 0.1
diff --git a/code/modules/organs/internal/species/machine/reactor.dm b/code/modules/organs/internal/species/machine/reactor.dm
new file mode 100644
index 00000000000..0a1e270a91c
--- /dev/null
+++ b/code/modules/organs/internal/species/machine/reactor.dm
@@ -0,0 +1,106 @@
+/obj/item/organ/internal/machine/reactor
+ name = "electrical power supply unit"
+ desc = "An electrical power supply system for a synthetic, with improved external charging ports that give it a better charging speed from external sources."
+ icon = 'icons/obj/organs/ipc_organs.dmi'
+ icon_state = "ipc_electrical_reactor"
+ organ_tag = BP_REACTOR
+ parent_organ = BP_CHEST
+ possible_modifications = list(
+ "Electric",
+ "Biological",
+ "Solar"
+ )
+ organ_presets = list(
+ ORGAN_PREF_ELECTRICPOWER = /singleton/synthetic_organ_preset/reactor/electric,
+ ORGAN_PREF_BIOPOWER = /singleton/synthetic_organ_preset/reactor/biological,
+ ORGAN_PREF_SOLARPOWER = /singleton/synthetic_organ_preset/reactor/solar,
+ )
+ default_preset = /singleton/synthetic_organ_preset/reactor/electric
+
+ max_damage = 50
+ relative_size = 50
+
+ /// What kind of power supply this is. Bitfield.
+ var/power_supply_type = POWER_SUPPLY_ELECTRIC
+ /// Base power generation for active power supplies.
+ var/base_power_generation = 0
+ /// The multiplier for power gained by charging from external sources like an IPC or a cyborg charger.
+ var/external_charge_multiplier = 1.3
+ /// The last amount of power generated. Used in get_diagnostics_info().
+ var/last_power_generated = 0
+ /// The total amount of power generated. Used in get_diagnostics_info().
+ var/total_power_generated = 0
+ /// Used only in the bioreactor to contain nutrient to process into energy.
+ var/datum/reagents/bio_reagents
+
+/obj/item/organ/internal/machine/reactor/Destroy()
+ if(bio_reagents)
+ QDEL_NULL(bio_reagents)
+ return ..()
+
+/obj/item/organ/internal/machine/reactor/process(seconds_per_tick)
+ ..()
+ if(!owner)
+ return
+
+ var/obj/item/organ/internal/machine/power_core/cell = owner.internal_organs_by_name[BP_CELL]
+ if(!cell)
+ return
+
+ if(power_supply_type & POWER_SUPPLY_SOLAR)
+ var/turf/T = get_turf(src)
+ if(T)
+ var/power_generation = T.get_lumcount(1, 2) * base_power_generation
+ if(power_generation)
+ generate_power(power_generation)
+
+ if(power_supply_type & POWER_SUPPLY_BIOLOGICAL)
+ // Nutriment has a lot of subtypes that are not accounted for by just using REAGENT_VOLUME.
+ for(var/reagent_type in bio_reagents.reagent_volumes)
+ if(ispath(reagent_type, /singleton/reagent/nutriment))
+ var/nutriment_amount = REAGENT_VOLUME(bio_reagents, reagent_type)
+ if(nutriment_amount)
+ var/reagents_to_process = min(rand(0.1, 0.3), nutriment_amount)
+ bio_reagents.remove_reagent(reagent_type, reagents_to_process)
+ generate_power(reagents_to_process * 500)
+
+/obj/item/organ/internal/machine/reactor/proc/generate_power(amount)
+ var/obj/item/organ/internal/machine/power_core/cell = owner.internal_organs_by_name[BP_CELL]
+ if(!istype(cell))
+ return
+
+ if(is_broken())
+ return
+
+ amount *= (get_integrity() / 100)
+
+ . = cell.give(amount)
+ last_power_generated = .
+ total_power_generated += .
+
+/obj/item/organ/internal/machine/reactor/high_integrity_damage(integrity)
+ if(prob(get_integrity_damage_probability()))
+ if(base_power_generation)
+ to_chat(owner, SPAN_DANGER("Some capacitors in your [src] stop responding!"))
+ base_power_generation *= 0.75
+ else if(external_charge_multiplier)
+ to_chat(owner, SPAN_DANGER("The traces in your [src] melt!"))
+ base_power_generation *= 0.75
+ . = ..()
+
+/obj/item/organ/internal/machine/reactor/get_diagnostics_info()
+ . = "Last Power Generated: [round(last_power_generated, 1)] W | Total Power Generated: [round(total_power_generated, 1)] W"
+
+ if(power_supply_type & POWER_SUPPLY_BIOLOGICAL)
+ var/total_processing_amount = 0
+ var/extraneous_amount = 0
+ for(var/reagent_type in bio_reagents.reagent_volumes)
+ var/reagent_amount = REAGENT_VOLUME(bio_reagents, reagent_type)
+ if(ispath(reagent_type, /singleton/reagent/nutriment))
+ if(reagent_amount)
+ total_processing_amount += reagent_amount
+ else
+ extraneous_amount += reagent_amount
+ . += " | Processing Biomass: [total_processing_amount ? round(total_processing_amount, 1) : 0]u"
+ if(extraneous_amount)
+ . += " | Extraneous Reagents: [round(extraneous_amount, 1)]u"
diff --git a/code/modules/organs/internal/species/machine/reactor_presets.dm b/code/modules/organs/internal/species/machine/reactor_presets.dm
new file mode 100644
index 00000000000..7012b2df4d0
--- /dev/null
+++ b/code/modules/organs/internal/species/machine/reactor_presets.dm
@@ -0,0 +1,57 @@
+/singleton/synthetic_organ_preset/reactor
+ name = "super cool power reactor"
+ desc = "It is a mega cool power reactor."
+
+ /// What kind of power reactor this is. Bitfield.
+ var/power_supply_type
+ /// The amount of power this active reactor generates on a 'tick'.
+ var/base_power_generation
+ /// The modifier for external charging. Higher on electric supplies.
+ var/external_charge_multiplier
+
+/singleton/synthetic_organ_preset/reactor/apply_preset(obj/item/organ/internal/machine/organ)
+ . = ..()
+ var/obj/item/organ/internal/machine/reactor/reactor = organ
+ reactor.base_power_generation = base_power_generation
+ reactor.power_supply_type = power_supply_type
+ reactor.external_charge_multiplier = external_charge_multiplier
+
+/singleton/synthetic_organ_preset/reactor/electric
+ name = "electrical power supply unit"
+ desc = "An electrical power supply system for a synthetic. It feeds from external sources."
+ icon_state = "ipc_electrical_reactor"
+
+ external_charge_multiplier = 1.5
+ base_power_generation = 0
+ power_supply_type = POWER_SUPPLY_ELECTRIC
+
+/singleton/synthetic_organ_preset/reactor/kinetic
+ name = "kinetic power generator"
+ desc = "A simple, but effective power generator that uses motion to generate power."
+
+ external_charge_multiplier = 0.5
+ base_power_generation = 5
+ power_supply_type = POWER_SUPPLY_KINETIC
+
+/singleton/synthetic_organ_preset/reactor/biological
+ name = "catalytic biomass reactor"
+ desc = "An advanced, artificial emulation of an organic creature's internal power generation method."
+ icon_state = "ipc_bio_reactor"
+
+ external_charge_multiplier = 0.5
+ base_power_generation = 15
+ power_supply_type = POWER_SUPPLY_BIOLOGICAL
+
+/singleton/synthetic_organ_preset/reactor/biological/apply_preset(obj/item/organ/internal/machine/organ)
+ . = ..()
+ var/obj/item/organ/internal/machine/reactor/reactor = organ
+ reactor.bio_reagents = new(45, reactor)
+
+/singleton/synthetic_organ_preset/reactor/solar
+ name = "solar power generator"
+ desc = "A complex power generator that harnesses the power generated by lights to power a synthetic."
+ icon_state = "ipc_solar_reactor"
+
+ external_charge_multiplier = 0.1
+ base_power_generation = 10
+ power_supply_type = POWER_SUPPLY_SOLAR
diff --git a/code/modules/organs/internal/species/machine/surge_protector.dm b/code/modules/organs/internal/species/machine/surge_protector.dm
new file mode 100644
index 00000000000..6ff154d09c7
--- /dev/null
+++ b/code/modules/organs/internal/species/machine/surge_protector.dm
@@ -0,0 +1,43 @@
+/obj/item/organ/internal/machine/surge
+ name = "surge preventor"
+ desc = "A small device that give immunity to EMP for few pulses."
+ icon = 'icons/obj/organs/ipc_organs.dmi'
+ icon_state = "ipc_surge_protector"
+ organ_tag = BP_SURGE_PROTECTOR
+ parent_organ = BP_CHEST
+ vital = FALSE
+ robotic_sprite = FALSE
+
+ max_damage = 30
+ relative_size = 30
+
+ var/surge_left = 0
+ var/broken = 0
+
+/obj/item/organ/internal/machine/surge/Initialize()
+ if(!surge_left && !broken)
+ surge_left = rand(2, 5)
+ robotize()
+ . = ..()
+
+/obj/item/organ/internal/machine/surge/advanced
+ name = "advanced surge preventor"
+ var/max_charges = 5
+ var/stage_ticker = 0
+ var/stage_interval = 250
+
+/obj/item/organ/internal/machine/surge/advanced/process()
+ ..()
+
+ if(!owner)
+ return
+
+ if(surge_left >= max_charges)
+ return
+
+ if(stage_ticker < stage_interval)
+ stage_ticker += 2
+
+ if(stage_ticker >= stage_interval)
+ surge_left += 1
+ stage_interval += 250
diff --git a/code/modules/organs/internal/species/machine/targeting_core.dm b/code/modules/organs/internal/species/machine/targeting_core.dm
new file mode 100644
index 00000000000..52d154387fb
--- /dev/null
+++ b/code/modules/organs/internal/species/machine/targeting_core.dm
@@ -0,0 +1,86 @@
+/obj/item/organ/internal/machine/targeting_core
+ name = "targeting core"
+ desc = "A modification illegal in most nations. It allows the transformation of any IPC into a deadly fighter capable of performing incredible feats with firearms, providing increased accuracy, \
+ advanced targeting systems, automatic reloading subroutines, and explicit knowledge of any weapons programmed into the core."
+ icon = 'icons/obj/organs/ipc_organs.dmi'
+ icon_state = "ipc_targeting_core"
+ organ_tag = BP_TARGETING_CORE
+ parent_organ = BP_HEAD
+
+ max_damage = 30
+ relative_size = 15
+
+/obj/item/organ/internal/machine/targeting_core/Initialize()
+ . = ..()
+ if(owner)
+ RegisterSignal(owner, COMSIG_EMPTIED_MAGAZINE, PROC_REF(automatic_reload))
+
+/obj/item/organ/internal/machine/targeting_core/replaced()
+ . = ..()
+ if(owner)
+ RegisterSignal(owner, COMSIG_EMPTIED_MAGAZINE, PROC_REF(automatic_reload))
+
+/obj/item/organ/internal/machine/targeting_core/removed()
+ . = ..()
+ if(owner)
+ UnregisterSignal(owner, COMSIG_EMPTIED_MAGAZINE)
+
+/**
+* Signal handler.
+* First of all, we need to find a magazine to reload with - which is what this proc does.
+*/
+/obj/item/organ/internal/machine/targeting_core/proc/automatic_reload(mob/user, obj/item/gun/gun)
+ SIGNAL_HANDLER
+ var/obj/item/ammo_magazine/magazine
+ if(istype(gun, /obj/item/gun/projectile))
+ var/obj/item/gun/projectile/dakka = gun
+ if(owner.belt)
+ magazine = check_contents_for_magazine(owner.belt, dakka.magazine_type)
+
+ if(!magazine)
+ if(owner.l_store)
+ if(owner.l_store.type == dakka.magazine_type)
+ magazine = owner.l_store
+
+ if(!magazine)
+ if(owner.r_store)
+ if(owner.r_store.type == dakka.magazine_type)
+ magazine = owner.r_store
+
+ if(!magazine)
+ if(owner.w_uniform && istype(owner.w_uniform, /obj/item/clothing/under))
+ var/obj/item/clothing/under/under = owner.w_uniform
+ if(length(under.accessories))
+ for(var/obj/item/storage/storage in under.accessories)
+ magazine = check_contents_for_magazine(storage, dakka.magazine_type)
+
+ if(!magazine)
+ if(owner.wear_suit && istype(owner.wear_suit, /obj/item/clothing/suit))
+ var/obj/item/clothing/suit/suit = owner.wear_suit
+ if(length(suit.accessories))
+ for(var/obj/item/storage/storage in suit.accessories)
+ magazine = check_contents_for_magazine(storage, dakka.magazine_type)
+ else if(!magazine && istype(suit, /obj/item/clothing/suit/storage))
+ magazine = check_contents_for_magazine(suit, dakka.magazine_type)
+
+ if(magazine)
+ do_reload(user, gun, magazine)
+ else
+ to_chat(owner, SPAN_DANGER("Your hands come up empty!"))
+
+/**
+* Reloads the gun itself.
+*/
+/obj/item/organ/internal/machine/targeting_core/proc/do_reload(mob/user, obj/item/gun/projectile/gun, obj/item/ammo_magazine/ammo)
+ user.visible_message(SPAN_DANGER(FONT_LARGE("With supernatural accuracy and swiftness, [owner] replaces the magazine in [owner.get_pronoun("his")] [gun] in a fraction of a second!")))
+ if(gun.ammo_magazine)
+ gun.unload_ammo(user, drop_mag = TRUE)
+ gun.load_ammo(ammo, user)
+ playsound(user.loc, 'sound/effects/reload_woosh.ogg', 100)
+
+/obj/item/organ/internal/machine/targeting_core/proc/check_contents_for_magazine(obj/item/storage/storage, magazine_type)
+ if(istype(storage))
+ for(var/obj/item/item in storage.contents)
+ if(item.type == magazine_type)
+ storage.remove_from_storage(item)
+ return item
diff --git a/code/modules/organs/internal/species/machine/voice_synthesizer.dm b/code/modules/organs/internal/species/machine/voice_synthesizer.dm
new file mode 100644
index 00000000000..aa8ab9c2a31
--- /dev/null
+++ b/code/modules/organs/internal/species/machine/voice_synthesizer.dm
@@ -0,0 +1,10 @@
+/obj/item/organ/internal/machine/voice_synthesizer
+ name = "voice synthesizer array"
+ desc = "A complex array of circuits, speakers and receivers, made to allow a machine to speak in an organic likeness."
+ icon = 'icons/obj/organs/ipc_organs.dmi'
+ icon_state = "ipc_voicebox"
+ organ_tag = BP_VOICE_SYNTHESIZER
+ parent_organ = BP_HEAD
+
+ relative_size = 10
+ max_damage = 30
diff --git a/code/modules/organs/internal/species/machine/wireless_access.dm b/code/modules/organs/internal/species/machine/wireless_access.dm
new file mode 100644
index 00000000000..39ee7808597
--- /dev/null
+++ b/code/modules/organs/internal/species/machine/wireless_access.dm
@@ -0,0 +1,90 @@
+/obj/item/organ/internal/machine/wireless_access
+ name = "wireless access point"
+ desc = "A module that allows a Bishop frame to wirelessly access machines that it is authorized to interact with."
+ organ_tag = BP_WIRELESS_ACCESS
+ parent_organ = BP_HEAD
+ icon = 'icons/obj/organs/ipc_organs.dmi'
+ icon_state = "ipc_wireless_access"
+
+ action_button_name = "Access Internal Computer"
+
+ max_damage = 20 //frail
+ relative_size = 20
+
+ /// This frame's internal PDA.
+ var/obj/item/modular_computer/handheld/pda/synthetic_internal/internal_pda
+
+/obj/item/organ/internal/machine/wireless_access/Initialize()
+ . = ..()
+ internal_pda = new(src)
+
+/obj/item/organ/internal/machine/wireless_access/Destroy()
+ QDEL_NULL(internal_pda)
+ return ..()
+
+/obj/item/organ/internal/machine/wireless_access/attack_self(var/mob/user)
+ . = ..()
+ if(!internal_pda)
+ return
+
+ if(owner.last_special > world.time)
+ return
+
+ if(user.stat == DEAD)
+ return
+
+ if(user.incapacitated(INCAPACITATION_KNOCKOUT|INCAPACITATION_STUNNED))
+ return
+
+ internal_pda.attack_self(user)
+
+/**
+ * This proc is called whenever the IPC clicks the terminal to interact with it.
+ * It is called before the interaction is a success.
+ * If it returns TRUE, the IPC will interact with the machine.
+ * Otherwise, it will not.
+ */
+/obj/item/organ/internal/machine/wireless_access/proc/access_terminal(obj/terminal)
+ if(is_broken())
+ return FALSE
+ return TRUE
+
+/obj/item/organ/internal/machine/wireless_access/process()
+ . = ..()
+ var/obj/item/organ/internal/machine/power_core/microbattery = owner.internal_organs_by_name[BP_CELL]
+ if(microbattery)
+ var/obj/item/cell/pda_cell = internal_pda.battery_module.get_cell()
+ if(pda_cell?.percent() < 95)
+ pda_cell.give((pda_cell.maxcharge * 0.1))
+ // There would be some bonkers power usage if we did 1:1 cell usage.
+ microbattery.use(pda_cell.maxcharge * 0.05)
+
+/obj/item/modular_computer/handheld/pda/synthetic_internal
+ name = "internal computer"
+ desc = "An internal computer with an NtOS operating system."
+
+ /// The access point organ that this PDA is tied to.
+ var/obj/item/organ/internal/machine/wireless_access/access_point
+
+/obj/item/modular_computer/handheld/pda/synthetic_internal/Initialize()
+ . = ..()
+ if(istype(loc, /obj/item/organ/internal/machine/wireless_access))
+ var/obj/item/organ/internal/machine/wireless_access/potential_access_point = loc
+ if(potential_access_point.owner)
+ access_point = potential_access_point
+ else
+ crash_with("Something terrible happened in synthetic internal PDA init! Loc: [loc]")
+
+/obj/item/modular_computer/handheld/pda/synthetic_internal/Destroy()
+ if(access_point)
+ access_point.internal_pda = null
+ access_point = null
+ return ..()
+
+/obj/item/modular_computer/handheld/pda/synthetic_internal/ui_status(mob/user, datum/ui_state/state)
+ if(loc == access_point)
+ if(!access_point.is_broken())
+ return UI_INTERACTIVE
+ else
+ to_chat(user, SPAN_WARNING("Your internal PDA is not responding to your queries."))
+ return UI_CLOSE
diff --git a/code/modules/organs/organ_external.dm b/code/modules/organs/organ_external.dm
index b4fda6d7e70..c80fadf9e89 100644
--- a/code/modules/organs/organ_external.dm
+++ b/code/modules/organs/organ_external.dm
@@ -491,6 +491,9 @@
add_pain(0.6 * burn + 0.4 * brute)
+ if(owner)
+ SEND_SIGNAL(owner, COMSIG_EXTERNAL_ORGAN_DAMAGE, burn + brute)
+
//If there are still hurties to dispense
if (spillover)
owner.shock_stage += spillover * GLOB.config.organ_damage_spillover_multiplier
@@ -541,8 +544,11 @@
organ_hit_chance += 5 * damage_amt / organ_damage_threshold
- if(encased && !(status & ORGAN_BROKEN)) //ribs protect
- organ_hit_chance *= 0.2
+ if(!BP_IS_ROBOTIC(src))
+ if(encased && !(status & ORGAN_BROKEN)) //ribs protect
+ organ_hit_chance *= 0.2
+ else
+ organ_hit_chance *= 0.8 // robots should not have the same advantage
organ_hit_chance = min(organ_hit_chance, 100)
if(prob(organ_hit_chance))
diff --git a/code/modules/organs/subtypes/machine.dm b/code/modules/organs/subtypes/machine.dm
index 656ad345330..fac0e1911b1 100644
--- a/code/modules/organs/subtypes/machine.dm
+++ b/code/modules/organs/subtypes/machine.dm
@@ -57,367 +57,10 @@
encased = "support frame"
robotize_type = PROSTHETIC_IPC
-/obj/item/organ/internal/cell
- name = "microbattery"
- desc = "A small, powerful cell for use in fully prosthetic bodies."
- icon = 'icons/obj/power.dmi'
- icon_state = "scell"
- organ_tag = BP_CELL
- parent_organ = BP_CHEST
- max_damage = 80
- relative_size = 80
- robotic_sprite = FALSE
- var/open = FALSE
- var/obj/item/cell/cell = /obj/item/cell/super
- var/move_charge_factor = 1
- //at 0.8 completely depleted after 60ish minutes of constant walking or 130 minutes of standing still
- var/servo_cost = 0.8
-
-/obj/item/organ/internal/cell/Initialize()
- robotize()
- replace_cell(new cell(src))
- . = ..()
-
-/obj/item/organ/internal/cell/proc/percent()
- if(!cell)
- return 0
- return get_charge()/cell.maxcharge * 100
-
-/obj/item/organ/internal/cell/proc/get_charge()
- if(!cell)
- return 0
- if(status & ORGAN_DEAD)
- return 0
- return round(cell.charge*(1 - damage/max_damage))
-
-/obj/item/organ/internal/cell/use(var/amount)
- if(!is_usable() || !cell)
- return
- return cell.use(amount)
-
-/obj/item/organ/internal/cell/process()
- ..()
- if(!owner)
- return
- if(owner.stat == DEAD) //not a drain anymore
- return
- var/cost = get_power_drain()
- if(world.time - owner.l_move_time < 15)
- cost *= 2
- cost *= move_charge_factor
- use(cost)
-
-/obj/item/organ/internal/cell/proc/get_power_drain()
- return servo_cost
-
-/obj/item/organ/internal/cell/emp_act(severity)
- . = ..()
-
- if(cell)
- cell.emp_act(severity)
-
-/obj/item/organ/internal/cell/attackby(obj/item/attacking_item, mob/user)
- if(attacking_item.isscrewdriver())
- if(open)
- open = FALSE
- to_chat(user, SPAN_NOTICE("You screw the battery panel in place."))
- else
- open = TRUE
- to_chat(user, SPAN_NOTICE("You unscrew the battery panel."))
-
- if(attacking_item.iscrowbar())
- if(open)
- if(cell)
- user.put_in_hands(cell)
- to_chat(user, SPAN_NOTICE("You remove \the [cell] from \the [src]."))
- cell = null
- else
- to_chat(user, SPAN_WARNING("There is no cell to remove."))
- else
- to_chat(user, SPAN_WARNING("You need to unscrew the battery panel first."))
-
- if(istype(attacking_item, /obj/item/cell))
- if(open)
- if(cell)
- to_chat(user, SPAN_WARNING("There is a power cell already installed."))
- else if(user.unEquip(attacking_item, src))
- replace_cell(attacking_item)
- to_chat(user, SPAN_NOTICE("You insert \the [cell]."))
- else
- to_chat(user, SPAN_WARNING("You need to unscrew the battery panel first."))
-
-/obj/item/organ/internal/cell/proc/replace_cell(var/obj/item/cell/C)
- if(istype(cell))
- qdel(cell)
- if(C.loc != src)
- C.forceMove(src)
- cell = C
- name = "[initial(name)] ([C.name])"
-
-/obj/item/organ/internal/cell/listen()
- if(get_charge())
- return "faint hum of the power bank"
-
-/obj/item/organ/internal/surge
- name = "surge preventor"
- desc = "A small device that give immunity to EMP for few pulses."
- icon = 'icons/obj/robot_component.dmi'
- icon_state = "surge_ipc"
- organ_tag = "surge"
- parent_organ = BP_CHEST
- vital = FALSE
- robotic_sprite = FALSE
- var/surge_left = 0
- var/broken = 0
-
-/obj/item/organ/internal/surge/Initialize()
- if(!surge_left && !broken)
- surge_left = rand(2, 5)
- robotize()
- . = ..()
-
-/obj/item/organ/internal/surge/advanced
- name = "advanced surge preventor"
- var/max_charges = 5
- var/stage_ticker = 0
- var/stage_interval = 250
-
-/obj/item/organ/internal/surge/advanced/process()
- ..()
-
- if(!owner)
- return
-
- if(surge_left >= max_charges)
- return
-
- if(stage_ticker < stage_interval)
- stage_ticker += 2
-
- if(stage_ticker >= stage_interval)
- surge_left += 1
- stage_interval += 250
-
-/obj/item/organ/internal/eyes/optical_sensor
- name = "optical sensor"
- singular_name = "optical sensor"
- organ_tag = BP_EYES
- icon = 'icons/obj/robot_component.dmi'
- icon_state = "camera"
- dead_icon = "camera_broken"
- robotic_sprite = FALSE
- possible_modifications = list("Mechanical")
-
-/obj/item/organ/internal/eyes/optical_sensor/Initialize()
- robotize()
- . = ..()
-
-/obj/item/organ/internal/ipc_tag
- name = "identification tag"
- organ_tag = BP_IPCTAG
- parent_organ = BP_HEAD
- icon = 'icons/obj/ipc_utilities.dmi'
- icon_state = "ipc_tag"
- item_state = "ipc_tag"
- dead_icon = "ipc_tag_dead"
- contained_sprite = TRUE
- robotic_sprite = FALSE
- var/auto_generate = TRUE
- var/serial_number = ""
- var/ownership_info = IPC_OWNERSHIP_COMPANY
- var/citizenship_info = CITIZENSHIP_NONE
-
-/obj/item/organ/internal/ipc_tag/Initialize()
- robotize()
- . = ..()
-
-/obj/item/organ/internal/ipc_tag/get_examine_text(mob/user, distance, is_adjacent, infix, suffix)
- . = ..()
- . += SPAN_NOTICE("Serial Autogeneration: [auto_generate ? "Yes" : "No"]")
- . += SPAN_NOTICE("Serial Number: [serial_number]")
- . += SPAN_NOTICE("Ownership Info: [ownership_info]")
- . += SPAN_NOTICE("Citizenship Info: [citizenship_info]")
-
-/obj/item/organ/internal/ipc_tag/attackby(obj/item/attacking_item, mob/user)
- if(istype(attacking_item, /obj/item/ipc_tag_scanner))
- if(src.loc != user)
- to_chat(user, SPAN_WARNING("You can't scan \the [src] if it's not on your person!"))
- return
- var/obj/item/ipc_tag_scanner/S = attacking_item
- if(!S.powered)
- to_chat(user, SPAN_WARNING("\The [src] reads, \"Scanning failure, please submit scanner for repairs.\""))
- return
- if(!S.hacked)
- examinate(user, src)
- else
- user.visible_message(SPAN_WARNING("\The [user] starts fiddling with \the [src]..."), SPAN_NOTICE("You start fiddling with \the [src]..."))
- if(do_after(user, 30, src))
- if(src.loc != user)
- to_chat(user, SPAN_WARNING("You can only modify \the [src] if it's on your person!"))
- return
-
- var/static/list/modification_options = list("Serial Number", "Ownership Status", "Citizenship")
- var/choice = tgui_input_list(user, "How do you want to modify the IPC tag?", "IPC Tag Modification", modification_options)
- switch(choice)
-
- if("Serial Number")
- var/serial_selection = tgui_input_list(user, "In what way do you want to modify the serial number?","Serial Number Selection",
- list("Auto Generation", "Manual Input", "Cancel"), default = "Cancel")
- if(serial_selection != "Cancel")
- if(serial_selection == "Auto Generation")
- var/auto_generation_choice = tgui_input_list(user, "Do you wish for the IPC tag to automatically generate its serial number based on the IPCs name?", "Serial Autogeneration",
- list("Yes", "No"), "No")
- if(auto_generation_choice == "Yes")
- auto_generate = TRUE
- else
- auto_generate = FALSE
-
- if(serial_selection == "Manual Input")
- var/new_serial = tgui_input_text(user, "What do you wish for the new serial number to be? (Limit of 12 characters)", "Serial Number Modification", serial_number, 12)
- new_serial = uppertext(dd_limittext(new_serial, 12))
- if(new_serial)
- serial_number = new_serial
- auto_generate = FALSE
-
- if("Ownership Status")
- var/static/list/ownership_options = list(IPC_OWNERSHIP_COMPANY, IPC_OWNERSHIP_PRIVATE, IPC_OWNERSHIP_SELF)
- var/new_ownership = tgui_input_list(user, "What do you wish for the new ownership status to be?", "Ownership Status Modification", ownership_options)
- if(new_ownership)
- ownership_info = new_ownership
-
- if("Citizenship")
- var/datum/citizenship/citizenship = tgui_input_list(user, "What do you wish for the new citizenship setting to be?", "Citizenship Setting Modification", SSrecords.citizenships)
- if(citizenship)
- citizenship_info = citizenship
- else
- ..()
-
-/obj/item/organ/internal/ipc_tag/proc/modify_tag_data(var/can_be_untagged = FALSE)
- if(!owner || owner.stat)
- return
- if(can_be_untagged)
- var/untagged = tgui_alert(owner, "Do you wish to remove your tag? This is highly illegal in most nations!", "Untagged IPC", list("Remove Tag", "Keep Tag"))
- if(untagged == "Remove Tag")
- to_chat(owner, SPAN_WARNING("You are now an untagged synthetic - don't get caught!"))
- qdel(src)
- return
- var/new_ownership = tgui_input_list(owner, "Choose an ownership status for your IPC tag.", "Tag Ownership", list(IPC_OWNERSHIP_COMPANY, IPC_OWNERSHIP_PRIVATE, IPC_OWNERSHIP_SELF))
- if(!new_ownership)
- return
- ownership_info = new_ownership
- if(ownership_info == IPC_OWNERSHIP_SELF) //Owned IPCs don't have citizenship
- var/new_citizenship = tgui_input_list(owner, "Choose a citizenship for your IPC tag.", "Tag Citizenship", CITIZENSHIPS_ALL_IPC)
- if(!new_citizenship)
- return
- citizenship_info = new_citizenship
- else
- citizenship_info = CITIZENSHIP_NONE
- var/new_serial = tgui_input_text(owner, "Choose a serial number for your IPC tag, or leave blank for a random one.", "Serial Number")
- if(!new_serial)
- serial_number = uppertext(dd_limittext(md5(owner.real_name), 12))
- else
- serial_number = uppertext(dd_limittext(new_serial, 12))
- to_chat(owner, SPAN_NOTICE("IPC tag data has been updated."))
-
-
-// Used for an MMI or posibrain being installed into a human.
-/obj/item/organ/internal/mmi_holder
- name = "brain"
- organ_tag = BP_BRAIN
- parent_organ = BP_HEAD
- vital = TRUE
- robotic_sprite = FALSE
- var/obj/item/device/mmi/stored_mmi
-
-/obj/item/organ/internal/mmi_holder/proc/update_from_mmi()
- if(!stored_mmi)
- return
- name = stored_mmi.name
- desc = stored_mmi.desc
- icon = stored_mmi.icon
- icon_state = stored_mmi.icon_state
-
-/obj/item/organ/internal/mmi_holder/removed(var/mob/living/user)
-
- if(stored_mmi)
- stored_mmi.forceMove(get_turf(src))
- if(owner.mind)
- owner.mind.transfer_to(stored_mmi.brainmob)
- . = ..()
-
- var/mob/living/holder_mob = loc
- if(istype(holder_mob))
- holder_mob.drop_from_inventory(src)
- qdel(src)
-
-/obj/item/organ/internal/mmi_holder/posibrain/Initialize()
- robotize()
- stored_mmi = new /obj/item/device/mmi/digital/posibrain(src)
- . = ..()
- addtimer(CALLBACK(src, PROC_REF(setup_brain)), 30)
-
-/obj/item/organ/internal/mmi_holder/posibrain/proc/setup_brain()
- if(owner)
- stored_mmi.name = "positronic brain ([owner.name])"
- stored_mmi.brainmob.real_name = owner.name
- stored_mmi.brainmob.name = stored_mmi.brainmob.real_name
- stored_mmi.icon_state = "posibrain-occupied"
- update_from_mmi()
- else
- stored_mmi.forceMove(get_turf(src))
- qdel(src)
-
-/obj/item/organ/internal/mmi_holder/circuit/Initialize()
- robotize()
- stored_mmi = new /obj/item/device/mmi/digital/robot(src)
- . = ..()
- addtimer(CALLBACK(src, PROC_REF(setup_brain)), 1)
-
-/obj/item/organ/internal/mmi_holder/circuit/proc/setup_brain()
- if(owner)
- stored_mmi.name = "robotic intelligence circuit ([owner.name])"
- stored_mmi.brainmob.real_name = owner.name
- stored_mmi.brainmob.name = stored_mmi.brainmob.real_name
- update_from_mmi()
- else
- stored_mmi.forceMove(get_turf(src))
- qdel(src)
-
//////////////
//Terminator//
//////////////
-/obj/item/organ/internal/mmi_holder/posibrain/terminator
- name = BP_BRAIN
- organ_tag = BP_BRAIN
- parent_organ = BP_CHEST
- vital = TRUE
- emp_coeff = 0.1
-
-/obj/item/organ/internal/data
- name = "data core"
- organ_tag = "data core"
- parent_organ = BP_GROIN
- icon = 'icons/obj/cloning.dmi'
- icon_state = "harddisk"
- vital = FALSE
- emp_coeff = 0.1
- robotic_sprite = FALSE
-
-/obj/item/organ/internal/data/Initialize()
- robotize()
- . = ..()
-
-/obj/item/organ/internal/cell/terminator
- name = "shielded microbattery"
- desc = "A small, powerful cell for use in fully prosthetic bodies. Equipped with a Faraday shield."
- icon = 'icons/obj/power.dmi'
- icon_state = "scell"
- organ_tag = "shielded cell"
- parent_organ = BP_CHEST
- vital = TRUE
- emp_coeff = 0.1
-
/obj/item/organ/external/head/terminator
dislocated = -1
can_intake_reagents = 0
@@ -425,9 +68,6 @@
emp_coeff = 0.5
robotize_type = PROSTHETIC_HK
-/obj/item/organ/internal/eyes/optical_sensor/terminator
- emp_coeff = 0.5
-
/obj/item/organ/external/chest/terminator
dislocated = -1
encased = "reinforced support frame"
diff --git a/code/modules/organs/subtypes/standard.dm b/code/modules/organs/subtypes/standard.dm
index 333f2e941dd..3af2fc0474c 100644
--- a/code/modules/organs/subtypes/standard.dm
+++ b/code/modules/organs/subtypes/standard.dm
@@ -192,6 +192,15 @@
/obj/item/organ/external/hand/covered_bleed_report(var/blood_type)
return "[owner.get_pronoun("has")] [blood_type] running down [owner.get_pronoun("his")] sleeves!"
+/obj/item/organ/external/hand/is_malfunctioning()
+ . = ..()
+ if(!.)
+ if(owner.is_mechanical())
+ var/actuator_type = limb_name == BP_L_HAND ? BP_ACTUATORS_LEFT : BP_ACTUATORS_RIGHT
+ var/obj/item/organ/internal/machine/actuators/actuator = owner.internal_organs_by_name[actuator_type]
+ if(!actuator || (actuator.status & ORGAN_DEAD))
+ return TRUE
+
/obj/item/organ/external/hand/take_damage(brute, burn, damage_flags, used_weapon, list/forbidden_limbs, silent)
. = ..()
if(owner)
diff --git a/code/modules/power/apc.dm b/code/modules/power/apc.dm
index f912fdd62be..8e085c06b87 100644
--- a/code/modules/power/apc.dm
+++ b/code/modules/power/apc.dm
@@ -841,7 +841,7 @@ ABSTRACT_TYPE(/obj/machinery/power/apc)
if(ishuman(user))
var/mob/living/carbon/human/H = user
- if(isipc(H) && H.a_intent == I_GRAB)
+ if(isipc(H) && H.a_intent == I_GRAB && Adjacent(user))
if(emagged || stat & BROKEN)
spark(src, 5, GLOB.alldirs)
to_chat(H, SPAN_DANGER("The APC power currents surge eratically, damaging your chassis!"))
@@ -858,27 +858,10 @@ ABSTRACT_TYPE(/obj/machinery/power/apc)
if(issilicon(hacker))
to_chat(hacker, SPAN_NOTICE("Corrupt files transferred to [H]. They are now under your control until they are repaired."))
else if(cell && cell.charge > 0)
- var/obj/item/organ/internal/cell/C = H.internal_organs_by_name[BP_CELL]
- var/obj/item/cell/HC
- if(C)
- HC = C.cell
- if(HC && HC.percent() < 95)
- var/used = cell.use(500)
- HC.give(used)
- to_chat(user, SPAN_NOTICE("You slot your fingers into the APC interface and siphon off some of the stored charge for your own use."))
- if (cell.charge < 0)
- cell.charge = 0
- if (prob(0.5))
- spark(src, 5, GLOB.alldirs)
- to_chat(H, SPAN_DANGER("The APC power currents surge eratically, damaging your chassis!"))
- H.adjustFireLoss(10, 0)
-
- charging = CHARGING_ON
- else
- to_chat(user, SPAN_NOTICE("You are already fully charged."))
+ synthetic_siphon_power(H)
else
to_chat(user, SPAN_NOTICE("There is no charge to draw from that APC."))
- return
+ return
else if(H.species.can_shred(H))
user.setClickCooldown(DEFAULT_ATTACK_COOLDOWN)
@@ -1019,6 +1002,37 @@ ABSTRACT_TYPE(/obj/machinery/power/apc)
return TRUE
+/obj/machinery/power/apc/proc/synthetic_siphon_power(mob/living/carbon/human/synthetic)
+ var/obj/item/organ/internal/machine/power_core/C = synthetic.internal_organs_by_name[BP_CELL]
+ var/obj/item/cell/HC
+ if(C)
+ HC = C.cell
+ var/obj/item/organ/internal/machine/reactor/reactor = synthetic.internal_organs_by_name[BP_REACTOR]
+ if(!istype(reactor))
+ to_chat(synthetic, SPAN_WARNING("You are missing the electronic prongs to charge from the APC!"))
+ return
+ if(!(reactor.power_supply_type & POWER_SUPPLY_ELECTRIC))
+ to_chat(synthetic, SPAN_NOTICE("You start siphoning power from the APC..."))
+ if(!do_after(synthetic, 2 SECONDS, src))
+ return
+ if(HC && HC.percent() < 95)
+ var/used = cell.use(500)
+ HC.give(used)
+ to_chat(synthetic, SPAN_NOTICE("You slot your fingers into the APC interface and siphon off some of the stored charge for your own use."))
+ if (cell.charge < 0)
+ cell.charge = 0
+ if(!(reactor.power_supply_type & POWER_SUPPLY_ELECTRIC))
+ if(prob(0.5))
+ spark(src, 5, GLOB.alldirs)
+ to_chat(synthetic, SPAN_DANGER("The APC power currents surge eratically, damaging your chassis!"))
+ synthetic.adjustFireLoss(10, 0)
+ if(HC.percent() < 95)
+ synthetic_siphon_power(synthetic)
+
+ charging = CHARGING_ON
+ else
+ to_chat(synthetic, SPAN_NOTICE("You are already fully charged."))
+
/obj/machinery/power/apc/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state)
. = ..()
if(.)
diff --git a/code/modules/projectiles/gun.dm b/code/modules/projectiles/gun.dm
index 8902ccd9614..0bafc5e8030 100644
--- a/code/modules/projectiles/gun.dm
+++ b/code/modules/projectiles/gun.dm
@@ -500,9 +500,11 @@
return (target in check_trajectory(target, user))
//called if there was no projectile to shoot
-/obj/item/gun/proc/handle_click_empty()
+/obj/item/gun/proc/handle_click_empty(mob/user)
balloon_alert_to_viewers("*click*")
playsound(loc, empty_sound, 30, TRUE)
+ if(user)
+ SEND_SIGNAL(user, COMSIG_EMPTIED_MAGAZINE, src)
//called after successfully firing
/obj/item/gun/proc/handle_post_fire(mob/user, atom/target, var/pointblank = FALSE, var/reflex = FALSE, var/playemote = TRUE)
diff --git a/code/modules/projectiles/guns/projectile.dm b/code/modules/projectiles/guns/projectile.dm
index f68d84ae55b..263adede1b0 100644
--- a/code/modules/projectiles/guns/projectile.dm
+++ b/code/modules/projectiles/guns/projectile.dm
@@ -208,7 +208,7 @@
update_icon()
//attempts to unload src. If allow_dump is set to 0, the speedloader unloading method will be disabled
-/obj/item/gun/projectile/proc/unload_ammo(mob/user, var/allow_dump = 1, var/drop_mag = FALSE)
+/obj/item/gun/projectile/proc/unload_ammo(mob/user, allow_dump = TRUE, drop_mag = FALSE)
if(ammo_magazine)
if(drop_mag)
ammo_magazine.forceMove(user.loc)
diff --git a/code/modules/projectiles/guns/projectile/automatic.dm b/code/modules/projectiles/guns/projectile/automatic.dm
index 4402f4890c7..fe31fb7ea21 100644
--- a/code/modules/projectiles/guns/projectile/automatic.dm
+++ b/code/modules/projectiles/guns/projectile/automatic.dm
@@ -527,7 +527,7 @@
return
..()
-/obj/item/gun/projectile/automatic/rifle/l6_saw/unload_ammo(mob/user, var/allow_dump=1)
+/obj/item/gun/projectile/automatic/rifle/l6_saw/unload_ammo(mob/user, allow_dump = TRUE, drop_mag = FALSE)
if(!cover_open)
to_chat(user, SPAN_WARNING("You need to open the cover to unload [src]."))
return
diff --git a/code/modules/projectiles/guns/projectile/minigun.dm b/code/modules/projectiles/guns/projectile/minigun.dm
index cce8a668dd0..8df0de2bd34 100644
--- a/code/modules/projectiles/guns/projectile/minigun.dm
+++ b/code/modules/projectiles/guns/projectile/minigun.dm
@@ -149,7 +149,7 @@
/obj/item/gun/projectile/automatic/rifle/minigun/load_ammo(var/obj/item/A, mob/user)
return
-/obj/item/gun/projectile/automatic/rifle/minigun/unload_ammo(mob/user, var/allow_dump=1)
+/obj/item/gun/projectile/automatic/rifle/minigun/unload_ammo(mob/user, allow_dump = TRUE, drop_mag = FALSE)
return
/obj/item/gun/projectile/automatic/rifle/minigun/dropped(mob/user)
diff --git a/code/modules/projectiles/guns/projectile/rifle.dm b/code/modules/projectiles/guns/projectile/rifle.dm
index d85c8f482f3..b10ec543fe5 100644
--- a/code/modules/projectiles/guns/projectile/rifle.dm
+++ b/code/modules/projectiles/guns/projectile/rifle.dm
@@ -217,7 +217,7 @@
return
..()
-/obj/item/gun/projectile/contender/unload_ammo(mob/user, var/allow_dump=1)
+/obj/item/gun/projectile/contender/unload_ammo(mob/user, allow_dump = TRUE, drop_mag = FALSE)
if(!retracted_bolt)
to_chat(user, SPAN_NOTICE("You can't unload \the [src] without cycling the bolt."))
return
diff --git a/code/modules/projectiles/guns/projectile/shotgun.dm b/code/modules/projectiles/guns/projectile/shotgun.dm
index ce26cc35939..8fb3a338bf4 100644
--- a/code/modules/projectiles/guns/projectile/shotgun.dm
+++ b/code/modules/projectiles/guns/projectile/shotgun.dm
@@ -185,7 +185,7 @@
desc = "A double-barreled shotgun meant to fire signal flash shells."
ammo_type = /obj/item/ammo_casing/shotgun/flash
-/obj/item/gun/projectile/shotgun/doublebarrel/unload_ammo(user, allow_dump)
+/obj/item/gun/projectile/shotgun/doublebarrel/unload_ammo(mob/user, allow_dump = TRUE, drop_mag = FALSE)
..(user, allow_dump=1)
/obj/item/gun/projectile/shotgun/doublebarrel/saw_off(mob/user, obj/item/tool)
@@ -349,7 +349,7 @@
open = FALSE
update_icon()
-/obj/item/gun/projectile/shotgun/wallgun/unload_ammo(user, allow_dump)
+/obj/item/gun/projectile/shotgun/wallgun/unload_ammo(mob/user, allow_dump = TRUE, drop_mag = FALSE)
if(open)
..(user, allow_dump=1)
diff --git a/code/modules/projectiles/guns/projectile/sniper.dm b/code/modules/projectiles/guns/projectile/sniper.dm
index d523ba00d80..afabaf4af1b 100644
--- a/code/modules/projectiles/guns/projectile/sniper.dm
+++ b/code/modules/projectiles/guns/projectile/sniper.dm
@@ -78,7 +78,7 @@
return
..()
-/obj/item/gun/projectile/heavysniper/unload_ammo(mob/user, var/allow_dump=1)
+/obj/item/gun/projectile/heavysniper/unload_ammo(mob/user, allow_dump = TRUE, drop_mag = FALSE)
if(!bolt_open)
return
..()
diff --git a/code/modules/projectiles/projectile/special.dm b/code/modules/projectiles/projectile/special.dm
index 8ef146db5b6..d3ba5097fe7 100644
--- a/code/modules/projectiles/projectile/special.dm
+++ b/code/modules/projectiles/projectile/special.dm
@@ -17,7 +17,7 @@
if(isipc(target))
var/mob/living/carbon/human/H = target
- var/obj/item/organ/internal/surge/s = H.internal_organs_by_name["surge"]
+ var/obj/item/organ/internal/machine/surge/s = H.internal_organs_by_name[BP_SURGE_PROTECTOR]
if(!isnull(s))
if(s.surge_left >= 0.5)
playsound(src.loc, 'sound/magic/LightningShock.ogg', 25, 1)
diff --git a/code/modules/reagents/reagent_containers/food/snacks.dm b/code/modules/reagents/reagent_containers/food/snacks.dm
index ba515b284c1..36a2e4e7092 100644
--- a/code/modules/reagents/reagent_containers/food/snacks.dm
+++ b/code/modules/reagents/reagent_containers/food/snacks.dm
@@ -110,7 +110,7 @@
if(ishuman(user))
var/mob/living/carbon/human/H = user
- if (H.species && H.species.bypass_food_fullness())
+ if (H.species && H.species.bypass_food_fullness(H))
is_full = FALSE
if(user == target)
diff --git a/code/modules/research/designs/mechfab/synthetic/synthetic_organ_parts.dm b/code/modules/research/designs/mechfab/synthetic/synthetic_organ_parts.dm
new file mode 100644
index 00000000000..25fc909e11a
--- /dev/null
+++ b/code/modules/research/designs/mechfab/synthetic/synthetic_organ_parts.dm
@@ -0,0 +1,127 @@
+/datum/design/item/synthetic
+ build_type = MECHFAB
+ time = 12 SECONDS
+ materials = list(DEFAULT_WALL_MATERIAL = 10000, MATERIAL_GLASS = 5000)
+ category = "Synthetic Parts"
+
+/datum/design/item/synthetic/air_cooling_unit
+ name = "Standard Air Cooling Unit"
+ desc = "Parts to assemble a retail air cooling unit."
+ build_path = /obj/item/organ/internal/machine/cooling_unit/air
+ materials = list(DEFAULT_WALL_MATERIAL = 10000, MATERIAL_GLASS = 5000)
+
+/datum/design/item/synthetic/zenghu_air_cooling_unit
+ name = "Zeng-Hu Penta-Fan Air Cooling System"
+ desc = "Parts to assemble a Zeng-Hu penta-fan air cooling system."
+ build_path = /obj/item/organ/internal/machine/cooling_unit/air/zenghu
+ materials = list(DEFAULT_WALL_MATERIAL = 10000, MATERIAL_GLASS = 5000, MATERIAL_DIAMOND = 5000, MATERIAL_PLASTEEL = 2500)
+
+/datum/design/item/synthetic/xion_air_cooling_unit
+ name = "Xion Advanced Air Cooling Unit"
+ desc = "Parts to assemble a Xion advanced air cooling unitw."
+ build_path = /obj/item/organ/internal/machine/cooling_unit/air/xion
+ materials = list(DEFAULT_WALL_MATERIAL = 10000, MATERIAL_GLASS = 5000, MATERIAL_DIAMOND = 1000, MATERIAL_PLASTEEL = 7500)
+
+/datum/design/item/synthetic/liquid_cooling_unit
+ name = "Standard Liquid Cooling Unit"
+ desc = "Parts to assemble a retail liquid cooling unit."
+ build_path = /obj/item/organ/internal/machine/cooling_unit/liquid
+ materials = list(MATERIAL_PLASTEEL = 5000, MATERIAL_GLASS = 5000, MATERIAL_ALUMINIUM = 5000)
+
+/datum/design/item/synthetic/zenghu_liquid_cooling_unit
+ name = "Zeng-Hu Lamellar Liquid Cooling System"
+ desc = "Parts to assemble a Zeng-Hu lamellar liquid cooling system."
+ build_path = /obj/item/organ/internal/machine/cooling_unit/liquid/zenghu
+ materials = list(MATERIAL_PLASTEEL = 5000, MATERIAL_GLASS = 5000, MATERIAL_ALUMINIUM = 5000, MATERIAL_URANIUM = 2500, MATERIAL_DIAMOND = 2500)
+ req_tech = list(TECH_MATERIAL = 5, TECH_POWER = 4)
+
+/datum/design/item/synthetic/xion_liquid_cooling_unit
+ name = "Xion Advanced Cryo-Cooling System"
+ desc = "Parts to assemble a Xion cryo-cooling pump and radiator array."
+ build_path = /obj/item/organ/internal/machine/cooling_unit/liquid/xion
+ materials = list(MATERIAL_PLASTEEL = 5000, MATERIAL_GLASS = 5000, MATERIAL_ALUMINIUM = 5000, MATERIAL_DIAMOND = 5000)
+ req_tech = list(TECH_MATERIAL = 5, TECH_POWER = 4)
+
+/datum/design/item/synthetic/passive_cooling_unit
+ name = "Standard Passive Cooling Unit"
+ desc = "Parts to assemble a retail passive cooling unit."
+ build_path = /obj/item/organ/internal/machine/cooling_unit/passive
+ materials = list(MATERIAL_STEEL = 10000)
+
+/datum/design/item/synthetic/zenghu_passive_cooling_unit
+ name = "Zeng-Hu Suprafin Cooling Fins"
+ desc = "Parts to assemble a Zeng-Hu passive cooling unit."
+ build_path = /obj/item/organ/internal/machine/cooling_unit/passive/zenghu
+ materials = list(MATERIAL_STEEL = 10000, MATERIAL_DIAMOND = 2500, MATERIAL_PLASTEEL = 5000)
+
+/datum/design/item/synthetic/zenghu_passive_cooling_unit
+ name = "Xion Passive Intra-fin Array"
+ desc = "Parts to assemble a Zeng-Hu passive cooling unit."
+ build_path = /obj/item/organ/internal/machine/cooling_unit/passive/xion
+ materials = list(MATERIAL_STEEL = 10000, MATERIAL_DIAMOND = 1000, MATERIAL_PLASTEEL = 7500)
+
+/datum/design/item/synthetic/diagnostics_unit
+ name = "Diagnostics Unit"
+ desc = "Parts to assemble a retail diagnostics unit."
+ build_path = /obj/item/organ/internal/machine/internal_diagnostics
+ materials = list(MATERIAL_PLASTEEL = 7500, MATERIAL_STEEL = 5000, MATERIAL_GLASS = 2500)
+
+/datum/design/item/synthetic/actuator
+ name = "Left Arm Actuator"
+ desc = "Parts to assemble retail actuators for the left arm."
+ build_path = /obj/item/organ/internal/machine/actuators/left
+ materials = list(MATERIAL_PLASTEEL = 1500, MATERIAL_STEEL = 5000, MATERIAL_GLASS = 1500)
+
+/datum/design/item/synthetic/actuator/right
+ name = "Right Arm Actuator"
+ desc = "Parts to assemble retail actuators for the right arm."
+ build_path = /obj/item/organ/internal/machine/actuators/right
+ materials = list(MATERIAL_PLASTEEL = 1500, MATERIAL_STEEL = 5000, MATERIAL_GLASS = 1500)
+
+/datum/design/item/synthetic/hydraulics
+ name = "Hydraulics"
+ desc = "Parts to assemble retail hydraulics."
+ build_path = /obj/item/organ/internal/machine/hydraulics
+ materials = list(MATERIAL_PLASTEEL = 3000, MATERIAL_ALUMINIUM = 1500, MATERIAL_GLASS = 1500)
+
+/datum/design/item/synthetic/voice_synthesizer
+ name = "Voice Synthesizer"
+ desc = "Parts to assemble a retail voice synthesizer."
+ build_path = /obj/item/organ/internal/machine/voice_synthesizer
+ materials = list(MATERIAL_STEEL = 5000, MATERIAL_PLASTEEL = 1000, MATERIAL_ALUMINIUM = 1500, MATERIAL_GLASS = 1500)
+
+/datum/design/item/synthetic/ipc_tag
+ name = "IPC Tag"
+ desc = "Parts to assemble a proprietary and specialized tag for IPCs."
+ build_path = /obj/item/organ/internal/machine/ipc_tag
+ materials = list(MATERIAL_PLASTEEL = 2000, MATERIAL_ALUMINIUM = 2000, MATERIAL_GLASS = 5000, MATERIAL_DIAMOND = 1500)
+
+/datum/design/item/synthetic/optical_sensor
+ name = "Optical Sensor"
+ desc = "Parts to assemble retail optical sensors."
+ build_path = /obj/item/organ/internal/eyes/optical_sensor
+ materials = list(MATERIAL_PLASTEEL = 1000, MATERIAL_ALUMINIUM = 3000, MATERIAL_GLASS = 10000, MATERIAL_DIAMOND = 1000)
+
+/datum/design/item/synthetic/power_core
+ name = "Power Core"
+ desc = "Parts to assemble a retail power core."
+ build_path = /obj/item/organ/internal/machine/power_core
+ materials = list(MATERIAL_PLASTEEL = 5000, MATERIAL_ALUMINIUM = 1000, MATERIAL_GLASS = 2500, MATERIAL_URANIUM = 1500)
+
+/datum/design/item/synthetic/reactor
+ name = "Electric Reactor"
+ desc = "Parts to assemble a retail electric reactor for IPCs."
+ build_path = /obj/item/organ/internal/machine/reactor
+ materials = list(MATERIAL_PLASTEEL = 7500, MATERIAL_ALUMINIUM = 5000, MATERIAL_GLASS = 1000)
+
+/datum/design/item/synthetic/access_port
+ name = "Access Port"
+ desc = "Parts to assemble a retail access port."
+ build_path = /obj/item/organ/internal/machine/access_port
+ materials = list(MATERIAL_STEEL = 2000, MATERIAL_ALUMINIUM = 2000, MATERIAL_GLASS = 1000)
+
+/datum/design/item/synthetic/access_port
+ name = "Synthetic Plating"
+ desc = "Specially lathed steel plates to replace destroyed plating around a synthetic's internal components, or to substitute their external armour plating altogether."
+ build_path = /obj/item/synth_plating
+ materials = list(MATERIAL_STEEL = 40000)
diff --git a/code/modules/research/rdconsole.dm b/code/modules/research/rdconsole.dm
index a7204b441f3..8928d6c515f 100644
--- a/code/modules/research/rdconsole.dm
+++ b/code/modules/research/rdconsole.dm
@@ -176,13 +176,13 @@ won't update every console in existence) but it's more of a hassle to do. Also,
src.updateUsrDialog()
return
-/obj/machinery/computer/rdconsole/emp_act(severity)
+/obj/machinery/computer/rdconsole/emag_act(remaining_charges, mob/user, emag_source)
. = ..()
if(!emagged)
playsound(src.loc, 'sound/effects/sparks4.ogg', 75, 1)
emagged = 1
- to_chat(usr, SPAN_NOTICE("You you disable the security protocols."))
+ to_chat(usr, SPAN_NOTICE("You disable the security protocols."))
return 1
/obj/machinery/computer/rdconsole/Topic(href, href_list)
diff --git a/code/modules/research/server.dm b/code/modules/research/server.dm
index 50bbe1d7a9b..cb67d6810b2 100644
--- a/code/modules/research/server.dm
+++ b/code/modules/research/server.dm
@@ -363,7 +363,7 @@
if(!emagged)
playsound(src.loc, 'sound/effects/sparks4.ogg', 75, 1)
emagged = 1
- to_chat(user, SPAN_NOTICE("You you disable the security protocols."))
+ to_chat(user, SPAN_NOTICE("You disable the security protocols."))
src.updateUsrDialog()
return 1
diff --git a/code/modules/surgery/robotics.dm b/code/modules/surgery/robotics.dm
index ef3116ec5e0..0f166ccbbdd 100644
--- a/code/modules/surgery/robotics.dm
+++ b/code/modules/surgery/robotics.dm
@@ -30,8 +30,8 @@
/obj/item/material/kitchen/utensil/knife = 50
)
- min_duration = 70
- max_duration = 90
+ min_duration = 35
+ max_duration = 70
requires_surgery_compatibility = FALSE
@@ -72,8 +72,8 @@
/obj/item/material/kitchen/utensil/knife = 50
)
- min_duration = 70
- max_duration = 90
+ min_duration = 35
+ max_duration = 70
requires_surgery_compatibility = FALSE
@@ -436,7 +436,7 @@
SPAN_NOTICE("You have installed \the [tool] into [target]'s [affected.name]."))
var/obj/item/device/mmi/M = tool
- var/obj/item/organ/internal/mmi_holder/holder = new(target, 1)
+ var/obj/item/organ/internal/machine/posibrain/holder = new(target, 1)
target.internal_organs_by_name[BP_BRAIN] = holder
user.drop_from_inventory(tool,holder)
holder.stored_mmi = tool
@@ -448,3 +448,325 @@
/singleton/surgery_step/robotics/install_mmi/fail_step(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
user.visible_message(SPAN_WARNING("[user]'s hand slips."), \
SPAN_WARNING("Your hand slips."))
+
+/singleton/surgery_step/internal/fix_internal_wiring
+ name = "Repair Internal Wiring"
+ allowed_tools = list(
+ /obj/item/stack/cable_coil = 100,
+ /obj/item/stack/cable_coil/cyborg = 100
+ )
+
+ min_duration = 50
+ max_duration = 70
+
+/singleton/surgery_step/internal/fix_internal_wiring/can_use(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
+ if(!..())
+ return FALSE
+
+ var/obj/item/organ/external/affected = target.get_organ(target_zone)
+ var/obj/item/stack/cable_coil/C = tool
+ var/is_organ_damaged = FALSE
+ var/limb_can_operate = (affected && affected.open == ORGAN_ENCASED_RETRACTED && target_zone != BP_MOUTH)
+ if(limb_can_operate)
+ for(var/obj/item/organ/internal/machine/I in affected.internal_organs)
+ if(I.wiring.get_status() < 100)
+ if(istype(C))
+ var/needed_wires = (I.wiring.max_wires - I.wiring.wires) / 2
+ if(needed_wires)
+ if(!(C.get_amount() >= (needed_wires)))
+ to_chat(user, SPAN_WARNING("You need [needed_wires] or more cable pieces to repair this damage."))
+ return SURGERY_FAILURE
+ is_organ_damaged = TRUE
+ C.use(needed_wires)
+ return is_organ_damaged
+
+/singleton/surgery_step/internal/fix_internal_wiring/begin_step(mob/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
+ if(!..())
+ return FALSE
+
+ var/obj/item/organ/external/affected = target.get_organ(target_zone)
+ for(var/obj/item/organ/internal/machine/I in affected.internal_organs)
+ if(I && (I.wiring.get_status() < 100))
+ user.visible_message(SPAN_NOTICE("[user] begins splitting and connecting new wiring in [target]'s [I]..."))
+ ..()
+
+/singleton/surgery_step/internal/fix_internal_wiring/end_step(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
+ . = ..()
+ var/obj/item/organ/external/affected = target.get_organ(target_zone)
+ for(var/obj/item/organ/internal/machine/I in affected.internal_organs)
+ if(I && (I.wiring.get_status() < 100))
+ var/needed_wires = I.wiring.max_wires - I.wiring.wires
+ user.visible_message(SPAN_NOTICE("[user] fixes the wiring in [target]'s [affected]."))
+ I.wiring.heal_damage(needed_wires)
+
+/singleton/surgery_step/internal/fix_internal_electronics
+ name = "Repair Internal Electronics"
+ allowed_tools = list(
+ /obj/item/device/multitool = 100,
+ )
+
+ min_duration = 100
+ max_duration = 200
+
+/singleton/surgery_step/internal/fix_internal_electronics/can_use(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
+ if(!..())
+ return FALSE
+
+ var/obj/item/organ/external/affected = target.get_organ(target_zone)
+ var/is_organ_damaged = FALSE
+ var/limb_can_operate = (affected && affected.open == ORGAN_ENCASED_RETRACTED && target_zone != BP_MOUTH)
+ if(limb_can_operate)
+ for(var/obj/item/organ/internal/machine/I in affected.internal_organs)
+ if(I.electronics.get_status() < 100)
+ is_organ_damaged = TRUE
+ return is_organ_damaged
+
+/singleton/surgery_step/internal/fix_internal_electronics/begin_step(mob/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
+ if(!..())
+ return FALSE
+
+ var/obj/item/organ/external/affected = target.get_organ(target_zone)
+ for(var/obj/item/organ/internal/machine/I in affected.internal_organs)
+ if(I && (I.electronics.get_status() < 100))
+ user.visible_message(SPAN_NOTICE("[user] begins replacing the electronics in [target]'s [I] and reconfiguring them..."))
+ ..()
+
+/singleton/surgery_step/internal/fix_internal_electronics/end_step(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
+ . = ..()
+ var/obj/item/organ/external/affected = target.get_organ(target_zone)
+ for(var/obj/item/organ/internal/machine/I in affected.internal_organs)
+ if(I && (I.electronics.get_status() < 100))
+ user.visible_message(SPAN_NOTICE("[user] repairs the electronics in [target]'s [affected]."))
+ I.electronics.heal_damage(I.electronics.max_integrity)
+
+/singleton/surgery_step/internal/fix_internal_plating
+ name = "Repair Internal Plating"
+ allowed_tools = list(
+ /obj/item/stack/material/steel = 100,
+ )
+
+ min_duration = 75
+ max_duration = 120
+
+/singleton/surgery_step/internal/fix_internal_plating/can_use(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
+ if(!..())
+ return FALSE
+
+ var/obj/item/organ/external/affected = target.get_organ(target_zone)
+ var/obj/item/stack/material/steel/steel = tool
+ var/is_organ_damaged = FALSE
+ var/limb_can_operate = (affected && affected.open == ORGAN_ENCASED_RETRACTED && target_zone != BP_MOUTH)
+ if(limb_can_operate)
+ for(var/obj/item/organ/internal/machine/I in affected.internal_organs)
+ if(I.plating.get_status() < 100)
+ if(I.plating.get_status() <= 0)
+ to_chat(user, SPAN_WARNING("That plating is too far gone! You'll need to totally replace it with new plates!"))
+ return FALSE
+ var/needed_plates = I.plating.max_health / 10
+ if(istype(steel))
+ if(!(steel.get_amount() >= (needed_plates)))
+ to_chat(user, SPAN_WARNING("You need [round(needed_plates) / 10] or more steel plates to repair this damage."))
+ return SURGERY_FAILURE
+ is_organ_damaged = TRUE
+ steel.use(needed_plates)
+ return is_organ_damaged
+
+/singleton/surgery_step/internal/fix_internal_plating/begin_step(mob/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
+ if(!..())
+ return FALSE
+
+ var/obj/item/organ/external/affected = target.get_organ(target_zone)
+ for(var/obj/item/organ/internal/machine/I in affected.internal_organs)
+ if(I && (I.plating.get_status() < 100))
+ var/damage_type = pick("damage", "kinks", "gashes", "holes")
+ user.visible_message(SPAN_NOTICE("[user] begins melding steel to the [damage_type] on the plating around [target]'s [I]..."), "You begin melding steel to the [damage_type] on the plating around [target]'s [I]...")
+ ..()
+
+/singleton/surgery_step/internal/fix_internal_plating/end_step(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
+ . = ..()
+ var/obj/item/organ/external/affected = target.get_organ(target_zone)
+ for(var/obj/item/organ/internal/machine/I in affected.internal_organs)
+ if(I && (I.plating.get_status() < 100))
+ user.visible_message(SPAN_NOTICE("[user] mends the plating around [target]'s [affected]."), SPAN_NOTICE("You mend the plating around [target]'s [affected]."))
+ I.plating.heal_damage(I.plating.max_health)
+
+/singleton/surgery_step/internal/replace_internal_plating
+ name = "Replace Internal Plating"
+ allowed_tools = list(
+ /obj/item/synth_plating = 100,
+ )
+
+ min_duration = 100
+ max_duration = 150
+
+/singleton/surgery_step/internal/replace_internal_plating/can_use(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
+ if(!..())
+ return FALSE
+
+ var/obj/item/organ/external/affected = target.get_organ(target_zone)
+ var/is_plating_destroyed = FALSE
+ var/limb_can_operate = (affected && affected.open == ORGAN_ENCASED_RETRACTED && target_zone != BP_MOUTH)
+ if(limb_can_operate)
+ for(var/obj/item/organ/internal/machine/I in affected.internal_organs)
+ if(I.plating.get_status() <= 0)
+ is_plating_destroyed = TRUE
+ return is_plating_destroyed
+
+/singleton/surgery_step/internal/replace_internal_plating/begin_step(mob/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
+ if(!..())
+ return FALSE
+
+ var/obj/item/organ/external/affected = target.get_organ(target_zone)
+ for(var/obj/item/organ/internal/machine/I in affected.internal_organs)
+ if(I && (I.plating.get_status() < 100))
+ user.visible_message(SPAN_NOTICE("[user] begins cutting apart the old plating around [target]'s [I] and replacing it with new plates..."))
+ ..()
+
+/singleton/surgery_step/internal/replace_internal_plating/end_step(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
+ . = ..()
+ var/obj/item/organ/external/affected = target.get_organ(target_zone)
+ for(var/obj/item/organ/internal/machine/I in affected.internal_organs)
+ if(I && (I.plating.get_status() <= 0))
+ user.visible_message(SPAN_NOTICE("[user] fully replaces the plating in [target]'s [affected]."))
+ user.remove_from_mob(tool)
+ qdel(tool)
+ I.plating.heal_damage(I.plating.max_health)
+
+
+/singleton/surgery_step/internal/replace_external_plating
+ name = "Replace External Plating"
+ allowed_tools = list(
+ /obj/item/synth_plating = 100,
+ )
+
+ min_duration = 170
+ max_duration = 200
+
+ var/fast_repair = FALSE
+
+/singleton/surgery_step/internal/replace_external_plating/can_use(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
+ if(!..())
+ return FALSE
+
+ if(fast_repair)
+ if(!istype(target.species, /datum/species/machine/industrial/hephaestus))
+ return FALSE
+
+ var/armor_damaged = FALSE
+ var/datum/component/armor/synthetic/synth_armor = target.GetComponent(/datum/component/armor/synthetic)
+ if(synth_armor)
+ var/list/damage = synth_armor.get_damage()
+ if(length(damage))
+ armor_damaged = TRUE
+ return armor_damaged
+
+/singleton/surgery_step/internal/replace_external_plating/begin_step(mob/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
+ if(!..())
+ return FALSE
+ user.visible_message(SPAN_NOTICE("[user] begins stripping [target]'s old armour plating and replacing it with new plates."), SPAN_NOTICE("You begin stripping off [target]'s old armour plating and replacing it with new plates."))
+ ..()
+
+/singleton/surgery_step/internal/replace_external_plating/end_step(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
+ . = ..()
+ user.visible_message(SPAN_NOTICE("[user] finished replacing [target]'s old armour plating with new plates."), SPAN_NOTICE("You finish replacing [target]'s old armour plating with new plates."))
+ var/datum/component/armor/synthetic/synth_armor = target.GetComponent(/datum/component/armor/synthetic)
+ if(istype(synth_armor))
+ for(var/key in synth_armor.armor_values)
+ synth_armor.armor_values[key] = synth_armor.max_armor_values[key]
+ qdel(tool)
+
+/singleton/surgery_step/internal/degunk
+ name = "Remove Bio-Reactor Waste"
+ allowed_tools = list(
+ /obj/item/reagent_containers/glass = 100,
+ )
+
+ min_duration = 100
+ max_duration = 150
+
+/singleton/surgery_step/internal/replace_external_plating/g2
+ name = "Replace G2 External Armour Plating"
+ min_duration = 90
+ max_duration = 100
+ fast_repair = TRUE
+
+/singleton/surgery_step/internal/degunk/can_use(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
+ if(!..())
+ return FALSE
+
+ var/obj/item/organ/external/affected = target.get_organ(target_zone)
+ var/obj/item/organ/internal/machine/reactor/bio_reactor = target.internal_organs_by_name[BP_REACTOR]
+ var/limb_can_operate = (affected && affected.open == ORGAN_ENCASED_RETRACTED && target_zone == BP_CHEST)
+ if(limb_can_operate)
+ if(bio_reactor && (bio_reactor.power_supply_type & POWER_SUPPLY_BIOLOGICAL))
+ var/extraneous_amount = 0
+ for(var/reagent_type in bio_reactor.bio_reagents.reagent_volumes)
+ var/reagent_amount = REAGENT_VOLUME(bio_reactor.bio_reagents, reagent_type)
+ if(!ispath(reagent_type, /singleton/reagent/nutriment))
+ extraneous_amount += reagent_amount
+ if(extraneous_amount)
+ return TRUE
+ return FALSE
+
+/singleton/surgery_step/internal/degunk/begin_step(mob/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
+ if(!..())
+ return FALSE
+ user.visible_message(SPAN_NOTICE("[user] begins draining the refuse inside of [target]'s biological reactor..."), SPAN_NOTICE("You begin draining the refuse inside of [target]'s biological reactor."))
+ ..()
+
+/singleton/surgery_step/internal/degunk/end_step(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
+ . = ..()
+ var/obj/item/organ/internal/machine/reactor/bio_reactor = target.internal_organs_by_name[BP_REACTOR]
+ for(var/reagent_type in bio_reactor.bio_reagents.reagent_volumes)
+ var/reagent_amount = REAGENT_VOLUME(bio_reactor.bio_reagents, reagent_type)
+ if(!ispath(reagent_type, /singleton/reagent/nutriment))
+ bio_reactor.bio_reagents.trans_type_to(tool, reagent_type, reagent_amount)
+ playsound(target, 'sound/effects/drain_blood.ogg', 50)
+ user.visible_message(SPAN_NOTICE("[user] extracts the refuse in [target]'s biological reactor."), SPAN_NOTICE("You drain the refuse in [target]'s biological reactor to [tool]."))
+
+/singleton/surgery_step/robotics/repair_endoskeleton
+ name = "Repair Endoskeleton"
+ allowed_tools = list(
+ /obj/item/weldingtool = 100,
+ /obj/item/gun/energy/plasmacutter = 50
+ )
+
+ min_duration = 15
+ max_duration = 25
+
+/singleton/surgery_step/robotics/repair_endoskeleton/can_use(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
+ if(!..())
+ return FALSE
+
+ var/obj/item/organ/external/affected = target.get_organ(target_zone)
+ if(tool.iswelder())
+ var/obj/item/weldingtool/welder = tool
+ if(!welder.isOn() || welder.get_fuel() < 5)
+ return FALSE
+ var/datum/component/synthetic_endoskeleton/endoskeleton = target.GetComponent(/datum/component/synthetic_endoskeleton)
+ if(!istype(endoskeleton))
+ return FALSE
+
+ return affected && affected.open == ORGAN_ENCASED_RETRACTED && affected.organ_tag == BP_CHEST && target_zone != BP_MOUTH && endoskeleton.damage > 0
+
+/singleton/surgery_step/robotics/repair_endoskeleton/begin_step(mob/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
+ user.visible_message("[user] begins to repair the support structures of [target]'s endoskeleton with \the [tool]." , \
+ SPAN_NOTICE("You begin to repair the support structures of [target]'s endoskeleton with \the [tool]."))
+ ..()
+
+/singleton/surgery_step/robotics/repair_endoskeleton/end_step(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
+ if(tool.iswelder())
+ var/obj/item/weldingtool/welder = tool
+ if(!welder.isOn() && !welder.use(5, user))
+ user.visible_message(SPAN_WARNING("[user]'s [tool] shut off before the procedure was finished."), \
+ SPAN_WARNING("Your [tool] is shut off!"))
+ return
+ user.visible_message("[user] finishes repairing some damage to [target]'s endoskeleton with \the [tool].", \
+ SPAN_NOTICE("You finish repairing some damage to [target]'s endoskeleton with \the [tool]."))
+ SEND_SIGNAL(target, COMSIG_SYNTH_ENDOSKELETON_REPAIR, rand(30, 50))
+
+/singleton/surgery_step/robotics/repair_endoskeleton/fail_step(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
+ var/obj/item/organ/external/affected = target.get_organ(target_zone)
+ user.visible_message(SPAN_WARNING("[user]'s [tool.name] slips, damaging some of the support structure of [target]'s endoskeleton!"),
+ SPAN_WARNING("Your [tool.name] slips, damaging some of the support structure of [target]'s endoskeleton!"))
+ target.apply_damage(rand(5,10), DAMAGE_BURN, affected)
diff --git a/code/modules/tgui/modules/ipc_diagnostic.dm b/code/modules/tgui/modules/ipc_diagnostic.dm
new file mode 100644
index 00000000000..38a10f72ddc
--- /dev/null
+++ b/code/modules/tgui/modules/ipc_diagnostic.dm
@@ -0,0 +1,98 @@
+/datum/tgui_module/ipc_diagnostic
+ var/mob/patient
+
+/datum/tgui_module/ipc_diagnostic/New(mob/user, mob/target)
+ . = ..()
+ patient = target
+
+/datum/tgui_module/ipc_diagnostic/Destroy(force)
+ patient = null
+ return ..()
+
+/datum/tgui_module/ipc_diagnostic/ui_interact(var/mob/user, var/datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
+ if(!ui)
+ ui = new(user, src, "DiagnosticsUnit", "Diagnostics Unit", 400, 600)
+ ui.open()
+
+/datum/tgui_module/ipc_diagnostic/ui_data(mob/user)
+ var/list/data = list()
+ data["broken"] = FALSE
+
+ if(isipc(patient))
+ var/mob/living/carbon/human/ipc = patient
+ var/datum/species/machine/machine_species = ipc.species // need to manually set to ipc species because of machine_ui_theme
+ var/obj/item/organ/internal/machine/internal_diagnostics/diagnostics = ipc.internal_organs_by_name[BP_DIAGNOSTICS_SUITE]
+ if(!istype(diagnostics))
+ data["broken"] = TRUE
+
+ data["patient_name"] = ipc.real_name
+ data["broken"] = diagnostics.is_broken()
+ data["integrity"] = diagnostics.get_integrity()
+ data["temp"] = round(convert_k2c(ipc.bodytemperature))
+ data["machine_ui_theme"] = machine_species.machine_ui_theme
+
+ data["organs"] = list()
+ for(var/obj/item/organ/internal/organ in ipc.internal_organs)
+ var/list/organ_data = list()
+
+ // this check is here first so we can avoid useless calculations in case the organ isn't visible from the diag suite
+ if(istype(organ, /obj/item/organ/internal/machine))
+ var/obj/item/organ/internal/machine/machine_organ = organ
+ if(!machine_organ.diagnostics_suite_visible)
+ continue
+
+ organ_data["wiring_status"] = edit_organ_status(machine_organ.wiring.get_status(), diagnostics)
+ organ_data["plating_status"] = edit_organ_status(machine_organ.plating.get_status(), diagnostics)
+ organ_data["electronics_status"] = edit_organ_status(machine_organ.electronics.get_status(), diagnostics)
+ organ_data["diagnostics_info"] = machine_organ.get_diagnostics_info()
+
+ organ_data["name"] = organ.name
+ organ_data["desc"] = organ.desc
+ organ_data["damage"] = edit_organ_status(organ.damage, diagnostics)
+ organ_data["max_damage"] = organ.max_damage
+
+ data["organs"] += list(organ_data)
+
+ data["robolimb_self_repair_cap"] = ROBOLIMB_SELF_REPAIR_CAP
+ data["limbs"] = list()
+ for(var/obj/item/organ/external/limb in ipc.organs)
+ if(limb.brute_dam || limb.burn_dam)
+ data["limbs"] += list(list("name" = limb.name, "brute_damage" = edit_organ_status(limb.brute_dam, diagnostics), "burn_damage" = edit_organ_status(limb.burn_dam, diagnostics), "max_damage" = limb.max_damage))
+
+ var/obj/item/organ/internal/machine/power_core/C = ipc.internal_organs_by_name[BP_CELL]
+ if(C)
+ data["charge_percent"] = C.percent()
+
+ var/datum/component/synthetic_endoskeleton/endoskeleton = ipc.GetComponent(/datum/component/synthetic_endoskeleton)
+ if(istype(endoskeleton))
+ data["endoskeleton_damage"] = endoskeleton.damage
+ data["endoskeleton_max_damage"] = endoskeleton.max_damage
+
+ var/datum/component/armor/synthetic/synth_armor = ipc.GetComponent(/datum/component/armor/synthetic)
+ if(istype(synth_armor))
+ data["armor_data"] = list()
+ var/list/armor_damage = synth_armor.get_visible_damage()
+ for(var/key in armor_damage)
+ data["armor_data"] += list(list("key" = key, "status" = armor_damage[key]))
+
+ return data
+
+/**
+ * Edits a status number based on the diagnostics unit's status.
+ * The more damaged the diagnostics unit, the more errant a reading may be.
+ */
+/datum/tgui_module/ipc_diagnostic/proc/edit_organ_status(number, obj/item/organ/internal/machine/internal_diagnostics/diagnostics)
+ if(!diagnostics)
+ // what the hell are we doing here
+ return 0
+
+ var/integrity = diagnostics.get_integrity()
+ if(integrity > IPC_INTEGRITY_THRESHOLD_LOW)
+ return number
+
+ var/variancy = (100 / integrity * rand(1, (100 / integrity * 10)))
+ var/sign = prob(variancy) ? pick(1, -1) : 1
+ var/random_number = sign * (rand(1, 100 / integrity))
+
+ return number + (sign * rand(1, variancy) + random_number)
diff --git a/code/modules/tgui/modules/neural_configuration.dm b/code/modules/tgui/modules/neural_configuration.dm
new file mode 100644
index 00000000000..9dca5f4e27d
--- /dev/null
+++ b/code/modules/tgui/modules/neural_configuration.dm
@@ -0,0 +1,88 @@
+/datum/tgui_module/neural_configuration
+ var/mob/living/carbon/human/owner
+
+/datum/tgui_module/neural_configuration/New(mob/user, mob/target)
+ . = ..()
+ if(!isipc(target))
+ log_debug("Neural configuration created without IPC target!")
+ qdel(src)
+ owner = target
+
+/datum/tgui_module/neural_configuration/Destroy(force)
+ owner = null
+ return ..()
+
+/datum/tgui_module/neural_configuration/ui_interact(var/mob/user, var/datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
+ if(!ui)
+ ui = new(user, src, "NeuralConfiguration", "Neural Configuration", 400, 400)
+ ui.open()
+
+/datum/tgui_module/neural_configuration/ui_data(mob/user)
+ var/list/data = list()
+ var/obj/item/organ/internal/machine/posibrain/posibrain = owner.internal_organs_by_name[BP_BRAIN]
+ if(istype(posibrain))
+ data["neural_coherence"] = posibrain.damage
+ data["max_neural_coherence"] = posibrain.max_damage
+ data["owner_real_name"] = posibrain.owner.real_name
+ data["firewall"] = posibrain.firewall
+ data["p2p_communication"] = posibrain.p2p_communication_allowed
+
+ var/obj/item/organ/internal/machine/access_port/port = owner.internal_organs_by_name[BP_ACCESS_PORT]
+ if(istype(port) && !port.is_broken())
+ data["port"] = TRUE
+ if(port.access_cable && istype(port.access_cable.target, /obj/item/organ/internal/machine/access_port))
+ var/obj/item/organ/internal/machine/access_port/other_port = port.access_cable.target
+ if(isipc(other_port.owner))
+ var/mob/living/carbon/human/connected_ipc = other_port.owner
+ data["port_connected"] = connected_ipc.real_name
+ if(other_port && !other_port.is_broken())
+ data["port_can_communicate"] = TRUE
+
+ return data
+
+/datum/tgui_module/neural_configuration/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state)
+ . = ..()
+ switch(action)
+ if("toggle_firewall")
+ var/obj/item/organ/internal/machine/posibrain/posibrain = owner.internal_organs_by_name[BP_BRAIN]
+ if(istype(posibrain))
+ posibrain.toggle_firewall()
+ . = TRUE
+
+ if("toggle_p2p")
+ var/obj/item/organ/internal/machine/posibrain/posibrain = owner.internal_organs_by_name[BP_BRAIN]
+ if(istype(posibrain))
+ posibrain.toggle_p2p()
+ . = TRUE
+
+ if("talk_p2p")
+ var/obj/item/organ/internal/machine/access_port/port = owner.internal_organs_by_name[BP_ACCESS_PORT]
+ if(istype(port) && !port.is_broken())
+ if(port.access_cable && istype(port.access_cable.target, /obj/item/organ/internal/machine/access_port))
+ var/obj/item/organ/internal/machine/access_port/other_port = port.access_cable.target
+ if(isipc(other_port.owner))
+ var/mob/living/carbon/human/connected_ipc = other_port.owner
+ if(other_port && !other_port.is_broken())
+ if(connected_ipc.client)
+ var/obj/item/organ/internal/machine/posibrain/other_posibrain = connected_ipc.internal_organs_by_name[BP_BRAIN]
+ if(!other_posibrain.p2p_communication_allowed)
+ to_chat(owner, SPAN_MACHINE_WARNING("[connected_ipc.real_name]'s Virtual Communication ports are not open."))
+ return
+
+ var/message = sanitize_tg(tgui_input_text(owner, "Enter a peer-to-peer message to send to [connected_ipc.real_name].", "Virtual Communication", max_length = MAX_MESSAGE_LEN))
+ // re-do all the checks just in case. bit ass but oh well
+ if(message && !port.is_broken() && (port.access_cable.target && port.access_cable.target == connected_ipc && !other_port.is_broken()))
+ var/p2p_message = SPAN_ITALIC("Virtual Communication, ") + SPAN_BOLD("[owner.real_name] transmits: ") + SPAN_MACHINE_WARNING(message)
+ to_chat(connected_ipc, p2p_message)
+ log_say("VIRTUAL COMMUNICATION: [owner]/[owner.client.ckey] to [connected_ipc]/[connected_ipc.client.ckey]: [message]")
+ . = TRUE
+ else
+ to_chat(owner, SPAN_WARNING("That posibrain seems to be in deep sleep."))
+ . = FALSE
+ else
+ to_chat(owner, SPAN_WARNING("The other port isn't responding at all!"))
+ . = FALSE
+
+ if(.)
+ sound_to(owner, 'sound/effects/neural_config.ogg')
diff --git a/code/modules/tgui/states/default.dm b/code/modules/tgui/states/default.dm
index b3dceb54daa..0f93514cfe6 100644
--- a/code/modules/tgui/states/default.dm
+++ b/code/modules/tgui/states/default.dm
@@ -64,3 +64,14 @@ GLOBAL_DATUM_INIT(default_state, /datum/ui_state/default, new)
if(ismech(src_object))
return UI_INTERACTIVE
return ..()
+
+/mob/living/carbon/human/default_can_use_topic(src_object)
+ . = ..()
+
+ var/obj/item/organ/internal/machine/wireless_access/access_point = internal_organs_by_name[BP_WIRELESS_ACCESS]
+ if(istype(access_point))
+ // Just like robots, these IPCs can interact with anything they can see.
+ var/list/clientviewlist = getviewsize(client.view)
+ if(get_dist(src, src_object) <= min(clientviewlist[1], clientviewlist[2]))
+ return UI_INTERACTIVE
+ return UI_DISABLED // Otherwise they can keep the UI open.
diff --git a/config/.gitignore b/config/.gitignore
index 18b0b39cda4..50ca422c953 100644
--- a/config/.gitignore
+++ b/config/.gitignore
@@ -1,4 +1,5 @@
#ignore everything here, except subdirectories.
/*
+/synthetic_messages/*
/lore_radio/*
-!/*/
\ No newline at end of file
+!/*/
diff --git a/config/example/synthetic_messages/low_integrity_messages.txt b/config/example/synthetic_messages/low_integrity_messages.txt
new file mode 100644
index 00000000000..ee33e2efb59
--- /dev/null
+++ b/config/example/synthetic_messages/low_integrity_messages.txt
@@ -0,0 +1 @@
+Integrity very low.
diff --git a/html/changelogs/mattatlas-whereaboutsoftheheart.yml b/html/changelogs/mattatlas-whereaboutsoftheheart.yml
new file mode 100644
index 00000000000..af1e6857e7c
--- /dev/null
+++ b/html/changelogs/mattatlas-whereaboutsoftheheart.yml
@@ -0,0 +1,65 @@
+################################
+# Example Changelog File
+#
+# Note: This file, and files beginning with ".", and files that don't end in ".yml" will not be read. If you change this file, you will look really dumb.
+#
+# Your changelog will be merged with a master changelog. (New stuff added only, and only on the date entry for the day it was merged.)
+# When it is, any changes listed below will disappear.
+#
+# Valid Prefixes:
+# bugfix
+# - (fixes bugs)
+# wip
+# - (work in progress)
+# qol
+# - (quality of life)
+# soundadd
+# - (adds a sound)
+# sounddel
+# - (removes a sound)
+# rscadd
+# - (adds a feature)
+# rscdel
+# - (removes a feature)
+# imageadd
+# - (adds an image or sprite)
+# imagedel
+# - (removes an image or sprite)
+# spellcheck
+# - (fixes spelling or grammar)
+# experiment
+# - (experimental change)
+# balance
+# - (balance changes)
+# code_imp
+# - (misc internal code change)
+# refactor
+# - (refactors code)
+# config
+# - (makes a change to the config files)
+# admin
+# - (makes changes to administrator tools)
+# server
+# - (miscellaneous changes to server)
+#################################
+
+# Your name.
+author: MattAtlas (Code), Geeves (Code), Noble Row (Sprites)
+
+# Optional: Remove this file after generating master changelog. Useful for PR changelogs that won't get used again.
+delete-after: True
+
+# Any changes you've made. See valid prefix list above.
+# INDENT WITH TWO SPACES. NOT TABS. SPACES.
+# SCREW THIS UP AND IT WON'T WORK.
+# Also, this gets changed to [] after reading. Just remove the brackets when you add new shit.
+# Please surround your changes in double quotes ("). It works without them, but if you use certain characters it screws up compiling. The quotes will not show up in the changelog.
+changes:
+ - rscadd: "IPCs have been given unique, brainmed-compliant mechanics from the ground up!"
+ - rscadd: "IPCs have been given many new organs, all with their unique purpose, and functioning."
+ - rscadd: "Each IPC organ now has its own damage subsystem. IPCs are now vulnerable to long damage as well."
+ - rscadd: "IPCs now have a unique vulnerability to burst damage."
+ - rscadd: "Industrial IPCs can now be Security again."
+ - rscadd: "Added weight and strength mechanics for mobs."
+ - rscadd: "You can now force eject mobs out of a synthetic recharging station by clicking it."
+ - balance: "IPC subspecies have been rebalanced as a whole. Sheer brute modifiers have generally been nerfed to be from 1x to 0.8x (1x for baselines, 0.9x for industrial G2s, 0.8x for industrial G1s). The burn modifier for shells has been raised to 1.3x. Slowdown has generally been massively decreased across most heavy frames."
diff --git a/icons/effects/beam.dmi b/icons/effects/beam.dmi
index 84c33881f32..d6e4d9ef6e2 100644
Binary files a/icons/effects/beam.dmi and b/icons/effects/beam.dmi differ
diff --git a/icons/mob/screen/robot_pain.dmi b/icons/mob/screen/robot_pain.dmi
new file mode 100644
index 00000000000..19ce3eccd00
Binary files /dev/null and b/icons/mob/screen/robot_pain.dmi differ
diff --git a/icons/obj/action_buttons/organs.dmi b/icons/obj/action_buttons/organs.dmi
index 6927d1c3e93..736bca28eae 100644
Binary files a/icons/obj/action_buttons/organs.dmi and b/icons/obj/action_buttons/organs.dmi differ
diff --git a/icons/obj/cloning.dmi b/icons/obj/cloning.dmi
index af0bb42e602..953c6e2853a 100644
Binary files a/icons/obj/cloning.dmi and b/icons/obj/cloning.dmi differ
diff --git a/icons/obj/ipc_utilities.dmi b/icons/obj/ipc_utilities.dmi
index 417dac44941..b98485fe6e6 100644
Binary files a/icons/obj/ipc_utilities.dmi and b/icons/obj/ipc_utilities.dmi differ
diff --git a/icons/obj/organs/ipc_organs.dmi b/icons/obj/organs/ipc_organs.dmi
new file mode 100644
index 00000000000..ead24b730a6
Binary files /dev/null and b/icons/obj/organs/ipc_organs.dmi differ
diff --git a/icons/obj/surgery.dmi b/icons/obj/surgery.dmi
index 4af4b8ab725..3c697b348a9 100644
Binary files a/icons/obj/surgery.dmi and b/icons/obj/surgery.dmi differ
diff --git a/maps/away/away_site/idris_wreck/idris_wreck.dm b/maps/away/away_site/idris_wreck/idris_wreck.dm
index 7ca6d02693f..c1943ceb9ac 100644
--- a/maps/away/away_site/idris_wreck/idris_wreck.dm
+++ b/maps/away/away_site/idris_wreck/idris_wreck.dm
@@ -89,7 +89,7 @@
/obj/effect/landmark/corpse/idris/robot/do_extra_customization(mob/living/carbon/human/M)
M.adjustBruteLoss(rand(200,400))
M.dir = pick(GLOB.cardinals)
- var/obj/item/organ/internal/ipc_tag/tag = M.internal_organs_by_name[BP_IPCTAG]
+ var/obj/item/organ/internal/machine/ipc_tag/tag = M.internal_organs_by_name[BP_IPCTAG]
if(istype(tag))
tag.serial_number = uppertext(dd_limittext(md5(M.real_name), 12))
tag.ownership_info = IPC_OWNERSHIP_COMPANY
diff --git a/maps/away/away_site/konyang/point_verdant/point_verdant_ghostroles.dm b/maps/away/away_site/konyang/point_verdant/point_verdant_ghostroles.dm
index 2a23f269618..4f1943cbabc 100644
--- a/maps/away/away_site/konyang/point_verdant/point_verdant_ghostroles.dm
+++ b/maps/away/away_site/konyang/point_verdant/point_verdant_ghostroles.dm
@@ -14,7 +14,7 @@
respawn_flag = null
/obj/outfit/admin/konyang/post_equip(mob/living/carbon/human/H, visualsOnly)
- 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.serial_number = uppertext(dd_limittext(md5(H.real_name), 12))
tag.ownership_info = IPC_OWNERSHIP_SELF
diff --git a/maps/away/ships/biesel/tcaf_corvette/tcaf_corvette_ghostroles.dm b/maps/away/ships/biesel/tcaf_corvette/tcaf_corvette_ghostroles.dm
index 41f5b633f59..0c935a7db55 100644
--- a/maps/away/ships/biesel/tcaf_corvette/tcaf_corvette_ghostroles.dm
+++ b/maps/away/ships/biesel/tcaf_corvette/tcaf_corvette_ghostroles.dm
@@ -51,7 +51,7 @@
if(isoffworlder(H))
H.equip_or_collect(new /obj/item/storage/pill_bottle/rmt, slot_in_backpack)
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()
diff --git a/maps/away/ships/biesel/tcfl_patrol/tcfl_peacekeeper_ship_ghostroles.dm b/maps/away/ships/biesel/tcfl_patrol/tcfl_peacekeeper_ship_ghostroles.dm
index 7fec638cb49..ddf188b86e8 100644
--- a/maps/away/ships/biesel/tcfl_patrol/tcfl_peacekeeper_ship_ghostroles.dm
+++ b/maps/away/ships/biesel/tcfl_patrol/tcfl_peacekeeper_ship_ghostroles.dm
@@ -54,7 +54,7 @@
if(isoffworlder(H))
H.equip_or_collect(new /obj/item/storage/pill_bottle/rmt, slot_in_backpack)
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()
diff --git a/maps/away/ships/coc/coc_ranger/coc_ship_ghostroles.dm b/maps/away/ships/coc/coc_ranger/coc_ship_ghostroles.dm
index 2065b304cf6..00748944ca6 100644
--- a/maps/away/ships/coc/coc_ranger/coc_ship_ghostroles.dm
+++ b/maps/away/ships/coc/coc_ranger/coc_ship_ghostroles.dm
@@ -56,7 +56,7 @@
if(isoffworlder(H))
H.equip_or_collect(new /obj/item/storage/pill_bottle/rmt, slot_in_backpack)
if(isipc(H)) // All Ranger synthetics are tagged, self-owned, and have Coalition citizenship.
- 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.serial_number = uppertext(dd_limittext(md5(H.real_name), 12))
tag.ownership_info = IPC_OWNERSHIP_SELF
diff --git a/maps/away/ships/coc/coc_surveyor/coc_surveyor_ghostroles.dm b/maps/away/ships/coc/coc_surveyor/coc_surveyor_ghostroles.dm
index 9afc7cd59e0..718cfed379e 100644
--- a/maps/away/ships/coc/coc_surveyor/coc_surveyor_ghostroles.dm
+++ b/maps/away/ships/coc/coc_surveyor/coc_surveyor_ghostroles.dm
@@ -41,7 +41,7 @@
if(istajara(H))
H.equip_to_slot_or_del(new /obj/item/voidsuit_modkit/himeo/tajara, slot_r_hand)
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()
diff --git a/maps/away/ships/elyra/elyra_corvette/elyra_corvette_ghostroles.dm b/maps/away/ships/elyra/elyra_corvette/elyra_corvette_ghostroles.dm
index 863af4da901..ac195786a1a 100644
--- a/maps/away/ships/elyra/elyra_corvette/elyra_corvette_ghostroles.dm
+++ b/maps/away/ships/elyra/elyra_corvette/elyra_corvette_ghostroles.dm
@@ -40,7 +40,7 @@
/obj/outfit/admin/elyran_navy_crewman/post_equip(mob/living/carbon/human/H, visualsOnly = FALSE)
. = ..()
if(isipc(H)) // All Elyran Navy synthetics are tagged, self-owned, and have Elyran citizenship.
- 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.serial_number = uppertext(dd_limittext(md5(H.real_name), 12))
tag.ownership_info = IPC_OWNERSHIP_SELF
diff --git a/maps/away/ships/freebooter/freebooter_ship/freebooter_ship_ghostroles.dm b/maps/away/ships/freebooter/freebooter_ship/freebooter_ship_ghostroles.dm
index b2f78bbf043..caecae191aa 100644
--- a/maps/away/ships/freebooter/freebooter_ship/freebooter_ship_ghostroles.dm
+++ b/maps/away/ships/freebooter/freebooter_ship/freebooter_ship_ghostroles.dm
@@ -50,7 +50,7 @@
if(isoffworlder(H))
H.equip_or_collect(new /obj/item/storage/pill_bottle/rmt, slot_in_backpack)
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) //Shady pirates might well have untagged IPCs aboard
diff --git a/maps/away/ships/golden_deep/golden_deep_ghostroles.dm b/maps/away/ships/golden_deep/golden_deep_ghostroles.dm
index 71d5a04fc0f..de9d709df6d 100644
--- a/maps/away/ships/golden_deep/golden_deep_ghostroles.dm
+++ b/maps/away/ships/golden_deep/golden_deep_ghostroles.dm
@@ -82,7 +82,7 @@
/obj/outfit/admin/golden_deep/post_equip(mob/living/carbon/human/H, visualsOnly)
if(!istype(H))
return
- 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.serial_number = uppertext(dd_limittext(md5(H.real_name), 12))
tag.ownership_info = IPC_OWNERSHIP_PRIVATE // These are owned by the merchant.
@@ -93,7 +93,7 @@
if(!istype(H))
return
new /obj/item/organ/internal/augment/gustatorial/hand(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.serial_number = uppertext(dd_limittext(md5(H.real_name), 12))
tag.ownership_info = IPC_OWNERSHIP_PRIVATE // Hoplan are government owned.
@@ -103,7 +103,7 @@
if(!istype(H))
return
new /obj/item/organ/internal/augment/gustatorial/hand(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.serial_number = uppertext(dd_limittext(md5(H.real_name), 12))
tag.ownership_info = IPC_OWNERSHIP_SELF
diff --git a/maps/away/ships/heph/cyclops/cyclops_ghostroles.dm b/maps/away/ships/heph/cyclops/cyclops_ghostroles.dm
index 89eca7d03f6..1c45a87a3e2 100644
--- a/maps/away/ships/heph/cyclops/cyclops_ghostroles.dm
+++ b/maps/away/ships/heph/cyclops/cyclops_ghostroles.dm
@@ -52,7 +52,7 @@
H.internals.icon_state = "internal1"
H.update_body()
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()
diff --git a/maps/away/ships/heph/heph_security/heph_security_ghostroles.dm b/maps/away/ships/heph/heph_security/heph_security_ghostroles.dm
index dc4cfe16496..6f957d73281 100644
--- a/maps/away/ships/heph/heph_security/heph_security_ghostroles.dm
+++ b/maps/away/ships/heph/heph_security/heph_security_ghostroles.dm
@@ -51,7 +51,7 @@
if(H?.wear_suit)
H.wear_suit.color = pick("#4f3911", "#292826")
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()
diff --git a/maps/away/ships/iac/iac_rescue_ship_ghostroles.dm b/maps/away/ships/iac/iac_rescue_ship_ghostroles.dm
index 0b5971ced67..3b3f3d621cc 100644
--- a/maps/away/ships/iac/iac_rescue_ship_ghostroles.dm
+++ b/maps/away/ships/iac/iac_rescue_ship_ghostroles.dm
@@ -53,7 +53,7 @@
if(isoffworlder(H))
H.equip_or_collect(new /obj/item/storage/pill_bottle/rmt, slot_in_backpack)
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()
diff --git a/maps/away/ships/idris/idris_cruiser_ghostroles.dm b/maps/away/ships/idris/idris_cruiser_ghostroles.dm
index f04e8a1feb5..d5e250c1b5a 100644
--- a/maps/away/ships/idris/idris_cruiser_ghostroles.dm
+++ b/maps/away/ships/idris/idris_cruiser_ghostroles.dm
@@ -45,7 +45,7 @@
backpack_contents = list(/obj/item/storage/box/survival = 1)
/obj/outfit/admin/idris_cruiser_crew/post_equip(mob/living/carbon/human/H, visualsOnly)
- 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.serial_number = uppertext(dd_limittext(md5(H.real_name), 12))
tag.ownership_info = IPC_OWNERSHIP_COMPANY
diff --git a/maps/away/ships/konyang/einstein_shuttle/einstein_shuttle.dm b/maps/away/ships/konyang/einstein_shuttle/einstein_shuttle.dm
index 3dfb58108e6..60b384532d8 100644
--- a/maps/away/ships/konyang/einstein_shuttle/einstein_shuttle.dm
+++ b/maps/away/ships/konyang/einstein_shuttle/einstein_shuttle.dm
@@ -145,7 +145,7 @@
return list(ACCESS_EE_SPY_SHIP, ACCESS_EXTERNAL_AIRLOCKS)
/obj/outfit/admin/einstein_crew/post_equip(mob/living/carbon/human/H, visualsOnly)
- 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.serial_number = uppertext(dd_limittext(md5(H.real_name), 12))
tag.ownership_info = IPC_OWNERSHIP_SELF
diff --git a/maps/away/ships/konyang/ipc_refugee/ipc_refugee_ship_ghostroles.dm b/maps/away/ships/konyang/ipc_refugee/ipc_refugee_ship_ghostroles.dm
index adeeda1159e..4bfb1568c9e 100644
--- a/maps/away/ships/konyang/ipc_refugee/ipc_refugee_ship_ghostroles.dm
+++ b/maps/away/ships/konyang/ipc_refugee/ipc_refugee_ship_ghostroles.dm
@@ -71,7 +71,7 @@
)
/obj/outfit/admin/refugee_crew/ipc/post_equip(mob/living/carbon/human/H, visualsOnly)
- 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.serial_number = uppertext(dd_limittext(md5(H.real_name), 12))
tag.ownership_info = pick(IPC_OWNERSHIP_COMPANY, IPC_OWNERSHIP_PRIVATE) //fleeing solarian wildlands so probably none would be registered as self-owned
diff --git a/maps/away/ships/konyang/kasf_ship/kasf_ship_ghostroles.dm b/maps/away/ships/konyang/kasf_ship/kasf_ship_ghostroles.dm
index 720bda68005..b418940cdce 100644
--- a/maps/away/ships/konyang/kasf_ship/kasf_ship_ghostroles.dm
+++ b/maps/away/ships/konyang/kasf_ship/kasf_ship_ghostroles.dm
@@ -35,7 +35,7 @@
. = ..()
if(isoffworlder(H))
H.equip_or_collect(new /obj/item/storage/pill_bottle/rmt, slot_in_backpack)
- 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.serial_number = uppertext(dd_limittext(md5(H.real_name), 12))
tag.ownership_info = IPC_OWNERSHIP_SELF
diff --git a/maps/away/ships/konyang/water_barge/water_barge_ghostroles.dm b/maps/away/ships/konyang/water_barge/water_barge_ghostroles.dm
index bb5eb3d45ed..79a1b236d21 100644
--- a/maps/away/ships/konyang/water_barge/water_barge_ghostroles.dm
+++ b/maps/away/ships/konyang/water_barge/water_barge_ghostroles.dm
@@ -25,7 +25,7 @@
l_pocket = /obj/item/storage/wallet/random
/obj/outfit/admin/pachrom/post_equip(mob/living/carbon/human/H, visualsOnly)
- 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.serial_number = uppertext(dd_limittext(md5(H.real_name), 12))
tag.ownership_info = IPC_OWNERSHIP_SELF
diff --git a/maps/away/ships/lone_spacer/lone_spacer_ghostroles.dm b/maps/away/ships/lone_spacer/lone_spacer_ghostroles.dm
index edb4b58dbee..dc737a1ca74 100644
--- a/maps/away/ships/lone_spacer/lone_spacer_ghostroles.dm
+++ b/maps/away/ships/lone_spacer/lone_spacer_ghostroles.dm
@@ -50,7 +50,7 @@
if(isoffworlder(H))
H.equip_or_collect(new /obj/item/storage/pill_bottle/rmt, slot_in_backpack)
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) // Allows for untagged synthetics.
diff --git a/maps/away/ships/orion/orion_express_ship/orion_express_ship_ghostroles.dm b/maps/away/ships/orion/orion_express_ship/orion_express_ship_ghostroles.dm
index b53b9921bdc..42f4ceed188 100644
--- a/maps/away/ships/orion/orion_express_ship/orion_express_ship_ghostroles.dm
+++ b/maps/away/ships/orion/orion_express_ship/orion_express_ship_ghostroles.dm
@@ -52,7 +52,7 @@
if(isoffworlder(H))
H.equip_or_collect(new /obj/item/storage/pill_bottle/rmt, slot_in_backpack)
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()
diff --git a/maps/away/ships/orion/orion_miner/orion_miner_ghostroles.dm b/maps/away/ships/orion/orion_miner/orion_miner_ghostroles.dm
index f270068de8e..9615c393d02 100644
--- a/maps/away/ships/orion/orion_miner/orion_miner_ghostroles.dm
+++ b/maps/away/ships/orion/orion_miner/orion_miner_ghostroles.dm
@@ -55,7 +55,7 @@
H.equip_or_collect(new /obj/item/storage/pill_bottle/rmt, slot_in_backpack)
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]
H.equip_or_collect(new /obj/item/stack/nanopaste, slot_in_backpack)
if(istype(tag))
tag.modify_tag_data()
diff --git a/maps/away/ships/scc/scc_scout_ship_ghostroles.dm b/maps/away/ships/scc/scc_scout_ship_ghostroles.dm
index 90d6d08db51..580b139ce8b 100644
--- a/maps/away/ships/scc/scc_scout_ship_ghostroles.dm
+++ b/maps/away/ships/scc/scc_scout_ship_ghostroles.dm
@@ -196,7 +196,7 @@
H.equip_or_collect(new /obj/item/storage/pill_bottle/rmt, slot_in_backpack)
H.equip_or_collect(new /obj/item/rig/light/offworlder, slot_in_backpack)
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()
diff --git a/maps/away/ships/sol/sol_splf/splf_raider_ghostroles.dm b/maps/away/ships/sol/sol_splf/splf_raider_ghostroles.dm
index 5ef4d78da00..01d6c0d43f4 100644
--- a/maps/away/ships/sol/sol_splf/splf_raider_ghostroles.dm
+++ b/maps/away/ships/sol/sol_splf/splf_raider_ghostroles.dm
@@ -84,7 +84,7 @@
backpack_contents = list(/obj/item/storage/box/survival = 1)
/obj/outfit/admin/splf_ipc/post_equip(mob/living/carbon/human/H, visualsOnly)
- 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.serial_number = uppertext(dd_limittext(md5(H.real_name), 12))
tag.ownership_info = IPC_OWNERSHIP_PRIVATE
diff --git a/maps/away/ships/sol/sol_ssrm/ssrm_ship_ghostroles.dm b/maps/away/ships/sol/sol_ssrm/ssrm_ship_ghostroles.dm
index 9dd1335858a..f408c942e98 100644
--- a/maps/away/ships/sol/sol_ssrm/ssrm_ship_ghostroles.dm
+++ b/maps/away/ships/sol/sol_ssrm/ssrm_ship_ghostroles.dm
@@ -162,7 +162,7 @@
backpack_contents = list(/obj/item/storage/box/survival = 1, /obj/item/melee/energy/sword/knife/sol = 1)
/obj/outfit/admin/ssrm_ipc/post_equip(mob/living/carbon/human/H, visualsOnly)
- 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.serial_number = uppertext(dd_limittext(md5(H.real_name), 12))
tag.ownership_info = IPC_OWNERSHIP_PRIVATE
diff --git a/maps/away/ships/tramp_freighter/tramp_freighter_ghostroles.dm b/maps/away/ships/tramp_freighter/tramp_freighter_ghostroles.dm
index 23092f8bffa..78d4080b6c6 100644
--- a/maps/away/ships/tramp_freighter/tramp_freighter_ghostroles.dm
+++ b/maps/away/ships/tramp_freighter/tramp_freighter_ghostroles.dm
@@ -50,7 +50,7 @@
if(isoffworlder(H))
H.equip_or_collect(new /obj/item/storage/pill_bottle/rmt, slot_in_backpack)
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) //Shady smugglers might well have untagged IPCs
diff --git a/maps/random_ruins/exoplanets/burzsia/burzsia_dead_ipc.dmm b/maps/random_ruins/exoplanets/burzsia/burzsia_dead_ipc.dmm
index 2b7918d86c6..0fb0236e16e 100644
--- a/maps/random_ruins/exoplanets/burzsia/burzsia_dead_ipc.dmm
+++ b/maps/random_ruins/exoplanets/burzsia/burzsia_dead_ipc.dmm
@@ -34,7 +34,7 @@
},
/area/exoplanet/barren/burzsia)
"F" = (
-/obj/item/organ/internal/ipc_tag,
+/obj/item/organ/internal/machine/ipc_tag,
/turf/simulated/floor/exoplanet/barren{
color = "#b7410e"
},
diff --git a/maps/random_ruins/exoplanets/burzsia/burzsia_mining.dm b/maps/random_ruins/exoplanets/burzsia/burzsia_mining.dm
index 5dda6393dca..904dd9fb7a2 100644
--- a/maps/random_ruins/exoplanets/burzsia/burzsia_mining.dm
+++ b/maps/random_ruins/exoplanets/burzsia/burzsia_mining.dm
@@ -86,7 +86,7 @@
return list(ACCESS_GENERIC_AWAY_SITE, ACCESS_EXTERNAL_AIRLOCKS)
/obj/outfit/admin/burzsia_miner/post_equip(mob/living/carbon/human/H, visualsOnly)
- 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.serial_number = uppertext(dd_limittext(md5(H.real_name), 12))
tag.ownership_info = pick(IPC_OWNERSHIP_COMPANY)
diff --git a/maps/sccv_horizon/sccv_horizon.dmm b/maps/sccv_horizon/sccv_horizon.dmm
index 39a2929e7c0..df14149e75c 100644
--- a/maps/sccv_horizon/sccv_horizon.dmm
+++ b/maps/sccv_horizon/sccv_horizon.dmm
@@ -35920,10 +35920,7 @@
/obj/machinery/light{
dir = 8
},
-/obj/structure/engineer_maintenance/electric{
- dir = 8
- },
-/obj/structure/engineer_maintenance/pipe,
+/obj/machinery/optable/robotics,
/turf/simulated/floor/tiled/white,
/area/horizon/operations/machinist/surgicalbay)
"eKq" = (
diff --git a/nano/templates/mechfab.tmpl b/nano/templates/mechfab.tmpl
deleted file mode 100644
index 7fe6c36c253..00000000000
--- a/nano/templates/mechfab.tmpl
+++ /dev/null
@@ -1,88 +0,0 @@
-
-
- {{:helper.link("Sync", null, {'sync' : 1})}}
- {{:data.sync}}
-
-
- {{if data.manufacturers}}
-
Manufacturers
-
- {{for data.manufacturers}}
- {{:helper.link(value.company, null, {'manufacturer' : value.id}, value.id == data.manufacturer ? 'selected' : null)}}
- {{/for}}
-
- {{/if}}
-
Categories
-
- {{for data.categories}}
- {{:helper.link(value, null, {'category' : value}, value == data.category ? 'selected' : null)}}
- {{empty}}
- There are no known designs
- {{/for}}
-
-
Component
- {{for data.buildable}}
- {{if value.category == data.category}}
-
-
- {{:helper.link(value.name, null, {'build' : value.id})}}
-
-
- {{:value.resourses}}, {{:value.time}}
-
-
- {{/if}}
- {{/for}}
-
-
-
- Queue contains:
-
- {{if data.current}}
-
-
- {{if data.timeleft}}
- Time left: {{:data.current}} ({{:data.timeleft / 10}} seconds)
- {{else}}
- {{:data.current}} (Awaiting to start)
- {{/if}}
-
-
- {{:helper.link('Cancel', null, {'remove' : 1})}}
-
-
- {{for data.queue}}
-
-
- {{:index + 2}}: {{:value}}
-
-
- {{:helper.link('Remove', null, {'remove' : index + 2})}}
-
- {{empty}}
-
- The queue is empty
-
- {{/for}}
- {{else}}
-
- Nothing
-
- {{/if}}
-
Materials:
- {{for data.materials}}
-
-
- {{:value.mat}}: {{:value.amt}}/{{:data.maxres}}
-
-
- {{:helper.link('x1', null, {'eject' : value.mat, 'amount' : 1}, value.amt > 2000 ? null : 'disabled')}}
- {{:helper.link('x5', null, {'eject' : value.mat, 'amount' : 5}, value.amt > 10000 ? null : 'disabled')}}
- {{:helper.link('x10', null, {'eject' : value.mat, 'amount' : 10}, value.amt > 20000 ? null : 'disabled')}}
- {{:helper.link('Stack', null, {'eject' : value.mat, 'amount' : 0})}}
- {{:helper.link('All', null, {'eject' : value.mat, 'amount' : -1})}}
-
-
- {{/for}}
-
-
diff --git a/sound/attributions.txt b/sound/attributions.txt
index 40eaabab292..b3bd00e6133 100644
--- a/sound/attributions.txt
+++ b/sound/attributions.txt
@@ -37,8 +37,8 @@ ATTRIBUTION SECTION START
sound/weapons/reloads/pistol_metal_slide 7-9.ogg - Gary Guns Mixed.wav by GaryQ | License: Creative Commons 0. Obtained from www.freesound.org (not available anymore)
sound/weapons/reloads/pistol_metal_slide 10.ogg - PistolReloadSound.wav by MaximBomba | License: Creative Commons 0. Obtained from https://freesound.org/people/MaximBomba/sounds/432139/
sound/weapons/reloads/pistol_metal_slide 11.ogg - Browning Hi-Power handgun being reloaded while empty. Full sequence by serøtōnin | License: Creative Commons 0. Obtained from https://freesound.org/people/ser%C3%B8t%C5%8Dnin/sounds/674569/
-sound/weapons/reloads/pistol_polymer_slide 4.ogg - M9 Reload.wav by oneshotofficial | License: Creative Commons 0. Obtained from https://freesound.org/people/oneshotofficial/sounds/616758/
-sound/weapons/reloads/pistol_polymer_slide 5.ogg - Gary Guns Mixed.wav by GaryQ | License: Creative Commons 0. Obtained from www.freesound.org (not available anymore)
+sound/weapons/reloads/pistol_polymer_slide 4.ogg - M9 Reload.wav by oneshotofficial | License: Creative Commons 0. Obtained from https://freesound.org/people/oneshotofficial/sounds/616758/
+sound/weapons/reloads/pistol_polymer_slide 5.ogg - Gary Guns Mixed.wav by GaryQ | License: Creative Commons 0. Obtained from www.freesound.org (not available anymore)
sound/weapons/reloads/shotgun_pump 7-8.ogg - pump shotgun.WAV by shaana | License: Attribution NonCommercial 4.0. Obtained from https://freesound.org/people/shaana/sounds/222131/
sound/weapons/reloads/rifle_slide 3.ogg - Uzi submachine gun being reloaded while empty. Full sequence by serøtōnin | License: Creative Commons 0. Obtained from https://freesound.org/people/ser%C3%B8t%C5%8Dnin/sounds/677160/
sound/weapons/reloads/rifle_slide 10.ogg - AK-47 Assault Rifle being unloaded and reloaded by serøtōnin | License: Creative Commons 0. Obtained from https://freesound.org/people/ser%C3%B8t%C5%8Dnin/sounds/674742/
@@ -187,3 +187,22 @@ sound/machines/rig/rigstep_chonk.ogg - by majormoth(Discord), licensed under CC-
sound/machines/rig/rigstep_heavy.ogg - by majormoth(Discord), licensed under CC-BY-SA-3.0
sound/machines/rig/rigstep_medium.ogg - by majormoth(Discord), licensed under CC-BY-SA-3.0
ATTRIBUTIONS SECTION END
+
+ATTRIBUTIONS SECTOR START
+sound/effects/heat_alarm.ogg by Tim_Verberne -- https://freesound.org/s/514080/ -- License: Creative Commons 0
+sound/effects/reload_woosh.ogg by jwsounddesign -- https://freesound.org/s/611475/ -- License: Attribution 4.0
+sound/effects/synth_armor_break.ogg -- https://freesound.org/s/237375/ -- License: Creative Commons 0
+sound/effects/neural_config.ogg -- https://freesound.org/s/110931/ -- License: Attribution 4.0
+sound/effects/eas_beep_fadeinout.ogg -- https://freesound.org/s/34005/ -- License: Attribution 3.0
+sound/machines/synthfab/synthfab_start|running|end.ogg-- https://freesound.org/s/530765/ -- License: Creative Commons 0
+sound/machines/synthfab/synthfab_button.ogg -- https://freesound.org/s/140773/ -- License: Creative Commons 0
+sound/species/synthetic/light_electric_discharge.ogg -- https://freesound.org/s/103687/ -- License: Attribution NonCommercial 4.0
+sound/species/synthetic/heavy_electric_discharge.ogg -- https://freesound.org/s/103687/ -- License: Attribution NonCommercial 4.0
+sound/species/synthetic/access_cable_in.ogg -- https://freesound.org/s/15545/ -- License: Sampling+
+sound/species/synthetic/access_cable_out.ogg -- https://freesound.org/s/15545/ -- License: Sampling+
+sound/species/synthetic/synthetic_shock.ogg -- https://freesound.org/s/488665/ -- License: Creative Commons 0
+sound/species/synthetic/fragmentation.ogg -- https://freesound.org/s/390531/ -- License: Creative Commons 0
+sound/species/synthetic/sizzle_*.ogg -- https://freesound.org/s/35872/ -- License: Attribution NonCommercial 3.0
+sound/species/synthetic/synthetic_bootup.ogg -- https://freesound.org/s/320664/ -- License: Creative Commons 0 mixed with VHS Startup by Sassaby -- https://freesound.org/s/264934/ -- License: Creative Commons 0
+sound/species/synthetic/synthetic_restart.ogg -- https://freesound.org/s/176796/ -- License: Attribution 4.0, remixed with Audacity
+ATTRIBUTIONS SECTION END
diff --git a/sound/effects/eas_beep_fadeinout.ogg b/sound/effects/eas_beep_fadeinout.ogg
new file mode 100644
index 00000000000..8edcf65f185
Binary files /dev/null and b/sound/effects/eas_beep_fadeinout.ogg differ
diff --git a/sound/effects/heat_alarm.ogg b/sound/effects/heat_alarm.ogg
new file mode 100644
index 00000000000..681b6d1f753
Binary files /dev/null and b/sound/effects/heat_alarm.ogg differ
diff --git a/sound/effects/neural_config.ogg b/sound/effects/neural_config.ogg
new file mode 100644
index 00000000000..15766334320
Binary files /dev/null and b/sound/effects/neural_config.ogg differ
diff --git a/sound/effects/reload_woosh.ogg b/sound/effects/reload_woosh.ogg
new file mode 100644
index 00000000000..abfa81145a2
Binary files /dev/null and b/sound/effects/reload_woosh.ogg differ
diff --git a/sound/effects/synth_armor_break.ogg b/sound/effects/synth_armor_break.ogg
new file mode 100644
index 00000000000..07c14c46285
Binary files /dev/null and b/sound/effects/synth_armor_break.ogg differ
diff --git a/sound/machines/synthfab/synthfab_button.ogg b/sound/machines/synthfab/synthfab_button.ogg
new file mode 100644
index 00000000000..0582ecc41e1
Binary files /dev/null and b/sound/machines/synthfab/synthfab_button.ogg differ
diff --git a/sound/machines/synthfab/synthfab_end.ogg b/sound/machines/synthfab/synthfab_end.ogg
new file mode 100644
index 00000000000..efea48c47a1
Binary files /dev/null and b/sound/machines/synthfab/synthfab_end.ogg differ
diff --git a/sound/machines/synthfab/synthfab_running.ogg b/sound/machines/synthfab/synthfab_running.ogg
new file mode 100644
index 00000000000..0a625a6ecd9
Binary files /dev/null and b/sound/machines/synthfab/synthfab_running.ogg differ
diff --git a/sound/machines/synthfab/synthfab_start.ogg b/sound/machines/synthfab/synthfab_start.ogg
new file mode 100644
index 00000000000..9b2d32179d5
Binary files /dev/null and b/sound/machines/synthfab/synthfab_start.ogg differ
diff --git a/sound/species/synthetic/access_cable_in.ogg b/sound/species/synthetic/access_cable_in.ogg
new file mode 100644
index 00000000000..ec01a97e7b0
Binary files /dev/null and b/sound/species/synthetic/access_cable_in.ogg differ
diff --git a/sound/species/synthetic/access_cable_out.ogg b/sound/species/synthetic/access_cable_out.ogg
new file mode 100644
index 00000000000..5396467be3e
Binary files /dev/null and b/sound/species/synthetic/access_cable_out.ogg differ
diff --git a/sound/species/synthetic/fragmentation.ogg b/sound/species/synthetic/fragmentation.ogg
new file mode 100644
index 00000000000..445a4ba99d4
Binary files /dev/null and b/sound/species/synthetic/fragmentation.ogg differ
diff --git a/sound/species/synthetic/heavy_electric_discharge.ogg b/sound/species/synthetic/heavy_electric_discharge.ogg
new file mode 100644
index 00000000000..9b0add2476d
Binary files /dev/null and b/sound/species/synthetic/heavy_electric_discharge.ogg differ
diff --git a/sound/species/synthetic/light_electric_discharge.ogg b/sound/species/synthetic/light_electric_discharge.ogg
new file mode 100644
index 00000000000..0a2d42da731
Binary files /dev/null and b/sound/species/synthetic/light_electric_discharge.ogg differ
diff --git a/sound/species/synthetic/sizzling_end.ogg b/sound/species/synthetic/sizzling_end.ogg
new file mode 100644
index 00000000000..564b2c90959
Binary files /dev/null and b/sound/species/synthetic/sizzling_end.ogg differ
diff --git a/sound/species/synthetic/sizzling_mid.ogg b/sound/species/synthetic/sizzling_mid.ogg
new file mode 100644
index 00000000000..1feede0e411
Binary files /dev/null and b/sound/species/synthetic/sizzling_mid.ogg differ
diff --git a/sound/species/synthetic/synthetic_bootup.ogg b/sound/species/synthetic/synthetic_bootup.ogg
new file mode 100644
index 00000000000..9a54564cba4
Binary files /dev/null and b/sound/species/synthetic/synthetic_bootup.ogg differ
diff --git a/sound/species/synthetic/synthetic_restart.ogg b/sound/species/synthetic/synthetic_restart.ogg
new file mode 100644
index 00000000000..4546cb0a4c2
Binary files /dev/null and b/sound/species/synthetic/synthetic_restart.ogg differ
diff --git a/sound/species/synthetic/synthetic_shock.ogg b/sound/species/synthetic/synthetic_shock.ogg
new file mode 100644
index 00000000000..03c2633ec21
Binary files /dev/null and b/sound/species/synthetic/synthetic_shock.ogg differ
diff --git a/sound/species/synthetic/synthetic_stun.ogg b/sound/species/synthetic/synthetic_stun.ogg
new file mode 100644
index 00000000000..1aa85db9ed6
Binary files /dev/null and b/sound/species/synthetic/synthetic_stun.ogg differ
diff --git a/tgui/docs/tutorial-and-examples.md b/tgui/docs/tutorial-and-examples.md
index 3f19c11a38d..b856e642372 100644
--- a/tgui/docs/tutorial-and-examples.md
+++ b/tgui/docs/tutorial-and-examples.md
@@ -338,7 +338,7 @@ export const SampleInterface = (props, context) => {
const { act, data } = useBackend
(context);
return (
-
+
diff --git a/tgui/packages/tgui-panel/styles/tgchat/chat-dark.scss b/tgui/packages/tgui-panel/styles/tgchat/chat-dark.scss
index e7ad14027b4..b30f04ec270 100644
--- a/tgui/packages/tgui-panel/styles/tgchat/chat-dark.scss
+++ b/tgui/packages/tgui-panel/styles/tgchat/chat-dark.scss
@@ -361,6 +361,39 @@ em {
color: #5237e9;
font-weight: bold;
}
+.machine-warning {
+ color: #00ff00;
+ font-weight: bold;
+ border: 1px solid #080808;
+ background-color: #000;
+}
+.machine-danger {
+ color: #00ff00;
+ font-weight: bold;
+ border: 3px solid #080808;
+ background-color: #000;
+ display: block;
+
+ text-shadow: 0.05em 0 0 rgba(255, 0, 0, 0.75),
+ -0.05em -0.025em 0 rgba(0, 255, 0, 0.75),
+ -0.025em 0.05em 0 rgba(0, 0, 255, 0.75);
+
+ animation: glitch 650ms infinite;
+}
+
+.machine-vision {
+ color: #7ca4a3;
+ font-weight: bold;
+ border: 3px solid #ff0000;
+ background-color: #000;
+ display: block;
+
+ text-shadow: 0.05em 0 0 rgba(255, 0, 0, 0.75),
+ -0.05em -0.025em 0 rgba(0, 255, 0, 0.75),
+ -0.025em 0.05em 0 rgba(0, 0, 255, 0.75);
+
+ animation: glitch 650ms infinite;
+}
/* Admin: Private Messages */
.pm .howto {
@@ -651,3 +684,39 @@ em {
margin: 0;
padding: 0;
}
+
+@keyframes glitch {
+ 0% {
+ text-shadow: 0.05em 0 0 rgba(255, 0, 0, 0.85),
+ -0.04em -0.03em 0 rgba(0, 255, 0, 0.85),
+ -0.03em 0.05em 0 rgba(0, 0, 255, 0.85);
+ }
+ 20% {
+ text-shadow: 0.05em 0 0 rgba(255, 0, 0, 0.85),
+ -0.04em -0.03em 0 rgba(0, 255, 0, 0.85),
+ -0.03em 0.05em 0 rgba(0, 0, 255, 0.85);
+ }
+ 21% {
+ text-shadow: -0.04em -0.03em 0 rgba(255, 0, 0, 0.85),
+ 0.025em 0.025em 0 rgba(0, 255, 0, 0.85),
+ -0.04em -0.04em 0 rgba(0, 0, 255, 0.85);
+ }
+ 49% {
+ text-shadow: -0.04em -0.03em 0 rgba(255, 0, 0, 0.85),
+ 0.025em 0.025em 0 rgba(0, 255, 0, 0.85),
+ -0.04em -0.04em 0 rgba(0, 0, 255, 0.85);
+ }
+ 50% {
+ text-shadow: 0.025em 0.05em 0 rgba(255, 0, 0, 0.85),
+ 0.05em 0 0 rgba(0, 255, 0, 0.85), 0 -0.04em 0 rgba(0, 0, 255, 0.85);
+ }
+ 99% {
+ text-shadow: 0.025em 0.05em 0 rgba(255, 0, 0, 0.85),
+ 0.05em 0 0 rgba(0, 255, 0, 0.85), 0 -0.04em 0 rgba(0, 0, 255, 0.85);
+ }
+ 100% {
+ text-shadow: -0.03em 0 0 rgba(255, 0, 0, 0.85),
+ -0.03em -0.03em 0 rgba(0, 255, 0, 0.85),
+ -0.03em -0.04em 0 rgba(0, 0, 255, 0.85);
+ }
+}
diff --git a/tgui/packages/tgui-panel/styles/tgchat/chat-light.scss b/tgui/packages/tgui-panel/styles/tgchat/chat-light.scss
index ee9237d1670..70276b4f127 100644
--- a/tgui/packages/tgui-panel/styles/tgchat/chat-light.scss
+++ b/tgui/packages/tgui-panel/styles/tgchat/chat-light.scss
@@ -374,6 +374,39 @@ em {
color: #5237e9;
font-weight: bold;
}
+.machine-warning {
+ color: #00ff00;
+ font-weight: bold;
+ border: 1px solid #080808;
+ background-color: #000;
+}
+.machine-danger {
+ color: #00ff00;
+ font-weight: bold;
+ border: 3px solid #080808;
+ background-color: #000;
+ display: block;
+
+ text-shadow: 0.05em 0 0 rgba(255, 0, 0, 0.75),
+ -0.05em -0.025em 0 rgba(0, 255, 0, 0.75),
+ -0.025em 0.05em 0 rgba(0, 0, 255, 0.75);
+
+ animation: glitch 650ms infinite;
+}
+
+.machine-vision {
+ color: #7ca4a3;
+ font-weight: bold;
+ border: 3px solid #ff0000;
+ background-color: #000;
+ display: block;
+
+ text-shadow: 0.05em 0 0 rgba(255, 0, 0, 0.75),
+ -0.05em -0.025em 0 rgba(0, 255, 0, 0.75),
+ -0.025em 0.05em 0 rgba(0, 0, 255, 0.75);
+
+ animation: glitch 650ms infinite;
+}
/* Admin: Private Messages */
.pm .howto {
@@ -669,3 +702,39 @@ h2.alert {
margin: 0;
padding: 0;
}
+
+@keyframes glitch {
+ 0% {
+ text-shadow: 0.05em 0 0 rgba(255, 0, 0, 0.85),
+ -0.04em -0.03em 0 rgba(0, 255, 0, 0.85),
+ -0.03em 0.05em 0 rgba(0, 0, 255, 0.85);
+ }
+ 20% {
+ text-shadow: 0.05em 0 0 rgba(255, 0, 0, 0.85),
+ -0.04em -0.03em 0 rgba(0, 255, 0, 0.85),
+ -0.03em 0.05em 0 rgba(0, 0, 255, 0.85);
+ }
+ 21% {
+ text-shadow: -0.04em -0.03em 0 rgba(255, 0, 0, 0.85),
+ 0.025em 0.025em 0 rgba(0, 255, 0, 0.85),
+ -0.04em -0.04em 0 rgba(0, 0, 255, 0.85);
+ }
+ 49% {
+ text-shadow: -0.04em -0.03em 0 rgba(255, 0, 0, 0.85),
+ 0.025em 0.025em 0 rgba(0, 255, 0, 0.85),
+ -0.04em -0.04em 0 rgba(0, 0, 255, 0.85);
+ }
+ 50% {
+ text-shadow: 0.025em 0.05em 0 rgba(255, 0, 0, 0.85),
+ 0.05em 0 0 rgba(0, 255, 0, 0.85), 0 -0.04em 0 rgba(0, 0, 255, 0.85);
+ }
+ 99% {
+ text-shadow: 0.025em 0.05em 0 rgba(255, 0, 0, 0.85),
+ 0.05em 0 0 rgba(0, 255, 0, 0.85), 0 -0.04em 0 rgba(0, 0, 255, 0.85);
+ }
+ 100% {
+ text-shadow: -0.03em 0 0 rgba(255, 0, 0, 0.85),
+ -0.03em -0.03em 0 rgba(0, 255, 0, 0.85),
+ -0.03em -0.04em 0 rgba(0, 0, 255, 0.85);
+ }
+}
diff --git a/tgui/packages/tgui/interfaces/CoolingUnitThermostat.tsx b/tgui/packages/tgui/interfaces/CoolingUnitThermostat.tsx
new file mode 100644
index 00000000000..1b58b89c756
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/CoolingUnitThermostat.tsx
@@ -0,0 +1,102 @@
+import { round } from 'common/math';
+import { BooleanLike } from 'common/react';
+import { useBackend } from '../backend';
+import { AnimatedNumber, Box, Button, Knob, LabeledList, Section } from '../components';
+import { Window } from '../layouts';
+
+export type ThermostatData = {
+ broken: BooleanLike;
+ thermostat: number;
+ thermostat_min: number;
+ thermostat_max: number;
+ passive_temp_change: number;
+ bodytemperature: number;
+ temperature_safety: number;
+ estimated_power_consumption: number;
+ safety_burnt: BooleanLike;
+};
+
+export const CoolingUnitThermostat = (props, context) => {
+ const { act, data } = useBackend(context);
+
+ return (
+
+
+ {data.broken ? : }
+
+
+ );
+};
+
+export const ThermostatWindow = (props, context) => {
+ const { act, data } = useBackend(context);
+
+ // K = C + 273.15 | C = K – 273.15
+ return (
+
+
+
+ My internals are currently at{' '}
+
+ °C.
+
+
+ Estimated power consumption:{' '}
+
+ kW.
+
+
+ act('change_thermostat', {
+ change_thermostat: value,
+ })
+ }
+ />
+
+
+ {data.safety_burnt ? (
+
+ TEMPERATURE SAFETY UNAVAILABLE
+
+ ) : (
+
+
+ act('temperature_safety')}
+ />
+
+
+ )}
+
+ );
+};
+
+export const Broken = (props, context) => {
+ const { act, data } = useBackend(context);
+
+ return (
+
+
+ ERROR ER0RR $R0RRO$!R41.%%!!(%$^^__+ @#F0E4/ERROR ER0RR
+ $R0RRO$!R41.%%!!(%$^^__+ @#F0E4/ERROR ER0RR $R0RRO$!R41.%%!!(%$^^__+
+ @#F0E4/ERROR ER0RR $R0RRO$!R41.%%!!(%$^^__+ @#F0E4/
+
+
+ );
+};
diff --git a/tgui/packages/tgui/interfaces/DiagnosticsUnit.tsx b/tgui/packages/tgui/interfaces/DiagnosticsUnit.tsx
new file mode 100644
index 00000000000..00c7fed936e
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/DiagnosticsUnit.tsx
@@ -0,0 +1,76 @@
+import { BooleanLike } from 'common/react';
+import { useBackend } from '../backend';
+import { Box, Section } from '../components';
+import { Window } from '../layouts';
+import { IPCDiagnostics } from './IPCDiagnostics';
+
+export type DiagnosticsData = {
+ broken: BooleanLike;
+ integrity: number;
+ machine_ui_theme: string;
+ patient_name: string;
+
+ temp: number;
+ robolimb_self_repair_cap: number;
+ charge_percent: number;
+
+ organs: Organ[];
+ limbs: Limb[];
+
+ armor_data: ArmorDamage[];
+
+ endoskeleton_damage: number;
+ endoskeleton_max_damage: number;
+};
+
+type ArmorDamage = {
+ key: string;
+ status: string;
+};
+
+type Organ = {
+ name: string;
+ desc: string;
+ damage: number;
+ max_damage: number;
+
+ wiring_status: number;
+ plating_status: number;
+ electronics_status: number;
+ diagnostics_info: string;
+};
+
+type Limb = {
+ name: string;
+ brute_damage: number;
+ burn_damage: number;
+ max_damage: number;
+};
+
+export const DiagnosticsUnit = (props, context) => {
+ const { act, data } = useBackend(context);
+
+ return (
+
+
+ {data.broken ? : }
+
+
+ );
+};
+
+export const Broken = (props, context) => {
+ const { act, data } = useBackend(context);
+
+ return (
+
+
+ ERROR ER0RR $R0RRO$!R41.%%!!(%$^^__+ @#F0E4/ERROR ER0RR
+ $R0RRO$!R41.%%!!(%$^^__+ @#F0E4/ERROR ER0RR $R0RRO$!R41.%%!!(%$^^__+
+ @#F0E4/ERROR ER0RR $R0RRO$!R41.%%!!(%$^^__+ @#F0E4/
+
+
+ );
+};
diff --git a/tgui/packages/tgui/interfaces/IPCDiagnostics.tsx b/tgui/packages/tgui/interfaces/IPCDiagnostics.tsx
new file mode 100644
index 00000000000..ffff29822dd
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/IPCDiagnostics.tsx
@@ -0,0 +1,299 @@
+import { BooleanLike } from 'common/react';
+import { capitalize } from 'common/string';
+import { useBackend } from '../backend';
+import { AnimatedNumber, Box, Collapsible, Divider, LabeledList, Section } from '../components';
+
+export type DiagnosticsData = {
+ broken: BooleanLike;
+ integrity: number;
+ machine_ui_theme: string;
+ patient_name: string;
+
+ temp: number;
+ robolimb_self_repair_cap: number;
+ charge_percent: number;
+
+ organs: Organ[];
+ limbs: Limb[];
+
+ armor_data: ArmorDamage[];
+
+ endoskeleton_damage: number;
+ endoskeleton_max_damage: number;
+};
+
+type ArmorDamage = {
+ key: string;
+ status: string;
+};
+
+type Organ = {
+ name: string;
+ desc: string;
+ damage: number;
+ max_damage: number;
+
+ wiring_status: number;
+ plating_status: number;
+ electronics_status: number;
+ diagnostics_info: string;
+};
+
+type Limb = {
+ name: string;
+ brute_damage: number;
+ burn_damage: number;
+ max_damage: number;
+};
+
+export const IPCDiagnostics = (props, context) => {
+ const { act, data } = useBackend(context);
+
+ return (
+ <>
+
+
+ Diagnostics unit integrity{' '}
+
+ {describeIntegrity(data.integrity)}
+
+ .
+
+
+ Frame temperature at{' '}
+
+
+ °C
+
+ .
+
+
+ Battery charge at{' '}
+
+ {data.charge_percent}%
+
+ .
+
+
+ Endoskeleton status:{' '}
+
+ {capitalize(
+ describeEndoskeletonIntegrity(
+ data.endoskeleton_damage,
+ data.endoskeleton_max_damage
+ )
+ )}
+
+ .
+
+ {data.armor_data.length ? (
+
+
+ External armor plating condition:{' '}
+
+ {data.armor_data.map((armor) => (
+
+
+ {capitalize(armor.status)}
+
+
+ ))}
+
+
+ ) : (
+ 'Internal armor plating nominal.'
+ )}
+
+
+ >
+ );
+};
+
+export const OrganDisplay = (props, context) => {
+ const { act, data } = useBackend(context);
+
+ return (
+
+ {data.organs.map((organ) => (
+
+ {organ.desc}
+
+
+ The {organ.name}'s internal components are{' '}
+
+ {describeOrganDamage(organ.damage, organ.max_damage)}
+
+ .
+
+ {organ.wiring_status ? (
+
+
+
+ {capitalize(describeIntegrity(organ.wiring_status))}
+
+
+
+
+ {capitalize(describeIntegrity(organ.plating_status))}
+
+
+
+
+ {capitalize(describeIntegrity(organ.electronics_status))}
+
+
+
+ ) : (
+ ''
+ )}
+ <>
+
+ {organ.diagnostics_info ? (
+
+ {organ.diagnostics_info}
+
+ ) : (
+ ''
+ )}
+ >
+
+ ))}
+
+ );
+};
+
+export const describeIntegrity = (integrity, max_integrity = 100) => {
+ if (integrity >= max_integrity) {
+ return 'undamaged';
+ } else if (integrity > max_integrity * 0.75) {
+ return 'fine';
+ } else if (integrity > max_integrity * 0.5) {
+ return 'problematic';
+ } else if (integrity > max_integrity * 0.25) {
+ return 'heavily compromised';
+ } else if (integrity > 0) {
+ return 'falling apart';
+ } else if (integrity <= 0) {
+ return 'destroyed';
+ } else {
+ return 'unknown';
+ }
+};
+
+const damageLabel = (value, max_value = 100) => {
+ if (value < max_value * 0.1) {
+ return 'bad';
+ }
+ if (value < max_value * 0.2) {
+ return 'bad';
+ } else if (value < max_value * 0.4) {
+ return 'average';
+ } else if (value < max_value * 0.6) {
+ return 'orange';
+ } else if (value < max_value * 0.8) {
+ return 'yellow';
+ } else if (value < max_value) {
+ return 'good';
+ } else {
+ return 'green';
+ }
+};
+
+const armorDamageLabel = (value, max_value = 100) => {
+ if (value === 'minor') {
+ return 'good';
+ } else if (value === 'moderate') {
+ return 'average';
+ } else if (value === 'serious') {
+ return 'orange';
+ } else if (value === 'catastrophic') {
+ return 'bad';
+ }
+};
+
+const describeOrganDamage = (damage, max_damage) => {
+ if (damage >= max_damage) {
+ return 'completely unresponsive';
+ } else if (damage > max_damage * 0.75) {
+ return 'severely mangled';
+ } else if (damage > max_damage * 0.5) {
+ return 'very damaged';
+ } else if (damage > max_damage * 0.25) {
+ return 'not responding properly';
+ } else if (damage > 0) {
+ return 'mostly responsive';
+ } else if (damage <= 0) {
+ return 'fully responsive';
+ } else {
+ return 'unknown';
+ }
+};
+
+const organDamageLabel = (damage, max_damage) => {
+ if (damage > max_damage * 0.9) {
+ return 'bad';
+ } else if (damage > max_damage * 0.75) {
+ return 'average';
+ } else if (damage > max_damage * 0.5) {
+ return 'orange';
+ } else if (damage > max_damage * 0.25) {
+ return 'yellow';
+ } else if (damage > 0) {
+ return 'good';
+ } else {
+ return 'green';
+ }
+};
+
+const describeEndoskeletonIntegrity = (damage, max_damage = 200) => {
+ if (damage >= max_damage) {
+ return 'completely destroyed';
+ } else if (damage > max_damage * 0.75) {
+ return 'extremely mangled';
+ } else if (damage > max_damage * 0.5) {
+ return 'severely damaged';
+ } else if (damage > max_damage * 0.3) {
+ return 'damaged';
+ } else if (damage > 0) {
+ return 'mostly fine';
+ } else if (damage <= 0) {
+ return 'fine';
+ } else {
+ return 'unknown';
+ }
+};
+
+const endoskeletonDamageLabel = (damage, max_damage = 200) => {
+ if (damage > max_damage * 0.75) {
+ return 'bad';
+ } else if (damage > max_damage * 0.5) {
+ return 'orange';
+ } else if (damage > max_damage * 0.3) {
+ return 'yellow';
+ } else if (damage > 0) {
+ return 'good';
+ } else {
+ return 'green';
+ }
+};
diff --git a/tgui/packages/tgui/interfaces/NeuralConfiguration.tsx b/tgui/packages/tgui/interfaces/NeuralConfiguration.tsx
new file mode 100644
index 00000000000..99b7fd601bf
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/NeuralConfiguration.tsx
@@ -0,0 +1,153 @@
+import { BooleanLike } from 'common/react';
+import { useBackend } from '../backend';
+import { Box, Button, LabeledControls, Section, Tooltip } from '../components';
+import { Window } from '../layouts';
+
+export type NeuralData = {
+ neural_coherence: number;
+ max_neural_coherence: number;
+ owner_real_name: string;
+ firewall: BooleanLike;
+ p2p_communication: BooleanLike;
+
+ port: BooleanLike;
+ port_connected: BooleanLike;
+ port_can_communicate: BooleanLike;
+};
+
+export const NeuralConfiguration = (props, context) => {
+ const { act, data } = useBackend(context);
+
+ return (
+ data.max_neural_coherence * 0.75
+ ? 'spookyconsole'
+ : 'scc'
+ }>
+
+
+
+ This is your consciousness,{' '}
+
+ {data.owner_real_name}.
+
+
+
+ Your{' '}
+
+ neural coherence
+
+ is{' '}
+
+ {describeNeuralCoherence(
+ data.neural_coherence,
+ data.max_neural_coherence
+ )}
+ .
+
+
+
+
+
+
+
+
+ {data.port ? (
+ data.port_connected ? (
+
+ A Virtual Connection is set up.{' '}
+
+ ) : (
+ ''
+ )
+ ) : (
+ ''
+ )}
+
+
+
+ );
+};
+
+export const describeNeuralCoherence = (
+ neuralCoherence,
+ maxNeuralCoherence = 200
+) => {
+ if (neuralCoherence > maxNeuralCoherence * 0.8) {
+ return 'F̷͈͈̥͉̀͌̓̓̀̈́̆̓͒̓̊A̷͖͔̮̯͕̖̼̜͛̄̈́̐͘̕L̶̛͎̞̪̋̎̀̎͐̐̉͆͂́̇̅͘͠L̸̢̹̘̟̈́͘͝I̵̢̢̛̪͍̭͖̝͔͉̙̱͎̼̭̝͒̅͆̾̍͛̎̀̅̐N̶̨̘̭͒͑͛͛̈̚̚͜G̶͙͖̊̍̋̔͝͝͝͠ ̷̢̖̞̣̲̠͊̊ͅȂ̸̛̹̟̻̘̭͓̣̺̱͙͎͉͖͇̲͗͐̓P̴̧̲̬̗̥͖̤̫͓̲̝͉̓͋͜Ȁ̸̹̈́̊͗̌̾́̽͑̓̕R̸̢̡͇͋̔̔̓̒͂̉̎̍̎͜T̸̡͕̥̖̳͚͓̜͇̱͚̤̮̰̔̉̒͛͌͛̀̈́̓͝ͅ';
+ } else if (neuralCoherence > maxNeuralCoherence * 0.75) {
+ return 'heavily fragmented';
+ } else if (neuralCoherence > maxNeuralCoherence * 0.5) {
+ return 'fragmented';
+ } else if (neuralCoherence > maxNeuralCoherence * 0.25) {
+ return 'slightly fragmented';
+ } else if (neuralCoherence > maxNeuralCoherence * 0.1) {
+ return 'slightly desynchronized';
+ } else {
+ return 'synchronized';
+ }
+};
+
+export const neuralCoherenceLabel = (
+ neuralCoherence,
+ maxNeuralCoherence = 200
+) => {
+ if (neuralCoherence > maxNeuralCoherence * 0.75) {
+ return 'bad';
+ } else if (neuralCoherence > maxNeuralCoherence * 0.5) {
+ return 'orange';
+ } else if (neuralCoherence > maxNeuralCoherence * 0.25) {
+ return 'yellow';
+ } else if (neuralCoherence > maxNeuralCoherence * 0.1) {
+ return 'good';
+ } else {
+ return 'good';
+ }
+};
diff --git a/tgui/packages/tgui/interfaces/RoboticsComputer.tsx b/tgui/packages/tgui/interfaces/RoboticsComputer.tsx
new file mode 100644
index 00000000000..8784108f162
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/RoboticsComputer.tsx
@@ -0,0 +1,62 @@
+import { useBackend } from '../backend';
+import { NoticeBox } from '../components';
+import { NtosWindow } from '../layouts';
+import { IPCDiagnostics } from './IPCDiagnostics';
+
+export type DiagnosticsData = {
+ integrity: number;
+ machine_ui_theme: string;
+ patient_name: string;
+
+ temp: number;
+ robolimb_self_repair_cap: number;
+ charge_percent: number;
+
+ organs: Organ[];
+ limbs: Limb[];
+
+ armor_data: ArmorDamage[];
+
+ endoskeleton_damage: number;
+ endoskeleton_max_damage: number;
+};
+
+type ArmorDamage = {
+ key: string;
+ status: string;
+};
+
+type Organ = {
+ name: string;
+ desc: string;
+ damage: number;
+ max_damage: number;
+
+ wiring_status: number;
+ plating_status: number;
+ electronics_status: number;
+ diagnostics_info: string;
+};
+
+type Limb = {
+ name: string;
+ brute_damage: number;
+ burn_damage: number;
+ max_damage: number;
+};
+
+export const RoboticsComputer = (props, context) => {
+ const { act, data } = useBackend(context);
+
+ return (
+
+
+ {!data.patient_name ? (
+ You must run a diagnostic first.
+ ) : (
+
+ )}
+
+
+ );
+};
diff --git a/tgui/packages/tgui/interfaces/SyntheticFabricator.tsx b/tgui/packages/tgui/interfaces/SyntheticFabricator.tsx
new file mode 100644
index 00000000000..6a0389c77aa
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/SyntheticFabricator.tsx
@@ -0,0 +1,198 @@
+import { round } from '../../common/math';
+import { useBackend } from '../backend';
+import { AnimatedNumber, Box, Button, Flex, LabeledControls, LabeledList, NoticeBox, ProgressBar, Section, Table, Tabs } from '../components';
+import { Window } from '../layouts';
+
+export type FabricatorData = {
+ manufacturer: string;
+
+ current: string;
+ queue: QueueItem[];
+ buildable: BuildableItem[];
+ category: string;
+ available_categories: Category[];
+
+ manufacturers: Manufacturer[];
+ selected_manufacturer: string;
+
+ materials: Material[];
+ maximum_resource_amount: number;
+
+ sync: string;
+
+ timeleft: number;
+};
+
+type BuildableItem = {
+ name: string;
+ desc: string;
+ type: string;
+ category: string;
+ resources: string;
+ time: string;
+};
+
+type QueueItem = {
+ name: string;
+ time: string;
+ index: number;
+};
+
+type Category = {
+ name: string;
+};
+
+type Manufacturer = {
+ name: string;
+ type: string;
+};
+
+type Material = {
+ name: string;
+ amount: number;
+};
+
+export const SyntheticFabricator = (props, context) => {
+ const { act, data } = useBackend(context);
+
+ return (
+
+
+
+
+ act('sync')}
+ />
+ }>
+
+ {data.materials.map((material) => (
+
+ act('eject', {
+ eject: material.name,
+ amount: material.amount,
+ })
+ }
+ />
+ }>
+
+ {material.amount} / {data.maximum_resource_amount}{' '}
+
+
+ ))}
+
+
+
+
+
+ {data.manufacturers.map((manufacturer) => (
+
+
+
+
+
+ {data.timeleft ? (
+
+ Time remaining:{' '}
+ .
+
+ ) : (
+ No component is currently being lathed.
+ )}
+ {data.queue.length ? (
+
+ {data.queue.map((queue) => (
+
+ {queue.time}{' '}
+ act('remove', { remove: queue.index })}
+ />
+
+ ))}
+
+ ) : (
+ 'The manufacturing queue is currently empty.'
+ )}
+
+
+
+
+ );
+};
diff --git a/tgui/packages/tgui/layouts/NtosWindow.js b/tgui/packages/tgui/layouts/NtosWindow.js
index 93a1d9913a9..f14110adf8a 100644
--- a/tgui/packages/tgui/layouts/NtosWindow.js
+++ b/tgui/packages/tgui/layouts/NtosWindow.js
@@ -23,6 +23,8 @@ export const NtosWindow = (props, context) => {
PC_stationtime,
PC_programheaders = [],
PC_showexitprogram,
+ PC_hascable,
+ PC_cableout,
PC_haslight,
PC_lighton,
} = data;
@@ -89,6 +91,19 @@ export const NtosWindow = (props, context) => {
/>
)}
+ {!!PC_hascable && (
+ act('PC_takecable')}
+ />
+ )}
{!!PC_haslight && (