mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2026-07-19 12:05:59 +01:00
Fixes attacks on mech equipment ignoring armor / melee damage, also fixes mech equipment not being disabled at 0% health, also also unit tests mech armor (#67411)
This commit is contained in:
@@ -104,6 +104,7 @@
|
||||
#include "load_map_security.dm"
|
||||
#include "machine_disassembly.dm"
|
||||
#include "mapping.dm"
|
||||
#include "mecha_damage.dm"
|
||||
#include "medical_wounds.dm"
|
||||
#include "merge_type.dm"
|
||||
#include "metabolizing.dm"
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
/**
|
||||
* Unit test to ensure that mechs take the correct amount of damage
|
||||
* based on armor, and that their equipment is properly damaged as well.
|
||||
*/
|
||||
/datum/unit_test/mecha_damage
|
||||
|
||||
/datum/unit_test/mecha_damage/Run()
|
||||
// "Loaded Mauler" was chosen deliberately here.
|
||||
// We need a mech that starts with arm equipment and has fair enough armor.
|
||||
var/obj/vehicle/sealed/mecha/demo_mech = allocate(/obj/vehicle/sealed/mecha/combat/marauder/mauler/loaded)
|
||||
// We need to face our guy explicitly, because mechs have directional armor
|
||||
demo_mech.setDir(EAST)
|
||||
|
||||
var/expected_melee_armor = demo_mech.armor.getRating(MELEE)
|
||||
var/expected_laser_armor = demo_mech.armor.getRating(LASER)
|
||||
var/expected_bullet_armor = demo_mech.armor.getRating(BULLET)
|
||||
|
||||
var/mob/living/carbon/human/dummy = allocate(/mob/living/carbon/human)
|
||||
dummy.forceMove(locate(run_loc_floor_bottom_left.x + 1, run_loc_floor_bottom_left.y, run_loc_floor_bottom_left.z))
|
||||
// The dummy needs to be targeting an arm. Left is chosen here arbitrarily.
|
||||
dummy.zone_selected = BODY_ZONE_L_ARM
|
||||
// Not strictly necessary, but you never know
|
||||
dummy.face_atom(demo_mech)
|
||||
|
||||
// Get a sample "melee" weapon.
|
||||
// The energy axe is chosen here due to having a high base force, to make sure we get over the equipment DT.
|
||||
var/obj/item/dummy_melee = allocate(/obj/item/melee/energy/axe)
|
||||
var/expected_melee_damage = round(dummy_melee.force * (1 - expected_melee_armor / 100), DAMAGE_PRECISION)
|
||||
|
||||
// Get a sample laser weapon.
|
||||
// The captain's laser gun here is chosen primarily because it deals more damage than normal lasers.
|
||||
var/obj/item/gun/energy/laser/dummy_laser = allocate(/obj/item/gun/energy/laser/captain)
|
||||
var/obj/item/ammo_casing/laser_ammo = dummy_laser.ammo_type[1]
|
||||
var/obj/projectile/beam/laser_fired = initial(laser_ammo.projectile_type)
|
||||
var/expected_laser_damage = round(dummy_laser.projectile_damage_multiplier * initial(laser_fired.damage) * (1 - expected_laser_armor / 100), DAMAGE_PRECISION)
|
||||
|
||||
// Get a sample ballistic weapon.
|
||||
// The syndicate .357 here is chosen because it does a lot of damage.
|
||||
var/obj/item/gun/ballistic/dummy_gun = allocate(/obj/item/gun/ballistic/revolver)
|
||||
var/obj/item/ammo_casing/ballistic_ammo = dummy_gun.magazine.ammo_type
|
||||
var/obj/projectile/bullet_fired = initial(ballistic_ammo.projectile_type)
|
||||
var/expected_bullet_damage = round(dummy_gun.projectile_damage_multiplier * initial(bullet_fired.damage) * (1 - expected_bullet_armor / 100), DAMAGE_PRECISION)
|
||||
|
||||
var/obj/item/mecha_parts/mecha_equipment/left_arm_equipment = demo_mech.equip_by_category[MECHA_L_ARM]
|
||||
TEST_ASSERT_NOTNULL(left_arm_equipment, "[demo_mech] spawned without any equipment in their left arm slot.")
|
||||
|
||||
// Now it's time to actually beat the heck out of the mech to see if it takes damage correctly.
|
||||
TEST_ASSERT_EQUAL(demo_mech.get_integrity(), demo_mech.max_integrity, "[demo_mech] was spawned at not its maximum integrity.")
|
||||
TEST_ASSERT_EQUAL(left_arm_equipment.get_integrity(), left_arm_equipment.max_integrity, "[left_arm_equipment] ([demo_mech]'s left arm) spawned at not its maximum integrity.")
|
||||
|
||||
// SMACK IT
|
||||
var/pre_melee_integrity = demo_mech.get_integrity()
|
||||
var/pre_melee_arm_integrity = left_arm_equipment.get_integrity()
|
||||
demo_mech.attacked_by(dummy_melee, dummy)
|
||||
|
||||
check_integrity(demo_mech, pre_melee_integrity, expected_melee_damage, "hit with a melee item")
|
||||
check_integrity(left_arm_equipment, pre_melee_arm_integrity, expected_melee_damage, "hit with a melee item")
|
||||
|
||||
// BLAST IT
|
||||
var/pre_laser_integrity = demo_mech.get_integrity()
|
||||
var/pre_laser_arm_integrity = left_arm_equipment.get_integrity()
|
||||
dummy_laser.fire_gun(demo_mech, dummy, FALSE)
|
||||
|
||||
check_integrity(demo_mech, pre_laser_integrity, expected_laser_damage, "shot with a laser")
|
||||
check_integrity(left_arm_equipment, pre_laser_arm_integrity, expected_laser_damage, "shot with a laser")
|
||||
|
||||
// SHOOT IT
|
||||
var/pre_bullet_integrity = demo_mech.get_integrity()
|
||||
var/pre_bullet_arm_integrity = left_arm_equipment.get_integrity()
|
||||
dummy_gun.fire_gun(demo_mech, dummy, FALSE)
|
||||
|
||||
check_integrity(demo_mech, pre_bullet_integrity, expected_bullet_damage, "shot with a bullet")
|
||||
check_integrity(left_arm_equipment, pre_bullet_arm_integrity, expected_bullet_damage, "shot with a bullet")
|
||||
|
||||
// Additional check: The right arm of the mech should have taken no damage by this point.
|
||||
var/obj/item/mecha_parts/mecha_equipment/right_arm_equipment = demo_mech.equip_by_category[MECHA_R_ARM]
|
||||
TEST_ASSERT_NOTNULL(right_arm_equipment, "[demo_mech] spawned without any equipment in their right arm slot.")
|
||||
TEST_ASSERT_EQUAL(right_arm_equipment.get_integrity(), right_arm_equipment.max_integrity, "[demo_mech] somehow took damage to its right arm, despite not being targeted.")
|
||||
|
||||
/// Simple helper to check if the integrity of an atom involved has taken damage, and if they took the amount of damage it should have.
|
||||
/datum/unit_test/mecha_damage/proc/check_integrity(atom/checking, pre_integrity, expected_damage, hit_by_phrase)
|
||||
var/post_hit_health = checking.get_integrity()
|
||||
TEST_ASSERT(post_hit_health < pre_integrity, "[checking] was [hit_by_phrase], but didn't take any damage.")
|
||||
|
||||
var/damage_taken = round(pre_integrity - post_hit_health, DAMAGE_PRECISION)
|
||||
TEST_ASSERT_EQUAL(damage_taken, expected_damage, "[checking] didn't take the expected amount of damage when [hit_by_phrase]. (Expected damage: [expected_damage], recieved damage: [damage_taken])")
|
||||
@@ -89,6 +89,9 @@
|
||||
if(chassis.equipment_disabled)
|
||||
to_chat(chassis.occupants, span_warning("Error -- Equipment control unit is unresponsive."))
|
||||
return FALSE
|
||||
if(get_integrity() <= 1)
|
||||
to_chat(chassis.occupants, span_warning("Error -- Equipment critically damaged."))
|
||||
return FALSE
|
||||
if(TIMER_COOLDOWN_CHECK(chassis, COOLDOWN_MECHA_EQUIPMENT(type)))
|
||||
return FALSE
|
||||
return TRUE
|
||||
|
||||
@@ -25,24 +25,31 @@
|
||||
gear = equip_by_category[MECHA_R_ARM]
|
||||
if(!gear)
|
||||
return
|
||||
var/brokenstatus = gear.get_integrity()
|
||||
var/component_health = gear.get_integrity()
|
||||
// always leave at least 1 health
|
||||
brokenstatus--
|
||||
var/damage_to_deal = min(brokenstatus, damage)
|
||||
if(!damage_to_deal)
|
||||
var/damage_to_deal = min(component_health - 1, damage)
|
||||
if(damage_to_deal <= 0)
|
||||
return
|
||||
|
||||
gear.take_damage(damage_to_deal)
|
||||
if(gear.get_integrity() <= 1)
|
||||
to_chat(occupants, "[icon2html(src, occupants)][span_danger("[gear] is critically damaged!")]")
|
||||
playsound(src, gear.destroy_sound, 50)
|
||||
|
||||
/obj/vehicle/sealed/mecha/take_damage(damage_amount, damage_type = BRUTE, damage_flag = 0, sound_effect = 1, attack_dir)
|
||||
. = ..()
|
||||
if(. && atom_integrity > 0)
|
||||
spark_system.start()
|
||||
try_deal_internal_damage(.)
|
||||
if(. >= 5 || prob(33))
|
||||
to_chat(occupants, "[icon2html(src, occupants)][span_userdanger("Taking damage!")]")
|
||||
log_message("Took [.] points of damage. Damage type: [damage_type]", LOG_MECHA)
|
||||
/obj/vehicle/sealed/mecha/take_damage(damage_amount, damage_type = BRUTE, damage_flag = "", sound_effect = TRUE, attack_dir, armour_penetration = 0)
|
||||
var/damage_taken = ..()
|
||||
if(damage_taken <= 0 || atom_integrity < 0)
|
||||
return damage_taken
|
||||
|
||||
/obj/vehicle/sealed/mecha/run_atom_armor(damage_amount, damage_type, damage_flag = 0, attack_dir, armour_penentration)
|
||||
spark_system.start()
|
||||
try_deal_internal_damage(damage_taken)
|
||||
if(damage_taken >= 5 || prob(33))
|
||||
to_chat(occupants, "[icon2html(src, occupants)][span_userdanger("Taking damage!")]")
|
||||
log_message("Took [damage_taken] points of damage. Damage type: [damage_type]", LOG_MECHA)
|
||||
|
||||
return damage_taken
|
||||
|
||||
/obj/vehicle/sealed/mecha/run_atom_armor(damage_amount, damage_type, damage_flag = 0, attack_dir, armour_penetration)
|
||||
. = ..()
|
||||
if(attack_dir)
|
||||
var/facing_modifier = get_armour_facing(abs(dir2angle(dir) - dir2angle(attack_dir)))
|
||||
@@ -113,7 +120,13 @@
|
||||
return BULLET_ACT_HIT
|
||||
log_message("Hit by projectile. Type: [hitting_projectile]([hitting_projectile.damage_type]).", LOG_MECHA, color="red")
|
||||
// yes we *have* to run the armor calc proc here I love tg projectile code too
|
||||
try_damage_component(run_atom_armor(hitting_projectile.damage, hitting_projectile.damage_type, hitting_projectile.damage_type, 0, REVERSE_DIR(hitting_projectile.dir), hitting_projectile.armour_penetration), hitting_projectile.def_zone)
|
||||
try_damage_component(run_atom_armor(
|
||||
damage_amount = hitting_projectile.damage,
|
||||
damage_type = hitting_projectile.damage_type,
|
||||
damage_flag = hitting_projectile.armor_flag,
|
||||
attack_dir = REVERSE_DIR(hitting_projectile.dir),
|
||||
armour_penetration = hitting_projectile.armour_penetration,
|
||||
), hitting_projectile.def_zone)
|
||||
return ..()
|
||||
|
||||
/obj/vehicle/sealed/mecha/ex_act(severity, target)
|
||||
@@ -258,11 +271,26 @@
|
||||
var/obj/item/mecha_parts/P = W
|
||||
P.try_attach_part(user, src, FALSE)
|
||||
return
|
||||
. = ..()
|
||||
log_message("Attacked by [W]. Attacker - [user], Damage - [.]", LOG_MECHA)
|
||||
if(isliving(user))
|
||||
var/mob/living/living_user = user
|
||||
try_damage_component(., living_user.zone_selected)
|
||||
|
||||
return ..()
|
||||
|
||||
/obj/vehicle/sealed/mecha/attacked_by(obj/item/attacking_item, mob/living/user)
|
||||
if(!attacking_item.force)
|
||||
return
|
||||
|
||||
var/damage_taken = take_damage(attacking_item.force * attacking_item.demolition_mod, attacking_item.damtype, MELEE, 1)
|
||||
try_damage_component(damage_taken, user.zone_selected)
|
||||
|
||||
var/hit_verb = length(attacking_item.attack_verb_simple) ? "[pick(attacking_item.attack_verb_simple)]" : "hit"
|
||||
user.visible_message(
|
||||
span_danger("[user] [hit_verb][plural_s(hit_verb)] [src] with [attacking_item][damage_taken ? "." : ", without leaving a mark!"]"),
|
||||
span_danger("You [hit_verb] [src] with [attacking_item][damage_taken ? "." : ", without leaving a mark!"]"),
|
||||
span_hear("You hear a [hit_verb]."),
|
||||
COMBAT_MESSAGE_RANGE,
|
||||
)
|
||||
|
||||
log_combat(user, src, "attacked", attacking_item)
|
||||
log_message("Attacked by [user]. Item - [attacking_item], Damage - [damage_taken]", LOG_MECHA)
|
||||
|
||||
/obj/vehicle/sealed/mecha/wrench_act(mob/living/user, obj/item/I)
|
||||
..()
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
if(HAS_TRAIT(M, TRAIT_PRIMITIVE)) //no lavalizards either.
|
||||
to_chat(M, span_warning("The knowledge to use this device eludes you!"))
|
||||
return
|
||||
log_message("[M] tries to move into [src].", LOG_MECHA)
|
||||
log_message("[M] tried to move into [src].", LOG_MECHA)
|
||||
if(dna_lock && M.has_dna())
|
||||
var/mob/living/carbon/entering_carbon = M
|
||||
if(entering_carbon.dna.unique_enzymes != dna_lock)
|
||||
|
||||
Reference in New Issue
Block a user