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

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

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

Key features:

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

To-do:

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

---------

Signed-off-by: Werner <1331699+Arrow768@users.noreply.github.com>
Co-authored-by: Matt Atlas <liermattia@gmail.com>
Co-authored-by: Geeves <22774890+Geevies@users.noreply.github.com>
Co-authored-by: Werner <1331699+Arrow768@users.noreply.github.com>
This commit is contained in:
Matt Atlas
2025-12-21 17:26:23 +01:00
committed by GitHub
parent 1898ad3417
commit e0aa218843
201 changed files with 5962 additions and 896 deletions
@@ -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
@@ -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
@@ -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, <span class='notice'>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)
@@ -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()
. = ..()
@@ -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
@@ -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
@@ -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()
. = ..()
@@ -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)
@@ -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)
@@ -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
@@ -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."))
@@ -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
@@ -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.")] <a href='byond://?src=[REF(src)];resist_self_preservation=1'>Resist it!</a>")))
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. <span class='danger'>It seems to be different from a regular positronic brain...</span>"
relative_size = 60
emp_coeff = 0.1
color = COLOR_RED
@@ -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
@@ -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"
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
+8 -2
View File
@@ -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))
-360
View File
@@ -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"
+9
View File
@@ -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)