/***************** 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 ///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 ///Handles an internal ore box for mining mechs 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 var/datum/effect_system/fluid_spread/smoke/smoke_system ////Action vars ///Ref to any active thrusters we might have var/obj/item/mecha_parts/mecha_equipment/thrusters/active_thrusters ///Bool for energy shield on/off var/defense_mode = FALSE ///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" //Bool for zoom on/off var/zoom_mode = FALSE ///Remaining smoke charges var/smoke_charges = 5 ///Cooldown between using smoke var/smoke_cooldown = 10 SECONDS ///check for phasing, if it is set to text (to describe how it is phasing: "flying", "phasing") it will let the mech walk through walls. var/phasing = "" ///Power we use every time we phaze through something var/phasing_energy_drain = 0.2 * STANDARD_CELL_CHARGE ///icon_state for flick() when phazing var/phase_state = "" ///Wether we are strafing var/strafe = FALSE ///Bool for whether this mech can only be used on lavaland var/lavaland_only = 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 /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) smoke_system = new(src, 3, holder = 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(smoke_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)) //FIXME: Nothiing about this block works 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 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 else mob_exit(ai, silent = TRUE, forced = TRUE) // so we dont ghost the AI continue else mob_exit(occupant, forced = TRUE) if(!isbrain(occupant)) // who would win.. 1 brain vs 1 sleep proc.. occupant.SetSleeping(destruction_sleep_duration) if(wreckage) var/obj/structure/mecha_wreckage/WR = new wreckage(loc, unlucky_ai) for(var/obj/item/mecha_parts/mecha_equipment/E in flat_equipment) if(E.detachable && prob(30)) WR.crowbar_salvage += E E.detach(WR) //detaches from src into WR E.active = TRUE else E.detach(loc) qdel(E) if(cell) WR.crowbar_salvage += cell cell.forceMove(WR) cell.use(rand(0, cell.charge), TRUE) cell = null return ..() /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/M) if(driver_amount() < max_drivers) add_control_flags(M, 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/CanPassThrough(atom/blocker, movement_dir, blocker_opinion) if(!phasing || get_charge() <= phasing_energy_drain || throwing) return ..() if(phase_state) flick(phase_state, src) var/turf/destination_turf = get_step(loc, movement_dir) if(!check_teleport_valid(src, destination_turf) || SSmapping.level_trait(destination_turf.z, ZTRAIT_NOPHASE)) return FALSE return TRUE /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() /obj/vehicle/sealed/mecha/proc/update_part_values() ///Updates the values given by scanning module and capacitor tier, called when a part is removed or inserted. 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 tag 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 += "ARMOR (I-X)" 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 += "DURABILITY (I-X)" 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("PROTECTION CLASSES
[jointext(readout, "\n")]") to_chat(usr, boxed_message(formatted_readout)) /obj/vehicle/sealed/mecha/generate_integrity_message() var/examine_text = "" var/integrity = atom_integrity*100/max_integrity 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(completely_disabled || is_currently_ejecting || (mecha_flags & CANNOT_INTERACT)) return if(phasing) balloon_alert(user, "not while [phasing]!") return if(user.incapacitated) return if(!get_charge()) 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) /// 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 = movedelay / overclock_coeff visible_message(span_notice("[src] starts heating up, making humming sounds.")) else movedelay = initial(movedelay) 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) phasing_energy_drain = initial(phasing_energy_drain) / capacitor.rating melee_energy_drain = initial(melee_energy_drain) / capacitor.rating light_power_drain = initial(light_power_drain) / capacitor.rating else phasing_energy_drain = initial(phasing_energy_drain) 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)