diff --git a/code/modules/vehicles/mecha/_mecha.dm b/code/modules/vehicles/mecha/_mecha.dm index 2d046b2596a..2e98a96fe66 100644 --- a/code/modules/vehicles/mecha/_mecha.dm +++ b/code/modules/vehicles/mecha/_mecha.dm @@ -37,15 +37,15 @@ 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 + /// 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 + /// 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 + /// Power we use to have the lights on var/light_power_drain = 0.002 * STANDARD_CELL_RATE - ///Modifiers for directional damage reduction + /// 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) + /// 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 @@ -55,59 +55,59 @@ 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 + /// 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 + /// Spark effects are handled by this datum var/datum/effect_system/basic/spark_spread/spark_system - ///How powerful our lights are + /// How powerful our lights are var/lights_power = 6 - ///Just stop the mech from doing anything + /// Just stop the mech from doing anything var/completely_disabled = FALSE - ///Whether this mech is allowed to move diagonally + /// 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. + /// 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. + /// Whether or not the mech destroys walls by running into it. var/bumpsmash = FALSE - ///////////ATMOS - ///Whether the cabin exchanges gases with the environment + // Atmos stuff + /// Whether the cabin exchanges gases with the environment var/cabin_sealed = FALSE - ///Internal air mix datum + /// Internal air mix datum var/datum/gas_mixture/cabin_air - ///Volume of the cabin + /// Volume of the cabin var/cabin_volume = TANK_STANDARD_VOLUME * 3 - ///List of installed remote tracking beacons, including AI control beacons + /// List of installed remote tracking beacons, including AI control beacons var/list/trackers = list() - ///Camera installed into the mech + /// Camera installed into the mech var/obj/machinery/camera/exosuit/chassis_camera var/max_temperature = 25000 - ///Bitflags for internal damage + /// 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 + /// 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 + /// 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 + /// 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 + /// 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 + /// 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 + /// Assoc list: key-typepathlist before init, key-equipmentlist after var/list/equip_by_category = list( MECHA_L_ARM = null, MECHA_R_ARM = null, @@ -115,7 +115,7 @@ MECHA_POWER = list(), MECHA_ARMOR = list(), ) - ///assoc list: max equips for modules key-count + /// Assoc list: max equips for modules key-count var/list/max_equip_by_category = list( MECHA_L_ARM = 1, MECHA_R_ARM = 1, @@ -123,94 +123,71 @@ MECHA_POWER = 1, MECHA_ARMOR = 0, ) - ///flat equipment for iteration + /// Flat equipment for iteration var/list/flat_equipment - ///Handles an internal ore box for mining mechs + /// 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 + /// Sound played when the mech moves var/stepsound = 'sound/vehicles/mecha/mechstep.ogg' - ///Sound played when the mech walks + /// Sound played when the mech walks var/turnsound = 'sound/vehicles/mecha/mechturn.ogg' - ///Sounds for types of melee attack + /// 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 + /// Sound on wall destroying var/destroy_wall_sound = 'sound/effects/meteorimpact.ogg' - ///Melee attack verb + /// Melee attack verb var/list/attack_verbs = list("hit", "hits", "hitting") - ///Cooldown duration between melee punches + /// Cooldown duration between melee punches var/melee_cooldown = CLICK_CD_SLOW - ///TIme taken to leave the mech + /// 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 + /// 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) + /// 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 + /// 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. + /// 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. + /// 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 + // 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 + /// Bool for leg overload on/off VAR_FINAL/overclock_mode = FALSE - ///Whether it is possible to toggle overclocking from the cabin + /// Whether it is possible to toggle overclocking from the cabin var/can_use_overclock = FALSE - ///Speed and energy usage modifier for leg overload + /// Speed and energy usage modifier for leg overload var/overclock_coeff = 1.5 - ///Current leg actuator temperature. Increases when overloaded, decreases when not. + /// Current leg actuator temperature. Increases when overloaded, decreases when not. var/overclock_temp = 0 - ///Temperature threshold at which actuators may start causing internal damage + /// 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 + /// Whether the mech has an option to enable safe overclocking var/overclock_safety_available = FALSE - ///Whether the overclocking turns off automatically when overheated + /// 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 + /// Whether 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 + /// 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 @@ -234,7 +211,6 @@ spark_system = new(src, 2, FALSE) spark_system.attach(src) - smoke_system = new(src, 3, holder = src) cabin_air = new(cabin_volume) @@ -274,9 +250,9 @@ 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 + // 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) @@ -297,7 +273,6 @@ 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) @@ -307,7 +282,7 @@ diag_hud.remove_atom_from_hud(src) //YEET return ..() -///Add parts on mech spawning. Skipped in manual construction. +/// 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) @@ -329,37 +304,42 @@ 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 + 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 - 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 ..() + 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() @@ -400,9 +380,9 @@ 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) +/obj/vehicle/sealed/mecha/auto_assign_occupant_flags(mob/occupant) if(driver_amount() < max_drivers) - add_control_flags(M, FULL_MECHA_CONTROL) + add_control_flags(occupant, FULL_MECHA_CONTROL) /obj/vehicle/sealed/mecha/generate_actions() initialize_passenger_action_type(/datum/action/vehicle/sealed/mecha/mech_eject) @@ -416,14 +396,15 @@ 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(..()) + . = ..() + 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. +/// 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 @@ -433,7 +414,7 @@ grant_equipment_action(occupant, equipment) -///Removes all equipment actions from a specific occupant. +/// 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) @@ -442,7 +423,6 @@ 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) /** @@ -497,16 +477,6 @@ 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 @@ -521,7 +491,8 @@ 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. +/// 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) @@ -599,7 +570,7 @@ /obj/vehicle/sealed/mecha/generate_integrity_message() var/examine_text = "" - var/integrity = atom_integrity*100/max_integrity + var/integrity = atom_integrity / max_integrity * 100 switch(integrity) if(85 to 100) @@ -615,7 +586,7 @@ return examine_text -///Locate an internal tack in the utility modules +/// 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 @@ -742,7 +713,7 @@ 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. +/// 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)) @@ -756,14 +727,7 @@ 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()) + if(can_interact_with(target, user, modifiers)) return if(src == target) return @@ -818,6 +782,15 @@ 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 @@ -837,13 +810,13 @@ toggle_strafe() -/// middle mouse click signal wrapper for AI users +/// 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 +/// 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() @@ -854,10 +827,7 @@ 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 //////// -///////////////////////////////////// - +// Atmospheric stuff /obj/vehicle/sealed/mecha/remove_air(amount) if((mecha_flags & IS_ENCLOSED) && cabin_sealed) return cabin_air.remove(amount) @@ -871,17 +841,17 @@ /obj/vehicle/sealed/mecha/return_analyzable_air() return cabin_air -///fetches pressure of the gas mixture we are using +/// 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 +/// 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 +/// 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!") @@ -962,16 +932,18 @@ 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 + movedelay /= overclock_coeff visible_message(span_notice("[src] starts heating up, making humming sounds.")) else - movedelay = initial(movedelay) + movedelay *= overclock_coeff visible_message(span_notice("[src] cools down and the humming stops.")) update_energy_drain() return TRUE @@ -982,15 +954,14 @@ 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) diff --git a/code/modules/vehicles/mecha/combat/durand.dm b/code/modules/vehicles/mecha/combat/durand.dm index 99f32ddc987..05641719ee0 100644 --- a/code/modules/vehicles/mecha/combat/durand.dm +++ b/code/modules/vehicles/mecha/combat/durand.dm @@ -20,7 +20,13 @@ MECHA_POWER = 1, MECHA_ARMOR = 1, ) - var/obj/durand_shield/shield + + /// Bool for energy shield on/off + var/defense_mode = FALSE + /// Fake shield object we use as a way to redirect attacks off ourselves + var/obj/durand_shield/shield = null + /// Is the shield currently switching modes? + var/switching = FALSE /datum/armor/mecha_durand melee = 40 @@ -33,137 +39,145 @@ /obj/vehicle/sealed/mecha/durand/Initialize(mapload) . = ..() - shield = new /obj/durand_shield(loc, src, plane, layer, dir) - RegisterSignal(src, COMSIG_MECHA_ACTION_TRIGGER, PROC_REF(relay)) + shield = new(src, src) + vis_contents += shield /obj/vehicle/sealed/mecha/durand/Destroy() if(shield) QDEL_NULL(shield) return ..() - /obj/vehicle/sealed/mecha/durand/generate_actions() . = ..() initialize_passenger_action_type(/datum/action/vehicle/sealed/mecha/mech_defense_mode) /obj/vehicle/sealed/mecha/durand/process() . = ..() - if(defense_mode && !use_energy(0.01 * STANDARD_CELL_CHARGE)) //Defence mode can only be on with a occupant so we check if one of them can toggle it and toggle - for(var/O in occupants) - var/mob/living/occupant = O - var/datum/action/action = LAZYACCESSASSOC(occupant_actions, occupant, /datum/action/vehicle/sealed/mecha/mech_defense_mode) - if(action) - action.Trigger() - break - -/obj/vehicle/sealed/mecha/durand/Move(direction) - . = ..() - if(shield) - shield.forceMove(loc) - shield.setDir(dir) - -/obj/vehicle/sealed/mecha/durand/forceMove(turf/T) - . = ..() - shield.forceMove(T) + // Defence mode can only be on with a occupant so we check if one of them can toggle it and toggle + if(defense_mode && !use_energy(0.01 * STANDARD_CELL_CHARGE)) + toggle_defense() /obj/vehicle/sealed/mecha/durand/mob_exit(mob/M, silent = FALSE, randomstep = FALSE, forced = FALSE) if(defense_mode) - var/datum/action/action = LAZYACCESSASSOC(occupant_actions, M, /datum/action/vehicle/sealed/mecha/mech_defense_mode) - if(action) - INVOKE_ASYNC(action, TYPE_PROC_REF(/datum/action, Trigger), null, NONE, FALSE) + toggle_defense() return ..() -///Relays the signal from the action button to the shield, and creates a new shield if the old one is MIA. -/obj/vehicle/sealed/mecha/durand/proc/relay(datum/source, mob/owner, list/signal_args) - SIGNAL_HANDLER - if(!shield) //if the shield somehow got deleted - stack_trace("Durand triggered relay without a shield") - shield = new /obj/durand_shield(loc, src, layer) - shield.setDir(dir) - //Redirects projectiles to the shield if defense_check decides they should be blocked and returns true. /obj/vehicle/sealed/mecha/durand/projectile_hit(obj/projectile/hitting_projectile, def_zone, piercing_hit, blocked) if(defense_check(hitting_projectile.loc) && shield) return shield.projectile_hit(hitting_projectile, def_zone, piercing_hit, blocked) return ..() -/**Checks if defense mode is enabled, and if the attacker is standing in an area covered by the shield. -Expects a turf. Returns true if the attack should be blocked, false if not.*/ +/** + * Checks if defense mode is enabled, and if the attacker is standing in an area covered by the shield. + * Expects a turf. Returns true if the attack should be blocked, false if not. + **/ /obj/vehicle/sealed/mecha/durand/proc/defense_check(turf/aloc) - if (!defense_mode || !shield || shield.switching) + if (!defense_mode || !shield || switching) return FALSE - . = FALSE + switch(dir) - if (1) - if(abs(x - aloc.x) <= (y - aloc.y) * -2) - . = TRUE - if (2) - if(abs(x - aloc.x) <= (y - aloc.y) * 2) - . = TRUE - if (4) - if(abs(y - aloc.y) <= (x - aloc.x) * -2) - . = TRUE - if (8) - if(abs(y - aloc.y) <= (x - aloc.x) * 2) - . = TRUE - return + if (NORTH) + return abs(x - aloc.x) <= (y - aloc.y) * -2 + if (SOUTH) + return abs(x - aloc.x) <= (y - aloc.y) * 2 + if (EAST) + return abs(y - aloc.y) <= (x - aloc.x) * -2 + if (WEST) + return abs(y - aloc.y) <= (x - aloc.x) * 2 + +/obj/vehicle/sealed/mecha/durand/proc/toggle_defense(mob/living/user) + if(!LAZYLEN(occupants)) + return + + if(switching && (user || !defense_mode)) // Allow force shutdowns during animation + return + + if(!defense_mode && cell?.charge < 100) // If it's off, and we have less than 100 units of power + if (user) + balloon_alert(user, "insufficient power") + return + + switching = TRUE + defense_mode = !defense_mode + + if(user) + balloon_alert(user, "shield [defense_mode ? "enabled" : "disabled"]") + log_message("User has toggled defense mode -- now [defense_mode ? "enabled" : "disabled"].", LOG_MECHA) + else + log_message("defense mode state changed -- now [defense_mode ? "enabled" : "disabled"].", LOG_MECHA) + + for(var/mob/living/occupant as anything in occupants) + var/datum/action/button = occupant_actions[occupant][/datum/action/vehicle/sealed/mecha/mech_defense_mode] + button.button_icon_state = "mech_defense_mode_[defense_mode ? "on" : "off"]" + button.build_all_button_icons() + + shield.set_light_on(defense_mode) + + if(defense_mode) + shield.icon_state = "shield" + flick("shield_raise", shield) + playsound(src, 'sound/vehicles/mecha/mech_shield_raise.ogg', 50, FALSE) + else + shield.icon_state = "shield_null" + flick("shield_drop", shield) + playsound(src, 'sound/vehicles/mecha/mech_shield_drop.ogg', 50, FALSE) + + addtimer(VARSET_CALLBACK(src, switching, FALSE), 0.7 SECONDS) // Shield animation length /obj/vehicle/sealed/mecha/durand/attack_generic(mob/user, damage_amount = 0, damage_type = BRUTE, damage_flag = 0, sound_effect = 1, armor_penetration = 0) - if(defense_check(user.loc)) + if(defense_check(get_turf(user))) log_message("Attack absorbed by defense field. Attacker - [user].", LOG_MECHA, color="orange") return shield.attack_generic(user, damage_amount, damage_type, damage_flag, sound_effect, armor_penetration) return ..() -/obj/vehicle/sealed/mecha/durand/blob_act(obj/structure/blob/B) - if(defense_check(B.loc)) - log_message("Attack by blob. Attacker - [B].", LOG_MECHA, color="red") - log_message("Attack absorbed by defense field.", LOG_MECHA, color="orange") - shield.blob_act(B) - else - . = ..() +/obj/vehicle/sealed/mecha/durand/blob_act(obj/structure/blob/blob) + if(!defense_check(get_turf(blob))) + return ..() + log_message("Attack by blob. Attacker - [blob].", LOG_MECHA, color="red") + log_message("Attack absorbed by defense field.", LOG_MECHA, color="orange") + return shield.blob_act(blob) -/obj/vehicle/sealed/mecha/durand/attackby(obj/item/W as obj, mob/user as mob, list/modifiers, list/attack_modifiers) - if(defense_check(user.loc)) - log_message("Attack absorbed by defense field. Attacker - [user], with [W]", LOG_MECHA, color="orange") - shield.attackby(W, user, modifiers) - else - . = ..() +/obj/vehicle/sealed/mecha/durand/attackby(obj/item/weapon, mob/user as mob, list/modifiers, list/attack_modifiers) + if(defense_check(get_turf(user))) + log_message("Attack absorbed by defense field. Attacker - [user], with [weapon]", LOG_MECHA, color="orange") + return shield.attackby(weapon, user, modifiers) + return ..() /obj/vehicle/sealed/mecha/durand/hitby(atom/movable/AM, skipcatch, hitpush, blocked, datum/thrownthing/throwingdatum) - if(defense_check(AM.loc)) + if(defense_check(get_turf(AM))) log_message("Impact with [AM] absorbed by defense field.", LOG_MECHA, color="orange") - shield.hitby(AM, skipcatch, hitpush, blocked, throwingdatum) - else - . = ..() + return shield.hitby(AM, skipcatch, hitpush, blocked, throwingdatum) + return ..() /datum/action/vehicle/sealed/mecha/mech_defense_mode - name = "Toggle an energy shield that blocks all attacks from the faced direction at a heavy power cost." + name = "Toggle Shield" + desc = "Toggle an energy shield that blocks all attacks from the faced direction at a heavy power cost." button_icon_state = "mech_defense_mode_off" /datum/action/vehicle/sealed/mecha/mech_defense_mode/Trigger(mob/clicker, trigger_flags, forced_state = FALSE) . = ..() if(!.) return - if(!chassis || !(owner in chassis.occupants)) - return - SEND_SIGNAL(chassis, COMSIG_MECHA_ACTION_TRIGGER, owner, args) //Signal sent to the mech, to be handed to the shield. See durand.dm for more details + if(chassis && (owner in chassis.occupants)) + var/obj/vehicle/sealed/mecha/durand/durand = chassis + durand.toggle_defense(owner) -//////////////////////////// -///// Shield processing //// -//////////////////////////// +// Shield processing -/**An object to take the hit for us when using the Durand's defense mode. -It is spawned in during the durand's initilization, and always stays on the same tile. -Normally invisible, until defense mode is actvated. When the durand detects an attack that should be blocked, the -attack is passed to the shield. The shield takes the damage, uses it to calculate charge cost, and then sets its -own integrity back to max. Shield is automatically dropped if we run out of power or the user gets out.*/ +/** + * An object to take the hit for us when using the Durand's defense mode. + * It is spawned in during the durand's initilization. + * Normally invisible, until defense mode is actvated. When the durand detects an attack that should be blocked, the + * attack is passed to the shield. The shield takes the damage, uses it to calculate charge cost, and then sets its + * own integrity back to max. Shield is automatically dropped if we run out of power or the user gets out. + **/ /obj/durand_shield //projectiles get passed to this when defense mode is enabled name = "defense grid" icon = 'icons/mob/effects/durand_shield.dmi' icon_state = "shield_null" - invisibility = INVISIBILITY_MAXIMUM //no showing on right-click - pixel_y = 4 + pixel_z = 4 max_integrity = 10000 anchored = TRUE light_system = OVERLAY_LIGHT @@ -171,100 +185,22 @@ own integrity back to max. Shield is automatically dropped if we run out of powe light_power = 1 light_color = LIGHT_COLOR_FAINT_CYAN light_on = FALSE - resistance_flags = LAVA_PROOF | FIRE_PROOF | ACID_PROOF //The shield should not take damage from fire, lava, or acid; that's the mech's job. - ///Our link back to the durand + light_flags = LIGHT_ATTACHED + vis_flags = VIS_INHERIT_DIR | VIS_INHERIT_LAYER | VIS_INHERIT_PLANE | VIS_INHERIT_ID + resistance_flags = LAVA_PROOF | FIRE_PROOF | ACID_PROOF // The shield should not take damage from fire, lava, or acid; that's the mech's job. + /// Our link back to the durand var/obj/vehicle/sealed/mecha/durand/chassis - ///To keep track of things during the animation - var/switching = FALSE -/obj/durand_shield/Initialize(mapload, chassis, plane, layer, dir) +/obj/durand_shield/Initialize(mapload, obj/vehicle/sealed/mecha/durand/chassis) . = ..() src.chassis = chassis - src.layer = ABOVE_MOB_LAYER - SET_PLANE_IMPLICIT(src, plane) - setDir(dir) - RegisterSignal(chassis, COMSIG_MECHA_ACTION_TRIGGER, PROC_REF(activate)) - RegisterSignal(chassis, COMSIG_MOVABLE_UPDATE_GLIDE_SIZE, PROC_REF(shield_glide_size_update)) /obj/durand_shield/Destroy() - UnregisterSignal(src, COMSIG_MECHA_ACTION_TRIGGER) if(chassis) - UnregisterSignal(chassis, COMSIG_MOVABLE_UPDATE_GLIDE_SIZE) chassis.shield = null chassis = null return ..() -/obj/durand_shield/proc/shield_glide_size_update(datum/source, target) - SIGNAL_HANDLER - glide_size = target - -/** - * Handles activating and deactivating the shield. - * - * This proc is called by a signal sent from the mech's action button and - * relayed by the mech itself. The "forced" variable, `signal_args[1]`, will - * skip the to-pilot text and is meant for when the shield is disabled by - * means other than the action button (like running out of power). - * - * Arguments: - * * source: the shield - * * owner: mob that activated the shield - * * signal_args: whether it's forced - */ -/obj/durand_shield/proc/activate(datum/source, mob/owner, list/signal_args) - SIGNAL_HANDLER - if(!LAZYLEN(chassis?.occupants)) - return - if(switching && !signal_args[1]) - return - if(!chassis.defense_mode && (!chassis.cell || chassis.cell.charge < 100)) //If it's off, and we have less than 100 units of power - chassis.balloon_alert(owner, "insufficient power") - return - switching = TRUE - chassis.defense_mode = !chassis.defense_mode - if(!signal_args[1]) - chassis.balloon_alert(owner, "shield [chassis.defense_mode?"enabled":"disabled"]") - chassis.log_message("User has toggled defense mode -- now [chassis.defense_mode?"enabled":"disabled"].", LOG_MECHA) - else - chassis.log_message("defense mode state changed -- now [chassis.defense_mode?"enabled":"disabled"].", LOG_MECHA) - for(var/occupant in chassis.occupants) - var/datum/action/button = chassis.occupant_actions[occupant][/datum/action/vehicle/sealed/mecha/mech_defense_mode] - button.button_icon_state = "mech_defense_mode_[chassis.defense_mode ? "on" : "off"]" - button.build_all_button_icons() - - set_light_on(chassis.defense_mode) - - if(chassis.defense_mode) - SetInvisibility(INVISIBILITY_NONE, id=type) - flick("shield_raise", src) - playsound(src, 'sound/vehicles/mecha/mech_shield_raise.ogg', 50, FALSE) - icon_state = "shield" - resetdir(chassis, dir, dir) // to set the plane for the shield properly when it's turned on - RegisterSignal(chassis, COMSIG_ATOM_DIR_CHANGE, PROC_REF(resetdir)) - else - flick("shield_drop", src) - playsound(src, 'sound/vehicles/mecha/mech_shield_drop.ogg', 50, FALSE) - icon_state = "shield_null" - addtimer(CALLBACK(src, PROC_REF(make_invisible)), 1 SECONDS, TIMER_UNIQUE|TIMER_OVERRIDE) - UnregisterSignal(chassis, COMSIG_ATOM_DIR_CHANGE) - switching = FALSE - -/** - * Sets invisibility to INVISIBILITY_MAXIMUM if defense mode is disabled - * - * We need invisibility set to higher than 25 for the shield to not appear - * in the right-click context menu, but if we do it too early, we miss the - * deactivate animation. Hense, timer and this proc. - */ -/obj/durand_shield/proc/make_invisible() - if(!chassis.defense_mode) - RemoveInvisibility(type) - -/obj/durand_shield/proc/resetdir(datum/source, olddir, newdir) - SIGNAL_HANDLER - - setDir(newdir) - /obj/durand_shield/take_damage(damage_amount, damage_type = BRUTE, damage_flag = "", sound_effect = TRUE, attack_dir, armour_penetration = 0) if(!chassis) qdel(src) @@ -277,10 +213,7 @@ own integrity back to max. Shield is automatically dropped if we run out of powe return if(!chassis.use_energy(. * (STANDARD_CELL_CHARGE / 150))) chassis.cell?.charge = 0 - for(var/O in chassis.occupants) - var/mob/living/occupant = O - var/datum/action/action = LAZYACCESSASSOC(chassis.occupant_actions, occupant, /datum/action/vehicle/sealed/mecha/mech_defense_mode) - action.Trigger() + chassis.toggle_defense() atom_integrity = 10000 /obj/durand_shield/play_attack_sound() diff --git a/code/modules/vehicles/mecha/combat/marauder.dm b/code/modules/vehicles/mecha/combat/marauder.dm index e316de70bf7..083bf66e3dd 100644 --- a/code/modules/vehicles/mecha/combat/marauder.dm +++ b/code/modules/vehicles/mecha/combat/marauder.dm @@ -24,6 +24,15 @@ ) bumpsmash = TRUE + /// Reusable smoke generator system + var/datum/effect_system/fluid_spread/smoke/smoke_system + /// Remaining smoke charges + var/smoke_charges = 5 + /// Cooldown between using smoke + var/smoke_cooldown = 10 SECONDS + /// Bool for zoom on/off + var/zoom_mode = FALSE + /datum/armor/mecha_marauder melee = 70 bullet = 60 @@ -33,6 +42,14 @@ fire = 100 acid = 100 +/obj/vehicle/sealed/mecha/marauder/Initialize(mapload, built_manually) + . = ..() + smoke_system = new(src, 3, holder = src) + +/obj/vehicle/sealed/mecha/marauder/Destroy() + QDEL_NULL(smoke_system) + return ..() + /obj/vehicle/sealed/mecha/marauder/generate_actions() . = ..() initialize_passenger_action_type(/datum/action/vehicle/sealed/mecha/mech_smoke) @@ -54,20 +71,51 @@ servo = new /obj/item/stock_parts/servo/femto(src) update_part_values() +/obj/vehicle/sealed/mecha/marauder/remove_occupant(mob/driver) + . = ..() + zoom_mode = FALSE + +/obj/vehicle/sealed/mecha/marauder/can_move(direction) + . = ..() + if(!. || !zoom_mode) + return + + if(TIMER_COOLDOWN_FINISHED(src, COOLDOWN_MECHA_MESSAGE)) + to_chat(occupants, "[icon2html(src, occupants)][span_warning("Unable to move while in zoom mode!")]") + TIMER_COOLDOWN_START(src, COOLDOWN_MECHA_MESSAGE, 2 SECONDS) + return FALSE + /datum/action/vehicle/sealed/mecha/mech_smoke name = "Smoke" button_icon_state = "mech_smoke" +/datum/action/vehicle/sealed/mecha/mech_smoke/IsAvailable(feedback) + . = ..() + if (!.) + return + + var/obj/vehicle/sealed/mecha/marauder/maradeur = chassis + if(!TIMER_COOLDOWN_FINISHED(maradeur, COOLDOWN_MECHA_SMOKE)) + if (feedback) + owner.balloon_alert(owner, "smoke charges on cooldown!") + return FALSE + + if (!maradeur.smoke_charges) + if (feedback) + owner.balloon_alert(owner, "out of smoke charges!") + return FALSE + /datum/action/vehicle/sealed/mecha/mech_smoke/Trigger(mob/clicker, trigger_flags) . = ..() if(!.) return if(!chassis || !(owner in chassis.occupants)) return - if(TIMER_COOLDOWN_FINISHED(src, COOLDOWN_MECHA_SMOKE) && chassis.smoke_charges>0) - chassis.smoke_system.start() - chassis.smoke_charges-- - TIMER_COOLDOWN_START(src, COOLDOWN_MECHA_SMOKE, chassis.smoke_cooldown) + var/obj/vehicle/sealed/mecha/marauder/maradeur = chassis + if(TIMER_COOLDOWN_FINISHED(maradeur, COOLDOWN_MECHA_SMOKE) && maradeur.smoke_charges) + maradeur.smoke_system.start() + maradeur.smoke_charges-- + TIMER_COOLDOWN_START(maradeur, COOLDOWN_MECHA_SMOKE, maradeur.smoke_cooldown) /datum/action/vehicle/sealed/mecha/mech_zoom name = "Zoom" @@ -79,11 +127,12 @@ return if(!owner.client || !chassis || !(owner in chassis.occupants)) return - chassis.zoom_mode = !chassis.zoom_mode - button_icon_state = "mech_zoom_[chassis.zoom_mode ? "on" : "off"]" - chassis.log_message("Toggled zoom mode.", LOG_MECHA) - to_chat(owner, "[icon2html(chassis, owner)]Zoom mode [chassis.zoom_mode?"en":"dis"]abled.") - if(chassis.zoom_mode) + var/obj/vehicle/sealed/mecha/marauder/maradeur = chassis + maradeur.zoom_mode = !maradeur.zoom_mode + button_icon_state = "mech_zoom_[maradeur.zoom_mode ? "on" : "off"]" + maradeur.log_message("Toggled zoom mode.", LOG_MECHA) + to_chat(owner, "[icon2html(maradeur, owner)]Zoom mode [maradeur.zoom_mode ? "en" : "dis"]abled.") + if(maradeur.zoom_mode) owner.client.view_size.setTo(4.5) SEND_SOUND(owner, sound('sound/vehicles/mecha/imag_enh.ogg', volume=50)) else diff --git a/code/modules/vehicles/mecha/combat/phazon.dm b/code/modules/vehicles/mecha/combat/phazon.dm index e193f4e5e1c..5ca91ce5817 100644 --- a/code/modules/vehicles/mecha/combat/phazon.dm +++ b/code/modules/vehicles/mecha/combat/phazon.dm @@ -1,6 +1,6 @@ /obj/vehicle/sealed/mecha/phazon - desc = "This is a Phazon exosuit. The pinnacle of scientific research and pride of Nanotrasen, it uses cutting edge anomalous technology and expensive materials." name = "\improper Phazon" + desc = "This is a Phazon exosuit. The pinnacle of scientific research and pride of Nanotrasen, it uses cutting edge anomalous technology and expensive materials." icon_state = "phazon" base_icon_state = "phazon" movedelay = 2 @@ -21,7 +21,13 @@ MECHA_POWER = 1, MECHA_ARMOR = 2, ) - phase_state = "phazon-phase" + + /// Are we currently phasing through walls? + var/phasing = FALSE + /// Power we use every time we phaze through something + var/phasing_energy_drain = 0.2 * STANDARD_CELL_CHARGE + /// Icon_state for flick() when phasing + var/phase_state = "phazon-phase" /datum/armor/mecha_phazon melee = 30 @@ -37,6 +43,42 @@ initialize_passenger_action_type(/datum/action/vehicle/sealed/mecha/mech_toggle_phasing) initialize_passenger_action_type(/datum/action/vehicle/sealed/mecha/mech_switch_damtype) +/obj/vehicle/sealed/mecha/phazon/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/phazon/vehicle_move(direction, forcerotate) + . = ..() + if(. && phasing) + use_energy(phasing_energy_drain) + +/obj/vehicle/sealed/mecha/phazon/try_bumpsmash(atom/obstacle) + if(phasing) // Theres only one cause for phasing canpass fails + to_chat(occupants, "[icon2html(src, occupants)][span_warning("A dull, universal force is preventing you from phasing here!")]") + spark_system.start() + return + return ..() + +/obj/vehicle/sealed/mecha/phazon/update_energy_drain() + . = ..() + if(capacitor) + phasing_energy_drain = initial(phasing_energy_drain) / capacitor.rating + else + phasing_energy_drain = initial(phasing_energy_drain) + +/obj/vehicle/sealed/mecha/phazon/can_interact_with(atom/target, mob/user, list/modifiers) + . = ..() + if (!. || !phasing) + return + balloon_alert(user, "not while phasing!") + return FALSE + /datum/action/vehicle/sealed/mecha/mech_switch_damtype name = "Reconfigure arm microtool arrays" button_icon_state = "mech_damtype_brute" @@ -73,7 +115,8 @@ return if(!chassis || !(owner in chassis.occupants)) return - chassis.phasing = chassis.phasing ? "" : "phasing" - button_icon_state = "mech_phasing_[chassis.phasing ? "on" : "off"]" - chassis.balloon_alert(owner, "[chassis.phasing ? "enabled" : "disabled"] phasing") + var/obj/vehicle/sealed/mecha/phazon/phazon = chassis + phazon.phasing = !phazon.phasing + button_icon_state = "mech_phasing_[phazon.phasing ? "on" : "off"]" + phazon.balloon_alert(owner, "[phazon.phasing ? "enabled" : "disabled"] phasing") build_all_button_icons() diff --git a/code/modules/vehicles/mecha/combat/savannah_ivanov.dm b/code/modules/vehicles/mecha/combat/savannah_ivanov.dm index 13f594f243b..4f9794a7c93 100644 --- a/code/modules/vehicles/mecha/combat/savannah_ivanov.dm +++ b/code/modules/vehicles/mecha/combat/savannah_ivanov.dm @@ -37,8 +37,8 @@ MECHA_POWER = 1, MECHA_ARMOR = 1, ) - //no tax on flying, since the power cost is in the leap itself. - phasing_energy_drain = 0 + /// Are we currently in the middle of a skyfall jump? + var/flying = FALSE /datum/armor/mecha_savannah_ivanov melee = 45 @@ -71,6 +71,22 @@ initialize_controller_action_type(/datum/action/vehicle/sealed/mecha/skyfall, VEHICLE_CONTROL_DRIVE) initialize_controller_action_type(/datum/action/vehicle/sealed/mecha/ivanov_strike, VEHICLE_CONTROL_EQUIPMENT) +// Pass through obstacles freely if we're flying +/obj/vehicle/sealed/mecha/savannah_ivanov/CanPassThrough(atom/blocker, movement_dir, blocker_opinion) + if(!flying || throwing) + return ..() + 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/savannah_ivanov/can_interact_with(atom/target, mob/user, list/modifiers) + . = ..() + if (!. || !flying) + return + balloon_alert(user, "not while airborne!") + return FALSE + ///Savannah Skyfall /datum/action/vehicle/sealed/mecha/skyfall name = "Savannah Skyfall" @@ -86,17 +102,18 @@ return if(!owner || !chassis || !(owner in chassis.occupants)) return - if(chassis.phasing) + var/obj/vehicle/sealed/mecha/savannah_ivanov/savannah = chassis + if(savannah.flying) to_chat(owner, span_warning("You're already airborne!")) return - if(TIMER_COOLDOWN_RUNNING(chassis, COOLDOWN_MECHA_SKYFALL)) - var/timeleft = S_TIMER_COOLDOWN_TIMELEFT(chassis, COOLDOWN_MECHA_SKYFALL) + if(TIMER_COOLDOWN_RUNNING(savannah, COOLDOWN_MECHA_SKYFALL)) + var/timeleft = S_TIMER_COOLDOWN_TIMELEFT(savannah, COOLDOWN_MECHA_SKYFALL) to_chat(owner, span_warning("You need to wait [DisplayTimeText(timeleft, 1)] before attempting to Skyfall.")) return if(skyfall_charge_level) abort_skyfall() return - chassis.balloon_alert(owner, "charging skyfall...") + savannah.balloon_alert(owner, "charging skyfall...") INVOKE_ASYNC(src, PROC_REF(skyfall_charge_loop)) /** @@ -146,7 +163,8 @@ new /obj/effect/skyfall_landingzone(launch_turf, chassis) chassis.resistance_flags |= INDESTRUCTIBLE //not while jumping at least chassis.mecha_flags |= QUIET_STEPS|QUIET_TURNS|CANNOT_INTERACT - chassis.phasing = "flying" + var/obj/vehicle/sealed/mecha/savannah_ivanov/savannah = chassis + savannah.flying = TRUE chassis.movedelay = 1 chassis.set_density(FALSE) chassis.layer = ABOVE_ALL_MOB_LAYER @@ -177,7 +195,8 @@ playsound(chassis, 'sound/effects/explosion/explosion1.ogg', 50, 1) chassis.resistance_flags &= ~INDESTRUCTIBLE chassis.mecha_flags &= ~(QUIET_STEPS|QUIET_TURNS|CANNOT_INTERACT) - chassis.phasing = initial(chassis.phasing) + var/obj/vehicle/sealed/mecha/savannah_ivanov/savannah = chassis + savannah.flying = FALSE chassis.movedelay = initial(chassis.movedelay) chassis.set_density(TRUE) chassis.layer = initial(chassis.layer) diff --git a/code/modules/vehicles/mecha/equipment/mecha_equipment.dm b/code/modules/vehicles/mecha/equipment/mecha_equipment.dm index e367837fb6d..f2f42d1f85b 100644 --- a/code/modules/vehicles/mecha/equipment/mecha_equipment.dm +++ b/code/modules/vehicles/mecha/equipment/mecha_equipment.dm @@ -160,8 +160,8 @@ if(flags & MECH_DO_AFTER_ADJACENCY_FLAG && !(chassis.Adjacent(target))) return FALSE -/obj/item/mecha_parts/mecha_equipment/proc/can_attach(obj/vehicle/sealed/mecha/M, attach_right = FALSE, mob/user) - return default_can_attach(M, attach_right, user) +/obj/item/mecha_parts/mecha_equipment/proc/can_attach(obj/vehicle/sealed/mecha/mech, attach_right = FALSE, mob/user) + return default_can_attach(mech, attach_right, user) /obj/item/mecha_parts/mecha_equipment/proc/default_can_attach(obj/vehicle/sealed/mecha/mech, attach_right = FALSE, mob/user) if(!(mech_flags & mech.mech_type)) diff --git a/code/modules/vehicles/mecha/mecha_actions.dm b/code/modules/vehicles/mecha/mecha_actions.dm index 2fd55c14e4b..34d9e82e53b 100644 --- a/code/modules/vehicles/mecha/mecha_actions.dm +++ b/code/modules/vehicles/mecha/mecha_actions.dm @@ -107,7 +107,6 @@ chassis.toggle_strafe() - /obj/vehicle/sealed/mecha/proc/toggle_strafe() if(!(mecha_flags & CAN_STRAFE)) to_chat(occupants, "this mecha doesn't support strafing!") @@ -124,7 +123,7 @@ var/datum/action/action = LAZYACCESSASSOC(occupant_actions, occupant, /datum/action/vehicle/sealed/mecha/strafe) action?.build_all_button_icons() -///swap seats, for two person mecha +/// Swap seats, for two person mecha /datum/action/vehicle/sealed/mecha/swap_seat name = "Switch Seats" button_icon_state = "mech_seat_swap" diff --git a/code/modules/vehicles/mecha/mecha_defense.dm b/code/modules/vehicles/mecha/mecha_defense.dm index f41b3006e03..393c478b519 100644 --- a/code/modules/vehicles/mecha/mecha_defense.dm +++ b/code/modules/vehicles/mecha/mecha_defense.dm @@ -59,7 +59,7 @@ /obj/vehicle/sealed/mecha/attack_hand(mob/living/user, list/modifiers) . = ..() - if(.) + if(. || !user.combat_mode) return user.changeNext_move(CLICK_CD_MELEE) // Ugh. Ideally we shouldn't be setting cooldowns outside of click code. user.do_attack_animation(src, ATTACK_EFFECT_PUNCH) @@ -73,26 +73,25 @@ /obj/vehicle/sealed/mecha/attack_alien(mob/living/user, list/modifiers) log_message("Attack by alien. Attacker - [user].", LOG_MECHA, color="red") playsound(loc, 'sound/items/weapons/slash.ogg', 100, TRUE) - attack_generic(user, rand(user.melee_damage_lower, user.melee_damage_upper), BRUTE, MELEE, 0) + return attack_generic(user, rand(user.melee_damage_lower, user.melee_damage_upper), BRUTE, MELEE, 0) /obj/vehicle/sealed/mecha/attack_animal(mob/living/simple_animal/user, list/modifiers) log_message("Attack by simple animal. Attacker - [user].", LOG_MECHA, color="red") if(!user.melee_damage_upper && !user.obj_damage) user.emote("custom", message = "[user.friendly_verb_continuous] [src].") - return 0 - else - var/play_soundeffect = 1 - if(user.environment_smash) - play_soundeffect = 0 - playsound(src, 'sound/effects/bang.ogg', 50, TRUE) - var/animal_damage = rand(user.melee_damage_lower,user.melee_damage_upper) - if(user.obj_damage) - animal_damage = user.obj_damage - animal_damage = min(animal_damage, 20*user.environment_smash) - log_combat(user, src, "attacked") - attack_generic(user, animal_damage, user.melee_damage_type, MELEE, play_soundeffect) - return 1 + return FALSE + var/play_soundeffect = 1 + if(user.environment_smash) + play_soundeffect = 0 + playsound(src, 'sound/effects/bang.ogg', 50, TRUE) + + var/animal_damage = rand(user.melee_damage_lower,user.melee_damage_upper) + if(user.obj_damage) + animal_damage = user.obj_damage + animal_damage = min(animal_damage, 20*user.environment_smash) + log_combat(user, src, "attacked") + return attack_generic(user, animal_damage, user.melee_damage_type, MELEE, play_soundeffect) /obj/vehicle/sealed/mecha/hulk_damage() return 15 @@ -420,16 +419,18 @@ if(servo) stock_parts += servo - if(length(stock_parts)) - var/obj/item/stock_parts/part_to_remove = tgui_input_list(user, "Which part to remove?", "Part Removal", stock_parts) - if(!(locate(part_to_remove) in contents)) - return - user.put_in_hands(part_to_remove) - locate_parts() - diag_hud_set_mechcell() - tool.play_tool_sound(src) + if(!length(stock_parts)) + balloon_alert(user, "no parts!") return - balloon_alert(user, "no parts!") + + var/obj/item/stock_parts/part_to_remove = tgui_input_list(user, "Which part to remove?", "Part Removal", stock_parts) + if(!(locate(part_to_remove) in contents)) + return + + user.put_in_hands(part_to_remove) + locate_parts() + diag_hud_set_mechcell() + tool.play_tool_sound(src) /obj/vehicle/sealed/mecha/welder_act(mob/living/user, obj/item/W) if(user.combat_mode) @@ -445,7 +446,7 @@ return user.balloon_alert_to_viewers("started welding [src]", "started repairing [src]") audible_message(span_hear("You hear welding.")) - var/did_the_thing + var/did_the_thing = FALSE while(atom_integrity < max_integrity) if(W.use_tool(src, user, 2.5 SECONDS, volume=50)) did_the_thing = TRUE @@ -453,12 +454,12 @@ audible_message(span_hear("You hear welding.")) else break + if(did_the_thing) user.balloon_alert_to_viewers("[(atom_integrity >= max_integrity) ? "fully" : "partially"] repaired [src]") else user.balloon_alert_to_viewers("stopped welding [src]", "interrupted the repair!") - /obj/vehicle/sealed/mecha/proc/full_repair(charge_cell) repair_damage(max_integrity) if(cell && charge_cell) @@ -489,62 +490,66 @@ visual_effect_icon = ATTACK_EFFECT_MECHFIRE else if(damtype == TOX) visual_effect_icon = ATTACK_EFFECT_MECHTOXIN - ..() + return ..() - -/obj/vehicle/sealed/mecha/proc/ammo_resupply(obj/item/mecha_ammo/A, mob/user,fail_chat_override = FALSE) - if(!A.rounds) +/obj/vehicle/sealed/mecha/proc/ammo_resupply(obj/item/mecha_ammo/ammo, mob/user,fail_chat_override = FALSE) + if(!ammo.rounds) if(!fail_chat_override) balloon_alert(user, "the box is empty!") return FALSE + var/ammo_needed var/found_gun + for(var/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/gun in flat_equipment) ammo_needed = 0 - if(gun.ammo_type != A.ammo_type) + if(gun.ammo_type != ammo.ammo_type) continue found_gun = TRUE - if(A.direct_load) + if(ammo.direct_load) ammo_needed = initial(gun.projectiles) - gun.projectiles else ammo_needed = gun.projectiles_cache_max - gun.projectiles_cache if(!ammo_needed) continue - if(ammo_needed < A.rounds) - if(A.direct_load) + + if(ammo_needed < ammo.rounds) + if(ammo.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,TRUE) - to_chat(user, span_notice("You add [ammo_needed] [A.ammo_type][ammo_needed > 1?"s":""] to \the [gun]")) - A.rounds = A.rounds - ammo_needed - if(A.custom_materials) //Change material content of the ammo box according to the amount of ammo deposited into the weapon + playsound(get_turf(user), ammo.load_audio, 50, TRUE) + to_chat(user, span_notice("You add [ammo_needed] [ammo.ammo_type][ammo_needed > 1 ? "s" : ""] to \the [gun]")) + ammo.rounds = ammo.rounds - ammo_needed + if(ammo.custom_materials) //Change material content of the ammo box according to the amount of ammo deposited into the weapon /// list of materials contained in the ammo box after we put it through the equation so we can stick this list into set_custom_materials() var/list/new_material_content = list() - for(var/datum/material/current_material in A.custom_materials) + for(var/datum/material/current_material in ammo.custom_materials) if(istype(current_material, /datum/material/iron)) //we can flatten an empty ammo box into a sheet of iron (2000 units) so we have to make sure the box always has this amount at minimum - new_material_content[current_material] = (A.custom_materials[current_material] - SHEET_MATERIAL_AMOUNT) * (A.rounds / initial(A.rounds)) + SHEET_MATERIAL_AMOUNT + new_material_content[current_material] = (ammo.custom_materials[current_material] - SHEET_MATERIAL_AMOUNT) * (ammo.rounds / initial(ammo.rounds)) + SHEET_MATERIAL_AMOUNT else - new_material_content[current_material] = A.custom_materials[current_material] * (A.rounds / initial(A.rounds)) - A.set_custom_materials(new_material_content) - A.update_name() + new_material_content[current_material] = ammo.custom_materials[current_material] * (ammo.rounds / initial(ammo.rounds)) + ammo.set_custom_materials(new_material_content) + ammo.update_name() return TRUE - if(A.direct_load) - gun.projectiles = gun.projectiles + A.rounds + if(ammo.direct_load) + gun.projectiles = gun.projectiles + ammo.rounds else - gun.projectiles_cache = gun.projectiles_cache + A.rounds - playsound(get_turf(user),A.load_audio,50,TRUE) - to_chat(user, span_notice("You add [A.rounds] [A.ammo_type][A.rounds > 1?"s":""] to \the [gun]")) - if(A.qdel_on_empty) - qdel(A) + gun.projectiles_cache = gun.projectiles_cache + ammo.rounds + + playsound(get_turf(user),ammo.load_audio,50,TRUE) + to_chat(user, span_notice("You add [ammo.rounds] [ammo.ammo_type][ammo.rounds > 1 ? "s" : ""] to \the [gun]")) + if(ammo.qdel_on_empty) + qdel(ammo) return TRUE - A.rounds = 0 - A.set_custom_materials(list(/datum/material/iron=SHEET_MATERIAL_AMOUNT)) - A.update_appearance() + ammo.rounds = 0 + ammo.set_custom_materials(list(/datum/material/iron=SHEET_MATERIAL_AMOUNT)) + ammo.update_appearance() return TRUE + if(!fail_chat_override) if(found_gun) balloon_alert(user, "ammo storage is full!") diff --git a/code/modules/vehicles/mecha/mecha_mob_interaction.dm b/code/modules/vehicles/mecha/mecha_mob_interaction.dm index 296352dc8c4..3b341120231 100644 --- a/code/modules/vehicles/mecha/mecha_mob_interaction.dm +++ b/code/modules/vehicles/mecha/mecha_mob_interaction.dm @@ -197,7 +197,6 @@ if(driver.client) driver.update_mouse_pointer() driver.client.view_size.resetToDefault() - zoom_mode = FALSE . = ..() update_appearance() diff --git a/code/modules/vehicles/mecha/mecha_movement.dm b/code/modules/vehicles/mecha/mecha_movement.dm index 5477f5f2581..d0121a1b333 100644 --- a/code/modules/vehicles/mecha/mecha_movement.dm +++ b/code/modules/vehicles/mecha/mecha_movement.dm @@ -75,49 +75,24 @@ /obj/vehicle/sealed/mecha/vehicle_move(direction, forcerotate = FALSE) if(!COOLDOWN_FINISHED(src, cooldown_vehicle_move)) return FALSE + COOLDOWN_START(src, cooldown_vehicle_move, movedelay) + if(completely_disabled) return FALSE + if(!direction) return FALSE + + if(!can_move(direction)) + return FALSE + if(ismovable(loc)) //Mech is inside an object, tell it we moved var/atom/loc_atom = loc return loc_atom.relaymove(src, direction) - var/obj/machinery/portable_atmospherics/canister/internal_tank = get_internal_tank() - if(internal_tank?.connected_port) - if(TIMER_COOLDOWN_FINISHED(src, COOLDOWN_MECHA_MESSAGE)) - to_chat(occupants, "[icon2html(src, occupants)][span_warning("Unable to move while connected to the air system port!")]") - TIMER_COOLDOWN_START(src, COOLDOWN_MECHA_MESSAGE, 2 SECONDS) - return FALSE + if(!Process_Spacemove(direction)) return FALSE - if(zoom_mode) - if(TIMER_COOLDOWN_FINISHED(src, COOLDOWN_MECHA_MESSAGE)) - to_chat(occupants, "[icon2html(src, occupants)][span_warning("Unable to move while in zoom mode!")]") - TIMER_COOLDOWN_START(src, COOLDOWN_MECHA_MESSAGE, 2 SECONDS) - return FALSE - var/list/missing_parts = list() - if(isnull(cell)) - missing_parts += "power cell" - if(isnull(capacitor)) - missing_parts += "capacitor" - if(isnull(servo)) - missing_parts += "servo" - if(length(missing_parts)) - if(TIMER_COOLDOWN_FINISHED(src, COOLDOWN_MECHA_MESSAGE)) - to_chat(occupants, "[icon2html(src, occupants)][span_warning("Missing [english_list(missing_parts)].")]") - TIMER_COOLDOWN_START(src, COOLDOWN_MECHA_MESSAGE, 2 SECONDS) - return FALSE - if((step_energy_drain != 0) && !use_energy(step_energy_drain)) - if(TIMER_COOLDOWN_FINISHED(src, COOLDOWN_MECHA_MESSAGE)) - to_chat(occupants, "[icon2html(src, occupants)][span_warning("Insufficient power to move!")]") - TIMER_COOLDOWN_START(src, COOLDOWN_MECHA_MESSAGE, 2 SECONDS) - return FALSE - if(lavaland_only && is_mining_level(z)) - if(TIMER_COOLDOWN_FINISHED(src, COOLDOWN_MECHA_MESSAGE)) - to_chat(occupants, "[icon2html(src, occupants)][span_warning("Invalid Environment.")]") - TIMER_COOLDOWN_START(src, COOLDOWN_MECHA_MESSAGE, 2 SECONDS) - return FALSE var/olddir = dir @@ -148,20 +123,46 @@ //Otherwise just walk normally . = try_step_multiz(direction) - if(phasing) - use_energy(phasing_energy_drain) if(strafe) setDir(olddir) +/// Check if anything is blocking our movement +/obj/vehicle/sealed/mecha/proc/can_move(direction) + var/obj/machinery/portable_atmospherics/canister/internal_tank = get_internal_tank() + if(internal_tank?.connected_port) + if(TIMER_COOLDOWN_FINISHED(src, COOLDOWN_MECHA_MESSAGE)) + to_chat(occupants, "[icon2html(src, occupants)][span_warning("Unable to move while connected to the air system port!")]") + TIMER_COOLDOWN_START(src, COOLDOWN_MECHA_MESSAGE, 2 SECONDS) + return FALSE + + var/list/missing_parts = list() + if(isnull(cell)) + missing_parts += "power cell" + if(isnull(capacitor)) + missing_parts += "capacitor" + if(isnull(servo)) + missing_parts += "servo" + + if(length(missing_parts)) + if(TIMER_COOLDOWN_FINISHED(src, COOLDOWN_MECHA_MESSAGE)) + to_chat(occupants, "[icon2html(src, occupants)][span_warning("Missing [english_list(missing_parts)].")]") + TIMER_COOLDOWN_START(src, COOLDOWN_MECHA_MESSAGE, 2 SECONDS) + return FALSE + + if((step_energy_drain != 0) && !use_energy(step_energy_drain)) + if(TIMER_COOLDOWN_FINISHED(src, COOLDOWN_MECHA_MESSAGE)) + to_chat(occupants, "[icon2html(src, occupants)][span_warning("Insufficient power to move!")]") + TIMER_COOLDOWN_START(src, COOLDOWN_MECHA_MESSAGE, 2 SECONDS) + return FALSE + return TRUE + /obj/vehicle/sealed/mecha/Bump(atom/obstacle) . = ..() - if(phasing) //Theres only one cause for phasing canpass fails - to_chat(occupants, "[icon2html(src, occupants)][span_warning("A dull, universal force is preventing you from [phasing] here!")]") - spark_system.start() - return if(.) //mech was thrown/door/whatever return + try_bumpsmash(obstacle) +/obj/vehicle/sealed/mecha/proc/try_bumpsmash(atom/obstacle) // Whether or not we're on our mecha melee cooldown var/on_cooldown = TIMER_COOLDOWN_RUNNING(src, COOLDOWN_MECHA_MELEE_ATTACK) @@ -172,6 +173,7 @@ TIMER_COOLDOWN_START(src, COOLDOWN_MECHA_MELEE_ATTACK, melee_cooldown * 0.3) if(!obstacle || obstacle.CanPass(src, get_dir(obstacle, src) || dir)) // The else is in case the obstacle is in the same turf. step(src,dir) + if(isobj(obstacle)) var/obj/obj_obstacle = obstacle if(!obj_obstacle.anchored && obj_obstacle.move_resist <= move_force) diff --git a/code/modules/vehicles/mecha/working/clarke.dm b/code/modules/vehicles/mecha/working/clarke.dm index 98907b8a26e..5a484fbe07b 100644 --- a/code/modules/vehicles/mecha/working/clarke.dm +++ b/code/modules/vehicles/mecha/working/clarke.dm @@ -47,19 +47,13 @@ . = ..() ore_box = new(src) -/obj/vehicle/sealed/mecha/clarke/atom_destruction() - if(ore_box) - INVOKE_ASYNC(ore_box, TYPE_PROC_REF(/obj/structure/ore_box, dump_box_contents)) - return ..() - /obj/vehicle/sealed/mecha/clarke/generate_actions() . = ..() initialize_passenger_action_type(/datum/action/vehicle/sealed/mecha/mech_search_ruins) initialize_passenger_action_type(/datum/action/vehicle/sealed/mecha/clarke_scoop_body) -//Ore Box Controls - -///Special equipment for the Clarke mech, handles moving ore without giving the mech a hydraulic clamp and cargo compartment. +// Ore Box Controls +/// Special equipment for the Clarke mech, handles moving ore without giving the mech a hydraulic clamp and cargo compartment. /obj/item/mecha_parts/mecha_equipment/orebox_manager name = "ore storage module" desc = "An automated ore box management device, complete with a built-in boulder processor."