mirror of
https://github.com/ParadiseSS13/Paradise.git
synced 2026-07-21 03:55:05 +01:00
203105788c
* fixes * fuck my stupid chungus life * Minion limit, heal fix, dead sac fix * cooldown, no sacrificing star gazer or ascended alive heretics * blade debuff * oopsy * Update tgui.bundle.js * map diff bot what ya doing * fuck that chat spam * lets heretic armour hold a haunted longsword * why not it makes sense * do_after * god I hate this bullshit * other lewc stuff * push * heretic id card fix * she tg on my ui till I css * yes * spent * fix / ipc buff (real)™️ * moderate again * revert * no reserve * bringing up to master * update map files to master * didnt replace centcomm * beginning some rebalancing * aggressive spread tweaks * lots of tweaks and fixes * trying to un-key the maps * maybe this time * this time???? * oops * sql fix * basicmob conversion * paintings! and a critical influence fix * rust + tweaks * monster tweak * small change * removing this * more tweaks. no more dusting * added some examine_more * flower seeds * various tweaks. more to come * no more conduit spacing * fixed some dumb stuff * silly stuff * its always prettier * bugfixes and linters * linters, wow * oops * bah * linter * fuck you * temp check * hidden influence drain * influence visible message * tweak fix * void cloak bugfix * small fixes * fixes * do_after_once broken * fixes and tweaks * heretic blade potential fix + sacrifice changes * batch of fixes * tiny tweak * rebuilt TGUI * no greentext + rerolls * logging + bugfix * unused var * small fix * various fixes * comment * projectile change * tgui rebuild * tgui bundle redo * rune issue solved * influence visible now * fix ui reloading * new moon ascension + fixes + icons * tweaks, species sprites * tgui rebuild * small tweak + linter * harvester icon tweak * spans * fixes and tweaks * caretaker nerf + tweaks * potential fix for knowledge * roller fix * mad mask * Update tgui.bundle.js * void phase tweak * Update tgui.bundle.js * misc tweaks * fix heretic not retargeting correctly with cryo * simplify logic * this is better * lots of fixes and tweaks * Update tgui.bundle.js * linter * linter * fireshark and greyscale insanity * fish * Update tgui.bundle.js * linter * linter * tgui * no window shopping * fish fix * tgui rebundle * moon smile runtime fix * various fixes * sacrifice fixes * insanity is easier now, madness mask changes. * bugfixing + teleport change * linters + tweaks * Update code/modules/antagonists/heretic/status_effects/mark_effects.dm Co-authored-by: DGamerL <108773801+DGamerL@users.noreply.github.com> Signed-off-by: Kyani <65205627+EmeraldCandy@users.noreply.github.com> * Update code/modules/antagonists/heretic/status_effects/mark_effects.dm Co-authored-by: DGamerL <108773801+DGamerL@users.noreply.github.com> Signed-off-by: Kyani <65205627+EmeraldCandy@users.noreply.github.com> --------- Signed-off-by: Kyani <65205627+EmeraldCandy@users.noreply.github.com> Co-authored-by: Qwertytoforty <52090703+Qwertytoforty@users.noreply.github.com> Co-authored-by: Burzah <116982774+Burzah@users.noreply.github.com> Co-authored-by: warriorstar-orion <orion@snowfrost.garden> Co-authored-by: Paul <pmerkamp@gmail.com> Co-authored-by: DGamerL <108773801+DGamerL@users.noreply.github.com>
1709 lines
58 KiB
Plaintext
1709 lines
58 KiB
Plaintext
#define OCCUPANT_LOGGING occupant ? occupant : "empty mech"
|
|
|
|
/obj/mecha
|
|
name = "Mecha"
|
|
desc = "Exosuit."
|
|
icon = 'icons/mecha/mecha.dmi'
|
|
|
|
density = TRUE //Dense. To raise the heat.
|
|
anchored = TRUE //no pulling around.
|
|
resistance_flags = FIRE_PROOF
|
|
layer = MOB_LAYER //icon draw layer
|
|
infra_luminosity = 15 //byond implementation is bugged.
|
|
force = 5
|
|
max_integrity = 300 //max_integrity is base health
|
|
armor = list(MELEE = 20, BULLET = 10, LASER = 0, ENERGY = 0, BOMB = 0, RAD = 0, FIRE = 100, ACID = 75)
|
|
bubble_icon = "machine"
|
|
cares_about_temperature = TRUE
|
|
var/alist/facing_modifiers = alist(MECHA_FRONT_ARMOUR = 1.5, MECHA_SIDE_ARMOUR = 1, MECHA_BACK_ARMOUR = 0.5)
|
|
var/initial_icon = null //Mech type for resetting icon. Only used for reskinning kits (see custom items)
|
|
var/can_move = 0 // time of next allowed movement
|
|
/// Time it takes to enter the mech
|
|
var/mech_enter_time = 4 SECONDS
|
|
var/mob/living/carbon/occupant = null
|
|
var/step_in = 10 //make a step in step_in/10 sec.
|
|
var/dir_in = 2//What direction will the mech face when entered/powered on? Defaults to South.
|
|
var/normal_step_energy_drain = 10
|
|
var/step_energy_drain = 10
|
|
var/melee_energy_drain = 15
|
|
var/overload_step_energy_drain_min = 100
|
|
var/deflect_chance = 10 //chance to deflect the incoming projectiles, hits, or lesser the effect of ex_act.
|
|
var/obj/item/stock_parts/cell/cell
|
|
var/state = MECHA_MAINT_OFF
|
|
var/list/log = list()
|
|
var/last_message = 0
|
|
var/add_req_access = 1
|
|
var/maint_access = 1
|
|
var/dna //dna-locking the mech
|
|
var/list/proc_res = list() //stores proc owners, like proc_res["functionname"] = owner reference
|
|
var/datum/effect_system/spark_spread/spark_system = new
|
|
var/lights = FALSE
|
|
var/lights_power = 6
|
|
var/lights_range = 6
|
|
var/lights_power_ambient = LIGHTING_MINIMUM_POWER
|
|
var/lights_range_ambient = MINIMUM_USEFUL_LIGHT_RANGE
|
|
var/frozen = FALSE
|
|
var/repairing = FALSE
|
|
var/emp_proof = FALSE //If it is immune to emps
|
|
var/emag_proof = FALSE //If it is immune to emagging. Used by CC mechs.
|
|
|
|
//inner atmos
|
|
var/use_internal_tank = 0
|
|
var/internal_tank_valve = ONE_ATMOSPHERE
|
|
var/obj/machinery/atmospherics/portable/canister/internal_tank
|
|
var/datum/gas_mixture/cabin_air
|
|
var/obj/machinery/atmospherics/unary/portables_connector/connected_port = null
|
|
|
|
var/obj/item/radio/radio = null
|
|
var/list/trackers = list()
|
|
|
|
var/max_temperature = 25000
|
|
var/internal_damage_threshold = 50 //health percentage below which internal damage is possible
|
|
var/internal_damage = 0 //contains bitflags
|
|
|
|
var/list/operation_req_access = list()//required access level for mecha operation
|
|
var/list/internals_req_access = list(ACCESS_ENGINE,ACCESS_ROBOTICS)//required access level to open cell compartment
|
|
|
|
var/obj/structure/mecha_wreckage/wreckage = null // type that the mecha becomes when destroyed
|
|
|
|
var/list/equipment = list()
|
|
var/obj/item/mecha_parts/mecha_equipment/selected
|
|
var/max_equip = 3
|
|
var/turf/crashing = null
|
|
var/occupant_sight_flags = 0
|
|
|
|
var/stepsound = 'sound/mecha/mechstep.ogg'
|
|
var/turnsound = 'sound/mecha/mechturn.ogg'
|
|
var/nominalsound = 'sound/mecha/nominal.ogg'
|
|
var/zoomsound = 'sound/mecha/imag_enh.ogg'
|
|
var/critdestrsound = 'sound/mecha/critdestr.ogg'
|
|
var/weapdestrsound = 'sound/mecha/weapdestr.ogg'
|
|
var/lowpowersound = 'sound/mecha/lowpower.ogg'
|
|
var/longactivationsound = 'sound/mecha/nominal.ogg'
|
|
var/starting_voice = /obj/item/mecha_modkit/voice
|
|
var/activated = FALSE
|
|
var/power_warned = FALSE
|
|
|
|
/// DMI containing greyscale emissive overlays, responsible for what parts of the mech glow in the dark
|
|
var/emissive_appearance_icon = 'icons/mecha/mecha_emissive.dmi'
|
|
|
|
var/destruction_sleep_duration = 2 SECONDS //Time that mech pilot is put to sleep for if mech is destroyed
|
|
|
|
var/melee_cooldown = 10
|
|
var/mecha_melee_cooldown = FALSE
|
|
|
|
/// How many ion thrusters we got on this bad boy
|
|
var/thruster_count = 0
|
|
|
|
// Action vars
|
|
var/defence_mode = FALSE
|
|
var/defence_mode_deflect_chance = 35
|
|
var/leg_overload_mode = FALSE
|
|
var/leg_overload_coeff = 100
|
|
var/thrusters_active = FALSE
|
|
var/smoke = 5
|
|
var/smoke_ready = 1
|
|
var/smoke_cooldown = 100
|
|
var/zoom_mode = FALSE
|
|
var/phasing = FALSE
|
|
var/phasing_energy_drain = 200
|
|
var/phase_state = "" //icon_state when phasing
|
|
/// How much speed the mech loses while the buffer is active
|
|
var/buffer_delay = 1
|
|
/// Does it clean the tile under it?
|
|
var/floor_buffer = FALSE
|
|
|
|
//Action datums
|
|
var/datum/action/innate/mecha/mech_eject/eject_action = new
|
|
var/datum/action/innate/mecha/mech_toggle_internals/internals_action = new
|
|
var/datum/action/innate/mecha/mech_toggle_lights/lights_action = new
|
|
var/datum/action/innate/mecha/mech_view_stats/stats_action = new
|
|
var/datum/action/innate/mecha/mech_defence_mode/defense_action = new
|
|
var/datum/action/innate/mecha/mech_overload_mode/overload_action = new
|
|
var/datum/action/innate/mecha/mech_toggle_thrusters/thrusters_action = new
|
|
var/datum/effect_system/smoke_spread/smoke_system = new //not an action, but trigged by one
|
|
var/datum/action/innate/mecha/mech_smoke/smoke_action = new
|
|
var/datum/action/innate/mecha/mech_zoom/zoom_action = new
|
|
var/datum/action/innate/mecha/mech_toggle_phasing/phasing_action = new
|
|
var/datum/action/innate/mecha/mech_switch_damtype/switch_damtype_action = new
|
|
var/list/select_actions = list()
|
|
|
|
hud_possible = list (DIAG_STAT_HUD, DIAG_BATT_HUD, DIAG_MECH_HUD, DIAG_TRACK_HUD)
|
|
|
|
/obj/mecha/Initialize(mapload)
|
|
. = ..()
|
|
icon_state += "-open"
|
|
add_radio()
|
|
add_cabin()
|
|
add_airtank()
|
|
spark_system.set_up(2, 0, src)
|
|
spark_system.attach(src)
|
|
smoke_system.set_up(3, FALSE, src)
|
|
smoke_system.attach(src)
|
|
add_cell()
|
|
START_PROCESSING(SSobj, src)
|
|
GLOB.poi_list |= src
|
|
log_message("[src] created.")
|
|
GLOB.mechas_list += src //global mech list
|
|
prepare_huds()
|
|
for(var/hud_key, hud in GLOB.huds)
|
|
var/datum/atom_hud/data/diagnostic/diag_hud = hud
|
|
if(!istype(diag_hud))
|
|
continue
|
|
diag_hud.add_to_hud(src)
|
|
diag_hud_set_mechhealth()
|
|
diag_hud_set_mechcell()
|
|
diag_hud_set_mechstat()
|
|
diag_hud_set_mechtracking()
|
|
|
|
var/obj/item/mecha_modkit/voice/V = new starting_voice(src)
|
|
V.install(src)
|
|
qdel(V)
|
|
|
|
set_light(lights_range_ambient, lights_power_ambient)
|
|
update_icon(UPDATE_OVERLAYS)
|
|
AddElement(/datum/element/hostile_machine)
|
|
|
|
/obj/mecha/update_overlays()
|
|
. = ..()
|
|
underlays.Cut()
|
|
underlays += emissive_appearance(emissive_appearance_icon, "[icon_state]_lightmask")
|
|
overlays.Cut()
|
|
if(istype(selected, /obj/item/mecha_parts/mecha_equipment/pulse_shield) && !leg_overload_mode && !istype(cell, /obj/item/stock_parts/cell/infinite) && occupant)
|
|
overlays += mutable_appearance('icons/effects/effects.dmi', "at_shield2", MOB_LAYER + 0.01, alpha = 120)
|
|
|
|
////////////////////////
|
|
////// Helpers /////////
|
|
////////////////////////
|
|
|
|
/obj/mecha/get_cell()
|
|
return cell
|
|
|
|
/obj/mecha/proc/add_airtank()
|
|
internal_tank = new /obj/machinery/atmospherics/portable/canister/air(src)
|
|
return internal_tank
|
|
|
|
/obj/mecha/proc/add_cell(obj/item/stock_parts/cell/C=null)
|
|
if(C)
|
|
C.forceMove(src)
|
|
cell = C
|
|
return
|
|
cell = new/obj/item/stock_parts/cell/high/plus(src)
|
|
|
|
/obj/mecha/proc/add_cabin()
|
|
cabin_air = new
|
|
cabin_air.set_temperature(T20C)
|
|
cabin_air.volume = 200
|
|
cabin_air.set_oxygen(O2STANDARD * cabin_air.volume / (R_IDEAL_GAS_EQUATION * cabin_air.temperature()))
|
|
cabin_air.set_nitrogen(N2STANDARD * cabin_air.volume / (R_IDEAL_GAS_EQUATION * cabin_air.temperature()))
|
|
return cabin_air
|
|
|
|
/obj/mecha/proc/add_radio()
|
|
radio = new(src)
|
|
radio.name = "[src] radio"
|
|
radio.icon = icon
|
|
radio.icon_state = icon_state
|
|
|
|
/obj/mecha/examine(mob/user)
|
|
. = ..()
|
|
var/integrity = obj_integrity * 100 / max_integrity
|
|
switch(integrity)
|
|
if(85 to 100)
|
|
. += "It's fully intact."
|
|
if(65 to 85)
|
|
. += "It's slightly damaged."
|
|
if(45 to 65)
|
|
. += "It's badly damaged."
|
|
if(25 to 45)
|
|
. += "It's heavily damaged."
|
|
else
|
|
. += "It's falling apart."
|
|
if(equipment && length(equipment))
|
|
. += "It's equipped with:"
|
|
for(var/obj/item/mecha_parts/mecha_equipment/ME in equipment)
|
|
. += "[bicon(ME)] [ME]"
|
|
|
|
/obj/mecha/hear_talk(mob/M, list/message_pieces)
|
|
if(M == occupant && radio.broadcasting)
|
|
radio.talk_into(M, message_pieces)
|
|
|
|
/obj/mecha/proc/click_action(atom/target, mob/user, params)
|
|
if(!occupant || occupant != user)
|
|
return
|
|
if(user.incapacitated())
|
|
return
|
|
if(phasing)
|
|
occupant_message(SPAN_WARNING("Unable to interact with objects while phasing."))
|
|
return
|
|
if(state)
|
|
occupant_message(SPAN_WARNING("Maintenance protocols in effect."))
|
|
return
|
|
if(!get_charge())
|
|
return
|
|
if(src == target)
|
|
return
|
|
|
|
var/dir_to_target = get_dir(src, target)
|
|
if(dir_to_target && !(dir_to_target & dir)) // Wrong direction
|
|
to_chat(user, SPAN_WARNING("You're facing the wrong way!"))
|
|
return
|
|
|
|
if(hasInternalDamage(MECHA_INT_CONTROL_LOST))
|
|
target = safepick(view(3, target))
|
|
if(!target)
|
|
return
|
|
|
|
var/mob/living/L = user
|
|
if(isliving(target) && selected.harmful && HAS_TRAIT(L, TRAIT_PACIFISM))
|
|
to_chat(user, SPAN_WARNING("You don't want to harm other living beings!"))
|
|
return
|
|
|
|
if(selected?.is_ranged())
|
|
if(!target.Adjacent(src) || (target.Adjacent(src) && (occupant.a_intent == INTENT_HELP || occupant.a_intent == INTENT_GRAB)))
|
|
if(user.mind?.martial_art?.no_guns)
|
|
to_chat(L, SPAN_WARNING("[L.mind.martial_art.no_guns_message]"))
|
|
return
|
|
selected.action(target, params)
|
|
return
|
|
|
|
do_melee_attack(target)
|
|
|
|
else if(selected && selected.is_melee())
|
|
selected.action(target, params)
|
|
else
|
|
do_melee_attack(target)
|
|
|
|
/obj/mecha/proc/do_melee_attack(atom/target)
|
|
if(internal_damage & MECHA_INT_CONTROL_LOST)
|
|
target = safepick(oview(1, src))
|
|
|
|
if(mecha_melee_cooldown || !isatom(target) || !target.Adjacent(src))
|
|
return
|
|
if(!iswallturf(target) && !isliving(target) && !isobj(target))
|
|
return
|
|
target.mech_melee_attack(src)
|
|
mecha_melee_cooldown = TRUE
|
|
addtimer(VARSET_CALLBACK(src, mecha_melee_cooldown, FALSE), melee_cooldown)
|
|
|
|
/obj/mecha/proc/mech_toxin_damage(mob/living/target)
|
|
playsound(src, 'sound/effects/spray2.ogg', 50, 1)
|
|
if(target.reagents)
|
|
if(target.reagents.get_reagent_amount("atropine") + force < force*2)
|
|
target.reagents.add_reagent("atropine", force/2)
|
|
if(target.reagents.get_reagent_amount("toxin") + force < force*2)
|
|
target.reagents.add_reagent("toxin", force/2.5)
|
|
|
|
/obj/mecha/proc/range_action(atom/target)
|
|
return
|
|
|
|
|
|
//////////////////////////////////
|
|
//////// MARK: Movement procs
|
|
//////////////////////////////////
|
|
|
|
/obj/mecha/Process_Spacemove(movement_dir = 0, continuous_move = FALSE)
|
|
. = ..()
|
|
if(.)
|
|
return TRUE
|
|
if(thrusters_active && movement_dir && use_power(step_energy_drain))
|
|
step_in = initial(step_in) / thruster_count
|
|
new /obj/effect/particle_effect/ion_trails(get_turf(src), dir)
|
|
return TRUE
|
|
|
|
var/atom/movable/backup = get_spacemove_backup()
|
|
if(backup)
|
|
if(istype(backup) && movement_dir && !backup.anchored)
|
|
if(backup.newtonian_move(turn(movement_dir, 180)))
|
|
if(occupant)
|
|
to_chat(occupant, SPAN_NOTICE("You push off of [backup] to propel yourself."))
|
|
return TRUE
|
|
|
|
/obj/mecha/relaymove(mob/user, direction)
|
|
if(!direction || frozen)
|
|
return
|
|
if(user != occupant) //While not "realistic", this piece is player friendly.
|
|
user.forceMove(get_turf(src))
|
|
to_chat(user, SPAN_NOTICE("You climb out from [src]."))
|
|
return FALSE
|
|
if(connected_port)
|
|
if(world.time - last_message > 20)
|
|
occupant_message(SPAN_WARNING("Unable to move while connected to the air system port!"))
|
|
last_message = world.time
|
|
return FALSE
|
|
if(state)
|
|
occupant_message(SPAN_DANGER("Maintenance protocols in effect."))
|
|
return
|
|
return domove(direction)
|
|
|
|
/obj/mecha/proc/domove(direction)
|
|
if(can_move >= world.time)
|
|
return FALSE
|
|
if(!Process_Spacemove(direction))
|
|
return FALSE
|
|
if(!has_charge(step_energy_drain))
|
|
return FALSE
|
|
if(defence_mode)
|
|
if(world.time - last_message > 20)
|
|
occupant_message(SPAN_DANGER("Unable to move while in defence mode."))
|
|
last_message = world.time
|
|
return FALSE
|
|
if(zoom_mode)
|
|
if(world.time - last_message > 20)
|
|
occupant_message(SPAN_DANGER("Unable to move while in zoom mode."))
|
|
last_message = world.time
|
|
return FALSE
|
|
|
|
if(thrusters_active && has_gravity(src))
|
|
thrusters_active = FALSE
|
|
to_chat(occupant, SPAN_NOTICE("Thrusters automatically disabled."))
|
|
step_in = initial(step_in)
|
|
var/move_result = 0
|
|
var/move_type = 0
|
|
if(internal_damage & MECHA_INT_CONTROL_LOST)
|
|
move_result = mechsteprand()
|
|
move_type = MECHAMOVE_RAND
|
|
else if(dir != direction)
|
|
move_result = mechturn(direction)
|
|
move_type = MECHAMOVE_TURN
|
|
else
|
|
move_result = mechstep(direction)
|
|
move_type = MECHAMOVE_STEP
|
|
|
|
if(move_result && move_type)
|
|
aftermove(move_type)
|
|
can_move = world.time + step_in
|
|
return TRUE
|
|
return FALSE
|
|
|
|
/obj/mecha/proc/aftermove(move_type)
|
|
use_power(step_energy_drain)
|
|
if(move_type & (MECHAMOVE_RAND | MECHAMOVE_STEP) && occupant)
|
|
var/obj/machinery/atmospherics/unary/portables_connector/possible_port = locate(/obj/machinery/atmospherics/unary/portables_connector) in loc
|
|
if(possible_port)
|
|
var/atom/movable/screen/alert/mech_port_available/A = occupant.throw_alert("mechaport", /atom/movable/screen/alert/mech_port_available)
|
|
if(A)
|
|
A.target = possible_port
|
|
else
|
|
occupant.clear_alert("mechaport")
|
|
if(leg_overload_mode)
|
|
log_message("Leg Overload damage.")
|
|
take_damage(1, BRUTE, FALSE, FALSE)
|
|
if(obj_integrity < max_integrity - max_integrity / 3)
|
|
leg_overload_mode = FALSE
|
|
step_in = initial(step_in)
|
|
step_energy_drain = initial(step_energy_drain)
|
|
occupant_message("<font color='red'>Leg actuators damage threshold exceded. Disabling overload.</font>")
|
|
|
|
/obj/mecha/proc/mechturn(direction)
|
|
dir = direction
|
|
if(turnsound)
|
|
playsound(src,turnsound,40,1)
|
|
return TRUE
|
|
|
|
/obj/mecha/proc/mechstep(direction)
|
|
. = step(src, direction)
|
|
if(!.)
|
|
if(phasing && get_charge() >= phasing_energy_drain)
|
|
if(can_move < world.time)
|
|
. = FALSE // We lie to mech code and say we didn't get to move, because we want to handle power usage + cooldown ourself
|
|
flick("[initial_icon]-phase", src)
|
|
forceMove(get_step(src, direction))
|
|
use_power(phasing_energy_drain)
|
|
playsound(src, stepsound, 40, 1)
|
|
can_move = world.time + (step_in * 3)
|
|
else if(stepsound)
|
|
playsound(src, stepsound, 40, 1)
|
|
|
|
/obj/mecha/proc/mechsteprand()
|
|
. = step_rand(src)
|
|
if(. && stepsound)
|
|
playsound(src, stepsound, 40, 1)
|
|
|
|
/obj/mecha/Bump(atom/obstacle)
|
|
if(throwing) //high velocity mechas in your face!
|
|
var/breakthrough = FALSE
|
|
if(istype(obstacle, /obj/structure/window))
|
|
qdel(obstacle)
|
|
breakthrough = TRUE
|
|
|
|
else if(istype(obstacle, /obj/structure/grille/))
|
|
var/obj/structure/grille/G = obstacle
|
|
G.obj_break()
|
|
breakthrough = TRUE
|
|
|
|
else if(istype(obstacle, /obj/structure/table))
|
|
var/obj/structure/table/T = obstacle
|
|
qdel(T)
|
|
breakthrough = TRUE
|
|
|
|
else if(istype(obstacle, /obj/structure/rack))
|
|
new /obj/item/rack_parts(obstacle.loc)
|
|
qdel(obstacle)
|
|
breakthrough = TRUE
|
|
|
|
else if(istype(obstacle, /obj/structure/reagent_dispensers/fueltank))
|
|
obstacle.ex_act(EXPLODE_DEVASTATE)
|
|
|
|
else if(isliving(obstacle))
|
|
var/mob/living/L = obstacle
|
|
var/hit_sound = list('sound/weapons/genhit1.ogg','sound/weapons/genhit2.ogg','sound/weapons/genhit3.ogg')
|
|
if(L.flags & GODMODE)
|
|
return
|
|
L.take_overall_damage(5,0)
|
|
if(L.buckled)
|
|
L.buckled = 0
|
|
L.Weaken(10 SECONDS)
|
|
L.apply_effect(STUTTER, 10 SECONDS)
|
|
playsound(src, pick(hit_sound), 50, FALSE, 0)
|
|
breakthrough = TRUE
|
|
|
|
else
|
|
if(throwing)
|
|
throwing.finalize(FALSE)
|
|
crashing = null
|
|
|
|
..()
|
|
|
|
if(breakthrough)
|
|
if(crashing)
|
|
spawn(1)
|
|
throw_at(crashing, 50, throw_speed)
|
|
else
|
|
spawn(1)
|
|
crashing = get_distant_turf(get_turf(src), dir, 3)//don't use get_dir(src, obstacle) or the mech will stop if he bumps into a one-direction window on his tile.
|
|
throw_at(crashing, 50, throw_speed)
|
|
|
|
else
|
|
if(..())
|
|
return
|
|
if(isobj(obstacle))
|
|
var/obj/O = obstacle
|
|
if(!O.anchored)
|
|
step(obstacle, dir)
|
|
else if(ismob(obstacle))
|
|
step(obstacle, dir)
|
|
|
|
|
|
///////////////////////////////////
|
|
//////// MARK: Internal damage
|
|
///////////////////////////////////
|
|
|
|
/obj/mecha/proc/check_for_internal_damage(list/possible_int_damage, ignore_threshold=null)
|
|
if(!islist(possible_int_damage) || isemptylist(possible_int_damage))
|
|
return
|
|
if(prob(20))
|
|
if(ignore_threshold || obj_integrity*100/max_integrity < internal_damage_threshold)
|
|
for(var/T in possible_int_damage)
|
|
if(internal_damage & T)
|
|
possible_int_damage -= T
|
|
var/int_dam_flag = safepick(possible_int_damage)
|
|
if(int_dam_flag)
|
|
setInternalDamage(int_dam_flag)
|
|
if(prob(5))
|
|
if(ignore_threshold || obj_integrity*100/max_integrity < internal_damage_threshold)
|
|
var/obj/item/mecha_parts/mecha_equipment/ME = safepick(equipment)
|
|
if(ME)
|
|
qdel(ME)
|
|
|
|
/obj/mecha/proc/hasInternalDamage(int_dam_flag=null)
|
|
return int_dam_flag ? internal_damage&int_dam_flag : internal_damage
|
|
|
|
|
|
/obj/mecha/proc/setInternalDamage(int_dam_flag)
|
|
internal_damage |= int_dam_flag
|
|
log_append_to_last("Internal damage of type [int_dam_flag].",1)
|
|
SEND_SOUND(occupant, sound('sound/machines/warning-buzzer.ogg'))
|
|
diag_hud_set_mechstat()
|
|
|
|
/obj/mecha/proc/clearInternalDamage(int_dam_flag)
|
|
internal_damage &= ~int_dam_flag
|
|
switch(int_dam_flag)
|
|
if(MECHA_INT_TEMP_CONTROL)
|
|
occupant_message(SPAN_NOTICE("Life support system reactivated."))
|
|
if(MECHA_INT_FIRE)
|
|
occupant_message(SPAN_NOTICE("Internal fire extinquished."))
|
|
if(MECHA_INT_TANK_BREACH)
|
|
occupant_message(SPAN_NOTICE("Damaged internal tank has been sealed."))
|
|
diag_hud_set_mechstat()
|
|
|
|
|
|
////////////////////////////////////////
|
|
//////// MARK: Health related procs
|
|
////////////////////////////////////////
|
|
|
|
/obj/mecha/proc/get_armour_facing(relative_dir)
|
|
switch(abs(relative_dir))
|
|
if(180) // BACKSTAB!
|
|
return facing_modifiers[MECHA_BACK_ARMOUR]
|
|
if(0, 45)
|
|
return facing_modifiers[MECHA_FRONT_ARMOUR]
|
|
return facing_modifiers[MECHA_SIDE_ARMOUR] //always return non-0
|
|
|
|
/obj/mecha/take_damage(damage_amount, damage_type = BRUTE, damage_flag = 0, sound_effect = 1, attack_dir)
|
|
for(var/obj/item/mecha_parts/mecha_equipment/pulse_shield/shield in equipment)
|
|
if(selected == shield && !leg_overload_mode && !istype(cell, /obj/item/stock_parts/cell/infinite) && occupant)
|
|
var/required_power = shield.energy_drain * damage_amount
|
|
if(cell && cell.use(required_power))
|
|
log_message("Took [damage_amount] points of damage. Damage absorbed by [shield].")
|
|
damage_amount = 0
|
|
spark_system.start()
|
|
else
|
|
occupant_message(SPAN_USERDANGER("Shield breached!"))
|
|
shield.on_unequip()
|
|
. = ..()
|
|
if(. && obj_integrity > 0)
|
|
spark_system.start()
|
|
switch(damage_flag)
|
|
if(FIRE)
|
|
check_for_internal_damage(list(MECHA_INT_FIRE,MECHA_INT_TEMP_CONTROL))
|
|
if(MELEE)
|
|
check_for_internal_damage(list(MECHA_INT_TEMP_CONTROL,MECHA_INT_TANK_BREACH,MECHA_INT_CONTROL_LOST))
|
|
else
|
|
check_for_internal_damage(list(MECHA_INT_FIRE,MECHA_INT_TEMP_CONTROL,MECHA_INT_TANK_BREACH,MECHA_INT_CONTROL_LOST,MECHA_INT_SHORT_CIRCUIT))
|
|
if((. >= 5 || prob(33)) && !(. == 1 && leg_overload_mode)) //If it takes 1 damage and leg_overload_mode is true, do not say TAKING DAMAGE! to the user several times a second.
|
|
occupant_message(SPAN_USERDANGER("Taking damage!"))
|
|
log_message("Took [damage_amount] points of damage. Damage type: [damage_type]")
|
|
|
|
/obj/mecha/run_obj_armor(damage_amount, damage_type, damage_flag = 0, attack_dir)
|
|
. = ..()
|
|
if(!damage_amount)
|
|
return FALSE
|
|
var/booster_deflection_modifier = 1
|
|
var/booster_damage_modifier = 1
|
|
if(damage_flag == BULLET || damage_flag == LASER || damage_flag == ENERGY)
|
|
for(var/obj/item/mecha_parts/mecha_equipment/antiproj_armor_booster/B in equipment)
|
|
if(B.projectile_react())
|
|
booster_deflection_modifier = B.deflect_coeff
|
|
booster_damage_modifier = B.damage_coeff
|
|
break
|
|
else if(damage_flag == MELEE)
|
|
for(var/obj/item/mecha_parts/mecha_equipment/anticcw_armor_booster/B in equipment)
|
|
if(B.attack_react())
|
|
booster_deflection_modifier *= B.deflect_coeff
|
|
booster_damage_modifier *= B.damage_coeff
|
|
break
|
|
|
|
if(attack_dir)
|
|
var/facing_modifier = get_armour_facing(dir2angle(attack_dir) - dir2angle(dir))
|
|
booster_damage_modifier /= facing_modifier
|
|
booster_deflection_modifier *= facing_modifier
|
|
if(prob(deflect_chance * booster_deflection_modifier))
|
|
visible_message(SPAN_DANGER("[src]'s armour deflects the attack!"))
|
|
log_message("Armor saved.")
|
|
return FALSE
|
|
if(.)
|
|
. *= booster_damage_modifier
|
|
|
|
/obj/mecha/attack_hand(mob/living/user)
|
|
user.changeNext_move(CLICK_CD_MELEE)
|
|
user.do_attack_animation(src, ATTACK_EFFECT_PUNCH)
|
|
playsound(loc, 'sound/weapons/tap.ogg', 40, TRUE, -1)
|
|
user.visible_message(SPAN_NOTICE("[user] hits [name]. Nothing happens"), SPAN_NOTICE("You hit [name] with no visible effect."))
|
|
log_message("Attack by hand/paw. Attacker - [user].")
|
|
|
|
|
|
/obj/mecha/attack_alien(mob/living/user)
|
|
log_message("Attack by alien. Attacker - [user].", TRUE)
|
|
add_attack_logs(user, OCCUPANT_LOGGING, "Alien attacked mech [src]")
|
|
playsound(src.loc, 'sound/weapons/slash.ogg', 100, TRUE)
|
|
attack_generic(user, 15, BRUTE, MELEE, 0)
|
|
|
|
/obj/mecha/attack_animal(mob/living/simple_animal/user)
|
|
log_message("Attack by simple animal. Attacker - [user].")
|
|
if(!user.melee_damage_upper && !user.obj_damage)
|
|
user.custom_emote(EMOTE_VISIBLE, "[user.friendly] [src].")
|
|
return FALSE
|
|
else
|
|
var/play_soundeffect = TRUE
|
|
var/animal_damage = rand(user.melee_damage_lower,user.melee_damage_upper)
|
|
if(user.obj_damage)
|
|
animal_damage = min(animal_damage * 2, user.obj_damage * 2)
|
|
animal_damage = max(animal_damage, min(10 * user.environment_smash, 40))
|
|
if(animal_damage)
|
|
add_attack_logs(user, OCCUPANT_LOGGING, "Animal attacked mech [src]")
|
|
if(user.environment_smash)
|
|
play_soundeffect = FALSE
|
|
playsound(src, 'sound/effects/bang.ogg', 50, TRUE)
|
|
attack_generic(user, animal_damage, user.melee_damage_type, MELEE, play_soundeffect)
|
|
return TRUE
|
|
|
|
/obj/mecha/handle_basic_attack(mob/living/basic/attacker, list/modifiers)
|
|
log_message("Attack by basic mob. Attacker - [attacker].")
|
|
if(!attacker.melee_damage_upper && !attacker.obj_damage)
|
|
attacker.custom_emote(EMOTE_VISIBLE, "[attacker.friendly_verb_continuous] [src].")
|
|
return FALSE
|
|
var/play_soundeffect = TRUE
|
|
var/animal_damage = rand(attacker.melee_damage_lower, attacker.melee_damage_upper)
|
|
if(attacker.obj_damage)
|
|
animal_damage = min(animal_damage * 2, attacker.obj_damage * 2)
|
|
animal_damage = max(animal_damage, min(10 * attacker.environment_smash, 40))
|
|
if(animal_damage)
|
|
add_attack_logs(attacker, OCCUPANT_LOGGING, "Basic mob attacked mech [src]")
|
|
if(attacker.environment_smash)
|
|
play_soundeffect = FALSE
|
|
playsound(src, 'sound/effects/bang.ogg', 50, TRUE)
|
|
attack_generic(attacker, animal_damage, attacker.melee_damage_type, MELEE, play_soundeffect)
|
|
if(!attacker.client)
|
|
var/melee_attack_cooldown = rand(attacker.melee_attack_cooldown_min, attacker.melee_attack_cooldown_max)
|
|
attacker.changeNext_move(melee_attack_cooldown)
|
|
return TRUE
|
|
|
|
/obj/mecha/hulk_damage()
|
|
return 15
|
|
|
|
/obj/mecha/attack_hulk(mob/living/carbon/human/user)
|
|
. = ..()
|
|
if(.)
|
|
log_message("Attack by hulk. Attacker - [user].", 1)
|
|
add_attack_logs(user, OCCUPANT_LOGGING, "Hulk punched mech [src]")
|
|
|
|
/obj/mecha/blob_act(obj/structure/blob/B)
|
|
log_message("Attack by blob. Attacker - [B].")
|
|
take_damage(30, BRUTE, MELEE, 0, get_dir(src, B))
|
|
|
|
/obj/mecha/attack_tk()
|
|
return
|
|
|
|
/obj/mecha/hitby(atom/movable/AM, skipcatch, hitpush, blocked, datum/thrownthing/throwingdatum) //wrapper
|
|
log_message("Hit by [AM].")
|
|
if(isitem(AM))
|
|
var/obj/item/I = AM
|
|
add_attack_logs(locateUID(I.thrownby), OCCUPANT_LOGGING, "threw [AM] at mech [src]")
|
|
. = ..()
|
|
|
|
/obj/mecha/bullet_act(obj/projectile/Proj) //wrapper
|
|
log_message("Hit by projectile. Type: [Proj.name]([Proj.flag]).")
|
|
add_attack_logs(Proj.firer, OCCUPANT_LOGGING, "shot [Proj.name]([Proj.flag]) at mech [src]")
|
|
if(Proj.shield_buster && istype(selected, /obj/item/mecha_parts/mecha_equipment/pulse_shield))
|
|
occupant_message(SPAN_USERDANGER("Shield breached!"))
|
|
selected.on_unequip()
|
|
..()
|
|
|
|
/obj/mecha/ex_act(severity, target)
|
|
log_message("Affected by explosion of severity: [severity].")
|
|
if(prob(deflect_chance))
|
|
severity++
|
|
log_message("Armor saved, changing severity to [severity]")
|
|
..()
|
|
severity++
|
|
for(var/X in equipment)
|
|
var/obj/item/mecha_parts/mecha_equipment/ME = X
|
|
ME.ex_act(severity)
|
|
for(var/Y in trackers)
|
|
var/obj/item/mecha_parts/mecha_tracking/MT = Y
|
|
MT.ex_act(severity)
|
|
if(occupant)
|
|
occupant.ex_act(severity)
|
|
|
|
/obj/mecha/handle_atom_del(atom/A)
|
|
if(A == occupant)
|
|
occupant = null
|
|
icon_state = reset_icon(icon_state)+"-open"
|
|
setDir(dir_in)
|
|
if(A in trackers)
|
|
trackers -= A
|
|
|
|
/obj/mecha/Destroy()
|
|
STOP_PROCESSING(SSobj, src)
|
|
if(occupant)
|
|
occupant.SetSleeping(destruction_sleep_duration)
|
|
go_out()
|
|
for(var/mob/M in src) //Let's just be ultra sure
|
|
if(is_ai(M))
|
|
var/mob/living/silicon/ai/AI = M //AIs are loaded into the mech computer itself. When the mech dies, so does the AI. They can be recovered with an AI card from the wreck.
|
|
AI.gib() //No wreck, no AI to recover
|
|
else
|
|
M.forceMove(loc)
|
|
occupant = null
|
|
selected = null
|
|
for(var/obj/item/mecha_parts/mecha_equipment/E in equipment)
|
|
E.detach(loc)
|
|
qdel(E)
|
|
equipment.Cut()
|
|
QDEL_NULL(cell)
|
|
QDEL_NULL(internal_tank)
|
|
QDEL_NULL(radio)
|
|
GLOB.poi_list.Remove(src)
|
|
var/turf/T = get_turf(src)
|
|
if(T)
|
|
T.blind_release_air(cabin_air)
|
|
else
|
|
qdel(cabin_air)
|
|
cabin_air = null
|
|
connected_port = null
|
|
QDEL_NULL(spark_system)
|
|
QDEL_NULL(smoke_system)
|
|
QDEL_LIST_CONTENTS(trackers)
|
|
remove_from_all_data_huds()
|
|
GLOB.mechas_list -= src //global mech list
|
|
return ..()
|
|
|
|
//TODO
|
|
/obj/mecha/emp_act(severity)
|
|
if(emp_proof)
|
|
return
|
|
if(istype(selected, /obj/item/mecha_parts/mecha_equipment/pulse_shield))
|
|
occupant_message(SPAN_USERDANGER("Shield breached!"))
|
|
selected.on_unequip()
|
|
if(get_charge())
|
|
use_power((cell.charge/3)/(severity*2))
|
|
take_damage(30 / severity, BURN, ENERGY, 1)
|
|
log_message("EMP detected", 1)
|
|
check_for_internal_damage(list(MECHA_INT_FIRE, MECHA_INT_TEMP_CONTROL, MECHA_INT_CONTROL_LOST, MECHA_INT_SHORT_CIRCUIT), 1)
|
|
|
|
/obj/mecha/temperature_expose(exposed_temperature, exposed_volume)
|
|
..()
|
|
if(exposed_temperature > max_temperature)
|
|
log_message("Exposed to dangerous temperature.", 1)
|
|
take_damage(5, BURN, 0, 1)
|
|
check_for_internal_damage(list(MECHA_INT_FIRE, MECHA_INT_TEMP_CONTROL))
|
|
|
|
//////////////////////
|
|
////// MARK: AttackBy
|
|
//////////////////////
|
|
|
|
/obj/mecha/attackby__legacy__attackchain(obj/item/W, mob/user, params)
|
|
if(istype(W, /obj/item/mmi))
|
|
if(mmi_move_inside(W,user))
|
|
to_chat(user, "[src]-MMI interface initialized successfuly")
|
|
else
|
|
to_chat(user, "[src]-MMI interface initialization failed.")
|
|
return
|
|
|
|
if(istype(W, /obj/item/mecha_parts/mecha_equipment))
|
|
var/obj/item/mecha_parts/mecha_equipment/E = W
|
|
spawn()
|
|
if(E.can_attach(src))
|
|
if(!user.drop_item())
|
|
return
|
|
E.attach(src)
|
|
user.visible_message("[user] attaches [W] to [src].", SPAN_NOTICE("You attach [W] to [src]."))
|
|
else
|
|
to_chat(user, SPAN_WARNING("You were unable to attach [W] to [src]!"))
|
|
return
|
|
|
|
if(W.GetID())
|
|
if(add_req_access || maint_access)
|
|
if(internals_access_allowed(usr))
|
|
var/obj/item/card/id/id_card
|
|
if(istype(W, /obj/item/card/id))
|
|
id_card = W
|
|
else
|
|
var/obj/item/pda/pda = W
|
|
id_card = pda.id
|
|
output_maintenance_dialog(id_card, user)
|
|
return
|
|
else
|
|
to_chat(user, SPAN_WARNING("Invalid ID: Access denied."))
|
|
else
|
|
to_chat(user, SPAN_WARNING("Maintenance protocols disabled by operator."))
|
|
|
|
else if(istype(W, /obj/item/stack/cable_coil))
|
|
if(state == MECHA_OPEN_HATCH && hasInternalDamage(MECHA_INT_SHORT_CIRCUIT))
|
|
var/obj/item/stack/cable_coil/CC = W
|
|
if(CC.get_amount() > 1)
|
|
CC.use(2)
|
|
clearInternalDamage(MECHA_INT_SHORT_CIRCUIT)
|
|
to_chat(user, "You replace the fused wires.")
|
|
else
|
|
to_chat(user, "There's not enough wire to finish the task.")
|
|
return
|
|
|
|
else if(istype(W, /obj/item/stock_parts/cell))
|
|
if(state == MECHA_BATTERY_UNSCREW)
|
|
if(!cell)
|
|
if(!user.drop_item())
|
|
return
|
|
to_chat(user, SPAN_NOTICE("You install the powercell."))
|
|
W.forceMove(src)
|
|
cell = W
|
|
log_message("Powercell installed")
|
|
if(istype(selected, /obj/item/mecha_parts/mecha_equipment/pulse_shield) && istype(cell, /obj/item/stock_parts/cell/infinite))
|
|
occupant_message(SPAN_DANGER("The immense power of the cell overloads the shields."))
|
|
selected.on_unequip()
|
|
else
|
|
to_chat(user, SPAN_NOTICE("There's already a powercell installed."))
|
|
return
|
|
|
|
else if(istype(W, /obj/item/mecha_parts/mecha_tracking))
|
|
if(!user.drop_item_to_ground(W))
|
|
to_chat(user, SPAN_NOTICE("\the [W] is stuck to your hand, you cannot put it in \the [src]"))
|
|
return
|
|
|
|
// Check if a tracker exists
|
|
var/obj/item/mecha_parts/mecha_tracking/new_tracker = W
|
|
for(var/obj/item/mecha_parts/mecha_tracking/current_tracker in trackers)
|
|
if(new_tracker.type == current_tracker.type)
|
|
to_chat(user, SPAN_WARNING("This exosuit already has a [current_tracker]."))
|
|
user.put_in_hands(new_tracker)
|
|
return
|
|
|
|
trackers -= current_tracker
|
|
to_chat(user, SPAN_NOTICE("You remove [current_tracker]."))
|
|
var/obj/item/mecha_parts/mecha_tracking/duplicate_tracker = new current_tracker.type
|
|
user.put_in_hands(duplicate_tracker)
|
|
qdel(current_tracker)
|
|
new_tracker.forceMove(src)
|
|
trackers += W
|
|
user.visible_message("[user] attaches [new_tracker] to [src].", SPAN_NOTICE("You attach [new_tracker] to [src]."))
|
|
diag_hud_set_mechtracking()
|
|
return
|
|
|
|
else if(istype(W, /obj/item/paintkit))
|
|
if(occupant)
|
|
to_chat(user, "You can't customize a mech while someone is piloting it - that would be unsafe!")
|
|
return
|
|
|
|
var/obj/item/paintkit/P = W
|
|
var/found = null
|
|
|
|
for(var/type in P.allowed_types)
|
|
if(type == initial_icon)
|
|
found = 1
|
|
break
|
|
|
|
if(!found)
|
|
to_chat(user, "That kit isn't meant for use on this class of exosuit.")
|
|
return
|
|
|
|
user.visible_message("[user] opens [P] and spends some quality time customising [src].")
|
|
if(do_after_once(user, 3 SECONDS, target = src))
|
|
name = P.new_name
|
|
desc = P.new_desc
|
|
initial_icon = P.new_icon
|
|
reset_icon()
|
|
user.drop_item()
|
|
qdel(P)
|
|
|
|
else if(istype(W, /obj/item/mecha_modkit))
|
|
if(occupant)
|
|
to_chat(user, SPAN_NOTICE("You can't access the mech's modification port while it is occupied."))
|
|
return
|
|
var/obj/item/mecha_modkit/M = W
|
|
if(do_after_once(user, M.install_time, target = src))
|
|
M.install(src, user)
|
|
else
|
|
to_chat(user, SPAN_NOTICE("You stop installing [M]."))
|
|
|
|
else
|
|
if(W.force)
|
|
add_attack_logs(user, OCCUPANT_LOGGING, "attacked mech '[src]' using [W]")
|
|
return ..()
|
|
|
|
|
|
/obj/mecha/crowbar_act(mob/user, obj/item/I)
|
|
if(state != MECHA_BOLTS_UP && state != MECHA_OPEN_HATCH && !(state == MECHA_BATTERY_UNSCREW && occupant))
|
|
return
|
|
. = TRUE
|
|
if(!I.use_tool(src, user, 0, volume = I.tool_volume))
|
|
return
|
|
if(state == MECHA_BOLTS_UP)
|
|
state = MECHA_OPEN_HATCH
|
|
to_chat(user, "You open the hatch to the power unit")
|
|
else if(state == MECHA_OPEN_HATCH)
|
|
state = MECHA_BOLTS_UP
|
|
to_chat(user, "You close the hatch to the power unit")
|
|
else if(ishuman(occupant))
|
|
user.visible_message(SPAN_NOTICE("[user] begins levering out the driver from the [src]."), SPAN_NOTICE("You begin to lever out the driver from the [src]."))
|
|
to_chat(occupant, SPAN_WARNING("[user] is prying you out of the exosuit!"))
|
|
if(I.use_tool(src, user, 8 SECONDS, volume = I.tool_volume))
|
|
user.visible_message(SPAN_NOTICE("[user] pries the driver out of the [src]!"), SPAN_NOTICE("You finish removing the driver from the [src]!"))
|
|
go_out()
|
|
else
|
|
// Since having maint protocols available is controllable by the MMI, I see this as a consensual way to remove an MMI without destroying the mech
|
|
user.visible_message(SPAN_NOTICE("[user] begins levering out the MMI from [src]."), SPAN_NOTICE("You begin to lever out the MMI from [src]."))
|
|
to_chat(occupant, SPAN_WARNING("[user] is prying you out of the exosuit!"))
|
|
if(I.use_tool(src, user, 8 SECONDS, volume = I.tool_volume) && pilot_is_mmi())
|
|
user.visible_message(SPAN_NOTICE("[user] pries the MMI out of [src]!"), SPAN_NOTICE("You finish removing the MMI from [src]!"))
|
|
go_out()
|
|
|
|
/obj/mecha/screwdriver_act(mob/user, obj/item/I)
|
|
if(user.a_intent == INTENT_HARM)
|
|
return
|
|
if(!(state == MECHA_OPEN_HATCH && cell) && !(state == MECHA_BATTERY_UNSCREW && cell))
|
|
return
|
|
. = TRUE
|
|
if(!I.use_tool(src, user, 0, volume = I.tool_volume))
|
|
return
|
|
if(hasInternalDamage(MECHA_INT_TEMP_CONTROL))
|
|
clearInternalDamage(MECHA_INT_TEMP_CONTROL)
|
|
to_chat(user, SPAN_NOTICE("You repair the damaged temperature controller."))
|
|
else if(state == MECHA_OPEN_HATCH && cell)
|
|
cell.forceMove(loc)
|
|
cell = null
|
|
state = MECHA_BATTERY_UNSCREW
|
|
to_chat(user, SPAN_NOTICE("You unscrew and pry out the powercell."))
|
|
log_message("Powercell removed")
|
|
else if(state == MECHA_BATTERY_UNSCREW && cell)
|
|
state = MECHA_OPEN_HATCH
|
|
to_chat(user, SPAN_NOTICE("You screw the cell in place."))
|
|
|
|
/obj/mecha/wrench_act(mob/user, obj/item/I)
|
|
if(state != MECHA_MAINT_ON && state != MECHA_BOLTS_UP)
|
|
return
|
|
. = TRUE
|
|
if(!I.use_tool(src, user, 0, volume = I.tool_volume))
|
|
return
|
|
if(state == MECHA_MAINT_ON)
|
|
state = MECHA_BOLTS_UP
|
|
to_chat(user, "You undo the securing bolts.")
|
|
else
|
|
state = MECHA_MAINT_ON
|
|
to_chat(user, "You tighten the securing bolts.")
|
|
|
|
/obj/mecha/welder_act(mob/user, obj/item/I)
|
|
if(user.a_intent == INTENT_HARM)
|
|
return
|
|
. = TRUE
|
|
if(!I.tool_use_check(user, 0))
|
|
return
|
|
if((obj_integrity >= max_integrity) && !internal_damage)
|
|
to_chat(user, SPAN_NOTICE("[src] is at full integrity!"))
|
|
return
|
|
if(repairing)
|
|
to_chat(user, SPAN_NOTICE("[src] is currently being repaired!"))
|
|
return
|
|
if(state == MECHA_MAINT_OFF) // If maint protocols are not active, the state is zero
|
|
to_chat(user, SPAN_WARNING("[src] can not be repaired without maintenance protocols active!"))
|
|
return
|
|
WELDER_ATTEMPT_REPAIR_MESSAGE
|
|
repairing = TRUE
|
|
if(I.use_tool(src, user, 15, volume = I.tool_volume))
|
|
if(internal_damage & MECHA_INT_TANK_BREACH)
|
|
clearInternalDamage(MECHA_INT_TANK_BREACH)
|
|
user.visible_message(SPAN_NOTICE("[user] repairs the damaged gas tank."), SPAN_NOTICE("You repair the damaged gas tank."))
|
|
else if(obj_integrity < max_integrity)
|
|
user.visible_message(SPAN_NOTICE("[user] repairs some damage to [name]."), SPAN_NOTICE("You repair some damage to [name]."))
|
|
obj_integrity += min(10, max_integrity - obj_integrity)
|
|
else
|
|
to_chat(user, SPAN_NOTICE("[src] is at full integrity!"))
|
|
repairing = FALSE
|
|
|
|
/obj/mecha/mech_melee_attack(obj/mecha/M)
|
|
if(!has_charge(melee_energy_drain))
|
|
return FALSE
|
|
use_power(melee_energy_drain)
|
|
if(M.damtype == BRUTE || M.damtype == BURN)
|
|
add_attack_logs(M.occupant, src, "Mecha-attacked with [M] ([uppertext(M.occupant.a_intent)]) ([uppertext(M.damtype)])")
|
|
. = ..()
|
|
|
|
/obj/mecha/emag_act(mob/user)
|
|
if(emag_proof)
|
|
to_chat(user, SPAN_WARNING("[src]'s ID slot rejects the card."))
|
|
return
|
|
user.visible_message(SPAN_NOTICE("[user] slides a card through [src]'s id slot."), SPAN_NOTICE("You slide the card through [src]'s ID slot, resetting the DNA and access locks."))
|
|
playsound(loc, "sparks", 100, TRUE, SHORT_RANGE_SOUND_EXTRARANGE)
|
|
dna = null
|
|
operation_req_access = list()
|
|
return TRUE
|
|
|
|
|
|
|
|
/////////////////////////////////////
|
|
//////////// MARK: AI piloting
|
|
/////////////////////////////////////
|
|
|
|
/obj/mecha/attack_ai(mob/living/silicon/ai/user)
|
|
if(!is_ai(user))
|
|
return
|
|
//Allows the Malf to scan a mech's status and loadout, helping it to decide if it is a worthy chariot.
|
|
if(user.can_dominate_mechs)
|
|
examine(user) //Get diagnostic information!
|
|
for(var/obj/item/mecha_parts/mecha_tracking/B in trackers)
|
|
to_chat(user, "<span class='danger'>Warning: Tracking Beacon detected. Enter at your own risk. Beacon Data:")
|
|
to_chat(user, "[B.get_mecha_info_text()]")
|
|
break
|
|
//Nothing like a big, red link to make the player feel powerful!
|
|
to_chat(user, "<a href='byond://?src=[user.UID()];ai_take_control=\ref[src]'>[SPAN_USERDANGER("ASSUME DIRECT CONTROL?")]</a><br>")
|
|
else
|
|
examine(user)
|
|
if(occupant)
|
|
to_chat(user, SPAN_WARNING("This exosuit has a pilot and cannot be controlled."))
|
|
return
|
|
var/can_control_mech = FALSE
|
|
for(var/obj/item/mecha_parts/mecha_tracking/ai_control/A in trackers)
|
|
can_control_mech = TRUE
|
|
to_chat(user, "[SPAN_NOTICE("[bicon(src)] Status of [name]:")]\n\
|
|
[A.get_mecha_info_text()]")
|
|
break
|
|
if(!can_control_mech)
|
|
to_chat(user, SPAN_WARNING("You cannot control exosuits without AI control beacons installed."))
|
|
return
|
|
to_chat(user, "<a href='byond://?src=[user.UID()];ai_take_control=\ref[src]'>[SPAN_BOLDNOTICE("Take control of exosuit?")]</a><br>")
|
|
|
|
/obj/mecha/transfer_ai(interaction, mob/user, mob/living/silicon/ai/AI, obj/item/aicard/card)
|
|
if(!..())
|
|
return
|
|
|
|
//Transfer from core or card to mech. Proc is called by mech.
|
|
switch(interaction)
|
|
if(AI_TRANS_TO_CARD) //Upload AI from mech to AI card.
|
|
if(!maint_access) //Mech must be in maint mode to allow carding.
|
|
to_chat(user, SPAN_WARNING("[name] must have maintenance protocols active in order to allow a transfer."))
|
|
return
|
|
AI = occupant
|
|
if(!AI || !is_ai(occupant)) //Mech does not have an AI for a pilot
|
|
to_chat(user, SPAN_WARNING("No AI detected in the [name] onboard computer."))
|
|
return
|
|
if(AI.mind.special_role) //Malf AIs cannot leave mechs. Except through death.
|
|
to_chat(user, SPAN_BOLDANNOUNCEIC("ACCESS DENIED."))
|
|
return
|
|
AI.aiRestorePowerRoutine = 0//So the AI initially has power.
|
|
AI.control_disabled = TRUE
|
|
AI.aiRadio.disabledAi = TRUE
|
|
AI.forceMove(card)
|
|
occupant = null
|
|
AI.controlled_mech = null
|
|
AI.remote_control = null
|
|
icon_state = reset_icon(icon_state)+"-open"
|
|
to_chat(AI, "You have been downloaded to a mobile storage device. Wireless connection offline.")
|
|
to_chat(user, "[SPAN_BOLDNOTICE("Transfer successful")]: [AI.name] ([rand(1000,9999)].exe) removed from [name] and stored within local memory.")
|
|
|
|
if(AI_MECH_HACK) //Called by AIs on the mech
|
|
AI.linked_core = new /obj/structure/ai_core/deactivated(AI.loc)
|
|
if(AI.can_dominate_mechs)
|
|
if(occupant) //Oh, I am sorry, were you using that?
|
|
to_chat(AI, "<span class='warning'>Pilot detected! Forced ejection initiated!")
|
|
to_chat(occupant, SPAN_DANGER("You have been forcibly ejected!"))
|
|
go_out(1) //IT IS MINE, NOW. SUCK IT, RD!
|
|
ai_enter_mech(AI, interaction)
|
|
|
|
if(AI_TRANS_FROM_CARD) //Using an AI card to upload to a mech.
|
|
AI = locate(/mob/living/silicon/ai) in card
|
|
if(!AI)
|
|
to_chat(user, SPAN_WARNING("There is no AI currently installed on this device."))
|
|
return
|
|
else if(AI.stat || !AI.client)
|
|
to_chat(user, SPAN_WARNING("[AI.name] is currently unresponsive, and cannot be uploaded."))
|
|
return
|
|
else if(occupant || dna) //Normal AIs cannot steal mechs!
|
|
to_chat(user, "<span class='warning'>Access denied. [name] is [occupant ? "currently occupied" : "secured with a DNA lock"].")
|
|
return
|
|
AI.control_disabled = FALSE
|
|
AI.aiRadio.disabledAi = FALSE
|
|
to_chat(user, "[SPAN_BOLDNOTICE("Transfer successful")]: [AI.name] ([rand(1000,9999)].exe) installed and executed successfully. Local copy has been removed.")
|
|
ai_enter_mech(AI, interaction)
|
|
|
|
//Hack and From Card interactions share some code, so leave that here for both to use.
|
|
/obj/mecha/proc/ai_enter_mech(mob/living/silicon/ai/AI, interaction)
|
|
var/mob/camera/eye/hologram/hologram_eye = AI.remote_control
|
|
if(istype(hologram_eye))
|
|
hologram_eye.release_control()
|
|
qdel(hologram_eye)
|
|
AI.aiRestorePowerRoutine = 0
|
|
AI.forceMove(src)
|
|
occupant = AI
|
|
icon_state = reset_icon(icon_state)
|
|
update_icon(UPDATE_OVERLAYS)
|
|
playsound(src, 'sound/machines/windowdoor.ogg', 50, 1)
|
|
if(!hasInternalDamage())
|
|
SEND_SOUND(occupant, sound(nominalsound, volume = 50))
|
|
AI.cancel_camera()
|
|
AI.controlled_mech = src
|
|
AI.remote_control = src
|
|
AI.reset_perspective(src)
|
|
AI.can_shunt = FALSE //ONE AI ENTERS. NO AI LEAVES.
|
|
to_chat(AI, "[AI.can_dominate_mechs ? SPAN_BOLDNOTICE("Takeover of [name] complete! You are now permanently loaded onto the onboard computer. Do not attempt to leave the station sector!") \
|
|
: "<span class='notice'>You have been uploaded to a mech's onboard computer."]")
|
|
to_chat(AI, SPAN_BOLDNOTICE("Use Middle-Mouse to activate mech functions and equipment. Click normally for AI interactions."))
|
|
if(interaction == AI_TRANS_FROM_CARD)
|
|
GrantActions(AI, FALSE)
|
|
else
|
|
GrantActions(AI, !AI.can_dominate_mechs)
|
|
|
|
/////////////////////////////////////
|
|
//////// MARK: Atmospheric stuff
|
|
/////////////////////////////////////
|
|
|
|
/obj/mecha/return_obj_air()
|
|
RETURN_TYPE(/datum/gas_mixture)
|
|
if(use_internal_tank)
|
|
return cabin_air
|
|
return null
|
|
|
|
/obj/mecha/return_analyzable_air()
|
|
if(use_internal_tank)
|
|
return cabin_air
|
|
return null
|
|
|
|
/obj/mecha/proc/connect(obj/machinery/atmospherics/unary/portables_connector/new_port)
|
|
//Make sure not already connected to something else
|
|
if(connected_port || !istype(new_port) || new_port.connected_device)
|
|
return FALSE
|
|
|
|
//Make sure are close enough for a valid connection
|
|
if(new_port.loc != loc)
|
|
return FALSE
|
|
|
|
//Perform the connection
|
|
connected_port = new_port
|
|
connected_port.connected_device = src
|
|
connected_port.parent.reconcile_air()
|
|
|
|
if(occupant)
|
|
occupant.clear_alert("mechaport")
|
|
occupant.throw_alert("mechaport_d", /atom/movable/screen/alert/mech_port_disconnect)
|
|
|
|
log_message("Connected to gas port.")
|
|
return TRUE
|
|
|
|
/obj/mecha/proc/disconnect()
|
|
if(!connected_port)
|
|
return FALSE
|
|
|
|
connected_port.connected_device = null
|
|
connected_port = null
|
|
log_message("Disconnected from gas port.")
|
|
if(occupant)
|
|
occupant.clear_alert("mechaport_d")
|
|
return TRUE
|
|
|
|
/obj/mecha/portableConnectorReturnAir()
|
|
return internal_tank.return_obj_air()
|
|
|
|
/obj/mecha/proc/toggle_lights(show_message = TRUE)
|
|
lights = !lights
|
|
if(lights)
|
|
set_light(lights_range, lights_power)
|
|
else
|
|
set_light(lights_range_ambient, lights_power_ambient)
|
|
if(show_message)
|
|
occupant_message("Toggled lights [lights ? "on" : "off"].")
|
|
log_message("Toggled lights [lights ? "on" : "off"].")
|
|
|
|
/obj/mecha/extinguish_light(force)
|
|
if(!lights)
|
|
return
|
|
toggle_lights(show_message = FALSE)
|
|
|
|
/obj/mecha/proc/toggle_internal_tank()
|
|
use_internal_tank = !use_internal_tank
|
|
occupant_message("Now taking air from [use_internal_tank ? "internal airtank" : "environment"].")
|
|
log_message("Now taking air from [use_internal_tank ? "internal airtank" : "environment"].")
|
|
|
|
/obj/mecha/MouseDrop_T(mob/M, mob/user)
|
|
if(frozen)
|
|
to_chat(user, SPAN_WARNING("Do not enter Admin-Frozen mechs."))
|
|
return TRUE
|
|
if(user.incapacitated())
|
|
return
|
|
if(user != M)
|
|
return
|
|
log_message("[user] tries to move in.")
|
|
if(occupant)
|
|
to_chat(user, SPAN_WARNING("[src] is already occupied!"))
|
|
log_append_to_last("Permission denied.")
|
|
return TRUE
|
|
var/passed
|
|
if(dna)
|
|
if(ishuman(user))
|
|
if(user.dna.unique_enzymes == dna)
|
|
passed = TRUE
|
|
else if(operation_allowed(user))
|
|
passed = TRUE
|
|
if(!passed)
|
|
to_chat(user, SPAN_WARNING("Access denied."))
|
|
log_append_to_last("Permission denied.")
|
|
return TRUE
|
|
if(user.buckled)
|
|
to_chat(user, SPAN_WARNING("You are currently buckled and cannot move."))
|
|
log_append_to_last("Permission denied.")
|
|
return TRUE
|
|
if(user.has_buckled_mobs()) //mob attached to us
|
|
to_chat(user, SPAN_WARNING("You can't enter the exosuit with other creatures attached to you!"))
|
|
return TRUE
|
|
|
|
visible_message("<span class='notice'>[user] starts to climb into [src]")
|
|
INVOKE_ASYNC(src, TYPE_PROC_REF(/obj/mecha, put_in), user)
|
|
return TRUE
|
|
|
|
/obj/mecha/proc/put_in(mob/user) // need this proc to use INVOKE_ASYNC in other proc. You're not recommended to use that one
|
|
if(do_after(user, mech_enter_time, target = src))
|
|
if(obj_integrity <= 0)
|
|
to_chat(user, SPAN_WARNING("You cannot get in the [name], it has been destroyed!"))
|
|
else if(occupant)
|
|
to_chat(user, SPAN_DANGER("[occupant] was faster! Try better next time, loser."))
|
|
else if(user.buckled)
|
|
to_chat(user, SPAN_WARNING("You can't enter the exosuit while buckled."))
|
|
else if(user.has_buckled_mobs())
|
|
to_chat(user, SPAN_WARNING("You can't enter the exosuit with other creatures attached to you!"))
|
|
else
|
|
moved_inside(user)
|
|
else
|
|
to_chat(user, SPAN_WARNING("You stop entering the exosuit!"))
|
|
|
|
/obj/mecha/proc/moved_inside(mob/living/carbon/human/H as mob)
|
|
if(H && H.client && (H in range(1)))
|
|
occupant = H
|
|
H.stop_pulling()
|
|
H.forceMove(src)
|
|
add_fingerprint(H)
|
|
GrantActions(H, human_occupant = 1)
|
|
forceMove(loc)
|
|
log_append_to_last("[H] moved in as pilot.")
|
|
icon_state = reset_icon()
|
|
dir = dir_in
|
|
playsound(src, 'sound/machines/windowdoor.ogg', 50, 1)
|
|
if(!activated)
|
|
SEND_SOUND(occupant, sound(longactivationsound, volume = 50))
|
|
activated = TRUE
|
|
else if(!hasInternalDamage())
|
|
SEND_SOUND(occupant, sound(nominalsound, volume = 50))
|
|
if(state)
|
|
H.throw_alert("locked", /atom/movable/screen/alert/mech_maintenance)
|
|
if(connected_port)
|
|
H.throw_alert("mechaport_d", /atom/movable/screen/alert/mech_port_disconnect)
|
|
update_icon(UPDATE_OVERLAYS)
|
|
return TRUE
|
|
else
|
|
return FALSE
|
|
|
|
/obj/mecha/proc/mmi_move_inside(obj/item/mmi/mmi_as_oc as obj,mob/user as mob)
|
|
if(!mmi_as_oc.brainmob || !mmi_as_oc.brainmob.client)
|
|
to_chat(user, SPAN_WARNING("Consciousness matrix not detected!"))
|
|
return FALSE
|
|
else if(mmi_as_oc.brainmob.stat)
|
|
to_chat(user, SPAN_WARNING("Beta-rhythm below acceptable level!"))
|
|
return FALSE
|
|
else if(occupant)
|
|
to_chat(user, SPAN_WARNING("Occupant detected!"))
|
|
return FALSE
|
|
else if(dna && dna != mmi_as_oc.brainmob.dna.unique_enzymes)
|
|
to_chat(user, SPAN_WARNING("Access denied. [name] is secured with a DNA lock."))
|
|
return FALSE
|
|
else if(!operation_allowed(user))
|
|
to_chat(user, SPAN_WARNING("Access denied. [name] is secured with an ID lock."))
|
|
return FALSE
|
|
|
|
if(do_after(user, 4 SECONDS, target = src))
|
|
if(!occupant)
|
|
return mmi_moved_inside(mmi_as_oc,user)
|
|
else
|
|
to_chat(user, SPAN_WARNING("Occupant detected!"))
|
|
else
|
|
to_chat(user, SPAN_NOTICE("You stop inserting the MMI."))
|
|
return FALSE
|
|
|
|
/obj/mecha/proc/mmi_moved_inside(obj/item/mmi/mmi_as_oc, mob/user)
|
|
if(mmi_as_oc && (user in range(1)))
|
|
if(!mmi_as_oc.brainmob || !mmi_as_oc.brainmob.client)
|
|
to_chat(user, "Consciousness matrix not detected.")
|
|
return FALSE
|
|
else if(mmi_as_oc.brainmob.stat)
|
|
to_chat(user, "Beta-rhythm below acceptable level.")
|
|
return FALSE
|
|
if(!user.drop_item_to_ground(mmi_as_oc))
|
|
to_chat(user, SPAN_NOTICE("\the [mmi_as_oc] is stuck to your hand, you cannot put it in \the [src]"))
|
|
return FALSE
|
|
var/mob/living/brain/brainmob = mmi_as_oc.brainmob
|
|
brainmob.reset_perspective(src)
|
|
occupant = brainmob
|
|
brainmob.forceMove(src) //should allow relaymove
|
|
if(istype(mmi_as_oc, /obj/item/mmi/robotic_brain))
|
|
var/obj/item/mmi/robotic_brain/R = mmi_as_oc
|
|
if(R.imprinted_master)
|
|
to_chat(brainmob, SPAN_BIGGERDANGER("Your imprint to [R.imprinted_master] has been temporarily disabled. You should help the crew and not commit harm."))
|
|
mmi_as_oc.loc = src
|
|
mmi_as_oc.mecha = src
|
|
Entered(mmi_as_oc)
|
|
Move(loc)
|
|
icon_state = reset_icon()
|
|
dir = dir_in
|
|
update_icon(UPDATE_OVERLAYS)
|
|
log_message("[mmi_as_oc] moved in as pilot.")
|
|
if(!hasInternalDamage())
|
|
SEND_SOUND(occupant, sound(nominalsound, volume = 50))
|
|
GrantActions(brainmob)
|
|
return TRUE
|
|
else
|
|
return FALSE
|
|
|
|
/obj/mecha/proc/pilot_is_mmi()
|
|
var/atom/movable/mob_container
|
|
if(isbrain(occupant))
|
|
var/mob/living/brain/brain = occupant
|
|
mob_container = brain.container
|
|
if(istype(mob_container, /obj/item/mmi))
|
|
return TRUE
|
|
return FALSE
|
|
|
|
/obj/mecha/proc/pilot_mmi_hud(mob/living/brain/pilot)
|
|
return
|
|
|
|
/obj/mecha/Exited(atom/movable/M, direction)
|
|
var/new_loc = get_step(M, direction)
|
|
if(occupant && occupant == M) // The occupant exited the mech without calling go_out()
|
|
if(!is_ai(occupant)) //This causes carded AIS to gib, so we do not want this to be called during carding.
|
|
go_out(1, new_loc)
|
|
|
|
/obj/mecha/proc/go_out(forced, atom/newloc = loc)
|
|
if(!occupant)
|
|
return
|
|
var/atom/movable/mob_container
|
|
occupant.clear_alert("charge")
|
|
occupant.clear_alert("locked")
|
|
occupant.clear_alert("mech damage")
|
|
occupant.clear_alert("mechaport")
|
|
occupant.clear_alert("mechaport_d")
|
|
if(ishuman(occupant))
|
|
mob_container = occupant
|
|
RemoveActions(occupant, human_occupant = 1)
|
|
else if(isbrain(occupant))
|
|
var/mob/living/brain/brain = occupant
|
|
RemoveActions(brain)
|
|
mob_container = brain.container
|
|
else if(is_ai(occupant))
|
|
var/mob/living/silicon/ai/AI = occupant
|
|
if(forced)//This should only happen if there are multiple AIs in a round, and at least one is Malf.
|
|
RemoveActions(occupant)
|
|
occupant.gib() //If one Malf decides to steal a mech from another AI (even other Malfs!), they are destroyed, as they have nowhere to go when replaced.
|
|
occupant = null
|
|
return
|
|
else
|
|
if(!AI.linked_core || QDELETED(AI.linked_core))
|
|
to_chat(AI, SPAN_USERDANGER("Inactive core destroyed. Unable to return."))
|
|
AI.linked_core = null
|
|
return
|
|
to_chat(AI, SPAN_NOTICE("Returning to core..."))
|
|
AI.controlled_mech = null
|
|
if(istype(AI.eyeobj))
|
|
AI.remote_control = AI.eyeobj
|
|
AI.reset_perspective(AI.eyeobj)
|
|
else
|
|
AI.eyeobj = new /mob/camera/eye/ai(loc, AI.name, AI, AI)
|
|
RemoveActions(occupant, 1)
|
|
mob_container = AI
|
|
newloc = get_turf(AI.linked_core)
|
|
AI.eyeobj?.set_loc(newloc)
|
|
qdel(AI.linked_core)
|
|
else
|
|
return
|
|
var/mob/living/L = occupant
|
|
occupant = null //we need it null when forceMove calls Exited().
|
|
if(mob_container.forceMove(newloc))//ejecting mob container
|
|
log_message("[mob_container] moved out.")
|
|
L << browse(null, "window=exosuit")
|
|
|
|
if(istype(mob_container, /obj/item/mmi))
|
|
var/obj/item/mmi/mmi = mob_container
|
|
if(mmi.brainmob)
|
|
L.forceMove(mmi)
|
|
L.reset_perspective()
|
|
mmi.mecha = null
|
|
mmi.update_icon()
|
|
if(istype(mmi, /obj/item/mmi/robotic_brain))
|
|
var/obj/item/mmi/robotic_brain/R = mmi
|
|
if(R.imprinted_master)
|
|
to_chat(L, SPAN_NOTICE("Imprint re-enabled, you are once again bound to [R.imprinted_master]'s commands."))
|
|
icon_state = reset_icon(icon_state)+"-open"
|
|
dir = dir_in
|
|
|
|
if(L && L.client)
|
|
L.client.RemoveViewMod("mecha")
|
|
zoom_mode = FALSE
|
|
|
|
if(ishuman(L))
|
|
var/mob/living/carbon/human/H = L
|
|
H.regenerate_icons() // workaround for 14457
|
|
update_icon(UPDATE_OVERLAYS)
|
|
|
|
/obj/mecha/force_eject_occupant(mob/target)
|
|
go_out()
|
|
|
|
// Called when a shuttle lands on top of the mech. Prevents the mech from just dropping the pilot unharmed inside the shuttle when destroyed.
|
|
/obj/mecha/proc/get_out_and_die()
|
|
var/mob/living/pilot = occupant
|
|
pilot.visible_message(
|
|
SPAN_WARNING("[src] is hit by a hyperspace ripple!"),
|
|
SPAN_USERDANGER("You feel an immense crushing pressure as the space around you ripples.")
|
|
)
|
|
go_out(TRUE)
|
|
if(iscarbon(pilot))
|
|
pilot.gib()
|
|
qdel(src)
|
|
|
|
/////////////////////////
|
|
////// MARK: Access stuff
|
|
/////////////////////////
|
|
|
|
/obj/mecha/proc/operation_allowed(mob/living/carbon/human/H)
|
|
if(!ishuman(H))
|
|
return 0
|
|
for(var/ID in list(H.get_active_hand(), H.wear_id, H.belt, H.wear_pda))
|
|
if(check_access(ID, operation_req_access))
|
|
return TRUE
|
|
return FALSE
|
|
|
|
|
|
/obj/mecha/proc/internals_access_allowed(mob/living/carbon/human/H)
|
|
for(var/atom/ID in list(H.get_active_hand(), H.wear_id, H.belt, H.wear_pda))
|
|
if(check_access(ID, internals_req_access))
|
|
return TRUE
|
|
return FALSE
|
|
|
|
|
|
/obj/mecha/check_access(obj/item/card/id/I, list/access_list)
|
|
if(!istype(access_list))
|
|
return TRUE
|
|
if(!length(access_list)) //no requirements
|
|
return TRUE
|
|
I = I?.GetID()
|
|
if(!istype(I) || !I.access) //not ID or no access
|
|
return FALSE
|
|
if(access_list==operation_req_access)
|
|
for(var/req in access_list)
|
|
if(!(req in I.access)) //doesn't have this access
|
|
return FALSE
|
|
else if(access_list==internals_req_access)
|
|
for(var/req in access_list)
|
|
if(req in I.access)
|
|
return TRUE
|
|
return TRUE
|
|
|
|
///////////////////////
|
|
///// MARK: Power stuff
|
|
///////////////////////
|
|
|
|
/obj/mecha/proc/has_charge(amount)
|
|
return (get_charge()>=amount)
|
|
|
|
/obj/mecha/proc/get_charge()
|
|
for(var/obj/item/mecha_parts/mecha_equipment/tesla_energy_relay/R in equipment)
|
|
var/relay_charge = R.get_charge()
|
|
if(relay_charge)
|
|
return relay_charge
|
|
if(cell)
|
|
return max(0, cell.charge)
|
|
|
|
/obj/mecha/proc/use_power(amount)
|
|
if(get_charge())
|
|
cell.use(amount)
|
|
if(occupant)
|
|
update_cell()
|
|
return TRUE
|
|
return FALSE
|
|
|
|
/obj/mecha/proc/give_power(amount)
|
|
if(!isnull(get_charge()))
|
|
cell.give(amount)
|
|
if(occupant)
|
|
update_cell()
|
|
return TRUE
|
|
return FALSE
|
|
|
|
/obj/mecha/proc/update_cell()
|
|
if(cell)
|
|
var/cellcharge = cell.charge/cell.maxcharge
|
|
switch(cellcharge)
|
|
if(0.75 to INFINITY)
|
|
occupant.clear_alert("charge")
|
|
if(0.5 to 0.75)
|
|
occupant.throw_alert("charge", /atom/movable/screen/alert/mech_lowcell, 1)
|
|
if(0.25 to 0.5)
|
|
occupant.throw_alert("charge", /atom/movable/screen/alert/mech_lowcell, 2)
|
|
if(power_warned)
|
|
power_warned = FALSE
|
|
if(0.01 to 0.25)
|
|
occupant.throw_alert("charge", /atom/movable/screen/alert/mech_lowcell, 3)
|
|
if(!power_warned)
|
|
SEND_SOUND(occupant, sound(lowpowersound, volume = 50))
|
|
power_warned = TRUE
|
|
else
|
|
occupant.throw_alert("charge", /atom/movable/screen/alert/mech_emptycell)
|
|
else
|
|
occupant.throw_alert("charge", /atom/movable/screen/alert/mech_nocell)
|
|
|
|
/obj/mecha/proc/reset_icon()
|
|
if(initial_icon)
|
|
icon_state = initial_icon
|
|
else
|
|
icon_state = reset_icon(icon_state)
|
|
return icon_state
|
|
|
|
//////////////////////////////////////////
|
|
//////// MARK: Mecha global iterators
|
|
//////////////////////////////////////////
|
|
|
|
/obj/mecha/process()
|
|
process_internal_damage()
|
|
regulate_temp()
|
|
give_air()
|
|
update_huds()
|
|
|
|
/obj/mecha/proc/process_internal_damage()
|
|
if(!internal_damage)
|
|
return
|
|
|
|
if(internal_damage & MECHA_INT_FIRE)
|
|
if(!(internal_damage & MECHA_INT_TEMP_CONTROL) && prob(5))
|
|
clearInternalDamage(MECHA_INT_FIRE)
|
|
if(internal_tank)
|
|
var/datum/gas_mixture/int_tank_air = internal_tank.return_obj_air()
|
|
if(int_tank_air.return_pressure() > internal_tank.maximum_pressure && !(internal_damage & MECHA_INT_TANK_BREACH))
|
|
setInternalDamage(MECHA_INT_TANK_BREACH)
|
|
|
|
if(int_tank_air && int_tank_air.return_volume() > 0)
|
|
int_tank_air.set_temperature(min(6000 + T0C, cabin_air.temperature() + rand(10, 15)))
|
|
|
|
if(cabin_air && cabin_air.return_volume() > 0)
|
|
cabin_air.set_temperature(min(6000 + T0C, cabin_air.temperature() + rand(10, 15)))
|
|
if(cabin_air.temperature() > max_temperature / 2)
|
|
take_damage(4 / round(max_temperature / cabin_air.temperature(), 0.1), BURN, 0, 0)
|
|
|
|
if(internal_damage & MECHA_INT_TANK_BREACH) //remove some air from internal tank
|
|
if(internal_tank)
|
|
var/datum/gas_mixture/int_tank_air = internal_tank.return_obj_air()
|
|
var/datum/gas_mixture/leaked_gas = int_tank_air.remove_ratio(0.10)
|
|
var/turf/T = get_turf(src)
|
|
if(T)
|
|
T.blind_release_air(leaked_gas)
|
|
else
|
|
qdel(leaked_gas)
|
|
|
|
if(internal_damage & MECHA_INT_SHORT_CIRCUIT)
|
|
if(get_charge())
|
|
spark_system.start()
|
|
cell.charge -= min(20,cell.charge)
|
|
cell.maxcharge -= min(20,cell.maxcharge)
|
|
|
|
/obj/mecha/proc/release_gas(datum/gas_mixture/environment, datum/gas_mixture/leaked_gas)
|
|
// Any proc that wants MILLA to be synchronous should not sleep.
|
|
SHOULD_NOT_SLEEP(TRUE)
|
|
|
|
environment.merge(leaked_gas)
|
|
|
|
/obj/mecha/proc/regulate_temp()
|
|
if(internal_damage & MECHA_INT_TEMP_CONTROL)
|
|
return
|
|
|
|
if(cabin_air && cabin_air.return_volume() > 0)
|
|
var/delta = cabin_air.temperature() - T20C
|
|
cabin_air.set_temperature(cabin_air.temperature() - max(-10, min(10, round(delta / 4, 0.1))))
|
|
|
|
/obj/mecha/proc/give_air()
|
|
if(!internal_tank)
|
|
return
|
|
|
|
var/datum/gas_mixture/tank_air = internal_tank.return_obj_air()
|
|
|
|
var/release_pressure = internal_tank_valve
|
|
var/cabin_pressure = cabin_air.return_pressure()
|
|
var/pressure_delta = min(release_pressure - cabin_pressure, (tank_air.return_pressure() - cabin_pressure)/2)
|
|
var/transfer_moles = 0
|
|
if(pressure_delta > 0) //cabin pressure lower than release pressure
|
|
if(tank_air.temperature() > 0)
|
|
transfer_moles = pressure_delta*cabin_air.return_volume() / (cabin_air.temperature() * R_IDEAL_GAS_EQUATION)
|
|
var/datum/gas_mixture/removed = tank_air.remove(transfer_moles)
|
|
cabin_air.merge(removed)
|
|
else if(pressure_delta < 0) //cabin pressure higher than release pressure
|
|
var/datum/gas_mixture/active_tank_air = return_obj_air()
|
|
pressure_delta = cabin_pressure - release_pressure
|
|
if(active_tank_air)
|
|
pressure_delta = min(cabin_pressure - active_tank_air.return_pressure(), pressure_delta)
|
|
if(pressure_delta > 0) //if location pressure is lower than cabin pressure
|
|
transfer_moles = pressure_delta * cabin_air.return_volume() / (cabin_air.temperature() * R_IDEAL_GAS_EQUATION)
|
|
var/datum/gas_mixture/removed = cabin_air.remove(transfer_moles)
|
|
if(active_tank_air)
|
|
active_tank_air.merge(removed)
|
|
else
|
|
var/turf/T = get_turf(src)
|
|
if(T)
|
|
T.blind_release_air(removed)
|
|
else
|
|
qdel(removed)
|
|
|
|
/obj/mecha/proc/update_huds()
|
|
diag_hud_set_mechhealth()
|
|
diag_hud_set_mechcell()
|
|
diag_hud_set_mechstat()
|
|
diag_hud_set_mechtracking()
|
|
|
|
|
|
/obj/mecha/speech_bubble(bubble_state = "", bubble_loc = src, list/bubble_recipients = list())
|
|
var/image/I = image('icons/mob/talk.dmi', bubble_loc, bubble_state, FLY_LAYER)
|
|
I.appearance_flags = APPEARANCE_UI_IGNORE_ALPHA
|
|
INVOKE_ASYNC(GLOBAL_PROC, GLOBAL_PROC_REF(flick_overlay), I, bubble_recipients, 30)
|
|
|
|
/obj/mecha/update_remote_sight(mob/living/user)
|
|
if(occupant_sight_flags)
|
|
if(user == occupant)
|
|
user.sight |= occupant_sight_flags
|
|
|
|
..()
|
|
|
|
/obj/mecha/do_attack_animation(atom/A, visual_effect_icon, obj/item/used_item, no_effect)
|
|
if(!no_effect)
|
|
if(selected)
|
|
used_item = selected
|
|
else if(!visual_effect_icon)
|
|
visual_effect_icon = ATTACK_EFFECT_SMASH
|
|
if(damtype == BURN)
|
|
visual_effect_icon = ATTACK_EFFECT_MECHFIRE
|
|
else if(damtype == TOX)
|
|
visual_effect_icon = ATTACK_EFFECT_MECHTOXIN
|
|
..()
|
|
|
|
/obj/mecha/obj_destruction()
|
|
if(wreckage)
|
|
var/mob/living/silicon/ai/AI
|
|
if(is_ai(occupant))
|
|
AI = occupant
|
|
occupant = null
|
|
var/obj/structure/mecha_wreckage/WR = new wreckage(loc, AI)
|
|
WR.icon_state = "[reset_icon(loc, AI)]-broken"
|
|
for(var/obj/item/mecha_parts/mecha_equipment/E in equipment)
|
|
if(E.salvageable && prob(30))
|
|
WR.crowbar_salvage += E
|
|
E.detach(WR) //detaches from src into WR
|
|
E.equip_ready = 1
|
|
else
|
|
E.detach(loc)
|
|
qdel(E)
|
|
if(cell)
|
|
WR.crowbar_salvage += cell
|
|
cell.forceMove(WR)
|
|
cell.charge = rand(0, cell.charge)
|
|
cell = null
|
|
if(internal_tank)
|
|
WR.crowbar_salvage += internal_tank
|
|
internal_tank.forceMove(WR)
|
|
internal_tank = null
|
|
. = ..()
|
|
|
|
/obj/mecha/CtrlClick(mob/living/L)
|
|
if(occupant != L || !istype(L))
|
|
return ..()
|
|
|
|
var/list/choices = list("Cancel / No Change" = mutable_appearance(icon = 'icons/mob/screen_gen.dmi', icon_state = "x"))
|
|
var/list/choices_to_refs = list()
|
|
|
|
for(var/obj/item/mecha_parts/mecha_equipment/MT in equipment)
|
|
if(!MT.selectable || selected == MT)
|
|
continue
|
|
var/mutable_appearance/clean/MA = mutable_appearance(MT.icon, MT.icon_state, MT.layer)
|
|
choices[MT.name] = MA
|
|
choices_to_refs[MT.name] = MT
|
|
|
|
var/choice = show_radial_menu(L, L, choices, radius = 48, custom_check = CALLBACK(src, PROC_REF(check_menu), L))
|
|
if(!check_menu(L) || choice == "Cancel / No Change")
|
|
return
|
|
|
|
var/obj/item/mecha_parts/mecha_equipment/new_sel = LAZYACCESS(choices_to_refs, choice)
|
|
if(istype(new_sel))
|
|
selected = new_sel
|
|
occupant_message(SPAN_NOTICE("You switch to [selected]."))
|
|
visible_message("[src] raises [selected]")
|
|
send_byjax(occupant, "exosuit.browser", "eq_list", get_equipment_list())
|
|
|
|
/obj/mecha/proc/check_menu(mob/living/L)
|
|
if(L != occupant || !istype(L))
|
|
return FALSE
|
|
if(L.incapacitated())
|
|
return FALSE
|
|
return TRUE
|
|
|
|
/obj/mecha/rust_heretic_act()
|
|
take_damage(500, BRUTE)
|
|
|
|
#undef OCCUPANT_LOGGING
|