#define MECHA_INT_FIRE (1<<0)
#define MECHA_INT_TEMP_CONTROL (1<<1)
#define MECHA_INT_SHORT_CIRCUIT (1<<2)
#define MECHA_INT_TANK_BREACH (1<<3)
#define MECHA_INT_CONTROL_LOST (1<<4)
#define MELEE 1
#define RANGED 2
#define FRONT_ARMOUR 1
#define SIDE_ARMOUR 2
#define BACK_ARMOUR 3
/obj/mecha
name = "mecha"
desc = "Exosuit"
icon = 'icons/mecha/mecha.dmi'
density = TRUE //Dense. To raise the heat.
opacity = 1 ///opaque. Menacing.
anchored = TRUE //no pulling around.
resistance_flags = FIRE_PROOF | ACID_PROOF
layer = BELOW_MOB_LAYER//icon draw layer
infra_luminosity = 15 //byond implementation is bugged.
force = 5
flags_1 = HEAR_1|BLOCK_FACE_ATOM_1
attack_hand_speed = CLICK_CD_MELEE
attack_hand_is_action = TRUE
var/can_move = 0 //time of next allowed movement
var/mob/living/occupant = null
var/step_in = 10 //make a step in step_in/10 sec.
var/dir_in = SOUTH //What direction will the mech face when entered/powered on? Defaults to South.
var/normal_step_energy_drain = 10 //How much energy the mech will consume each time it moves. This variable is a backup for when leg actuators affect the energy drain.
var/step_energy_drain = 10
var/melee_energy_drain = 15
var/overload_step_energy_drain_min = 100
max_integrity = 300 //max_integrity is base health
var/deflect_chance = 10 //chance to deflect the incoming projectiles, hits, or lesser the effect of ex_act.
armor = list("melee" = 20, "bullet" = 10, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 100)
var/list/facing_modifiers = list(FRONT_ARMOUR = 1.5, SIDE_ARMOUR = 1, BACK_ARMOUR = 0.5)
var/obj/item/stock_parts/cell/cell
var/state = 0
var/list/log = new
var/last_message = 0
var/add_req_access = 1
var/maint_access = 0
var/equipment_disabled = 0 //disabled due to EMP
var/dna_lock //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/last_user_hud = 1 // used to show/hide the mecha hud while preserving previous preference
var/completely_disabled = FALSE //stops the mech from doing anything
var/breach_time = 0
var/recharge_rate = 0
var/bumpsmash = 0 //Whether or not the mech destroys walls by running into it.
//inner atmos
var/use_internal_tank = 0
var/internal_tank_valve = ONE_ATMOSPHERE
var/obj/machinery/portable_atmospherics/canister/internal_tank
var/datum/gas_mixture/cabin_air
var/obj/machinery/atmospherics/components/unary/portables_connector/connected_port = null
var/obj/item/radio/mech/radio
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_ROBOTICS)//REQUIRED ACCESS LEVEL TO OPEN CELL COMPARTMENT
var/wreckage
var/list/equipment = new
var/obj/item/mecha_parts/mecha_equipment/selected
var/max_equip = 3
var/datum/events/events
var/stepsound = 'sound/mecha/mechstep.ogg'
var/turnsound = 'sound/mecha/mechturn.ogg'
var/melee_cooldown = 10
var/melee_can_hit = 1
//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_cycle_equip/cycle_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_toggle_thrusters/thrusters_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/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_switch_damtype/switch_damtype_action = new
var/datum/action/innate/mecha/mech_toggle_phasing/phasing_action = new
var/datum/action/innate/mecha/strafe/strafing_action = new
//Action vars
var/thrusters_active = FALSE
var/defence_mode = FALSE
var/defence_mode_deflect_chance = 35
var/leg_overload_mode = FALSE
var/leg_overload_coeff = 100
var/zoom_mode = FALSE
var/smoke = 5
var/smoke_ready = 1
var/smoke_cooldown = 100
var/phasing = FALSE
var/phasing_energy_drain = 200
var/phase_state = "" //icon_state when phasing
var/strafe = FALSE //If we are strafing
var/nextsmash = 0
var/smashcooldown = 3 //deciseconds
var/occupant_sight_flags = 0 //sight flags to give to the occupant (e.g. mech mining scanner gives meson-like vision)
var/mouse_pointer
hud_possible = list (DIAG_STAT_HUD, DIAG_BATT_HUD, DIAG_MECH_HUD, DIAG_TRACK_HUD)
/obj/item/radio/mech //this has to go somewhere
/obj/mecha/Initialize()
. = ..()
events = new
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, src)
smoke_system.attach(src)
add_cell()
START_PROCESSING(SSobj, src)
GLOB.poi_list |= src
mecha_log_message("[src.name] created.")
GLOB.mechas_list += src //global mech list
prepare_huds()
for(var/datum/atom_hud/data/diagnostic/diag_hud in GLOB.huds)
diag_hud.add_to_hud(src)
diag_hud_set_mechhealth()
diag_hud_set_mechcell()
diag_hud_set_mechstat()
/obj/mecha/get_cell()
return cell
/obj/mecha/rust_heretic_act()
take_damage(500, BRUTE)
/obj/mecha/Destroy()
go_out()
var/mob/living/silicon/ai/AI
for(var/mob/M in src) //Let's just be ultra sure
if(isAI(M))
occupant = null
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.
else
M.forceMove(loc)
if(wreckage)
if(prob(30))
explosion(get_turf(src), 0, 0, 1, 3)
var/obj/structure/mecha_wreckage/WR = new wreckage(loc, AI)
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)
if(internal_tank)
WR.crowbar_salvage += internal_tank
internal_tank.forceMove(WR)
else
for(var/obj/item/mecha_parts/mecha_equipment/E in equipment)
E.detach(loc)
qdel(E)
if(cell)
qdel(cell)
if(internal_tank)
qdel(internal_tank)
if(AI)
AI.gib() //No wreck, no AI to recover
STOP_PROCESSING(SSobj, src)
GLOB.poi_list.Remove(src)
equipment.Cut()
cell = null
internal_tank = null
if(loc)
loc.assume_air(cabin_air)
air_update_turf()
else
qdel(cabin_air)
cabin_air = null
qdel(spark_system)
spark_system = null
qdel(smoke_system)
smoke_system = null
GLOB.mechas_list -= src //global mech list
return ..()
/obj/mecha/proc/restore_equipment()
equipment_disabled = 0
if(istype(src, /obj/mecha/combat))
mouse_pointer = 'icons/mecha/mecha_mouse.dmi'
if(occupant)
SEND_SOUND(occupant, sound('sound/items/timer.ogg', volume=50))
to_chat(occupant, "Equipment control unit has been rebooted successfuly.")
occupant.update_mouse_pointer()
/obj/mecha/CheckParts(list/parts_list)
..()
cell = locate(/obj/item/stock_parts/cell) in contents
var/obj/item/stock_parts/scanning_module/SM = locate() in contents
var/obj/item/stock_parts/capacitor/CP = locate() in contents
if(SM)
normal_step_energy_drain = 20 - (5 * SM.rating) //10 is normal, so on lowest part its worse, on second its ok and on higher its real good up to 0 on best
step_energy_drain = normal_step_energy_drain
qdel(SM)
if(CP)
armor = armor.modifyRating(energy = (CP.rating * 10)) //Each level of capacitor protects the mech against emp by 10%
qdel(CP)
////////////////////////
////// Helpers /////////
////////////////////////
/obj/mecha/proc/add_airtank()
internal_tank = new /obj/machinery/portable_atmospherics/canister/air(src)
return internal_tank
/obj/mecha/proc/add_cell(var/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.set_volume(200)
cabin_air.set_moles(/datum/gas/oxygen,O2STANDARD*cabin_air.return_volume()/(R_IDEAL_GAS_EQUATION*cabin_air.return_temperature()))
cabin_air.set_moles(/datum/gas/nitrogen,N2STANDARD*cabin_air.return_volume()/(R_IDEAL_GAS_EQUATION*cabin_air.return_temperature()))
return cabin_air
/obj/mecha/proc/add_radio()
radio = new(src)
radio.name = "[src] radio"
radio.icon = icon
radio.icon_state = icon_state
radio.subspace_transmission = TRUE
/obj/mecha/proc/can_use(mob/user)
if(user != occupant)
return 0
if(user && ismob(user))
if(!user.incapacitated())
return 1
return 0
////////////////////////////////////////////////////////////////////////////////
/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 && equipment.len)
. += "It's equipped with:"
for(var/obj/item/mecha_parts/mecha_equipment/ME in equipment)
. += "[icon2html(ME, user)] \A [ME]."
//processing internal damage, temperature, air regulation, alert updates, lights power use.
/obj/mecha/process()
var/internal_temp_regulation = 1
if(internal_damage)
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_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) //heat the air_contents
int_tank_air.set_temperature(min(6000+T0C, int_tank_air.return_temperature()+rand(10,15)))
if(cabin_air && cabin_air.return_volume()>0)
cabin_air.set_temperature(min(6000+T0C, cabin_air.return_temperature()+rand(10,15)))
if(cabin_air.return_temperature() > max_temperature/2)
take_damage(4/round(max_temperature/cabin_air.return_temperature(),0.1), BURN, 0, 0)
if(internal_damage & MECHA_INT_TEMP_CONTROL)
internal_temp_regulation = 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_air()
var/datum/gas_mixture/leaked_gas = int_tank_air.remove_ratio(0.1)
if(loc)
loc.assume_air(leaked_gas)
air_update_turf()
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)
if(internal_temp_regulation)
if(cabin_air && cabin_air.return_volume() > 0)
var/delta = cabin_air.return_temperature() - T20C
cabin_air.set_temperature(cabin_air.return_temperature() - max(-10, min(10, round(delta/4,0.1))))
if(internal_tank)
var/datum/gas_mixture/tank_air = internal_tank.return_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.return_temperature() > 0)
transfer_moles = pressure_delta*cabin_air.return_volume()/(cabin_air.return_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/t_air = return_air()
pressure_delta = cabin_pressure - release_pressure
if(t_air)
pressure_delta = min(cabin_pressure - t_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.return_temperature() * R_IDEAL_GAS_EQUATION)
var/datum/gas_mixture/removed = cabin_air.remove(transfer_moles)
if(t_air)
t_air.merge(removed)
else //just delete the cabin gas, we're in space or some shit
qdel(removed)
if(occupant)
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", /obj/screen/alert/lowcell, 1)
if(0.25 to 0.5)
occupant.throw_alert("charge", /obj/screen/alert/lowcell, 2)
if(0.01 to 0.25)
occupant.throw_alert("charge", /obj/screen/alert/lowcell, 3)
else
occupant.throw_alert("charge", /obj/screen/alert/emptycell)
var/integrity = obj_integrity/max_integrity*100
switch(integrity)
if(30 to 45)
occupant.throw_alert("mech damage", /obj/screen/alert/low_mech_integrity, 1)
if(15 to 35)
occupant.throw_alert("mech damage", /obj/screen/alert/low_mech_integrity, 2)
if(-INFINITY to 15)
occupant.throw_alert("mech damage", /obj/screen/alert/low_mech_integrity, 3)
else
occupant.clear_alert("mech damage")
var/atom/checking = occupant.loc
// recursive check to handle all cases regarding very nested occupants,
// such as brainmob inside brainitem inside MMI inside mecha
while (!isnull(checking))
if (isturf(checking))
// hit a turf before hitting the mecha, seems like they have
// been moved out
occupant.clear_alert("charge")
occupant.clear_alert("mech damage")
RemoveActions(occupant, human_occupant=1)
occupant = null
break
else if (checking == src)
break // all good
checking = checking.loc
if(lights)
var/lights_energy_drain = 2
use_power(lights_energy_drain)
//Diagnostic HUD updates
diag_hud_set_mechhealth()
diag_hud_set_mechcell()
diag_hud_set_mechstat()
/obj/mecha/proc/drop_item()//Derpfix, but may be useful in future for engineering exosuits.
return
/obj/mecha/Hear(message, atom/movable/speaker, message_language, raw_message, radio_freq, list/spans, message_mode, atom/movable/source)
. = ..()
if(speaker == occupant)
if(radio.broadcasting)
radio.talk_into(speaker, text, , spans, message_language)
//flick speech bubble
var/list/speech_bubble_recipients = list()
for(var/mob/M in get_hearers_in_view(7,src))
if(M.client)
speech_bubble_recipients.Add(M.client)
INVOKE_ASYNC(GLOBAL_PROC, /proc/flick_overlay, image('icons/mob/talk.dmi', src, "machine[say_test(raw_message)]",MOB_LAYER+1), speech_bubble_recipients, 30)
////////////////////////////
///// Action processing ////
////////////////////////////
/obj/mecha/proc/click_action(atom/target,mob/user,params)
if(!occupant || occupant != user )
return
if(!locate(/turf) in list(target,target.loc)) // Prevents inventory from being drilled
return
if(completely_disabled)
return
if(phasing)
occupant_message("Unable to interact with objects while phasing")
return
if(user.incapacitated())
return
if(state)
occupant_message("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
return
if(internal_damage & MECHA_INT_CONTROL_LOST)
target = safepick(view(3,target))
if(!target)
return
var/mob/living/L = user
if(!Adjacent(target))
if(selected && selected.is_ranged())
if(HAS_TRAIT(L, TRAIT_PACIFISM) && selected.harmful)
to_chat(user, "You don't want to harm other living beings!")
return
if(selected.action(target,params))
selected.start_cooldown()
else if(selected && selected.is_melee())
if(isliving(target) && selected.harmful && HAS_TRAIT(L, TRAIT_PACIFISM))
to_chat(user, "You don't want to harm other living beings!")
return
if(selected.action(target,params))
selected.start_cooldown()
else
if(internal_damage & MECHA_INT_CONTROL_LOST)
target = safepick(oview(1,src))
if(!melee_can_hit || !istype(target, /atom))
return
target.mech_melee_attack(src)
melee_can_hit = 0
spawn(melee_cooldown)
melee_can_hit = 1
/obj/mecha/proc/range_action(atom/target)
return
//////////////////////////////////
//////// Movement procs ////////
//////////////////////////////////
/obj/mecha/Move(atom/newloc, direct)
. = ..()
if(.)
events.fireEvent("onMove",get_turf(src))
if (internal_tank.disconnect()) // Something moved us and broke connection
occupant_message("Air port connection teared off!")
mecha_log_message("Lost connection to gas port.")
/obj/mecha/setDir(newdir)
. = ..()
occupant?.setDir(newdir)
/obj/mecha/Process_Spacemove(var/movement_dir = 0)
. = ..()
if(.)
return 1
if(thrusters_active && movement_dir && use_power(step_energy_drain))
return 1
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, "You push off of [backup] to propel yourself.")
return 1
/obj/mecha/relaymove(mob/user,direction)
if(completely_disabled)
return
if(!direction)
return
if(user != occupant) //While not "realistic", this piece is player friendly.
user.forceMove(get_turf(src))
to_chat(user, "You climb out from [src].")
return 0
if(internal_tank.connected_port)
if(world.time - last_message > 20)
occupant_message("Unable to move while connected to the air system port!")
last_message = world.time
return 0
if(state)
occupant_message("Maintenance protocols in effect.")
return
return domove(direction)
/obj/mecha/proc/domove(direction)
if(can_move >= world.time)
return 0
if(!Process_Spacemove(direction))
return 0
if(!has_charge(step_energy_drain))
return 0
if(defence_mode)
if(world.time - last_message > 20)
occupant_message("Unable to move while in defence mode")
last_message = world.time
return 0
if(zoom_mode)
if(world.time - last_message > 20)
occupant_message("Unable to move while in zoom mode.")
last_message = world.time
return 0
var/move_result = 0
var/oldloc = loc
if(internal_damage & MECHA_INT_CONTROL_LOST)
move_result = mechsteprand()
else if(dir != direction && (!strafe || occupant.client.keys_held["Alt"]))
move_result = mechturn(direction)
else
move_result = mechstep(direction)
if(move_result || loc != oldloc)// halfway done diagonal move still returns false
use_power(step_energy_drain)
can_move = world.time + step_in
return 1
return 0
/obj/mecha/proc/mechturn(direction)
setDir(direction)
if(turnsound)
playsound(src,turnsound,40,1)
return 1
/obj/mecha/proc/mechstep(direction)
var/current_dir = dir
set_glide_size(DELAY_TO_GLIDE_SIZE(step_in))
var/result = step(src,direction)
if(strafe)
setDir(current_dir)
if(result && stepsound)
playsound(src,stepsound,40,1)
return result
/obj/mecha/proc/mechsteprand()
var/result = step_rand(src)
if(result && stepsound)
playsound(src,stepsound,40,1)
return result
/obj/mecha/Bump(var/atom/obstacle)
if(phasing && get_charge() >= phasing_energy_drain && !throwing)
spawn()
if(can_move)
can_move = 0
if(phase_state)
flick(phase_state, src)
forceMove(get_step(src,dir))
use_power(phasing_energy_drain)
sleep(step_in*3)
can_move = 1
else
if(..()) //mech was thrown
return
if(bumpsmash && occupant) //Need a pilot to push the PUNCH button.
if(nextsmash < world.time)
obstacle.mech_melee_attack(src)
nextsmash = world.time + smashcooldown
if(!obstacle || obstacle.CanPass(src,get_step(src,dir)))
step(src,dir)
if(isobj(obstacle))
var/obj/O = obstacle
if(!O.anchored)
step(obstacle, dir)
else if(ismob(obstacle))
var/mob/M = obstacle
if(!M.anchored)
step(obstacle, dir)
///////////////////////////////////
//////// 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)
return
/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',wait=0))
diag_hud_set_mechstat()
return
/obj/mecha/proc/clearInternalDamage(int_dam_flag)
if(internal_damage & int_dam_flag)
switch(int_dam_flag)
if(MECHA_INT_TEMP_CONTROL)
occupant_message("Life support system reactivated.")
if(MECHA_INT_FIRE)
occupant_message("Internal fire extinquished.")
if(MECHA_INT_TANK_BREACH)
occupant_message("Damaged internal tank has been sealed.")
internal_damage &= ~int_dam_flag
diag_hud_set_mechstat()
/////////////////////////////////////
//////////// AI piloting ////////////
/////////////////////////////////////
/obj/mecha/attack_ai(mob/living/silicon/ai/user)
if(!isAI(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, "Warning: Tracking Beacon detected. Enter at your own risk. Beacon Data:")
to_chat(user, "[B.get_mecha_info()]")
break
//Nothing like a big, red link to make the player feel powerful!
to_chat(user, "ASSUME DIRECT CONTROL?
")
else
examine(user)
if(occupant)
to_chat(user, "This exosuit has a pilot and cannot be controlled.")
return
var/can_control_mech = 0
for(var/obj/item/mecha_parts/mecha_tracking/ai_control/A in trackers)
can_control_mech = 1
to_chat(user, "[icon2html(src, user)] Status of [name]:\n[A.get_mecha_info()]")
break
if(!can_control_mech)
to_chat(user, "You cannot control exosuits without AI control beacons installed.")
return
to_chat(user, "Take control of exosuit?
")
/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(!state) //Mech must be in maint mode to allow carding.
to_chat(user, "[name] must have maintenance protocols active in order to allow a transfer.")
return
AI = occupant
if(!AI || !isAI(occupant)) //Mech does not have an AI for a pilot
to_chat(user, "No AI detected in the [name] onboard computer.")
return
AI.ai_restore_power()//So the AI initially has power.
AI.control_disabled = 1
AI.radio_enabled = 0
AI.disconnect_shell()
RemoveActions(AI, TRUE)
occupant = null
AI.forceMove(card)
card.AI = AI
AI.controlled_mech = null
AI.remote_control = null
icon_state = initial(icon_state)+"-open"
to_chat(AI, "You have been downloaded to a mobile storage device. Wireless connection offline.")
to_chat(user, "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/AIcore/deactivated(AI.loc)
if(AI.can_dominate_mechs)
if(occupant) //Oh, I am sorry, were you using that?
to_chat(AI, "Pilot detected! Forced ejection initiated!")
to_chat(occupant, "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 = card.AI
if(!AI)
to_chat(user, "There is no AI currently installed on this device.")
return
if(AI.deployed_shell) //Recall AI if shelled so it can be checked for a client
AI.disconnect_shell()
if(AI.stat || !AI.client)
to_chat(user, "[AI.name] is currently unresponsive, and cannot be uploaded.")
return
if(occupant || dna_lock) //Normal AIs cannot steal mechs!
to_chat(user, "Access denied. [name] is [occupant ? "currently occupied" : "secured with a DNA lock"].")
return
AI.control_disabled = 0
AI.radio_enabled = 1
to_chat(user, "Transfer successful: [AI.name] ([rand(1000,9999)].exe) installed and executed successfully. Local copy has been removed.")
card.AI = null
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)
AI.ai_restore_power()
AI.forceMove(src)
occupant = AI
icon_state = initial(icon_state)
playsound(src, 'sound/machines/windowdoor.ogg', 50, 1)
if(!internal_damage)
SEND_SOUND(occupant, sound('sound/mecha/nominal.ogg',volume=50))
AI.cancel_camera()
AI.controlled_mech = src
AI.remote_control = src
AI.mobility_flags = MOBILITY_FLAGS_DEFAULT //Much easier than adding AI checks! Be sure to set this back to 0 if you decide to allow an AI to leave a mech somehow.
AI.can_shunt = 0 //ONE AI ENTERS. NO AI LEAVES.
to_chat(AI, AI.can_dominate_mechs ? "Takeover of [name] complete! You are now loaded onto the onboard computer. Do not attempt to leave the station sector!" :\
"You have been uploaded to a mech's onboard computer.")
to_chat(AI, "Use Middle-Mouse to activate mech functions and equipment. Click normally for AI interactions.")
if(interaction == AI_TRANS_FROM_CARD)
GrantActions(AI, FALSE) //No eject/return to core action for AI uploaded by card
else
GrantActions(AI, !AI.can_dominate_mechs)
//An actual AI (simple_animal mecha pilot) entering the mech
/obj/mecha/proc/aimob_enter_mech(mob/living/simple_animal/hostile/syndicate/mecha_pilot/pilot_mob)
if(pilot_mob && pilot_mob.Adjacent(src))
if(occupant)
return
icon_state = initial(icon_state)
occupant = pilot_mob
pilot_mob.mecha = src
pilot_mob.forceMove(src)
GrantActions(pilot_mob)//needed for checks, and incase a badmin puts somebody in the mob
/obj/mecha/proc/aimob_exit_mech(mob/living/simple_animal/hostile/syndicate/mecha_pilot/pilot_mob)
if(occupant == pilot_mob)
occupant = null
if(pilot_mob.mecha == src)
pilot_mob.mecha = null
icon_state = "[initial(icon_state)]-open"
pilot_mob.forceMove(get_turf(src))
RemoveActions(pilot_mob)
/////////////////////////////////////
//////// Atmospheric stuff ////////
/////////////////////////////////////
/obj/mecha/remove_air(amount)
if(use_internal_tank)
return cabin_air.remove(amount)
return ..()
/obj/mecha/return_air()
if(use_internal_tank)
return cabin_air
return ..()
/obj/mecha/proc/return_pressure()
var/datum/gas_mixture/t_air = return_air()
if(t_air)
. = t_air.return_pressure()
return
/obj/mecha/return_temperature()
var/datum/gas_mixture/t_air = return_air()
if(t_air)
. = t_air.return_temperature()
return
/obj/mecha/portableConnectorReturnAir()
return internal_tank.return_air()
/obj/mecha/MouseDrop_T(mob/M, mob/user)
if (!user.canUseTopic(src) || (user != M))
return
if(!ishuman(user)) // no silicons or drones in mechas.
return
mecha_log_message("[user] tries to move in.")
if (occupant)
to_chat(usr, "The [name] is already occupied!")
log_append_to_last("Permission denied.")
return
if(dna_lock)
var/passed = FALSE
if(user.has_dna())
var/mob/living/carbon/C = user
if(C.dna.unique_enzymes==dna_lock)
passed = TRUE
if (!passed)
to_chat(user, "Access denied. [name] is secured with a DNA lock.")
log_append_to_last("Permission denied.")
return
if(!operation_allowed(user))
to_chat(user, "Access denied. Insufficient operation keycodes.")
log_append_to_last("Permission denied.")
return
if(user.buckled)
to_chat(user, "You are currently buckled and cannot move.")
log_append_to_last("Permission denied.")
return
if(user.has_buckled_mobs()) //mob attached to us
to_chat(user, "You can't enter the exosuit with other creatures attached to you!")
return
visible_message("[user] starts to climb into [name].")
if(do_after(user, 40, target = src))
if(obj_integrity <= 0)
to_chat(user, "You cannot get in the [name], it has been destroyed!")
else if(occupant)
to_chat(user, "[occupant] was faster! Try better next time, loser.")
else if(user.buckled)
to_chat(user, "You can't enter the exosuit while buckled.")
else if(user.has_buckled_mobs())
to_chat(user, "You can't enter the exosuit with other creatures attached to you!")
else
moved_inside(user)
else
to_chat(user, "You stop entering the exosuit!")
return
/obj/mecha/proc/moved_inside(mob/living/carbon/human/H)
if(H?.client && (H in range(1)))
occupant = H
H.forceMove(src)
H.update_mouse_pointer()
add_fingerprint(H)
GrantActions(H, human_occupant=1)
forceMove(loc)
log_append_to_last("[H] moved in as pilot.")
icon_state = initial(icon_state)
setDir(dir_in)
playsound(src, 'sound/machines/windowdoor.ogg', 50, 1)
if(!internal_damage)
SEND_SOUND(occupant, sound('sound/mecha/nominal.ogg',volume=50))
return 1
else
return 0
/obj/mecha/proc/mmi_move_inside(obj/item/mmi/mmi_as_oc, mob/user)
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
else if(occupant)
to_chat(user, "Occupant detected!")
return FALSE
else if(dna_lock && (!mmi_as_oc.brainmob.stored_dna || (dna_lock != mmi_as_oc.brainmob.stored_dna.unique_enzymes)))
to_chat(user, "Access denied. [name] is secured with a DNA lock.")
return FALSE
visible_message("[user] starts to insert an MMI into [name].")
if(do_after(user, 40, target = src))
if(!occupant)
return mmi_moved_inside(mmi_as_oc, user)
else
to_chat(user, "Occupant detected!")
else
to_chat(user, "You stop inserting the MMI.")
return FALSE
/obj/mecha/proc/mmi_moved_inside(obj/item/mmi/mmi_as_oc, mob/user)
if(!(Adjacent(mmi_as_oc) && Adjacent(user)))
return FALSE
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.transferItemToLoc(mmi_as_oc, src))
to_chat(user, "\the [mmi_as_oc] is stuck to your hand, you cannot put it in \the [src]!")
return FALSE
var/mob/living/brainmob = mmi_as_oc.brainmob
mmi_as_oc.mecha = src
occupant = brainmob
brainmob.forceMove(src) //should allow relaymove
brainmob.reset_perspective(src)
brainmob.remote_control = src
brainmob.update_mobility()
brainmob.update_mouse_pointer()
icon_state = initial(icon_state)
update_icon()
setDir(dir_in)
mecha_log_message("[mmi_as_oc] moved in as pilot.")
if(!internal_damage)
SEND_SOUND(occupant, sound('sound/mecha/nominal.ogg',volume=50))
GrantActions(brainmob)
return TRUE
/obj/mecha/container_resist(mob/living/user)
go_out()
/obj/mecha/Exited(atom/movable/M, atom/newloc)
if(occupant && occupant == M) // The occupant exited the mech without calling go_out()
go_out(TRUE, newloc)
/obj/mecha/proc/go_out(forced, atom/newloc = loc)
if(!occupant)
return
var/atom/movable/mob_container
occupant.clear_alert("charge")
occupant.clear_alert("mech damage")
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(isAI(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)
to_chat(AI, "Inactive core destroyed. Unable to return.")
AI.linked_core = null
return
to_chat(AI, "Returning to core...")
AI.controlled_mech = null
AI.remote_control = null
RemoveActions(occupant, 1)
mob_container = AI
newloc = get_turf(AI.linked_core)
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
mecha_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()
L.mobility_flags = NONE
icon_state = initial(icon_state)+"-open"
setDir(dir_in)
if(L && L.client)
L.update_mouse_pointer()
L.client.view_size.resetToDefault()
zoom_mode = 0
/////////////////////////
////// Access stuff /////
/////////////////////////
/obj/mecha/proc/operation_allowed(mob/M)
req_access = operation_req_access
req_one_access = list()
return allowed(M)
/obj/mecha/proc/internals_access_allowed(mob/M)
req_one_access = internals_req_access
req_access = list()
return allowed(M)
////////////////////////////////
/////// Messages and Log ///////
////////////////////////////////
/obj/mecha/proc/occupant_message(message as text)
if(message)
if(occupant && occupant.client)
to_chat(occupant, "[icon2html(src, occupant)] [message]")
return
/obj/mecha/proc/mecha_log_message(message, color)
log.len++
log[log.len] = list("time"="[STATION_TIME_TIMESTAMP("hh:mm:ss", world.time)]","date","year"="[GLOB.year_integer]","message"="[color?"":null][message][color?"":null]")
log_message(message, LOG_GAME, color) //also do the normal admin logs I guess.
return log.len
/obj/mecha/proc/log_append_to_last(message as text,red=null)
var/list/last_entry = log[log.len]
last_entry["message"] += "
[red?"":null][message][red?"":null]"
return
///////////////////////
///// 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)
return 1
return 0
/obj/mecha/proc/give_power(amount)
if(!isnull(get_charge()))
cell.give(amount)
return 1
return 0
/obj/mecha/update_remote_sight(mob/living/user)
if(occupant_sight_flags)
if(user == occupant)
user.sight |= occupant_sight_flags
///////////////////////
////// Ammo stuff /////
///////////////////////
/obj/mecha/proc/ammo_resupply(var/obj/item/mecha_ammo/A, mob/user,var/fail_chat_override = FALSE)
if(!A.rounds)
if(!fail_chat_override)
to_chat(user, "This box of ammo is empty!")
return FALSE
var/ammo_needed
var/found_gun
for(var/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/gun in equipment)
ammo_needed = 0
if(istype(gun, /obj/item/mecha_parts/mecha_equipment/weapon/ballistic) && gun.ammo_type == A.ammo_type)
found_gun = TRUE
if(A.direct_load)
ammo_needed = initial(gun.projectiles) - gun.projectiles
else
ammo_needed = gun.projectiles_cache_max - gun.projectiles_cache
if(ammo_needed)
if(ammo_needed < A.rounds)
if(A.direct_load)
gun.projectiles = gun.projectiles + ammo_needed
else
gun.projectiles_cache = gun.projectiles_cache + ammo_needed
playsound(get_turf(user),A.load_audio,50,1)
to_chat(user, "You add [ammo_needed] [A.round_term][ammo_needed > 1?"s":""] to the [gun.name]")
A.rounds = A.rounds - ammo_needed
A.update_name()
return TRUE
else
if(A.direct_load)
gun.projectiles = gun.projectiles + A.rounds
else
gun.projectiles_cache = gun.projectiles_cache + A.rounds
playsound(get_turf(user),A.load_audio,50,1)
to_chat(user, "You add [A.rounds] [A.round_term][A.rounds > 1?"s":""] to the [gun.name]")
A.rounds = 0
A.update_name()
return TRUE
if(!fail_chat_override)
if(found_gun)
to_chat(user, "You can't fit any more ammo of this type!")
else
to_chat(user, "None of the equipment on this exosuit can use this ammo!")
return FALSE