diff --git a/data/json/flags.json b/data/json/flags.json index ceeaa1d8789f9..be7860cd506de 100644 --- a/data/json/flags.json +++ b/data/json/flags.json @@ -2293,6 +2293,11 @@ "info": "This is meant for exoskeleton foot plating.", "inherit": false }, + { + "id": "ENERGY_SHIELD", + "type": "json_flag", + "info": "This piece of armor is an energy barrier and will break after absorbing a certain amount of damage." + }, { "id": "ROBOFAC_ROBOT_MEDIUM", "type": "json_flag", diff --git a/data/json/items/armor/cloaks.json b/data/json/items/armor/cloaks.json index 92297d7f41767..40d1dbe4b48b8 100644 --- a/data/json/items/armor/cloaks.json +++ b/data/json/items/armor/cloaks.json @@ -24,6 +24,36 @@ } ] }, + { + "id": "debug_energy_shield", + "type": "ARMOR", + "name": { "str": "weak energy shield" }, + "description": "A weak energy shield that protects against ballistic damage.", + "weight": "1 g", + "volume": "1 ml", + "symbol": "o", + "color": "blue", + "material": [ "ballistic_shield" ], + "material_thickness": 0.3, + "max_energy_shield_hp": 4, + "flags": [ + "AURA", + "ENERGY_SHIELD", + "OVERSIZE", + "ONLY_ONE", + "TRADER_AVOID", + "NO_TAKEOFF", + "NONCONDUCTIVE", + "ALLOWS_NATURAL_ATTACKS" + ], + "armor": [ + { + "encumbrance": 0, + "coverage": 100, + "covers": [ "leg_l", "leg_r", "torso", "arm_l", "arm_r", "hand_l", "hand_r", "head", "foot_l", "foot_r", "mouth", "eyes" ] + } + ] + }, { "id": "pride_flag", "type": "ARMOR", diff --git a/data/json/materials.json b/data/json/materials.json index b8c3bb0dac857..d7eefdff2b790 100644 --- a/data/json/materials.json +++ b/data/json/materials.json @@ -2263,6 +2263,21 @@ "cut_dmg_verb": "agitated", "resist": { "bash": 160, "cut": 200, "acid": 100, "heat": 100, "bullet": 160 } }, + { + "type": "material", + "id": "ballistic_shield", + "name": "Ballistic Barrier", + "density": 1, + "specific_heat_liquid": 0.52, + "specific_heat_solid": 0.52, + "latent_heat": 5200, + "chip_resist": 100, + "breathability": "SECOND_SKIN", + "dmg_adj": [ "resonating", "cascading", "unraveling", "fractal" ], + "bash_dmg_verb": "agitated", + "cut_dmg_verb": "agitated", + "resist": { "bash": 0, "cut": 0, "acid": 0, "heat": 0, "bullet": 100 } + }, { "type": "material", "id": "monolith_heavy", diff --git a/doc/JSON_FLAGS.md b/doc/JSON_FLAGS.md index 03f169bf215e9..8217c0ee65263 100644 --- a/doc/JSON_FLAGS.md +++ b/doc/JSON_FLAGS.md @@ -186,6 +186,7 @@ Some armor flags, such as `WATCH` and `ALARMCLOCK` are compatible with other ite - ```DEAF``` Makes the player deaf. - ```DECAY_EXPOSED_ATMOSPHERE``` Consumable will go bad once exposed to the atmosphere (such as MREs). - ```ELECTRIC_IMMUNE``` This gear completely protects you from electric discharges. +- ```ENERGY_SHIELD``` Marks a piece of armor as an energy shield. Energy shields do not suffer degradation from attacks and instead have an hp pool defined by the dialogue variable `ENERGY_SHIELD_HP` that is depleted by blocked attacks and a second variable `ENERGY_SHIELD_MAX_HP` which just stores the max hp of the shield in case it's needed for EOC manipulation. When the hp pool is depleted, the shield is destroyed. The fields `ENERGY SHIELD_HP` and `ENERGY_SHIELD_MAX_HP` are dialogue variables stored in the item, and can be modified through effects on condition. - ```EXTRA_PLATING``` Item can be worn over some armors, as additional layer of protection (like armor above brigandine); specifically can be put in pocket for armor with this flag restriction. - ```FANCY``` Wearing this clothing gives a morale bonus if the player has the `Stylish` trait. - ```FIN``` This item is swim fins aka diving fins aka flippets, and provide speed boost when you swim. diff --git a/doc/JSON_INFO.md b/doc/JSON_INFO.md index cbe92fbd5e524..5cbac8af44dbe 100644 --- a/doc/JSON_INFO.md +++ b/doc/JSON_INFO.md @@ -3672,6 +3672,7 @@ Armor can be defined like this: "cover_vitals": 10, // What percentage of critical hit damage is mitigated "material_thickness" : 1, // Thickness of material, in millimeter units (approximately). Ordinary clothes range from 0.1 to 0.5. Particularly rugged cloth may reach as high as 1-2mm, and armor or protective equipment can range as high as 10 or rarely more. "power_armor" : false, // If this is a power armor item (those are special). +"energy_shield_max_hp" : false, // Determines the inital value for the `ENERGY_SHIELD_HP` and `ENERGY_SHIELD_MAX_HP` item variables used by energy shields. The field has no effect for armor pieces without the `ENERGY_SHIELD` flag. "non_functional" : "destroyed", //this is the itype_id of an item that this turns into when destroyed. Currently only works for ablative armor. "damage_verb": "makes a crunch, something has shifted", // if an item uses non-functional this will be the description when it turns into its non functional variant. "valid_mods" : ["steel_padded"], // List of valid clothing mods. Note that if the clothing mod doesn't have "restricted" listed, this isn't needed. diff --git a/src/character_armor.cpp b/src/character_armor.cpp index beeceac69f29a..95a4a994b825c 100644 --- a/src/character_armor.cpp +++ b/src/character_armor.cpp @@ -285,18 +285,23 @@ bool Character::armor_absorb( damage_unit &du, item &armor, const bodypart_id &b armor.energy_consume( units::from_kilojoule( du.amount ), pos(), nullptr ); } + // We copy the damage unit here since it will be mutated by mitigate_damage() + damage_unit pre_mitigation = du; + // reduce the damage // -1 is passed as roll so that each material is rolled individually armor.mitigate_damage( du, sbp, -1 ); // check if the armor was damaged - item::armor_status damaged = armor.damage_armor_durability( du, bp, calculate_by_enchantment( 1, - enchant_vals::mod::EQUIPMENT_DAMAGE_CHANCE ) ); + item::armor_status damaged = armor.damage_armor_durability( du, pre_mitigation, bp, + calculate_by_enchantment( 1, + enchant_vals::mod::EQUIPMENT_DAMAGE_CHANCE ) ); // describe what happened if the armor took damage if( damaged == item::armor_status::DAMAGED || damaged == item::armor_status::DESTROYED ) { describe_damage( du, armor ); } + return damaged == item::armor_status::DESTROYED; } @@ -318,18 +323,23 @@ bool Character::armor_absorb( damage_unit &du, item &armor, const bodypart_id &b armor.energy_consume( units::from_kilojoule( du.amount ), pos(), nullptr ); } + // We copy the damage unit here since it will be mutated by mitigate_damage() + damage_unit pre_mitigation = du; + // reduce the damage // -1 is passed as roll so that each material is rolled individually armor.mitigate_damage( du, bp, -1 ); // check if the armor was damaged - item::armor_status damaged = armor.damage_armor_durability( du, bp, calculate_by_enchantment( 1, - enchant_vals::mod::EQUIPMENT_DAMAGE_CHANCE ) ); + item::armor_status damaged = armor.damage_armor_durability( du, pre_mitigation, bp, + calculate_by_enchantment( 1, + enchant_vals::mod::EQUIPMENT_DAMAGE_CHANCE ) ); // describe what happened if the armor took damage if( damaged == item::armor_status::DAMAGED || damaged == item::armor_status::DESTROYED ) { describe_damage( du, armor ); } + return damaged == item::armor_status::DESTROYED; } @@ -361,8 +371,9 @@ bool Character::ablative_armor_absorb( damage_unit &du, item &armor, const sub_b damaged = ablative_armor.damage_armor_transforms( pre_mitigation, calculate_by_enchantment( 1, enchant_vals::mod::EQUIPMENT_DAMAGE_CHANCE ) ); } else { - damaged = ablative_armor.damage_armor_durability( du, bp->parent, calculate_by_enchantment( 1, - enchant_vals::mod::EQUIPMENT_DAMAGE_CHANCE ) ); + damaged = ablative_armor.damage_armor_durability( du, pre_mitigation, bp->parent, + calculate_by_enchantment( 1, + enchant_vals::mod::EQUIPMENT_DAMAGE_CHANCE ) ); } if( damaged == item::armor_status::TRANSFORMED ) { diff --git a/src/character_attire.cpp b/src/character_attire.cpp index 0dbe4c497d9b7..1bd9d06d193ff 100644 --- a/src/character_attire.cpp +++ b/src/character_attire.cpp @@ -1950,7 +1950,7 @@ void outfit::absorb_damage( Character &guy, damage_unit &elem, bodypart_id bp, // if not already destroyed to an armor absorb destroy = guy.armor_absorb( elem, armor, bp, sbp, roll ); // for the torso we also need to consider if it hits anything hanging off the character or their neck - if( secondary_sbp != sub_bodypart_id() ) { + if( secondary_sbp != sub_bodypart_id() && !destroy ) { destroy = guy.armor_absorb( elem, armor, bp, secondary_sbp, roll ); } } diff --git a/src/flag.cpp b/src/flag.cpp index 306a8af2b4cbc..58b03ebf00dc3 100644 --- a/src/flag.cpp +++ b/src/flag.cpp @@ -103,6 +103,7 @@ const flag_id flag_EFFECT_LIMB_SCORE_MOD( "EFFECT_LIMB_SCORE_MOD" ); const flag_id flag_EFFECT_LIMB_SCORE_MOD_LOCAL( "EFFECT_LIMB_SCORE_MOD_LOCAL" ); const flag_id flag_ELECTRIC_IMMUNE( "ELECTRIC_IMMUNE" ); const flag_id flag_ELECTRONIC( "ELECTRONIC" ); +const flag_id flag_ENERGY_SHIELD( "ENERGY_SHIELD" ); const flag_id flag_ETHEREAL_ITEM( "ETHEREAL_ITEM" ); const flag_id flag_EXO_ARM_PLATE( "EXO_ARM_PLATE" ); const flag_id flag_EXO_BOOT_PLATE( "EXO_BOOT_PLATE" ); diff --git a/src/flag.h b/src/flag.h index 4da6192e1f348..01de785026fac 100644 --- a/src/flag.h +++ b/src/flag.h @@ -111,6 +111,7 @@ extern const flag_id flag_EFFECT_LIMB_SCORE_MOD; extern const flag_id flag_EFFECT_LIMB_SCORE_MOD_LOCAL; extern const flag_id flag_ELECTRIC_IMMUNE; extern const flag_id flag_ELECTRONIC; +extern const flag_id flag_ENERGY_SHIELD; extern const flag_id flag_ETHEREAL_ITEM; extern const flag_id flag_EXO_ARM_PLATE; extern const flag_id flag_EXO_BOOT_PLATE; diff --git a/src/item.cpp b/src/item.cpp index 6b5eb7bd8bf9f..61bf162b9e510 100644 --- a/src/item.cpp +++ b/src/item.cpp @@ -341,6 +341,12 @@ item::item( const itype *type, time_point turn, int qty ) : type( type ), bday( activate(); } + if( has_flag( flag_ENERGY_SHIELD ) ) { + const islot_armor *sh = find_armor_data(); + set_var( "npctalk_var_MAX_ENERGY_SHIELD_HP", sh->max_energy_shield_hp ); + set_var( "npctalk_var_ENERGY_SHIELD_HP", sh->max_energy_shield_hp ); + } + if( has_flag( flag_COLLAPSE_CONTENTS ) ) { for( item_pocket *pocket : contents.get_all_standard_pockets() ) { pocket->settings.set_collapse( true ); @@ -9040,9 +9046,25 @@ int item::repairable_levels() const : damage() > degradation(); // partial level of damage can still be repaired } -item::armor_status item::damage_armor_durability( damage_unit &du, const bodypart_id &bp, +item::armor_status item::damage_armor_durability( damage_unit &du, damage_unit &premitigated, + const bodypart_id &bp, double enchant_multiplier ) { + //Energy shields aren't damaged by attacks but do get their health variable reduced. They are also only + //damaged by the damage types they actually protect against. + if( has_var( "npctalk_var_ENERGY_SHIELD_HP" ) && resist( du.type, false, bp ) > 0.0f ) { + double shield_hp = get_var( "npctalk_var_ENERGY_SHIELD_HP", 0.0 ); + shield_hp -= premitigated.amount; + set_var( "npctalk_var_ENERGY_SHIELD_HP", shield_hp ); + if( shield_hp > 0 ) { + return armor_status::UNDAMAGED; + } else { + //Shields deliberately ignore the enchantment multiplier, as the health mechanic wouldn't make sense otherwise. + mod_damage( itype::damage_scale * 6 ); + return armor_status::DESTROYED; + } + } + if( has_flag( flag_UNBREAKABLE ) || enchant_multiplier <= 0.0f ) { return armor_status::UNDAMAGED; } @@ -9053,8 +9075,9 @@ item::armor_status item::damage_armor_durability( damage_unit &du, const bodypar // This is some weird type that doesn't damage armors return armor_status::UNDAMAGED; } - // Fragile items take damage if the block more than 15% of their armor value - if( has_flag( flag_FRAGILE ) && du.amount / armors_own_resist > ( 0.15f / enchant_multiplier ) ) { + // Fragile items take damage if they block more than 15% of their armor value, this uses the pre-mitigated damage. + if( has_flag( flag_FRAGILE ) && + premitigated.amount / armors_own_resist > ( 0.15f / enchant_multiplier ) ) { return mod_damage( itype::damage_scale * enchant_multiplier ) ? armor_status::DESTROYED : armor_status::DAMAGED; } diff --git a/src/item.h b/src/item.h index 79f63ee4675ff..6c5f00df08d8e 100644 --- a/src/item.h +++ b/src/item.h @@ -1408,7 +1408,8 @@ class item : public visitable * This version is for items with durability * @return the state of the armor */ - armor_status damage_armor_durability( damage_unit &du, const bodypart_id &bp, + armor_status damage_armor_durability( damage_unit &du, damage_unit &premitigated, + const bodypart_id &bp, double enchant_multiplier = 1 ); /** diff --git a/src/item_factory.cpp b/src/item_factory.cpp index 94675ed073fbb..763c7c2a282e1 100644 --- a/src/item_factory.cpp +++ b/src/item_factory.cpp @@ -2032,6 +2032,9 @@ void Item_factory::check_definitions() const if( !type->category_force.is_valid() ) { msg += "undefined category " + type->category_force.str() + "\n"; } + if( type->has_flag( flag_ENERGY_SHIELD ) && !type->armor ) { + msg += "has ENERGY_SHIELD flag specified but the item isn't armor"; + } if( type->armor ) { cata::flat_set observed_bps; @@ -3111,6 +3114,7 @@ void islot_armor::load( const JsonObject &jo ) optional( jo, was_loaded, "non_functional", non_functional, itype_id() ); optional( jo, was_loaded, "damage_verb", damage_verb ); optional( jo, was_loaded, "power_armor", power_armor, false ); + optional( jo, was_loaded, "max_energy_shield_hp", max_energy_shield_hp, 0 ); optional( jo, was_loaded, "valid_mods", valid_mods ); } diff --git a/src/itype.h b/src/itype.h index 99c12781cc565..b29de877b3917 100644 --- a/src/itype.h +++ b/src/itype.h @@ -414,6 +414,12 @@ struct islot_armor { * How much warmth this item provides. */ int warmth = 0; + /** + * The max health of an energy shield type armor. Value is completely ignored if the + * ENERGY_SHIELD flag is not set. This value and "energy_shield_hp" are then stored + * through item variables so that they might be manipulated with EOCS and magic. + */ + int max_energy_shield_hp = 0; /** * Whether this is a power armor item. */ diff --git a/src/melee.cpp b/src/melee.cpp index 14cff849ec013..18c185843562b 100644 --- a/src/melee.cpp +++ b/src/melee.cpp @@ -2052,7 +2052,7 @@ bool Character::block_hit( Creature *source, bodypart_id &bp_hit, damage_instanc if( source != nullptr && !source->is_hallucination() ) { for( damage_unit &du : dam.damage_units ) { - shield->damage_armor_durability( du, bp_hit, calculate_by_enchantment( 1, + shield->damage_armor_durability( du, du, bp_hit, calculate_by_enchantment( 1, enchant_vals::mod::EQUIPMENT_DAMAGE_CHANCE ) ); } }