Files
+37 21b4095dfd [MDB IGNORE] [IDB IGNORE] Upstream Sync - 04/17/2026 (#5453)
Upstream 04/17/2026

fixes https://github.com/Bubberstation/Bubberstation/issues/5549

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: tgstation-ci[bot] <179393467+tgstation-ci[bot]@users.noreply.github.com>
Co-authored-by: ArcaneMusic <41715314+ArcaneMusic@users.noreply.github.com>
Co-authored-by: MrMelbert <51863163+MrMelbert@users.noreply.github.com>
Co-authored-by: Rhials <28870487+Rhials@users.noreply.github.com>
Co-authored-by: rageguy505 <54517726+rageguy505@users.noreply.github.com>
Co-authored-by: LemonInTheDark <58055496+LemonInTheDark@users.noreply.github.com>
Co-authored-by: John Willard <53777086+JohnFulpWillard@users.noreply.github.com>
Co-authored-by: Aliceee2ch <160794176+Aliceee2ch@users.noreply.github.com>
Co-authored-by: Time-Green <7501474+Time-Green@users.noreply.github.com>
Co-authored-by: Tsar-Salat <62388554+Tsar-Salat@users.noreply.github.com>
Co-authored-by: SmArtKar <44720187+SmArtKar@users.noreply.github.com>
Co-authored-by: Maxipat <108554989+Maxipat112@users.noreply.github.com>
Co-authored-by: SyncIt21 <110812394+SyncIt21@users.noreply.github.com>
Co-authored-by: deltanedas <39013340+deltanedas@users.noreply.github.com>
Co-authored-by: SimplyLogan <47579821+loganuk@users.noreply.github.com>
Co-authored-by: loganuk <fakeemail123@aol.com>
Co-authored-by: Leland Kemble <70413276+lelandkemble@users.noreply.github.com>
Co-authored-by: FalloutFalcon <86381784+FalloutFalcon@users.noreply.github.com>
Co-authored-by: Roxy <75404941+TealSeer@users.noreply.github.com>
Co-authored-by: Lucy <lucy@absolucy.moe>
Co-authored-by: siliconOpossum <138069572+siliconOpossum@users.noreply.github.com>
Co-authored-by: Isratosh <Isratosh@hotmail.com>
Co-authored-by: TheRyeGuyWhoWillNowDie <70169560+TheRyeGuyWhoWillNowDie@users.noreply.github.com>
Co-authored-by: Neocloudy <88008002+Neocloudy@users.noreply.github.com>
Co-authored-by: Alexander V. <volas@ya.ru>
Co-authored-by: ElGitificador <168473461+ElGitificador@users.noreply.github.com>
Co-authored-by: Twaticus <46540570+Twaticus@users.noreply.github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Bloop <13398309+vinylspiders@users.noreply.github.com>
Co-authored-by: Cameron Lennox <killer65311@gmail.com>
Co-authored-by: Tim <timothymtorres@gmail.com>
Co-authored-by: Iamgoofball <iamgoofball@gmail.com>
Co-authored-by: Layzu666 <121319428+Layzu666@users.noreply.github.com>
Co-authored-by: Arturlang <24881678+Arturlang@users.noreply.github.com>
Co-authored-by: _0Steven <42909981+00-Steven@users.noreply.github.com>
Co-authored-by: mrmanlikesbt <99309552+mrmanlikesbt@users.noreply.github.com>
Co-authored-by: Ben10Omintrix <138636438+Ben10Omintrix@users.noreply.github.com>
Co-authored-by: John F. Kennedy <54908920+MacaroniCritter@users.noreply.github.com>
Co-authored-by: Cursor <102828457+theselfish@users.noreply.github.com>
Co-authored-by: Josh <josh.adam.powell@gmail.com>
Co-authored-by: Josh Powell <josh.powell@softwire.com>
Co-authored-by: Yobrocharlie <Charliemiller5617@gmail.com>
Co-authored-by: Hardly3D <66234359+Hardly3D@users.noreply.github.com>
Co-authored-by: shayoki <96078776+shayoki@users.noreply.github.com>
Co-authored-by: LT3 <83487515+lessthnthree@users.noreply.github.com>
2026-05-16 00:56:00 +02:00

292 lines
11 KiB
Plaintext

/obj/vehicle/sealed/mecha/durand
desc = "An aging combat exosuit utilized by the Nanotrasen corporation. Originally developed to combat hostile alien lifeforms."
name = "\improper Durand"
icon_state = "durand"
base_icon_state = "durand"
movedelay = 4
max_integrity = 400
accesses = list(ACCESS_MECH_SCIENCE, ACCESS_MECH_SECURITY)
armor_type = /datum/armor/mecha_durand
max_temperature = 30000
force = 40
destruction_sleep_duration = 40
exit_delay = 40
wreckage = /obj/structure/mecha_wreckage/durand
mech_type = EXOSUIT_MODULE_DURAND
max_equip_by_category = list(
MECHA_L_ARM = 1,
MECHA_R_ARM = 1,
MECHA_UTILITY = 3,
MECHA_POWER = 1,
MECHA_ARMOR = 1,
)
var/obj/durand_shield/shield
/datum/armor/mecha_durand
melee = 40
bullet = 35
laser = 15
energy = 10
bomb = 20
fire = 100
acid = 100
/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))
/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)
/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)
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.*/
/obj/vehicle/sealed/mecha/durand/proc/defense_check(turf/aloc)
if (!defense_mode || !shield || 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
/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))
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/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/hitby(atom/movable/AM, skipcatch, hitpush, blocked, datum/thrownthing/throwingdatum)
if(defense_check(AM.loc))
log_message("Impact with [AM] absorbed by defense field.", LOG_MECHA, color="orange")
shield.hitby(AM, skipcatch, hitpush, blocked, throwingdatum)
else
. = ..()
/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."
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
////////////////////////////
///// 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.*/
/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
max_integrity = 10000
anchored = TRUE
light_system = OVERLAY_LIGHT
light_range = 2.8
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
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)
. = ..()
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)
return
if(!chassis.defense_mode) //if defense mode is disabled, we're taking damage that we shouldn't be taking
return
. = ..()
flick("shield_impact", src)
if(!.)
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()
atom_integrity = 10000
/obj/durand_shield/play_attack_sound()
playsound(src, 'sound/vehicles/mecha/mech_shield_deflect.ogg', 100, TRUE)
/obj/durand_shield/bullet_act()
play_attack_sound()
. = ..()