From 81b8f962b72c41a858306bf0f1611a644ca4dcfd Mon Sep 17 00:00:00 2001 From: Useroth <37159550+Useroth@users.noreply.github.com> Date: Sun, 12 Jun 2022 22:03:17 +0200 Subject: [PATCH] Mirror 66967 & 67411 (#14279) * Implements a Demolition Modifier variable to items, affects damage vs structures and robots. (#66967) Adds a modifier variable which can be used to increase or decrease a given items damage to structures, machinery, vehicles, and robots (including cyborgs, simple-bots, and anything else with the MOB_ROBOTIC biotype) * 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) Co-authored-by: itseasytosee <55666666+itseasytosee@users.noreply.github.com> Co-authored-by: MrMelbert <51863163+MrMelbert@users.noreply.github.com> --- code/_onclick/item_attack.dm | 27 +++--- code/game/machinery/_machinery.dm | 1 + code/game/objects/items/chainsaw.dm | 1 + code/game/objects/items/extinguisher.dm | 1 + code/game/objects/items/fireaxe.dm | 1 + code/game/objects/items/his_grace.dm | 1 + code/game/objects/items/knives.dm | 1 + code/game/objects/items/maintenance_loot.dm | 1 + code/game/objects/items/melee/misc.dm | 3 + code/game/objects/items/spear.dm | 1 + code/game/objects/items/stacks/rods.dm | 1 + code/game/objects/items/storage/toolbox.dm | 1 + code/game/objects/items/tanks/tanks.dm | 3 +- code/game/objects/items/tools/crowbar.dm | 1 + code/game/objects/items/tools/screwdriver.dm | 1 + code/game/objects/items/tools/wrench.dm | 1 + code/game/objects/items/weaponry.dm | 1 + code/game/objects/objs.dm | 21 +++++ .../objects/structures/beds_chairs/chair.dm | 1 + .../heretic/knowledge/side_void_blade.dm | 14 +-- .../food_and_drinks/drinks/drinks/bottle.dm | 2 + code/modules/mining/equipment/mining_tools.dm | 1 + code/modules/paperwork/pen.dm | 7 ++ code/modules/surgery/tools.dm | 2 + code/modules/unit_tests/_unit_tests.dm | 1 + code/modules/unit_tests/mecha_damage.dm | 86 +++++++++++++++++++ .../mecha/equipment/mecha_equipment.dm | 3 + code/modules/vehicles/mecha/mecha_defense.dm | 66 ++++++++++---- .../vehicles/mecha/mecha_mob_interaction.dm | 2 +- 29 files changed, 207 insertions(+), 46 deletions(-) create mode 100644 code/modules/unit_tests/mecha_damage.dm diff --git a/code/_onclick/item_attack.dm b/code/_onclick/item_attack.dm index bc3d802e481..f124f8bf72a 100644 --- a/code/_onclick/item_attack.dm +++ b/code/_onclick/item_attack.dm @@ -241,18 +241,21 @@ /area/attacked_by(obj/item/attacking_item, mob/living/user) CRASH("areas are NOT supposed to have attacked_by() called on them!") -/mob/living/attacked_by(obj/item/I, mob/living/user) - send_item_attack_message(I, user) - if(I.force) - apply_damage(I.force, I.damtype) - if(I.damtype == BRUTE) - if(prob(33)) - I.add_mob_blood(src) - var/turf/location = get_turf(src) - add_splatter_floor(location) - if(get_dist(user, src) <= 1) //people with TK won't get smeared with blood - user.add_mob_blood(src) - return TRUE //successful attack +/mob/living/attacked_by(obj/item/attacking_item, mob/living/user) + send_item_attack_message(attacking_item, user) + if(!attacking_item.force) + return FALSE + var/damage = attacking_item.force + if(mob_biotypes & MOB_ROBOTIC) + damage *= attacking_item.demolition_mod + apply_damage(damage, attacking_item.damtype) + if(attacking_item.damtype == BRUTE && prob(33)) + attacking_item.add_mob_blood(src) + var/turf/location = get_turf(src) + add_splatter_floor(location) + if(get_dist(user, src) <= 1) //people with TK won't get smeared with blood + user.add_mob_blood(src) + return TRUE //successful attack /mob/living/simple_animal/attacked_by(obj/item/I, mob/living/user) if(!attack_threshold_check(I.force, I.damtype, MELEE, FALSE)) diff --git a/code/game/machinery/_machinery.dm b/code/game/machinery/_machinery.dm index 6f6e5c6a71b..70a8dc71943 100644 --- a/code/game/machinery/_machinery.dm +++ b/code/game/machinery/_machinery.dm @@ -746,6 +746,7 @@ LAZYCLEARLIST(component_parts) return ..() + /** * Spawns a frame where this machine is. If the machine was not disassmbled, the * frame is spawned damaged. If the frame couldn't exist on this turf, it's smashed diff --git a/code/game/objects/items/chainsaw.dm b/code/game/objects/items/chainsaw.dm index aec9919518f..0140d3d39d2 100644 --- a/code/game/objects/items/chainsaw.dm +++ b/code/game/objects/items/chainsaw.dm @@ -13,6 +13,7 @@ throwforce = 13 throw_speed = 2 throw_range = 4 + demolition_mod = 1.5 custom_materials = list(/datum/material/iron=13000) attack_verb_continuous = list("saws", "tears", "lacerates", "cuts", "chops", "dices") attack_verb_simple = list("saw", "tear", "lacerate", "cut", "chop", "dice") diff --git a/code/game/objects/items/extinguisher.dm b/code/game/objects/items/extinguisher.dm index d8c4366031c..f2dd8facd86 100644 --- a/code/game/objects/items/extinguisher.dm +++ b/code/game/objects/items/extinguisher.dm @@ -12,6 +12,7 @@ throw_speed = 2 throw_range = 7 force = 10 + demolition_mod = 1.25 custom_materials = list(/datum/material/iron = 90) attack_verb_continuous = list("slams", "whacks", "bashes", "thunks", "batters", "bludgeons", "thrashes") attack_verb_simple = list("slam", "whack", "bash", "thunk", "batter", "bludgeon", "thrash") diff --git a/code/game/objects/items/fireaxe.dm b/code/game/objects/items/fireaxe.dm index 33a3b1d873a..38075a6a9b5 100644 --- a/code/game/objects/items/fireaxe.dm +++ b/code/game/objects/items/fireaxe.dm @@ -10,6 +10,7 @@ desc = "Truly, the weapon of a madman. Who would think to fight fire with an axe?" force = 5 throwforce = 15 + demolition_mod = 1.25 w_class = WEIGHT_CLASS_BULKY slot_flags = ITEM_SLOT_BACK attack_verb_continuous = list("attacks", "chops", "cleaves", "tears", "lacerates", "cuts") diff --git a/code/game/objects/items/his_grace.dm b/code/game/objects/items/his_grace.dm index e3a040cc9a1..453af6f6e7c 100644 --- a/code/game/objects/items/his_grace.dm +++ b/code/game/objects/items/his_grace.dm @@ -14,6 +14,7 @@ righthand_file = 'icons/mob/inhands/equipment/toolbox_righthand.dmi' w_class = WEIGHT_CLASS_GIGANTIC force = 12 + demolition_mod = 1.25 attack_verb_continuous = list("robusts") attack_verb_simple = list("robust") hitsound = 'sound/weapons/smash.ogg' diff --git a/code/game/objects/items/knives.dm b/code/game/objects/items/knives.dm index 7ad625fd366..234795614b9 100644 --- a/code/game/objects/items/knives.dm +++ b/code/game/objects/items/knives.dm @@ -10,6 +10,7 @@ desc = "The original knife, it is said that all other knives are only copies of this one." flags_1 = CONDUCT_1 force = 10 + demolition_mod = 0.75 w_class = WEIGHT_CLASS_SMALL throwforce = 10 hitsound = 'sound/weapons/bladeslice.ogg' diff --git a/code/game/objects/items/maintenance_loot.dm b/code/game/objects/items/maintenance_loot.dm index c6a5b412622..1df9c861a32 100644 --- a/code/game/objects/items/maintenance_loot.dm +++ b/code/game/objects/items/maintenance_loot.dm @@ -18,6 +18,7 @@ throw_range = 4 w_class = WEIGHT_CLASS_BULKY wound_bonus = 20 + demolition_mod = 1.25 grind_results = list(/datum/reagent/lead = 20) //A good battery early in the shift. Source of lead & sulfuric acid reagents. diff --git a/code/game/objects/items/melee/misc.dm b/code/game/objects/items/melee/misc.dm index 267753303ad..3006069931f 100644 --- a/code/game/objects/items/melee/misc.dm +++ b/code/game/objects/items/melee/misc.dm @@ -21,6 +21,7 @@ slot_flags = ITEM_SLOT_BELT force = 10 throwforce = 7 + demolition_mod = 0.25 wound_bonus = 15 bare_wound_bonus = 10 w_class = WEIGHT_CLASS_NORMAL @@ -64,6 +65,7 @@ obj_flags = UNIQUE_RENAME force = 15 throwforce = 10 + demolition_mod = 0.75 //but not metal w_class = WEIGHT_CLASS_BULKY block_chance = 50 armour_penetration = 75 @@ -283,6 +285,7 @@ worn_icon_state = "whip" slot_flags = ITEM_SLOT_BELT force = 15 + demolition_mod = 0.25 w_class = WEIGHT_CLASS_NORMAL attack_verb_continuous = list("flogs", "whips", "lashes", "disciplines") attack_verb_simple = list("flog", "whip", "lash", "discipline") diff --git a/code/game/objects/items/spear.dm b/code/game/objects/items/spear.dm index ff886c68137..c58e1ba03f1 100644 --- a/code/game/objects/items/spear.dm +++ b/code/game/objects/items/spear.dm @@ -10,6 +10,7 @@ slot_flags = ITEM_SLOT_BACK throwforce = 20 throw_speed = 4 + demolition_mod = 0.75 embedding = list("impact_pain_mult" = 2, "remove_pain_mult" = 4, "jostle_chance" = 2.5) armour_penetration = 10 custom_materials = list(/datum/material/iron=1150, /datum/material/glass=2075) diff --git a/code/game/objects/items/stacks/rods.dm b/code/game/objects/items/stacks/rods.dm index 6e4812868cc..3095a08aa12 100644 --- a/code/game/objects/items/stacks/rods.dm +++ b/code/game/objects/items/stacks/rods.dm @@ -23,6 +23,7 @@ GLOBAL_LIST_INIT(rod_recipes, list ( \ throwforce = 10 throw_speed = 3 throw_range = 7 + demolition_mod = 1.25 mats_per_unit = list(/datum/material/iron=1000) max_amount = 50 attack_verb_continuous = list("hits", "bludgeons", "whacks") diff --git a/code/game/objects/items/storage/toolbox.dm b/code/game/objects/items/storage/toolbox.dm index 5dba4d3db50..ec345cfa949 100644 --- a/code/game/objects/items/storage/toolbox.dm +++ b/code/game/objects/items/storage/toolbox.dm @@ -11,6 +11,7 @@ throwforce = 12 throw_speed = 2 throw_range = 7 + demolition_mod = 1.25 w_class = WEIGHT_CLASS_BULKY custom_materials = list(/datum/material/iron = 500) attack_verb_continuous = list("robusts") diff --git a/code/game/objects/items/tanks/tanks.dm b/code/game/objects/items/tanks/tanks.dm index 093048267da..6a50e0fb7c6 100644 --- a/code/game/objects/items/tanks/tanks.dm +++ b/code/game/objects/items/tanks/tanks.dm @@ -22,6 +22,7 @@ throwforce = 10 throw_speed = 1 throw_range = 4 + demolition_mod = 1.25 custom_materials = list(/datum/material/iron = 500) actions_types = list(/datum/action/item_action/set_internals) armor = list(MELEE = 0, BULLET = 0, LASER = 0, ENERGY = 0, BOMB = 10, BIO = 0, FIRE = 80, ACID = 30) @@ -89,7 +90,7 @@ AddComponent(/datum/component/atmos_reaction_recorder, reset_criteria = list(COMSIG_GASMIX_MERGING = air_contents, COMSIG_GASMIX_REMOVING = air_contents), target_list = reaction_info) - // This is separate from the reaction recorder. + // This is separate from the reaction recorder. // In this case we are only listening to determine if the tank is overpressurized but not destroyed. RegisterSignal(air_contents, COMSIG_GASMIX_MERGED, .proc/merging_information) diff --git a/code/game/objects/items/tools/crowbar.dm b/code/game/objects/items/tools/crowbar.dm index 0c2ab89028f..dcc4b4d17ca 100644 --- a/code/game/objects/items/tools/crowbar.dm +++ b/code/game/objects/items/tools/crowbar.dm @@ -10,6 +10,7 @@ slot_flags = ITEM_SLOT_BELT force = 5 throwforce = 7 + demolition_mod = 1.25 w_class = WEIGHT_CLASS_SMALL custom_materials = list(/datum/material/iron=50) drop_sound = 'sound/items/handling/crowbar_drop.ogg' diff --git a/code/game/objects/items/tools/screwdriver.dm b/code/game/objects/items/tools/screwdriver.dm index 4976e7e8915..0a840481a20 100644 --- a/code/game/objects/items/tools/screwdriver.dm +++ b/code/game/objects/items/tools/screwdriver.dm @@ -11,6 +11,7 @@ flags_1 = CONDUCT_1 | IS_PLAYER_COLORABLE_1 slot_flags = ITEM_SLOT_BELT force = 5 + demolition_mod = 0.5 w_class = WEIGHT_CLASS_TINY throwforce = 5 throw_speed = 3 diff --git a/code/game/objects/items/tools/wrench.dm b/code/game/objects/items/tools/wrench.dm index 2c72e0b6539..819c3242ce3 100644 --- a/code/game/objects/items/tools/wrench.dm +++ b/code/game/objects/items/tools/wrench.dm @@ -10,6 +10,7 @@ slot_flags = ITEM_SLOT_BELT force = 5 throwforce = 7 + demolition_mod = 1.25 w_class = WEIGHT_CLASS_SMALL usesound = 'sound/items/ratchet.ogg' custom_materials = list(/datum/material/iron=150) diff --git a/code/game/objects/items/weaponry.dm b/code/game/objects/items/weaponry.dm index 5dd708b39b8..3463f7ad9a6 100644 --- a/code/game/objects/items/weaponry.dm +++ b/code/game/objects/items/weaponry.dm @@ -644,6 +644,7 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301 force = 12 wound_bonus = -10 throwforce = 12 + demolition_mod = 1.25 attack_verb_continuous = list("beats", "smacks") attack_verb_simple = list("beat", "smack") custom_materials = list(/datum/material/wood = MINERAL_MATERIAL_AMOUNT * 3.5) diff --git a/code/game/objects/objs.dm b/code/game/objects/objs.dm index 4a1d436adc7..aa1a6b6fe86 100644 --- a/code/game/objects/objs.dm +++ b/code/game/objects/objs.dm @@ -13,6 +13,9 @@ /// If this attacks a human with no wound armor on the affected body part, add this to the wound mod. Some attacks may be significantly worse at wounding if there's even a slight layer of armor to absorb some of it vs bare flesh var/bare_wound_bonus = 0 + /// A multiplier to an objecet's force when used against a stucture, vechicle, machine, or robot. + var/demolition_mod = 1 + var/current_skin //Has the item been reskinned? var/list/unique_reskin //List of options to reskin. @@ -82,6 +85,24 @@ SStgui.close_uis(src) . = ..() +/obj/attacked_by(obj/item/attacking_item, mob/living/user) + if(!attacking_item.force) + return + + var/total_force = (attacking_item.force * attacking_item.demolition_mod) + + var/damage = take_damage(total_force, attacking_item.damtype, MELEE, 1) + + var/damage_verb = "hit" + + if(attacking_item.demolition_mod > 1 && damage) + damage_verb = "pulverise" + if(attacking_item.demolition_mod < 1) + damage_verb = "ineffectively pierce" + + user.visible_message(span_danger("[user] [damage_verb][plural_s(damage_verb)] [src] with [attacking_item][damage ? "." : ", without leaving a mark!"]"), \ + span_danger("You [damage_verb] [src] with [attacking_item][damage ? "." : ", without leaving a mark!"]"), null, COMBAT_MESSAGE_RANGE) + log_combat(user, src, "attacked", attacking_item) /obj/throw_at(atom/target, range, speed, mob/thrower, spin=1, diagonals_first = 0, datum/callback/callback, force, gentle = FALSE, quickstart = TRUE) . = ..() diff --git a/code/game/objects/structures/beds_chairs/chair.dm b/code/game/objects/structures/beds_chairs/chair.dm index 9cafd702fb1..07f06156781 100644 --- a/code/game/objects/structures/beds_chairs/chair.dm +++ b/code/game/objects/structures/beds_chairs/chair.dm @@ -321,6 +321,7 @@ MAPPING_DIRECTIONAL_HELPERS(/obj/structure/chair/stool/bar, 0) w_class = WEIGHT_CLASS_HUGE force = 8 throwforce = 10 + demolition_mod = 1.25 throw_range = 3 hitsound = 'sound/items/trayhit1.ogg' hit_reaction_chance = 50 diff --git a/code/modules/antagonists/heretic/knowledge/side_void_blade.dm b/code/modules/antagonists/heretic/knowledge/side_void_blade.dm index 4a0f993dc54..f9445e8516c 100644 --- a/code/modules/antagonists/heretic/knowledge/side_void_blade.dm +++ b/code/modules/antagonists/heretic/knowledge/side_void_blade.dm @@ -112,6 +112,7 @@ sharpness = SHARP_EDGED wound_bonus = -30 bare_wound_bonus = 15 + demolition_mod = 1.5 /obj/item/risen_hand/Initialize(mapload) . = ..() @@ -129,19 +130,6 @@ else icon_state = "[base_icon_state]_left" -/obj/item/risen_hand/pre_attack(atom/hit, mob/living/user, params) - . = ..() - if(.) - return - - // If it's a structure or machine, we get a damage bonus (allowing us to break down doors) - if(isstructure(hit) || ismachinery(hit)) - force = initial(force) * 1.5 - - // If it's another other item make sure we're at normal force - else - force = initial(force) - /datum/heretic_knowledge/rune_carver name = "Carving Knife" desc = "Allows you to transmute a knife, a shard of glass, and a piece of paper to create a Carving Knife. \ diff --git a/code/modules/food_and_drinks/drinks/drinks/bottle.dm b/code/modules/food_and_drinks/drinks/drinks/bottle.dm index d451dafd99e..1eb2b5798bb 100644 --- a/code/modules/food_and_drinks/drinks/drinks/bottle.dm +++ b/code/modules/food_and_drinks/drinks/drinks/bottle.dm @@ -15,6 +15,7 @@ volume = 100 force = 15 //Smashing bottles over someone's head hurts. throwforce = 15 + demolition_mod = 0.25 inhand_icon_state = "beer" //Generic held-item sprite until unique ones are made. lefthand_file = 'icons/mob/inhands/misc/food_lefthand.dmi' righthand_file = 'icons/mob/inhands/misc/food_righthand.dmi' @@ -119,6 +120,7 @@ throwforce = 5 throw_speed = 3 throw_range = 5 + demolition_mod = 0.25 w_class = WEIGHT_CLASS_TINY inhand_icon_state = "broken_beer" lefthand_file = 'icons/mob/inhands/misc/food_lefthand.dmi' diff --git a/code/modules/mining/equipment/mining_tools.dm b/code/modules/mining/equipment/mining_tools.dm index f05077134ba..34801bcff68 100644 --- a/code/modules/mining/equipment/mining_tools.dm +++ b/code/modules/mining/equipment/mining_tools.dm @@ -7,6 +7,7 @@ slot_flags = ITEM_SLOT_BELT | ITEM_SLOT_BACK force = 15 throwforce = 10 + demolition_mod = 1.15 lefthand_file = 'icons/mob/inhands/equipment/mining_lefthand.dmi' righthand_file = 'icons/mob/inhands/equipment/mining_righthand.dmi' w_class = WEIGHT_CLASS_BULKY diff --git a/code/modules/paperwork/pen.dm b/code/modules/paperwork/pen.dm index 954ba49f78e..e87e0ec59fa 100644 --- a/code/modules/paperwork/pen.dm +++ b/code/modules/paperwork/pen.dm @@ -309,3 +309,10 @@ toolspeed = 10 //You will never willingly choose to use one of these over a shovel. font = FOUNTAIN_PEN_FONT colour = "#0000FF" + +/obj/item/pen/destroyer + name = "Fine Tipped Pen" + desc = "A pen with an infinitly sharpened tip. Capable of striking the weakest point of a strucutre or robot and annihilating it instantly. Good at putting holes in people too." + force = 5 + wound_bonus = 100 + demolition_mod = 9000 diff --git a/code/modules/surgery/tools.dm b/code/modules/surgery/tools.dm index bcd4b36aa6b..9067980f569 100644 --- a/code/modules/surgery/tools.dm +++ b/code/modules/surgery/tools.dm @@ -116,6 +116,7 @@ flags_1 = CONDUCT_1 item_flags = SURGICAL_TOOL force = 15 + demolition_mod = 0.5 w_class = WEIGHT_CLASS_NORMAL attack_verb_continuous = list("drills") attack_verb_simple = list("drill") @@ -154,6 +155,7 @@ flags_1 = CONDUCT_1 item_flags = SURGICAL_TOOL force = 10 + demolition_mod = 0.25 w_class = WEIGHT_CLASS_TINY throwforce = 5 throw_speed = 3 diff --git a/code/modules/unit_tests/_unit_tests.dm b/code/modules/unit_tests/_unit_tests.dm index 44483204809..b4f880c61ca 100644 --- a/code/modules/unit_tests/_unit_tests.dm +++ b/code/modules/unit_tests/_unit_tests.dm @@ -107,6 +107,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" diff --git a/code/modules/unit_tests/mecha_damage.dm b/code/modules/unit_tests/mecha_damage.dm new file mode 100644 index 00000000000..6bc5e37069f --- /dev/null +++ b/code/modules/unit_tests/mecha_damage.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])") diff --git a/code/modules/vehicles/mecha/equipment/mecha_equipment.dm b/code/modules/vehicles/mecha/equipment/mecha_equipment.dm index cc2794675ab..62032b6ab38 100644 --- a/code/modules/vehicles/mecha/equipment/mecha_equipment.dm +++ b/code/modules/vehicles/mecha/equipment/mecha_equipment.dm @@ -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 diff --git a/code/modules/vehicles/mecha/mecha_defense.dm b/code/modules/vehicles/mecha/mecha_defense.dm index c8a4b6322af..9b25bbb6c7b 100644 --- a/code/modules/vehicles/mecha/mecha_defense.dm +++ b/code/modules/vehicles/mecha/mecha_defense.dm @@ -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) ..() diff --git a/code/modules/vehicles/mecha/mecha_mob_interaction.dm b/code/modules/vehicles/mecha/mecha_mob_interaction.dm index aa3f6f3f56f..de2e6b68c75 100644 --- a/code/modules/vehicles/mecha/mecha_mob_interaction.dm +++ b/code/modules/vehicles/mecha/mecha_mob_interaction.dm @@ -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)