mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2026-08-21 03:59:59 +01:00
## About The Pull Request <img width="194" height="63" alt="image" src="https://github.com/user-attachments/assets/8b81ede5-3ac1-427c-b580-f75a4b05fae5" /> Tripwires, for mechs. A very sturdy cable hung over a space 2-5 tiles wide, that mechs will trip over if they aren't paying attention. https://github.com/user-attachments/assets/eb77ac44-73ad-43f7-aa48-3008aedc79e0 It also hurts people it falls on in pretty much the same way as a vending machine. (it's using `fall_and_crush()`) https://github.com/user-attachments/assets/97fccb33-d479-4170-8601-07a5b32fe740 To humans, the tripwire blocks movement unless you carefully step over it(walk) or crawl under it. All beings suffer no issue walking across the tripwire the long way. The tripwire is created from wiring together two tripwire poles with a tripwire cable item. The poles are created from iron rods and iron, the cable is created from just cable. The wiring takes 3 seconds * how far the posts are to complete, to a maximum of 12 seconds. They take significantly increased (10x) damage from any sharp object, and can be cut in a few seconds using wirecutters. They otherwise have a `max_integrity` of 500, so a powerful demolition tool(like a mech's fists or a drill) or a sharp object are fairly necessary to take them down in a combat-reasonable amount of time. The tripping has four effects - the damage to the being that the mech fell on, the paralyzing of that being, damage to the mech pilot's head, and the time it takes for the mech to get back up. All four of these numbers are based off of the mech's max health divided by 5, or `damage_value`. `damage_value` is applied directly as damage to whoever it falls on. That person is paralyzed for `damage_value` / 10 seconds. The pilot of the falling mech recieves `damage_value` / 5 to their head, with a significant wound bonus. Wear a helmet. The mech itself recieves `damage_value` * 0.15 seconds of immobility(can still attack, but can't rotate). (`damage_value` / 10 * 1.5) What these numbers mean on common exosuits: - odysseus - mob under hit for 24 damage & 2.4 seconds, pilot hit for ~5 damage, immobile for 3.6 seconds - ripley - mob under hit for 40 damage & 4 seconds, pilot hit for 8 damage, immobile for 6 seconds - gygax - mob under hit for 50 damage & 5 seconds, pilot hit for 10 damage, immobile for 7.5 seconds - durand - mob under hit for 80 damage & 8 seconds, pilot hit for 16 damage, immobile for 12 seconds - marauder - mob under hit for 100 damage & 10 seconds, pilot hit for 20 damage, immobile for 15 seconds All of these stun times are large enough to where if there were multiple people with good weapons around, they could most certainly kill you before you stand back up. So, don't trip. Finally, obviously, the clarke is immune. Who'd have thunk. ## Why It's Good For The Game First, let's all thank the ideas guy. The ideas guy for today is [dubblywumps](https://discord.com/channels/699727870846697493/705679956021215264/1523883105276919969). Thank you, dubblywumps. Good idea. So, what's the point of it? It's an effective area denial tool and fortification specifically effective against mechs. You can set them up in the hallways in a reasonable length of time, and it'll at the very least slow a rampaging mech down, as while they're not strong enough to handle more than a few punches from a mech, they're strong enough to handle a few. Falling over as a mech is obviously extremely punishing, so unless they can take a delay to destroy the wire(or a long delay to destroy the wire at range), or get around it by tearing through walls(also a delay), they have to abide by your preferred positioning. No other tool exists that can truly force a delay in a mech's movement besides actual walls & windows, which delay humans far more significantly. It is not remotely practical in a directly offensive sense, with even the shortest 2-tile barricades taking too long to set up to do so before you've been clobbered and perforated. It is practical in an indirectly offensive sense, to force a fight to continue and prevent the worst part about fighting a mech: when it walks away. This requires forethought or teamwork, though. It is also practical in a defensive sense, in combination with normal barricades to block projectiles you could reasonably hold a mech off for a while or even potentially force a stalemate using ranged weapons. They're very, very cheap materials-wise, but the expense in their creation is in the time to set them up in a good location. Because they also slow down people, people won't be setting scores of them up in the middle of the halls. And if they did, wirecutters take them down faster than they can be put up. You may be balking about the immobility time applied to the mech. The numbers mean that in order to destroy the mech while it lays on the ground, you must deal a flat 33 + armor damage per second for the duration of the stun. This is an unreasonable task for one person with a good crew melee weapon, a reasonable task for two, and an easy task for three. I think that two people being able to beat you to death after you failed to take heed of the not-at-all-hidden anti-mech weapon is reasonable. This is also assuming you have no support team or second mech, which would be perfectly capable of defending you during your stun. ## Changelog 🆑 add: Added craftable anti-mech tripwires. A mech that foolishly steps on one will fall down and be immobilized temporarily. Don't let it fall on you! /🆑
999 lines
38 KiB
Plaintext
999 lines
38 KiB
Plaintext
/***************** WELCOME TO MECHA.DM, ENJOY YOUR STAY *****************/
|
|
|
|
/**
|
|
* Mechs are now (finally) vehicles, this means you can make them multicrew
|
|
* They can also grant select ability buttons based on occupant bitflags
|
|
*
|
|
* Movement is handled through vehicle_move() which is called by relaymove
|
|
* Clicking is done by way of signals registering to the entering mob
|
|
* NOTE: MMIS are NOT mobs but instead contain a brain that is, so you need special checks
|
|
* AI also has special checks becaus it gets in and out of the mech differently
|
|
* Always call remove_occupant(mob) when leaving the mech so the mob is removed properly
|
|
*
|
|
* For multi-crew, you need to set how the occupants receive ability bitflags corresponding to their status on the vehicle(i.e: driver, gunner etc)
|
|
* Abilities can then be set to only apply for certain bitflags and are assigned as such automatically
|
|
*
|
|
* Clicks are wither translated into mech_melee_attack (see mech_melee_attack.dm)
|
|
* Or are used to call action() on equipped gear
|
|
* Cooldown for gear is on the mech because exploits
|
|
* Cooldown for melee is on mech_melee_attack also because exploits
|
|
*/
|
|
/obj/vehicle/sealed/mecha
|
|
name = "exosuit"
|
|
desc = "Exosuit"
|
|
icon = 'icons/mob/rideables/mecha.dmi'
|
|
resistance_flags = FIRE_PROOF | ACID_PROOF
|
|
max_integrity = 300
|
|
armor_type = /datum/armor/sealed_mecha
|
|
force = 5
|
|
movedelay = 1 SECONDS
|
|
move_force = MOVE_FORCE_VERY_STRONG
|
|
move_resist = MOVE_FORCE_EXTREMELY_STRONG
|
|
light_system = OVERLAY_LIGHT_DIRECTIONAL
|
|
light_on = FALSE
|
|
light_range = 6
|
|
generic_canpass = FALSE
|
|
hud_possible = list(DIAG_STAT_HUD, DIAG_BATT_HUD, DIAG_MECH_HUD, DIAG_TRACK_HUD, DIAG_CAMERA_HUD)
|
|
mouse_pointer = 'icons/effects/mouse_pointers/mecha_mouse.dmi'
|
|
/// Significantly heavier than humans
|
|
inertia_force_weight = 5
|
|
/// How much energy the mech will consume each time it moves. this is the current active energy consumed
|
|
var/step_energy_drain = 0.008 * STANDARD_CELL_CHARGE
|
|
/// How much energy we drain each time we mechpunch someone
|
|
var/melee_energy_drain = 0.015 * STANDARD_CELL_CHARGE
|
|
/// Power we use to have the lights on
|
|
var/light_power_drain = 0.002 * STANDARD_CELL_RATE
|
|
/// Modifiers for directional damage reduction
|
|
var/list/facing_modifiers = list(MECHA_FRONT_ARMOUR = 0.5, MECHA_SIDE_ARMOUR = 1, MECHA_BACK_ARMOUR = 1.5)
|
|
/// If we cant use our equipment(such as due to EMP)
|
|
var/equipment_disabled = FALSE
|
|
/// Keeps track of the mech's cell
|
|
var/obj/item/stock_parts/power_store/cell
|
|
/// Keeps track of the mech's scanning module
|
|
var/obj/item/stock_parts/scanning_module/scanmod
|
|
/// Keeps track of the mech's capacitor
|
|
var/obj/item/stock_parts/capacitor/capacitor
|
|
/// Keeps track of the mech's servo motor
|
|
var/obj/item/stock_parts/servo/servo
|
|
/// Contains flags for the mecha
|
|
var/mecha_flags = CAN_STRAFE | IS_ENCLOSED | HAS_LIGHTS | MMI_COMPATIBLE | BEACON_TRACKABLE | AI_COMPATIBLE | BEACON_CONTROLLABLE
|
|
|
|
/// Spark effects are handled by this datum
|
|
var/datum/effect_system/basic/spark_spread/spark_system
|
|
/// How powerful our lights are
|
|
var/lights_power = 6
|
|
/// Just stop the mech from doing anything
|
|
var/completely_disabled = FALSE
|
|
/// Whether this mech is allowed to move diagonally
|
|
var/allow_diagonal_movement = TRUE
|
|
/// Whether this mech moves into a direct as soon as it goes to move. Basically, turn and step in the same key press.
|
|
var/pivot_step = FALSE
|
|
/// Whether or not the mech destroys walls by running into it.
|
|
var/bumpsmash = FALSE
|
|
|
|
// Atmos stuff
|
|
/// Whether the cabin exchanges gases with the environment
|
|
var/cabin_sealed = FALSE
|
|
/// Internal air mix datum
|
|
var/datum/gas_mixture/cabin_air
|
|
/// Volume of the cabin
|
|
var/cabin_volume = TANK_STANDARD_VOLUME * 3
|
|
|
|
/// List of installed remote tracking beacons, including AI control beacons
|
|
var/list/trackers = list()
|
|
/// Camera installed into the mech
|
|
var/obj/machinery/camera/exosuit/chassis_camera
|
|
|
|
var/max_temperature = 25000
|
|
|
|
/// Bitflags for internal damage
|
|
var/internal_damage = NONE
|
|
/// % chance for internal damage to occur
|
|
var/internal_damage_probability = 20
|
|
/// List of possibly dealt internal damage for this mech type
|
|
var/possible_int_damage = MECHA_INT_FIRE|MECHA_INT_TEMP_CONTROL|MECHA_CABIN_AIR_BREACH|MECHA_INT_CONTROL_LOST|MECHA_INT_SHORT_CIRCUIT
|
|
/// Damage threshold above which we take component damage
|
|
var/component_damage_threshold = 10
|
|
|
|
/// Stores the DNA enzymes of a carbon so tht only they can access the mech
|
|
var/dna_lock
|
|
/// A list of all granted accesses
|
|
var/list/accesses = list()
|
|
/// If the mech should require ALL or only ONE of the listed accesses
|
|
var/one_access = TRUE
|
|
|
|
/// Typepath for the wreckage it spawns when destroyed
|
|
var/wreckage
|
|
/// Single flag for the type of this mech, determines what kind of equipment can be attached to it
|
|
var/mech_type
|
|
|
|
/// Assoc list: key-typepathlist before init, key-equipmentlist after
|
|
var/list/equip_by_category = list(
|
|
MECHA_L_ARM = null,
|
|
MECHA_R_ARM = null,
|
|
MECHA_UTILITY = list(),
|
|
MECHA_POWER = list(),
|
|
MECHA_ARMOR = list(),
|
|
)
|
|
/// Assoc list: max equips for modules key-count
|
|
var/list/max_equip_by_category = list(
|
|
MECHA_L_ARM = 1,
|
|
MECHA_R_ARM = 1,
|
|
MECHA_UTILITY = 2,
|
|
MECHA_POWER = 1,
|
|
MECHA_ARMOR = 0,
|
|
)
|
|
/// Flat equipment for iteration
|
|
var/list/flat_equipment
|
|
|
|
/// Internal ore box for mining mechs, or one picked up by a hydraulic clamp
|
|
var/obj/structure/ore_box/ore_box
|
|
|
|
///Whether our steps are silent due to no gravity
|
|
var/step_silent = FALSE
|
|
/// Sound played when the mech moves
|
|
var/stepsound = 'sound/vehicles/mecha/mechstep.ogg'
|
|
/// Sound played when the mech walks
|
|
var/turnsound = 'sound/vehicles/mecha/mechturn.ogg'
|
|
/// Sounds for types of melee attack
|
|
var/brute_attack_sound = 'sound/items/weapons/punch4.ogg'
|
|
var/burn_attack_sound = 'sound/items/tools/welder.ogg'
|
|
var/tox_attack_sound = 'sound/effects/spray2.ogg'
|
|
/// Sound on wall destroying
|
|
var/destroy_wall_sound = 'sound/effects/meteorimpact.ogg'
|
|
|
|
/// Melee attack verb
|
|
var/list/attack_verbs = list("hit", "hits", "hitting")
|
|
|
|
/// Cooldown duration between melee punches
|
|
var/melee_cooldown = CLICK_CD_SLOW
|
|
|
|
/// Time taken to leave the mech
|
|
var/exit_delay = 2 SECONDS
|
|
/// Time you get slept for if you get forcible ejected by the mech exploding
|
|
var/destruction_sleep_duration = 2 SECONDS
|
|
/// In case theres a different iconstate for AI/MMI pilot(currently only used for ripley)
|
|
var/silicon_icon_state = null
|
|
/// Currently ejecting, and unable to do things
|
|
var/is_currently_ejecting = FALSE
|
|
/// Safety for weapons. Won't fire if enabled, and toggled by middle click.
|
|
var/weapons_safety = FALSE
|
|
/// Don't play standard sound when set safety if TRUE.
|
|
var/safety_sound_custom = FALSE
|
|
|
|
// Action vars
|
|
/// Ref to any active thrusters we might have
|
|
var/obj/item/mecha_parts/mecha_equipment/thrusters/active_thrusters
|
|
|
|
/// Bool for leg overload on/off
|
|
VAR_FINAL/overclock_mode = FALSE
|
|
/// Whether it is possible to toggle overclocking from the cabin
|
|
var/can_use_overclock = FALSE
|
|
/// Speed and energy usage modifier for leg overload
|
|
var/overclock_coeff = 1.5
|
|
/// Current leg actuator temperature. Increases when overloaded, decreases when not.
|
|
var/overclock_temp = 0
|
|
/// Temperature threshold at which actuators may start causing internal damage
|
|
var/overclock_temp_danger = 15
|
|
/// Whether the mech has an option to enable safe overclocking
|
|
var/overclock_safety_available = FALSE
|
|
/// Whether the overclocking turns off automatically when overheated
|
|
var/overclock_safety = FALSE
|
|
/// Action type for overclocking, if null - no action
|
|
var/overclock_action_type = /datum/action/vehicle/sealed/mecha/mech_overclock
|
|
/// Name of the overclock action
|
|
var/overclock_name = "overclock"
|
|
|
|
/// Whether we are strafing
|
|
var/strafe = FALSE
|
|
|
|
/// Ref to screen object that displays in the middle of the UI
|
|
var/atom/movable/screen/map_view/ui_view
|
|
|
|
/// Theme of the mech TGUI
|
|
var/ui_theme = "ntos"
|
|
/// Module selected by default when mech UI is opened
|
|
var/ui_selected_module_index
|
|
|
|
/// Whether we've been knocked down, unable to move
|
|
var/toppled = FALSE
|
|
|
|
/datum/armor/sealed_mecha
|
|
melee = 20
|
|
bullet = 10
|
|
bomb = 10
|
|
fire = 100
|
|
acid = 100
|
|
|
|
/obj/vehicle/sealed/mecha/Initialize(mapload, built_manually)
|
|
. = ..()
|
|
ui_view = new()
|
|
ui_view.generate_view("mech_view_[REF(src)]")
|
|
RegisterSignal(src, COMSIG_MOVABLE_MOVED, PROC_REF(on_move))
|
|
RegisterSignal(src, COMSIG_LIGHT_EATER_ACT, PROC_REF(on_light_eater))
|
|
|
|
spark_system = new(src, 2, FALSE)
|
|
spark_system.attach(src)
|
|
|
|
cabin_air = new(cabin_volume)
|
|
|
|
if(!built_manually)
|
|
populate_parts()
|
|
update_access()
|
|
set_wires(new /datum/wires/mecha(src))
|
|
START_PROCESSING(SSobj, src)
|
|
SSpoints_of_interest.make_point_of_interest(src)
|
|
log_message("[src.name] created.", LOG_MECHA)
|
|
GLOB.mechas_list += src //global mech list
|
|
prepare_huds()
|
|
var/datum/atom_hud/data/diagnostic/diag_hud = GLOB.huds[DATA_HUD_DIAGNOSTIC]
|
|
diag_hud.add_atom_to_hud(src)
|
|
diag_hud_set_mechhealth()
|
|
diag_hud_set_mechcell()
|
|
diag_hud_set_mechstat()
|
|
update_appearance()
|
|
|
|
AddElement(/datum/element/atmos_sensitive, mapload)
|
|
become_hearing_sensitive(trait_source = ROUNDSTART_TRAIT)
|
|
add_traits(list(TRAIT_ASHSTORM_IMMUNE, TRAIT_SNOWSTORM_IMMUNE), ROUNDSTART_TRAIT) //stormy weather (keeps rainin' all the time)
|
|
for(var/key in equip_by_category)
|
|
if(key == MECHA_L_ARM || key == MECHA_R_ARM)
|
|
var/path = equip_by_category[key]
|
|
if(!path)
|
|
continue
|
|
var/obj/item/mecha_parts/mecha_equipment/thing = new path
|
|
thing.attach(src, key == MECHA_R_ARM)
|
|
continue
|
|
for(var/path in equip_by_category[key])
|
|
var/obj/item/mecha_parts/mecha_equipment/thing = new path
|
|
thing.attach(src, FALSE)
|
|
equip_by_category[key] -= path
|
|
|
|
AddElement(/datum/element/falling_hazard, damage = 80, wound_bonus = 10, hardhat_safety = FALSE, crushes = TRUE)
|
|
AddElement(/datum/element/hostile_machine)
|
|
|
|
/obj/vehicle/sealed/mecha/Destroy()
|
|
// If the former occupants get polymorphed, mutated, chestburstered,
|
|
// or otherwise replaced by another mob, that mob is no longer in .occupants
|
|
// and gets deleted with the mech. However, they do remain in .contents
|
|
var/list/potential_occupants = contents | occupants
|
|
for(var/mob/buggy_ejectee in potential_occupants)
|
|
mob_exit(buggy_ejectee, silent = TRUE, forced = TRUE)
|
|
|
|
if(LAZYLEN(flat_equipment))
|
|
for(var/obj/item/mecha_parts/mecha_equipment/equip as anything in flat_equipment)
|
|
equip.detach(loc)
|
|
qdel(equip)
|
|
|
|
STOP_PROCESSING(SSobj, src)
|
|
LAZYCLEARLIST(flat_equipment)
|
|
|
|
QDEL_NULL(ore_box)
|
|
|
|
QDEL_NULL(cell)
|
|
QDEL_NULL(scanmod)
|
|
QDEL_NULL(capacitor)
|
|
QDEL_NULL(servo)
|
|
QDEL_NULL(cabin_air)
|
|
QDEL_NULL(spark_system)
|
|
QDEL_NULL(ui_view)
|
|
QDEL_LIST(trackers)
|
|
QDEL_NULL(chassis_camera)
|
|
|
|
GLOB.mechas_list -= src //global mech list
|
|
var/datum/atom_hud/data/diagnostic/diag_hud = GLOB.huds[DATA_HUD_DIAGNOSTIC]
|
|
diag_hud.remove_atom_from_hud(src) //YEET
|
|
return ..()
|
|
|
|
/// Add parts on mech spawning. Skipped in manual construction.
|
|
/obj/vehicle/sealed/mecha/proc/populate_parts()
|
|
cell = new /obj/item/stock_parts/power_store/cell/high(src)
|
|
scanmod = new /obj/item/stock_parts/scanning_module(src)
|
|
capacitor = new /obj/item/stock_parts/capacitor(src)
|
|
servo = new /obj/item/stock_parts/servo(src)
|
|
update_part_values()
|
|
|
|
/obj/vehicle/sealed/mecha/proc/locate_parts()
|
|
cell = locate(/obj/item/stock_parts/power_store) in contents
|
|
diag_hud_set_mechcell()
|
|
scanmod = locate(/obj/item/stock_parts/scanning_module) in contents
|
|
capacitor = locate(/obj/item/stock_parts/capacitor) in contents
|
|
servo = locate(/obj/item/stock_parts/servo) in contents
|
|
update_part_values()
|
|
|
|
/obj/vehicle/sealed/mecha/atom_destruction()
|
|
spark_system?.start()
|
|
loc.assume_air(cabin_air)
|
|
|
|
var/mob/living/silicon/ai/unlucky_ai
|
|
for(var/mob/living/occupant as anything in occupants)
|
|
if(!isAI(occupant))
|
|
mob_exit(occupant, forced = TRUE)
|
|
if(!isbrain(occupant)) // who would win.. 1 brain vs 1 sleep proc..
|
|
occupant.SetSleeping(destruction_sleep_duration)
|
|
continue
|
|
|
|
var/mob/living/silicon/ai/ai = occupant
|
|
if(ai.linked_core || ai.can_shunt) // we probably shouldnt gib AIs with a core or shunting abilities
|
|
mob_exit(ai, silent = TRUE, forced = TRUE) // so we dont ghost the AI
|
|
continue
|
|
|
|
unlucky_ai = occupant
|
|
ai.investigate_log("has been gibbed by having their mech destroyed.", INVESTIGATE_DEATHS)
|
|
ai.gib(DROP_ALL_REMAINS) //No wreck, no AI to recover
|
|
|
|
if(ore_box)
|
|
INVOKE_ASYNC(ore_box, TYPE_PROC_REF(/obj/structure/ore_box, dump_box_contents))
|
|
|
|
if(!wreckage)
|
|
return ..()
|
|
|
|
var/obj/structure/mecha_wreckage/wreck = new wreckage(loc, unlucky_ai)
|
|
for(var/obj/item/mecha_parts/mecha_equipment/equipment in flat_equipment)
|
|
if(equipment.detachable && prob(30))
|
|
wreck.crowbar_salvage += equipment
|
|
equipment.detach(wreck) //detaches from src into wreck
|
|
equipment.active = TRUE
|
|
else
|
|
equipment.detach(loc)
|
|
qdel(equipment)
|
|
|
|
if(cell)
|
|
wreck.crowbar_salvage += cell
|
|
cell.forceMove(wreck)
|
|
cell.use(rand(0, cell.charge), TRUE)
|
|
cell = null
|
|
|
|
|
|
/obj/vehicle/sealed/mecha/update_icon_state()
|
|
icon_state = get_mecha_occupancy_state()
|
|
return ..()
|
|
|
|
/**
|
|
* Toggles Weapons Safety
|
|
*
|
|
* Handles enabling or disabling the safety function.
|
|
*/
|
|
/obj/vehicle/sealed/mecha/proc/set_safety(mob/user)
|
|
weapons_safety = !weapons_safety
|
|
if(!safety_sound_custom)
|
|
SEND_SOUND(user, sound('sound/machines/beep/beep.ogg', volume = 25))
|
|
balloon_alert(user, "equipment [weapons_safety ? "safe" : "ready"]")
|
|
set_mouse_pointer()
|
|
SEND_SIGNAL(src, COMSIG_MECH_SAFETIES_TOGGLE, user, weapons_safety)
|
|
|
|
/**
|
|
* Updates the pilot's mouse cursor override.
|
|
*
|
|
* If the mech's weapons safety is enabled, there should be no override, and the user gets their regular mouse cursor. If safety
|
|
* is off but the mech's equipment is disabled (such as by EMP), the cursor should be the red disabled version. Otherwise, if
|
|
* safety is off and the equipment is functional, the cursor should be the regular green cursor. This proc sets the cursor.
|
|
* correct and then updates it for each mob in the occupants list.
|
|
*/
|
|
/obj/vehicle/sealed/mecha/proc/set_mouse_pointer()
|
|
if(weapons_safety)
|
|
mouse_pointer = ""
|
|
else
|
|
if(equipment_disabled)
|
|
mouse_pointer = 'icons/effects/mouse_pointers/mecha_mouse-disable.dmi'
|
|
else
|
|
mouse_pointer = 'icons/effects/mouse_pointers/mecha_mouse.dmi'
|
|
|
|
for(var/mob/mob_occupant as anything in occupants)
|
|
mob_occupant.update_mouse_pointer()
|
|
|
|
//override this proc if you need to split up mecha control between multiple people (see savannah_ivanov.dm)
|
|
/obj/vehicle/sealed/mecha/auto_assign_occupant_flags(mob/occupant)
|
|
if(driver_amount() < max_drivers)
|
|
add_control_flags(occupant, FULL_MECHA_CONTROL)
|
|
|
|
/obj/vehicle/sealed/mecha/generate_actions()
|
|
initialize_passenger_action_type(/datum/action/vehicle/sealed/mecha/mech_eject)
|
|
if(mecha_flags & IS_ENCLOSED)
|
|
initialize_controller_action_type(/datum/action/vehicle/sealed/mecha/mech_toggle_cabin_seal, VEHICLE_CONTROL_SETTINGS)
|
|
if(can_use_overclock && overclock_action_type)
|
|
initialize_passenger_action_type(overclock_action_type)
|
|
initialize_controller_action_type(/datum/action/vehicle/sealed/mecha/mech_toggle_lights, VEHICLE_CONTROL_SETTINGS)
|
|
initialize_controller_action_type(/datum/action/vehicle/sealed/mecha/mech_toggle_safeties, VEHICLE_CONTROL_SETTINGS)
|
|
initialize_controller_action_type(/datum/action/vehicle/sealed/mecha/mech_view_stats, VEHICLE_CONTROL_SETTINGS)
|
|
initialize_controller_action_type(/datum/action/vehicle/sealed/mecha/strafe, VEHICLE_CONTROL_DRIVE)
|
|
|
|
/obj/vehicle/sealed/mecha/add_occupant(mob/M, control_flags, forced)
|
|
. = ..()
|
|
if(.)
|
|
generate_equipment_actions(M)
|
|
|
|
/obj/vehicle/sealed/mecha/remove_occupant(mob/M)
|
|
remove_all_equipment_actions(M)
|
|
return ..()
|
|
|
|
/// Generates action buttons for all eligible equipment and grants them to the occupant with VEHICLE_CONTROL_SETTINGS flag.
|
|
/obj/vehicle/sealed/mecha/proc/generate_equipment_actions(mob/occupant)
|
|
if(!(occupant in occupants) || !(occupants[occupant] & VEHICLE_CONTROL_SETTINGS))
|
|
return
|
|
for(var/obj/item/mecha_parts/mecha_equipment/equipment in flat_equipment)
|
|
if(!is_equipment_valid_for_action(equipment))
|
|
continue
|
|
|
|
grant_equipment_action(occupant, equipment)
|
|
|
|
/// Removes all equipment actions from a specific occupant.
|
|
/obj/vehicle/sealed/mecha/proc/remove_all_equipment_actions(mob/occupant)
|
|
var/list/actions = LAZYACCESS(occupant_actions, occupant)
|
|
if(!actions)
|
|
return
|
|
|
|
for(var/equipment_type in actions)
|
|
if(!ispath(equipment_type, /obj/item/mecha_parts/mecha_equipment))
|
|
continue
|
|
remove_action_type_from_mob(equipment_type, occupant)
|
|
|
|
/**
|
|
* Grants a specific equipment action to an occupant.
|
|
* Creates a new action, sets up the chassis and equipment references, and grants it to the mob.
|
|
*/
|
|
/obj/vehicle/sealed/mecha/proc/grant_equipment_action(mob/occupant, obj/item/mecha_parts/mecha_equipment/equipment)
|
|
var/datum/action/vehicle/sealed/mecha/equipment/action = new equipment.action_type // We cannot use grant_action_type_to_mob() because:
|
|
action.set_chassis(src) // 1. grant_action_type_to_mob() works with a single predefined action type
|
|
action.set_equipment(equipment) // 2. We create unique action instances for each equipment with specific equipment references
|
|
|
|
action.Grant(occupant)
|
|
LAZYINITLIST(occupant_actions[occupant])
|
|
// Use equipment type as actiontype for remove_action_type_from_mob() compatibility
|
|
occupant_actions[occupant][equipment.type] = action
|
|
|
|
/**
|
|
* Called when equipment is attached to the mecha.
|
|
* Grants equipment actions to current occupants with VEHICLE_CONTROL_SETTINGS flag.
|
|
*/
|
|
/obj/vehicle/sealed/mecha/proc/on_equipment_attach(obj/item/mecha_parts/mecha_equipment/equipment)
|
|
if(!is_equipment_valid_for_action(equipment))
|
|
return
|
|
|
|
for(var/mob/occupant in occupants)
|
|
if(!(occupants[occupant] & VEHICLE_CONTROL_SETTINGS))
|
|
continue
|
|
grant_equipment_action(occupant, equipment)
|
|
|
|
/**
|
|
* Called when equipment is detached from the mecha.
|
|
* Removes equipment actions from all current occupants.
|
|
*/
|
|
/obj/vehicle/sealed/mecha/proc/on_equipment_detach(obj/item/mecha_parts/mecha_equipment/equipment)
|
|
for(var/mob/occupant in occupants)
|
|
remove_action_type_from_mob(equipment.type, occupant)
|
|
|
|
/// Create actions only for equipment that can be toggled or triggered, excluding air tanks.
|
|
/obj/vehicle/sealed/mecha/proc/is_equipment_valid_for_action(obj/item/mecha_parts/mecha_equipment/equipment)
|
|
if(!(equipment.can_be_toggled || equipment.can_be_triggered))
|
|
return FALSE
|
|
|
|
if(istype(equipment, /obj/item/mecha_parts/mecha_equipment/air_tank)) // this thing has its own button
|
|
return FALSE
|
|
|
|
return TRUE
|
|
|
|
/obj/vehicle/sealed/mecha/proc/get_mecha_occupancy_state()
|
|
if((mecha_flags & SILICON_PILOT) && silicon_icon_state)
|
|
return silicon_icon_state
|
|
if(LAZYLEN(occupants))
|
|
return base_icon_state
|
|
return "[base_icon_state]-open"
|
|
|
|
/obj/vehicle/sealed/mecha/get_cell()
|
|
return cell
|
|
|
|
/obj/vehicle/sealed/mecha/rust_heretic_act()
|
|
take_damage(500, BRUTE)
|
|
|
|
/obj/vehicle/sealed/mecha/proc/restore_equipment()
|
|
equipment_disabled = FALSE
|
|
for(var/occupant in occupants)
|
|
var/mob/mob_occupant = occupant
|
|
SEND_SOUND(mob_occupant, sound('sound/items/timer.ogg', volume=50))
|
|
to_chat(mob_occupant, span_notice("Equipment control unit has been rebooted successfully."))
|
|
set_mouse_pointer()
|
|
|
|
/// Updates the values given by scanning module and capacitor tier, called when a part is removed or inserted.
|
|
/obj/vehicle/sealed/mecha/proc/update_part_values()
|
|
update_energy_drain()
|
|
|
|
if(capacitor)
|
|
overclock_temp_danger = initial(overclock_temp_danger) * capacitor.rating
|
|
else
|
|
overclock_temp_danger = initial(overclock_temp_danger)
|
|
|
|
/obj/vehicle/sealed/mecha/examine(mob/user)
|
|
. = ..()
|
|
if(LAZYLEN(flat_equipment))
|
|
. += span_notice("It's equipped with:")
|
|
for(var/obj/item/mecha_parts/mecha_equipment/ME as anything in flat_equipment)
|
|
if(istype(ME, /obj/item/mecha_parts/mecha_equipment/concealed_weapon_bay))
|
|
continue
|
|
. += span_notice("[icon2html(ME, user)] \A [ME].")
|
|
if(mecha_flags & PANEL_OPEN)
|
|
if(servo)
|
|
. += span_notice("Servo reduces movement power usage by [100 - round(100 / servo.rating)]%")
|
|
else
|
|
. += span_warning("It's missing a servo.")
|
|
if(capacitor)
|
|
. += span_notice("Capacitor increases armor against energy attacks by [capacitor.rating * 5].")
|
|
else
|
|
. += span_warning("It's missing a capacitor.")
|
|
if(!scanmod)
|
|
. += span_warning("It's missing a scanning module.")
|
|
if(!(mecha_flags & IS_ENCLOSED))
|
|
if(mecha_flags & SILICON_PILOT)
|
|
. += span_notice("[src] appears to be piloting itself...")
|
|
else
|
|
for(var/occupante in occupants)
|
|
. += span_notice("You can see [occupante] inside.")
|
|
if(ishuman(user))
|
|
var/mob/living/carbon/human/H = user
|
|
for(var/held_item in H.held_items)
|
|
if(!isgun(held_item))
|
|
continue
|
|
. += span_warning("It looks like you can hit the pilot directly if you target the center or above.")
|
|
break //in case user is holding two guns
|
|
. += span_notice("It has a <a href='byond://?src=[REF(src)];list_armor=1'>tag</a> listing its protection classes.")
|
|
|
|
/obj/vehicle/sealed/mecha/Topic(href, href_list)
|
|
. = ..()
|
|
|
|
if(href_list["list_armor"])
|
|
var/list/readout = list()
|
|
|
|
var/datum/armor/armor = get_armor()
|
|
var/added_damage_header = FALSE
|
|
for(var/damage_key in ARMOR_LIST_DAMAGE)
|
|
var/rating = armor.get_rating(damage_key)
|
|
if(!rating)
|
|
continue
|
|
if(!added_damage_header)
|
|
readout += "<b><u>ARMOR (I-X)</u></b>"
|
|
added_damage_header = TRUE
|
|
readout += "[armor_to_protection_name(damage_key)] [armor_to_protection_class(rating)]"
|
|
|
|
var/added_durability_header = FALSE
|
|
for(var/durability_key in ARMOR_LIST_DURABILITY)
|
|
var/rating = armor.get_rating(durability_key)
|
|
if(!rating)
|
|
continue
|
|
if(!added_durability_header)
|
|
readout += "<b><u>DURABILITY (I-X)</u></b>"
|
|
added_durability_header = TRUE
|
|
readout += "[armor_to_protection_name(durability_key)] [armor_to_protection_class(rating)]"
|
|
|
|
readout += "It can withstand temperatures up to [max_temperature]K."
|
|
if(mecha_flags & IS_ENCLOSED)
|
|
readout += "It fully encloses its occupants, protecting them from the atmosphere or lack thereof."
|
|
|
|
var/formatted_readout = span_notice("<b>PROTECTION CLASSES</b><hr>[jointext(readout, "\n")]")
|
|
to_chat(usr, boxed_message(formatted_readout))
|
|
|
|
/obj/vehicle/sealed/mecha/generate_integrity_message()
|
|
var/examine_text = ""
|
|
var/integrity = atom_integrity / max_integrity * 100
|
|
|
|
switch(integrity)
|
|
if(85 to 100)
|
|
examine_text = "It's fully intact."
|
|
if(65 to 85)
|
|
examine_text = "It's slightly damaged."
|
|
if(45 to 65)
|
|
examine_text = "It's badly damaged."
|
|
if(25 to 45)
|
|
examine_text = "It's heavily damaged."
|
|
else
|
|
examine_text = "It's falling apart."
|
|
|
|
return examine_text
|
|
|
|
/// Locate an internal tack in the utility modules
|
|
/obj/vehicle/sealed/mecha/proc/get_internal_tank()
|
|
var/obj/item/mecha_parts/mecha_equipment/air_tank/module = locate(/obj/item/mecha_parts/mecha_equipment/air_tank) in equip_by_category[MECHA_UTILITY]
|
|
return module?.internal_tank
|
|
|
|
//processing internal damage, temperature, air regulation, alert updates, lights power use.
|
|
/obj/vehicle/sealed/mecha/process(seconds_per_tick)
|
|
if(overclock_mode || overclock_temp > 0)
|
|
process_overclock_effects(seconds_per_tick)
|
|
if(internal_damage)
|
|
process_internal_damage_effects(seconds_per_tick)
|
|
if(cabin_sealed)
|
|
process_cabin_air(seconds_per_tick)
|
|
if(length(occupants))
|
|
process_occupants(seconds_per_tick)
|
|
process_constant_power_usage(seconds_per_tick)
|
|
//Diagnostic HUD updates
|
|
diag_hud_set_mechhealth()
|
|
diag_hud_set_mechcell()
|
|
diag_hud_set_mechstat()
|
|
|
|
/obj/vehicle/sealed/mecha/proc/process_overclock_effects(seconds_per_tick)
|
|
if(!overclock_mode && overclock_temp > 0)
|
|
overclock_temp -= seconds_per_tick
|
|
return
|
|
var/temp_gain = seconds_per_tick * (1 + 1 / movedelay)
|
|
overclock_temp = min(overclock_temp + temp_gain, overclock_temp_danger * 2)
|
|
if(overclock_temp < overclock_temp_danger)
|
|
return
|
|
if(overclock_temp >= overclock_temp_danger && overclock_safety)
|
|
toggle_overclock(FALSE)
|
|
return
|
|
var/damage_chance = 100 * ((overclock_temp - overclock_temp_danger) / (overclock_temp_danger * 2))
|
|
if(SPT_PROB(damage_chance, seconds_per_tick))
|
|
do_sparks(5, TRUE, src)
|
|
try_deal_internal_damage(damage_chance)
|
|
take_damage(seconds_per_tick, BURN, 0, 0)
|
|
|
|
/obj/vehicle/sealed/mecha/proc/process_internal_damage_effects(seconds_per_tick)
|
|
if(internal_damage & MECHA_INT_FIRE)
|
|
if(!(internal_damage & MECHA_INT_TEMP_CONTROL) && SPT_PROB(2.5, seconds_per_tick))
|
|
clear_internal_damage(MECHA_INT_FIRE)
|
|
if(cabin_air && cabin_sealed && cabin_air.return_volume()>0)
|
|
if(cabin_air.return_pressure() > (PUMP_DEFAULT_PRESSURE * 30) && !(internal_damage & MECHA_CABIN_AIR_BREACH))
|
|
set_internal_damage(MECHA_CABIN_AIR_BREACH)
|
|
cabin_air.temperature = min(6000+T0C, cabin_air.temperature+rand(5,7.5)*seconds_per_tick)
|
|
if(cabin_air.return_temperature() > max_temperature/2)
|
|
take_damage(seconds_per_tick*2/round(max_temperature/cabin_air.return_temperature(),0.1), BURN, 0, 0)
|
|
|
|
if(internal_damage & MECHA_CABIN_AIR_BREACH && cabin_air && cabin_sealed) //remove some air from cabin_air
|
|
var/datum/gas_mixture/leaked_gas = cabin_air.remove_ratio(SPT_PROB_RATE(0.05, seconds_per_tick))
|
|
if(loc)
|
|
loc.assume_air(leaked_gas)
|
|
else
|
|
qdel(leaked_gas)
|
|
|
|
if(internal_damage & MECHA_INT_SHORT_CIRCUIT && get_charge())
|
|
spark_system.start()
|
|
var/damage_energy_consumption = 0.005 * STANDARD_CELL_CHARGE * seconds_per_tick
|
|
use_energy(damage_energy_consumption)
|
|
cell.maxcharge -= min(damage_energy_consumption, cell.maxcharge)
|
|
|
|
/obj/vehicle/sealed/mecha/proc/process_cabin_air(seconds_per_tick)
|
|
if(!(internal_damage & MECHA_INT_TEMP_CONTROL) && cabin_air && cabin_air.return_volume() > 0)
|
|
var/heat_capacity = cabin_air.heat_capacity()
|
|
var/required_energy = abs(T20C - cabin_air.temperature) * heat_capacity
|
|
required_energy = min(required_energy, 1000)
|
|
if(required_energy < 1)
|
|
return
|
|
var/delta_temperature = required_energy / heat_capacity
|
|
if(delta_temperature)
|
|
if(cabin_air.temperature < T20C)
|
|
cabin_air.temperature += delta_temperature
|
|
else
|
|
cabin_air.temperature -= delta_temperature
|
|
|
|
/obj/vehicle/sealed/mecha/proc/process_occupants(seconds_per_tick)
|
|
for(var/mob/living/occupant as anything in occupants)
|
|
if(!(mecha_flags & IS_ENCLOSED) && occupant?.incapacitated) //no sides mean it's easy to just sorta fall out if you're incapacitated.
|
|
mob_exit(occupant, randomstep = TRUE) //bye bye
|
|
continue
|
|
if(cell && cell.maxcharge)
|
|
var/cellcharge = cell.charge/cell.maxcharge
|
|
switch(cellcharge)
|
|
if(0.75 to INFINITY)
|
|
occupant.clear_alert(ALERT_CHARGE)
|
|
if(0.5 to 0.75)
|
|
occupant.throw_alert(ALERT_CHARGE, /atom/movable/screen/alert/lowcell/mech, 1)
|
|
if(0.25 to 0.5)
|
|
occupant.throw_alert(ALERT_CHARGE, /atom/movable/screen/alert/lowcell/mech, 2)
|
|
if(0.01 to 0.25)
|
|
occupant.throw_alert(ALERT_CHARGE, /atom/movable/screen/alert/lowcell/mech, 3)
|
|
else
|
|
occupant.throw_alert(ALERT_CHARGE, /atom/movable/screen/alert/emptycell/mech)
|
|
else
|
|
occupant.throw_alert(ALERT_CHARGE, /atom/movable/screen/alert/nocell)
|
|
var/integrity = atom_integrity/max_integrity*100
|
|
switch(integrity)
|
|
if(30 to 45)
|
|
occupant.throw_alert(ALERT_MECH_DAMAGE, /atom/movable/screen/alert/low_mech_integrity, 1)
|
|
if(15 to 35)
|
|
occupant.throw_alert(ALERT_MECH_DAMAGE, /atom/movable/screen/alert/low_mech_integrity, 2)
|
|
if(-INFINITY to 15)
|
|
occupant.throw_alert(ALERT_MECH_DAMAGE, /atom/movable/screen/alert/low_mech_integrity, 3)
|
|
else
|
|
occupant.clear_alert(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(ALERT_CHARGE)
|
|
occupant.clear_alert(ALERT_MECH_DAMAGE)
|
|
occupant = null
|
|
break
|
|
else if (checking == src)
|
|
break // all good
|
|
checking = checking.loc
|
|
|
|
/obj/vehicle/sealed/mecha/proc/process_constant_power_usage(seconds_per_tick)
|
|
if(mecha_flags & LIGHTS_ON && !use_energy(light_power_drain * seconds_per_tick))
|
|
mecha_flags &= ~LIGHTS_ON
|
|
set_light_on(mecha_flags & LIGHTS_ON)
|
|
playsound(src,'sound/machines/clockcult/brass_skewer.ogg', 40, TRUE)
|
|
log_message("Toggled lights off due to the lack of power.", LOG_MECHA)
|
|
|
|
/// Called when a driver clicks somewhere. Handles everything like equipment, punches, etc.
|
|
/obj/vehicle/sealed/mecha/proc/on_mouseclick(mob/user, atom/target, list/modifiers)
|
|
SIGNAL_HANDLER
|
|
if(LAZYACCESS(modifiers, MIDDLE_CLICK))
|
|
set_safety(user)
|
|
return COMSIG_MOB_CANCEL_CLICKON
|
|
if(weapons_safety)
|
|
return
|
|
if(isAI(user)) //For AIs: If safeties are off, use mech functions. If safeties are on, use AI functions.
|
|
. = COMSIG_MOB_CANCEL_CLICKON
|
|
if(modifiers[SHIFT_CLICK]) //Allows things to be examined.
|
|
return
|
|
if(!isturf(target) && !isturf(target.loc)) // Prevents inventory from being drilled
|
|
return
|
|
if(!can_interact_with(target, user, modifiers))
|
|
return
|
|
if(src == target)
|
|
return
|
|
var/dir_to_target = get_dir(src,target)
|
|
if(!(mecha_flags & OMNIDIRECTIONAL_ATTACKS) && dir_to_target && !(dir_to_target & dir))//wrong direction
|
|
return
|
|
if(internal_damage & MECHA_INT_CONTROL_LOST)
|
|
target = pick(view(3,target))
|
|
var/mob/living/livinguser = user
|
|
if(!(livinguser in return_controllers_with_flag(VEHICLE_CONTROL_EQUIPMENT)))
|
|
balloon_alert(user, "wrong seat for equipment!")
|
|
return
|
|
var/obj/item/mecha_parts/mecha_equipment/selected
|
|
if(modifiers[BUTTON] == RIGHT_CLICK)
|
|
selected = equip_by_category[MECHA_R_ARM]
|
|
else
|
|
selected = equip_by_category[MECHA_L_ARM]
|
|
if(selected)
|
|
if(!Adjacent(target) && (selected.range & MECHA_RANGED))
|
|
if(HAS_TRAIT(livinguser, TRAIT_PACIFISM) && selected.harmful)
|
|
to_chat(livinguser, span_warning("You don't want to harm other living beings!"))
|
|
return
|
|
if(SEND_SIGNAL(src, COMSIG_MECHA_EQUIPMENT_CLICK, livinguser, target) & COMPONENT_CANCEL_EQUIPMENT_CLICK)
|
|
return
|
|
INVOKE_ASYNC(selected, TYPE_PROC_REF(/obj/item/mecha_parts/mecha_equipment, action), user, target, modifiers)
|
|
return
|
|
if(Adjacent(target) && (selected.range & MECHA_MELEE))
|
|
if(isliving(target) && selected.harmful && HAS_TRAIT(livinguser, TRAIT_PACIFISM))
|
|
to_chat(livinguser, span_warning("You don't want to harm other living beings!"))
|
|
return
|
|
if(SEND_SIGNAL(src, COMSIG_MECHA_EQUIPMENT_CLICK, livinguser, target) & COMPONENT_CANCEL_EQUIPMENT_CLICK)
|
|
return
|
|
INVOKE_ASYNC(selected, TYPE_PROC_REF(/obj/item/mecha_parts/mecha_equipment, action), user, target, modifiers)
|
|
return
|
|
if(!(livinguser in return_controllers_with_flag(VEHICLE_CONTROL_MELEE)))
|
|
to_chat(livinguser, span_warning("You're in the wrong seat to interact with your hands."))
|
|
return
|
|
var/on_cooldown = TIMER_COOLDOWN_RUNNING(src, COOLDOWN_MECHA_MELEE_ATTACK)
|
|
var/adjacent = Adjacent(target)
|
|
if(SEND_SIGNAL(src, COMSIG_MECHA_MELEE_CLICK, livinguser, target, on_cooldown, adjacent) & COMPONENT_CANCEL_MELEE_CLICK)
|
|
return
|
|
if(on_cooldown || !adjacent)
|
|
return
|
|
if(internal_damage & MECHA_INT_CONTROL_LOST)
|
|
target = pick(oview(1,src))
|
|
|
|
if(!has_charge(melee_energy_drain))
|
|
return
|
|
use_energy(melee_energy_drain)
|
|
|
|
SEND_SIGNAL(user, COMSIG_MOB_USED_CLICK_MECH_MELEE, src)
|
|
if(target.mech_melee_attack(src, user))
|
|
TIMER_COOLDOWN_START(src, COOLDOWN_MECHA_MELEE_ATTACK, melee_cooldown)
|
|
|
|
/obj/vehicle/sealed/mecha/proc/can_interact_with(atom/target, mob/user, list/modifiers)
|
|
if(completely_disabled || is_currently_ejecting || (mecha_flags & CANNOT_INTERACT))
|
|
return FALSE
|
|
if(user.incapacitated)
|
|
return FALSE
|
|
if(!get_charge())
|
|
return FALSE
|
|
return TRUE
|
|
|
|
/// Driver alt clicks anything while in mech
|
|
/obj/vehicle/sealed/mecha/proc/on_click_alt(mob/user, atom/target, params)
|
|
SIGNAL_HANDLER
|
|
|
|
. = COMSIG_MOB_CANCEL_CLICKON // Cancel base_click_alt
|
|
|
|
if(target != src)
|
|
return
|
|
|
|
if(!(user in occupants))
|
|
return
|
|
|
|
if(!(user in return_controllers_with_flag(VEHICLE_CONTROL_DRIVE)))
|
|
to_chat(user, span_warning("You're in the wrong seat to control movement."))
|
|
return
|
|
|
|
toggle_strafe()
|
|
|
|
|
|
/// Middle mouse click signal wrapper for AI users
|
|
/obj/vehicle/sealed/mecha/proc/on_middlemouseclick(mob/user, atom/target, params)
|
|
SIGNAL_HANDLER
|
|
if(isAI(user))
|
|
on_mouseclick(user, target, params)
|
|
|
|
/// Displays a special speech bubble when someone inside the mecha speaks
|
|
/obj/vehicle/sealed/mecha/proc/display_speech_bubble(datum/source, list/speech_args)
|
|
SIGNAL_HANDLER
|
|
var/list/speech_bubble_recipients = list()
|
|
for(var/mob/listener in get_hearers_in_view(7, src))
|
|
if(listener.client)
|
|
speech_bubble_recipients += listener.client
|
|
|
|
var/image/mech_speech = image('icons/mob/effects/talk.dmi', src, "machine[say_test(speech_args[SPEECH_MESSAGE])]",MOB_LAYER+1)
|
|
INVOKE_ASYNC(GLOBAL_PROC, GLOBAL_PROC_REF(flick_overlay_global), mech_speech, speech_bubble_recipients, 3 SECONDS)
|
|
|
|
// Atmospheric stuff
|
|
/obj/vehicle/sealed/mecha/remove_air(amount)
|
|
if((mecha_flags & IS_ENCLOSED) && cabin_sealed)
|
|
return cabin_air.remove(amount)
|
|
return ..()
|
|
|
|
/obj/vehicle/sealed/mecha/return_air()
|
|
if((mecha_flags & IS_ENCLOSED) && cabin_sealed)
|
|
return cabin_air
|
|
return ..()
|
|
|
|
/obj/vehicle/sealed/mecha/return_analyzable_air()
|
|
return cabin_air
|
|
|
|
/// Fetches pressure of the gas mixture we are using
|
|
/obj/vehicle/sealed/mecha/proc/return_pressure()
|
|
var/datum/gas_mixture/air = return_air()
|
|
return air?.return_pressure()
|
|
|
|
/// Fetches temp of the gas mixture we are using
|
|
/obj/vehicle/sealed/mecha/return_temperature()
|
|
var/datum/gas_mixture/air = return_air()
|
|
return air?.return_temperature()
|
|
|
|
/// Makes cabin unsealed, dumping cabin air outside or airtight filling the cabin with external air mix
|
|
/obj/vehicle/sealed/mecha/proc/set_cabin_seal(mob/user, cabin_sealed)
|
|
if(!(mecha_flags & IS_ENCLOSED))
|
|
balloon_alert(user, "cabin can't be sealed!")
|
|
log_message("Tried to seal cabin. This mech can't be airtight.", LOG_MECHA)
|
|
return
|
|
if(TIMER_COOLDOWN_RUNNING(src, COOLDOWN_MECHA_CABIN_SEAL))
|
|
balloon_alert(user, "on cooldown!")
|
|
return
|
|
TIMER_COOLDOWN_START(src, COOLDOWN_MECHA_CABIN_SEAL, 1 SECONDS)
|
|
|
|
src.cabin_sealed = cabin_sealed
|
|
|
|
var/datum/gas_mixture/environment_air = loc.return_air()
|
|
if(!isnull(environment_air))
|
|
if(cabin_sealed)
|
|
// Fill cabin with air
|
|
environment_air.pump_gas_to(cabin_air, environment_air.return_pressure())
|
|
else
|
|
// Dump cabin air
|
|
var/datum/gas_mixture/removed_gases = cabin_air.remove_ratio(1)
|
|
if(loc)
|
|
loc.assume_air(removed_gases)
|
|
else
|
|
qdel(removed_gases)
|
|
|
|
var/obj/item/mecha_parts/mecha_equipment/air_tank/tank = locate(/obj/item/mecha_parts/mecha_equipment/air_tank) in equip_by_category[MECHA_UTILITY]
|
|
for(var/mob/occupant as anything in occupants)
|
|
var/datum/action/action = locate(/datum/action/vehicle/sealed/mecha/mech_toggle_cabin_seal) in occupant.actions
|
|
if(!isnull(tank) && cabin_sealed && tank.auto_pressurize_on_seal)
|
|
if(!tank.active)
|
|
tank.set_active(TRUE)
|
|
else
|
|
action.button_icon_state = "mech_cabin_pressurized"
|
|
action.build_all_button_icons()
|
|
else
|
|
action.button_icon_state = "mech_cabin_[cabin_sealed ? "closed" : "open"]"
|
|
action.build_all_button_icons()
|
|
|
|
balloon_alert(occupant, "cabin [cabin_sealed ? "sealed" : "unsealed"]")
|
|
log_message("Cabin [cabin_sealed ? "sealed" : "unsealed"].", LOG_MECHA)
|
|
playsound(src, 'sound/machines/airlock/airlock.ogg', 50, TRUE)
|
|
|
|
/// Special light eater handling
|
|
/obj/vehicle/sealed/mecha/proc/on_light_eater(obj/vehicle/sealed/source, datum/light_eater)
|
|
SIGNAL_HANDLER
|
|
if(mecha_flags & HAS_LIGHTS)
|
|
visible_message(span_danger("[src]'s lights burn out!"))
|
|
mecha_flags &= ~HAS_LIGHTS
|
|
set_light_on(FALSE)
|
|
for(var/occupant in occupants)
|
|
remove_action_type_from_mob(/datum/action/vehicle/sealed/mecha/mech_toggle_lights, occupant)
|
|
return COMPONENT_BLOCK_LIGHT_EATER
|
|
|
|
/obj/vehicle/sealed/mecha/on_saboteur(datum/source, disrupt_duration)
|
|
. = ..()
|
|
if((mecha_flags & HAS_LIGHTS) && light_on)
|
|
set_light_on(FALSE)
|
|
return TRUE
|
|
|
|
/// Apply corresponding accesses
|
|
/obj/vehicle/sealed/mecha/proc/update_access()
|
|
req_access = one_access ? list() : accesses
|
|
req_one_access = one_access ? accesses : list()
|
|
|
|
/// Electrocute user from power celll
|
|
/obj/vehicle/sealed/mecha/shock(mob/living/shocking, chance = 100, shock_source, siemens_coeff)
|
|
if(get_charge() < 1)
|
|
return FALSE
|
|
if(isnull(siemens_coeff))
|
|
siemens_coeff = 0.7
|
|
return ..()
|
|
|
|
/// Toggle mech overclock with a button or by hacking
|
|
/obj/vehicle/sealed/mecha/proc/toggle_overclock(forced_state = null)
|
|
if(!isnull(forced_state))
|
|
if(overclock_mode == forced_state)
|
|
return FALSE
|
|
overclock_mode = forced_state
|
|
else
|
|
overclock_mode = !overclock_mode
|
|
|
|
log_message("Toggled [overclock_name].", LOG_MECHA)
|
|
for(var/mob/occupant as anything in occupants)
|
|
balloon_alert(occupant, "[overclock_name] [overclock_mode ? "on":"off"]")
|
|
var/datum/action/act = locate(/datum/action/vehicle/sealed/mecha/mech_overclock) in occupant.actions
|
|
act?.build_all_button_icons(UPDATE_BUTTON_ICON)
|
|
|
|
if(overclock_mode)
|
|
movedelay /= overclock_coeff
|
|
visible_message(span_notice("[src] starts heating up, making humming sounds."))
|
|
else
|
|
movedelay *= overclock_coeff
|
|
visible_message(span_notice("[src] cools down and the humming stops."))
|
|
update_energy_drain()
|
|
return TRUE
|
|
|
|
/// Update the energy drain according to parts and status
|
|
/obj/vehicle/sealed/mecha/proc/update_energy_drain()
|
|
if(servo)
|
|
step_energy_drain = initial(step_energy_drain) / servo.rating
|
|
else
|
|
step_energy_drain = 2 * initial(step_energy_drain)
|
|
|
|
if(overclock_mode)
|
|
step_energy_drain *= overclock_coeff
|
|
|
|
if(capacitor)
|
|
melee_energy_drain = initial(melee_energy_drain) / capacitor.rating
|
|
light_power_drain = initial(light_power_drain) / capacitor.rating
|
|
else
|
|
melee_energy_drain = initial(melee_energy_drain)
|
|
light_power_drain = initial(light_power_drain)
|
|
|
|
/// Toggle lights on/off
|
|
/obj/vehicle/sealed/mecha/proc/toggle_lights(forced_state = null, mob/user)
|
|
if(!(mecha_flags & HAS_LIGHTS))
|
|
if(user)
|
|
balloon_alert(user, "mech has no lights!")
|
|
return
|
|
if((!(mecha_flags & LIGHTS_ON) && forced_state != FALSE) && get_charge() < power_to_energy(light_power_drain, scheduler = SSobj))
|
|
if(user)
|
|
balloon_alert(user, "no power for lights!")
|
|
return
|
|
mecha_flags ^= LIGHTS_ON
|
|
set_light_on(mecha_flags & LIGHTS_ON)
|
|
playsound(src,'sound/machines/clockcult/brass_skewer.ogg', 40, TRUE)
|
|
log_message("Toggled lights [(mecha_flags & LIGHTS_ON)?"on":"off"].", LOG_MECHA)
|
|
for(var/mob/occupant as anything in occupants)
|
|
var/datum/action/act = locate(/datum/action/vehicle/sealed/mecha/mech_toggle_lights) in occupant.actions
|
|
if(mecha_flags & LIGHTS_ON)
|
|
act.button_icon_state = "mech_lights_on"
|
|
else
|
|
act.button_icon_state = "mech_lights_off"
|
|
balloon_alert(occupant, "lights [mecha_flags & LIGHTS_ON ? "on":"off"]")
|
|
act.build_all_button_icons()
|
|
|
|
/obj/vehicle/sealed/mecha/proc/melee_attack_effect(mob/living/victim, heavy)
|
|
if(heavy)
|
|
victim.Unconscious(2 SECONDS)
|
|
else
|
|
victim.Knockdown(4 SECONDS)
|