From 3f514c18c25ca9d296d1f8432faa984b4ebda615 Mon Sep 17 00:00:00 2001 From: Venera3 Date: Fri, 25 Aug 2023 07:39:49 +0200 Subject: [PATCH 01/16] WIP attack vector --- data/json/mutations/mutation_techs.json | 17 ++- src/bodypart.cpp | 5 + src/bodypart.h | 2 + src/character_martial_arts.h | 2 + src/init.cpp | 1 + src/martialarts.cpp | 148 ++++++++++++++++++++++++ src/martialarts.h | 27 +++++ src/melee.cpp | 2 + src/type_id.h | 3 + 9 files changed, 203 insertions(+), 4 deletions(-) diff --git a/data/json/mutations/mutation_techs.json b/data/json/mutations/mutation_techs.json index dfa8b9c0c79a0..e8c7844995c9a 100644 --- a/data/json/mutations/mutation_techs.json +++ b/data/json/mutations/mutation_techs.json @@ -7,10 +7,8 @@ "unarmed_allowed": true, "melee_allowed": true, "attack_override": true, - "weighting": -2, + "weighting": 200, "crit_ok": true, - "required_char_flags": [ "SEESLEEP" ], - "forbidden_char_flags": [ "EYE_MEMBRANE" ], "repeat_min": 1, "repeat_max": 5, "mult_bonuses": [ { "stat": "damage", "type": "bullet", "scale": 15 } ], @@ -30,6 +28,17 @@ "message": "You bleed the %s dry!" }, { "id": "downed", "chance": 50, "duration": 2, "message": "You bonk the %s real good", "req_flag": "DREAMY" } - ] + ], + "wip_attack_vectors": [ "test_test" ], + "attack_vectors": [ "HAND" ] + }, + { + "type": "attack_vector", + "id": "test_test", + "limbs": [ "debug_tail" ], + "limb_types": [ "sensor" ], + "sub_limbs": [ "sub_limb_debug_tail"], + "encumbrance_limit": 33, + "bp_hp_limit": 75 } ] diff --git a/src/bodypart.cpp b/src/bodypart.cpp index 73f9a04ed6988..e6f000e294d7c 100644 --- a/src/bodypart.cpp +++ b/src/bodypart.cpp @@ -607,6 +607,11 @@ float body_part_type::unarmed_damage( const damage_type_id &dt ) const return damage.type_damage( dt ); } +float body_part_type::total_unarmed_damage() const +{ + return damage.total_damage(); +} + float body_part_type::unarmed_arpen( const damage_type_id &dt ) const { return damage.type_arpen( dt ); diff --git a/src/bodypart.h b/src/bodypart.h index 72e0f8cdf2fab..bf2255e340f44 100644 --- a/src/bodypart.h +++ b/src/bodypart.h @@ -368,6 +368,8 @@ struct body_part_type { } float unarmed_damage( const damage_type_id &dt ) const; + // return the total amount of unarmed damage this limb would do + float total_unarmed_damage() const; float unarmed_arpen( const damage_type_id &dt ) const; float damage_resistance( const damage_type_id &dt ) const; diff --git a/src/character_martial_arts.h b/src/character_martial_arts.h index 958414e393371..ffc450248b636 100644 --- a/src/character_martial_arts.h +++ b/src/character_martial_arts.h @@ -80,6 +80,8 @@ class character_martial_arts /** Fires all kill-triggered martial arts events */ void ma_onkill_effects( Character &owner ); + // Selects a valid attack vector + attack_vector_id choose_attack_vector( const Character &user, const matec_id &tech ) const; /** Returns an attack vector that the player can use */ std::string get_valid_attack_vector( const Character &user, const std::vector &attack_vectors ) const; diff --git a/src/init.cpp b/src/init.cpp index a50437e9a717f..319c26141a747 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -407,6 +407,7 @@ void DynamicDataLoader::initialize() add( "weapon_category", &weapon_category::load_weapon_categories ); add( "martial_art", &load_martial_art ); add( "climbing_aid", &climbing_aid::load_climbing_aid ); + add( "attack_vector", &wip_attack_vector::load_attack_vectors ); add( "effect_type", &load_effect_type ); add( "oter_id_migration", &overmap::load_oter_id_migration ); add( "overmap_terrain", &overmap_terrains::load ); diff --git a/src/martialarts.cpp b/src/martialarts.cpp index 8422e26fff781..8d6dc02502e83 100644 --- a/src/martialarts.cpp +++ b/src/martialarts.cpp @@ -34,6 +34,9 @@ #include "translations.h" #include "ui_manager.h" #include "value_ptr.h" +#include "weighted_list.h" + +static const attack_vector_id attack_vector_null( "null" ); static const bionic_id bio_armor_arms( "bio_armor_arms" ); static const bionic_id bio_armor_legs( "bio_armor_legs" ); @@ -50,14 +53,51 @@ static const skill_id skill_unarmed( "unarmed" ); static const weapon_category_id weapon_category_OTHER_INVALID_WEAP_CAT( "OTHER_INVALID_WEAP_CAT" ); + namespace { generic_factory weapon_category_factory( "weapon category" ); generic_factory ma_techniques( "martial art technique" ); generic_factory martialarts( "martial art style" ); generic_factory ma_buffs( "martial art buff" ); +generic_factory attack_vector_factory( "attack vector" ); } // namespace +/** @relates string_id */ +template<> +const wip_attack_vector &string_id::obj() const +{ + return attack_vector_factory.obj( *this ); +} + +template<> +bool attack_vector_id::is_valid() const +{ + return attack_vector_factory.is_valid( *this ); +} + +void wip_attack_vector::load_attack_vectors( const JsonObject &jo, const std::string &src ) +{ + attack_vector_factory.load( jo, src ); +} + +void wip_attack_vector::reset() +{ + attack_vector_factory.reset(); +} + +void wip_attack_vector::load( const JsonObject &jo, const std::string_view ) +{ + mandatory( jo, was_loaded, "id", id ); + optional( jo, was_loaded, "weapon", weapon, false ); + optional( jo, was_loaded, "limbs", limbs ); + optional( jo, was_loaded, "sub_limbs", sub_limbs ); + optional( jo, was_loaded, "limb_types", limb_types ); + optional( jo, was_loaded, "encumbrance_limit", encumbrance_limit ); + optional( jo, was_loaded, "bp_hp_limit", bp_hp_limit ); + +} + template<> const weapon_category &weapon_category_id::obj() const { @@ -277,6 +317,9 @@ void ma_technique::load( const JsonObject &jo, const std::string &src ) has_condition = true; } + if( jo.has_array( "wip_attack_vectors" ) ) { + optional( jo, was_loaded, "wip_attack_vectors", wip_attack_vectors ); + } reqs.load( jo, src ); bonuses.load( jo ); } @@ -1384,6 +1427,111 @@ std::string character_martial_arts::get_valid_attack_vector( const Character &us return "NONE"; } +attack_vector_id character_martial_arts::choose_attack_vector( const Character &user, + const matec_id &tech ) const +{ + // Handle choosing the attack vector + // Deal with hard filters - DONE + // Consider expected damage? + // Weighted list based on damage? Hard priority? + // Return explicit bodypart id for unarmed stuff? + // + const std::vector anat = user.get_all_body_parts(); + weighted_float_list list; + for( const attack_vector_id &vec : tech.obj().wip_attack_vectors ) { + wip_attack_vector tmp = vec.obj(); + bool found = false; + float weight = 0.0f; + // Early break for armed vectors + if( tmp.weapon && user.is_armed() ) { + item *weapon = user.get_wielded_item().get_item(); + weight = weapon->average_dps( user ); + if( tmp.primary ) { + return vec; + } else { + list.add_or_replace( vec, weight ); + } + add_msg_debug( debugmode::DF_MELEE, "Weapon %s eligable for attack vector %s with weight %.1f", + weapon->display_name(), + vec.c_str(), weight ); + found = true; + break; + } + if( found ) { + continue; + } + // If we defined explicit bodyparts + for( const bodypart_id &bp : vec.obj().limbs ) { + if( std::find( anat.begin(), anat.end(), bp ) != anat.end() ) { + const bodypart &part = *user.get_part( bp ); + if( ( 100 * part.get_hp_cur() / part.get_hp_max() ) > tmp.bp_hp_limit && + part.get_encumbrance_data().encumbrance < tmp.encumbrance_limit ) { + // Use the BP unarmed damage as the weight + weight = bp.obj().total_unarmed_damage(); + if( tmp.primary ) { + return vec; + } else { + list.add_or_replace( vec, weight ); + } + add_msg_debug( debugmode::DF_MELEE, "Bodypart %s eligable for attack vector %s with weight %.1f", + bp.id().c_str(), + vec.c_str(), weight ); + found = true; + break; + } + } + } + if( found ) { + continue; + } + // Sublimb check + for( const sub_bodypart_str_id &sbp : tmp.sub_limbs ) { + for( const bodypart_id &bp : anat ) { + if( std::find( bp.obj().sub_parts.begin(), bp.obj().sub_parts.end(), + sbp ) != bp.obj().sub_parts.end() ) { + list.add_or_replace( vec, weight ); + add_msg_debug( debugmode::DF_MELEE, + "Sub-bodypart %s eligable for attack vector %s with weight %.1f", sbp.c_str(), + vec.c_str(), weight ); + found = true; + break; + } + } + if( found ) { + break; + } + } + if( found ) { + continue; + } + // More flexible search for limb types + for( const body_part_type::type &type : tmp.limb_types ) { + std::vector limbs = user.get_all_body_parts_of_type( type ); + for( const bodypart_id &bp : limbs ) { + const bodypart &part = *user.get_part( bp ); + if( ( 100 * part.get_hp_cur() / part.get_hp_max() ) > tmp.bp_hp_limit && + part.get_encumbrance_data().encumbrance < tmp.encumbrance_limit ) { + list.add_or_replace( vec, weight ); + add_msg_debug( debugmode::DF_MELEE, + "Bodypart %s eligable for attack vector %s (limb type filter) with weight %.1f", + bp.id().c_str(), + vec.c_str(), weight ); + found = true; + break; + + } + } + if( found ) { + break; + } + } + if( found ) { + continue; + } + } + return *list.pick(); +} + bool character_martial_arts::can_use_attack_vector( const Character &user, const std::string &av ) const { diff --git a/src/martialarts.h b/src/martialarts.h index 196d473e9b93e..494fe583f6dd2 100644 --- a/src/martialarts.h +++ b/src/martialarts.h @@ -66,6 +66,32 @@ class weapon_category matype_id martial_art_learned_from( const itype & ); +struct wip_attack_vector { + attack_vector_id id; + translation name; + // Used with a weapon, precludes most other checks + bool weapon = false; + // If true the vector will always be preferentially used when eligable + bool primary = false; + // Explicit bodypart definitions + std::vector limbs; + // Flexible bodypart type definition + std::vector limb_types; + // Explicit sublimb requirement + std::vector sub_limbs; + + // Encumbrance limit in absolute encumbrance + int encumbrance_limit = 100; + // Percent of bodypart HP required + int bp_hp_limit = 100; + + bool was_loaded = false; + + static void load_attack_vectors( const JsonObject &jo, const std::string &src ); + static void reset(); + void load( const JsonObject &jo, std::string_view ); +}; + struct ma_requirements { bool was_loaded = false; @@ -170,6 +196,7 @@ class ma_technique // What way is the technique delivered to the target? std::vector attack_vectors; // by priority std::vector attack_vectors_random; // randomly + std::vector wip_attack_vectors; int repeat_min = 1; // Number of times the technique is repeated on a successful proc int repeat_max = 1; diff --git a/src/melee.cpp b/src/melee.cpp index 9c092735ebd09..83650ed6bae14 100644 --- a/src/melee.cpp +++ b/src/melee.cpp @@ -1624,6 +1624,8 @@ std::vector Character::evaluate_techniques( Creature &t, const item_lo continue; } + attack_vector_id wip = martial_arts_data->choose_attack_vector( *this, tec.id ); + // Does the player have a functional attack vector to deliver the technique? std::vector shuffled_attack_vectors = tec.attack_vectors_random; std::shuffle( shuffled_attack_vectors.begin(), shuffled_attack_vectors.end(), rng_get_engine() ); diff --git a/src/type_id.h b/src/type_id.h index b13f55a9d9469..ccbd4a267cd70 100644 --- a/src/type_id.h +++ b/src/type_id.h @@ -23,6 +23,9 @@ struct ammo_effect; using ammo_effect_id = int_id; using ammo_effect_str_id = string_id; +struct wip_attack_vector; +using attack_vector_id = string_id; + struct bionic_data; using bionic_id = string_id; From 77e067ce8a36f1bead3312f5aa307939fc901db2 Mon Sep 17 00:00:00 2001 From: Venera3 Date: Sat, 20 Apr 2024 21:25:43 +0200 Subject: [PATCH 02/16] Version Two --- data/json/attack_vectors.json | 28 ++ data/json/bionics.json | 3 +- data/json/body_parts.json | 3 + data/json/items/armor/integrated.json | 20 +- data/json/mutations/mutation_techs.json | 9 - data/json/mutations/mutations.json | 3 - data/json/techniques.json | 9 + data/mods/Magiclysm/items/armor.json | 77 ++++++ data/mods/Magiclysm/mutations/mutations.json | 17 +- .../traits/temporary_demon_traits.json | 3 - doc/JSON_FLAGS.md | 1 - doc/MUTATIONS.md | 2 +- src/bodypart.cpp | 5 + src/bodypart.h | 1 + src/character.cpp | 3 +- src/character.h | 21 +- src/character_attire.cpp | 15 ++ src/character_attire.h | 1 + src/character_martial_arts.h | 6 +- src/martialarts.cpp | 247 ++++++++++++------ src/martialarts.h | 23 +- src/melee.cpp | 232 +++++++--------- src/subbodypart.cpp | 1 + src/subbodypart.h | 3 + 24 files changed, 435 insertions(+), 298 deletions(-) create mode 100644 data/json/attack_vectors.json diff --git a/data/json/attack_vectors.json b/data/json/attack_vectors.json new file mode 100644 index 0000000000000..7b991c951554e --- /dev/null +++ b/data/json/attack_vectors.json @@ -0,0 +1,28 @@ +[ + { + "type": "attack_vector", + "id": "vector_base", + "//": "Fallback weapon vector, unarmed attacks always go through techs", + "weapon": true + }, + { + "type": "attack_vector", + "id": "test_test", + "name": "test debug tail", + "limbs": [ "debug_tail" ], + "encumbrance_limit": 33, + "bp_hp_limit": 75 + }, + { + "type": "attack_vector", + "id": "test_punch", + "name": "test punch", + "limbs": [ "hand_l", "hand_r", "foot_r", "foot_l", "eyes" ], + "contact_area": [ "hand_fingers_l", "hand_fingers_r", "foot_sole_l", "foot_sole_r", "eyes_left", "eyes_right" ], + "strict_limb_definition": false, + "armor_bonus": true, + "limb_count_min": 1, + "limb_count_max": 2, + "forbidden_limb_flags": [ "IGNORE_TEMP" ] + } +] diff --git a/data/json/bionics.json b/data/json/bionics.json index 0f63b42c787d8..4fb4b4f00823f 100644 --- a/data/json/bionics.json +++ b/data/json/bionics.json @@ -1417,7 +1417,8 @@ "description": "You possess razor-sharp claws underneath your fingernails that do a small amount of unarmed slashing damage whenever your fingertips are uncovered.", "occupied_bodyparts": [ [ "hand_l", 1 ], [ "hand_r", 1 ] ], "passive_pseudo_items": [ "fake_razor" ], - "flags": [ "BIONIC_NPC_USABLE" ] + "flags": [ "BIONIC_NPC_USABLE" ], + "enchantments": [ { "condition": { "not": "u_has_weapon" }, "values": [ { "value": "ITEM_DAMAGE_STAB", "add": 2 } ] } ] }, { "id": "bio_recycler", diff --git a/data/json/body_parts.json b/data/json/body_parts.json index 2b84c094f938a..14a5fdd28ddc2 100644 --- a/data/json/body_parts.json +++ b/data/json/body_parts.json @@ -550,6 +550,8 @@ "limb_scores": [ [ "grip", 0.5 ], [ "manip", 0.5, 1.0 ], [ "swim", 0.15 ] ], "side": "left", "legacy_id": "HAND_L", + "techniques": [ "tech_base_punch" ], + "unarmed_damage": [ { "damage_type": "bash", "amount": 10 } ], "stylish_bonus": 0.5, "hot_morale_mod": 0.5, "cold_morale_mod": 0.5, @@ -581,6 +583,7 @@ "limb_scores": [ [ "grip", 0.5 ], [ "manip", 0.5, 1.0 ], [ "swim", 0.15 ] ], "side": "right", "legacy_id": "HAND_R", + "unarmed_damage": [ { "damage_type": "bash", "amount": 15 } ], "stylish_bonus": 0.5, "hot_morale_mod": 0.5, "cold_morale_mod": 0.5, diff --git a/data/json/items/armor/integrated.json b/data/json/items/armor/integrated.json index b588e1aa79fef..759bc5e7d509a 100644 --- a/data/json/items/armor/integrated.json +++ b/data/json/items/armor/integrated.json @@ -1531,10 +1531,7 @@ "encumbrance": 1 } ], - "melee_damage": { "cut": 9 }, - "relic_data": { - "passive_effects": [ { "has": "WORN", "condition": { "not": "u_has_weapon" }, "values": [ { "value": "ATTACK_SPEED", "add": -20 } ] } ] - } + "melee_damage": { "cut": 9 } }, { "id": "integrated_claws_rat", @@ -1560,10 +1557,7 @@ "encumbrance": 3 } ], - "melee_damage": { "cut": 10 }, - "relic_data": { - "passive_effects": [ { "has": "WORN", "condition": { "not": "u_has_weapon" }, "values": [ { "value": "ATTACK_SPEED", "add": -20 } ] } ] - } + "melee_damage": { "cut": 10 } }, { "id": "integrated_claws_st", @@ -1589,10 +1583,7 @@ "encumbrance": 5 } ], - "melee_damage": { "cut": 11 }, - "relic_data": { - "passive_effects": [ { "has": "WORN", "condition": { "not": "u_has_weapon" }, "values": [ { "value": "ATTACK_SPEED", "add": -20 } ] } ] - } + "melee_damage": { "cut": 11 } }, { "id": "integrated_talons", @@ -1618,10 +1609,7 @@ "encumbrance": 7 } ], - "melee_damage": { "cut": 16 }, - "relic_data": { - "passive_effects": [ { "has": "WORN", "condition": { "not": "u_has_weapon" }, "values": [ { "value": "ATTACK_SPEED", "add": -20 } ] } ] - } + "melee_damage": { "cut": 16 } }, { "id": "integrated_fangs", diff --git a/data/json/mutations/mutation_techs.json b/data/json/mutations/mutation_techs.json index e8c7844995c9a..7cb362bde29a8 100644 --- a/data/json/mutations/mutation_techs.json +++ b/data/json/mutations/mutation_techs.json @@ -31,14 +31,5 @@ ], "wip_attack_vectors": [ "test_test" ], "attack_vectors": [ "HAND" ] - }, - { - "type": "attack_vector", - "id": "test_test", - "limbs": [ "debug_tail" ], - "limb_types": [ "sensor" ], - "sub_limbs": [ "sub_limb_debug_tail"], - "encumbrance_limit": 33, - "bp_hp_limit": 75 } ] diff --git a/data/json/mutations/mutations.json b/data/json/mutations/mutations.json index 47597d00eb9fd..693706ebec38e 100644 --- a/data/json/mutations/mutations.json +++ b/data/json/mutations/mutations.json @@ -3612,7 +3612,6 @@ "visibility": 3, "ugliness": 2, "integrated_armor": [ "integrated_claws" ], - "flags": [ "UNARMED_BONUS" ], "description": "You have claws on the ends of your fingers. If you aren't wearing gloves, your unarmed attacks deal a minor amount of cutting damage.", "types": [ "CLAWS" ], "prereqs": [ "NAILS" ], @@ -3628,7 +3627,6 @@ "visibility": 3, "ugliness": 4, "integrated_armor": [ "integrated_claws_rat" ], - "flags": [ "UNARMED_BONUS" ], "description": "Your claws have grown tougher and slightly gnarled.", "types": [ "CLAWS" ], "prereqs": [ "CLAWS" ], @@ -3770,7 +3768,6 @@ "visibility": 4, "ugliness": 3, "integrated_armor": [ "integrated_talons" ], - "flags": [ "UNARMED_BONUS" ], "mixed_effect": true, "description": "Your index fingers have grown into huge talons. After a bit of practice, you find that this does not affect your dexterity, but allows for a deadly unarmed attack. They also prevent wearing gloves.", "types": [ "CLAWS" ], diff --git a/data/json/techniques.json b/data/json/techniques.json index 657e81f65ab52..9b57a279d907d 100644 --- a/data/json/techniques.json +++ b/data/json/techniques.json @@ -3,8 +3,17 @@ "type": "technique", "id": "tec_none", "name": "Not a technique at all", + "wip_attack_vectors": [ "vector_base" ], "dummy": true }, + { + "type": "technique", + "id": "tech_base_punch", + "name": "Basic punch", + "messages": [ "You punch %s!", " punches %s!" ], + "description": "Just a basic punch", + "wip_attack_vectors": [ "test_punch" ] + }, { "type": "technique", "id": "WBLOCK_1", diff --git a/data/mods/Magiclysm/items/armor.json b/data/mods/Magiclysm/items/armor.json index 305610abcc80f..2076cf9f0aff0 100644 --- a/data/mods/Magiclysm/items/armor.json +++ b/data/mods/Magiclysm/items/armor.json @@ -144,5 +144,82 @@ "volume": "150 L", "material_thickness": 6, "environmental_protection": 8 + }, + { + "id": "integrated_scaled_hands", + "type": "ARMOR", + "category": "armor", + "name": { "str_sp": "talons" }, + "description": "Your scales have grown to cover your hands and elongate the tips in to jagged claws that can be serviceable as cutting and piercing weapons in unarmed combat.", + "weight": "100 g", + "volume": "350 ml", + "qualities": [ [ "CUT", 2 ], [ "CUT_FINE", 1 ], [ "BUTCHER", 6 ] ], + "price": 0, + "price_postapoc": 0, + "material": [ "black_dragon_scales" ], + "symbol": ";", + "color": "dark_gray", + "flags": [ "INTEGRATED", "ALLOWS_NATURAL_ATTACKS", "UNBREAKABLE", "OUTER", "PADDED", "NO_SALVAGE" ], + "armor": [ + { + "material": [ { "type": "black_dragon_scales", "covered_by_mat": 100, "thickness": 3 } ], + "covers": [ "hand_l", "hand_r" ], + "coverage": 100, + "encumbrance": 5 + } + ], + "melee_damage": { "cut": 5, "stab": 5 } + }, + { + "id": "integrated_demon_claws", + "type": "ARMOR", + "category": "armor", + "name": { "str_sp": "talons" }, + "description": "Your index fingers have become talons. They make for wicked natural daggers. Unfortunately, they get in the way of everything including gloves.", + "weight": "100 g", + "volume": "350 ml", + "qualities": [ [ "CUT", 2 ], [ "CUT_FINE", 1 ], [ "BUTCHER", 12 ] ], + "price": 0, + "price_postapoc": 0, + "material": [ "demon_chitin" ], + "symbol": ";", + "color": "dark_gray", + "flags": [ "INTEGRATED", "ALLOWS_NATURAL_ATTACKS", "UNBREAKABLE", "OUTER", "PADDED", "NO_SALVAGE" ], + "armor": [ + { + "material": [ { "type": "demon_chitin", "covered_by_mat": 100, "thickness": 3 } ], + "covers": [ "hand_l", "hand_r" ], + "specifically_covers": [ "hand_fingers_l", "hand_fingers_r" ], + "coverage": 10, + "encumbrance": 3 + } + ], + "melee_damage": { "cut": 10 } + }, + { + "id": "integrated_dragon_talons_black", + "type": "ARMOR", + "category": "armor", + "name": { "str_sp": "talons" }, + "description": "All of your fingers have developed into huge scaled talons. They prevent wearing gloves, but you can use them to powerful effect in melee combat and you appear to have uncovered some secrets about black dragons.", + "weight": "100 g", + "volume": "350 ml", + "qualities": [ [ "CUT", 2 ], [ "CUT_FINE", 1 ], [ "BUTCHER", 12 ] ], + "price": 0, + "price_postapoc": 0, + "material": [ "black_dragon_scales" ], + "symbol": ";", + "color": "dark_gray", + "flags": [ "INTEGRATED", "ALLOWS_NATURAL_ATTACKS", "UNBREAKABLE", "OUTER", "PADDED", "NO_SALVAGE" ], + "armor": [ + { + "material": [ { "type": "black_dragon_scales", "covered_by_mat": 100, "thickness": 3 } ], + "covers": [ "hand_l", "hand_r" ], + "specifically_covers": [ "hand_fingers_l", "hand_fingers_r" ], + "coverage": 100, + "encumbrance": 7 + } + ], + "melee_damage": { "cut": 10, "stab": 10 } } ] diff --git a/data/mods/Magiclysm/mutations/mutations.json b/data/mods/Magiclysm/mutations/mutations.json index a51d612aa9d39..130dc1241c8bd 100644 --- a/data/mods/Magiclysm/mutations/mutations.json +++ b/data/mods/Magiclysm/mutations/mutations.json @@ -768,14 +768,7 @@ "points": 2, "visibility": 4, "ugliness": 3, - "enchantments": [ - { - "condition": { "not": "u_has_weapon" }, - "values": [ { "value": "ITEM_DAMAGE_CUT", "add": 5 }, { "value": "ITEM_DAMAGE_STAB", "add": 5 } ] - } - ], - "butchering_quality": 4, - "flags": [ "UNARMED_BONUS" ], + "integrated_armor": [ "integrated_scaled_hands" ], "mixed_effect": true, "restricts_gear": [ "hand_l", "hand_r" ], "destroys_gear": true, @@ -793,14 +786,6 @@ "points": 2, "visibility": 4, "ugliness": 3, - "enchantments": [ - { - "condition": { "not": "u_has_weapon" }, - "values": [ { "value": "ITEM_DAMAGE_CUT", "add": 10 }, { "value": "ITEM_DAMAGE_STAB", "add": 10 } ] - } - ], - "butchering_quality": 4, - "flags": [ "UNARMED_BONUS" ], "mixed_effect": true, "restricts_gear": [ "hand_l", "hand_r" ], "destroys_gear": true, diff --git a/data/mods/Magiclysm/traits/temporary_demon_traits.json b/data/mods/Magiclysm/traits/temporary_demon_traits.json index 64fdc959ec4ef..c4b947fd7c0a1 100644 --- a/data/mods/Magiclysm/traits/temporary_demon_traits.json +++ b/data/mods/Magiclysm/traits/temporary_demon_traits.json @@ -69,9 +69,6 @@ "points": 2, "visibility": 4, "ugliness": 4, - "enchantments": [ { "condition": { "not": "u_has_weapon" }, "values": [ { "value": "ITEM_DAMAGE_CUT", "add": 10 } ] } ], - "butchering_quality": 4, - "flags": [ "UNARMED_BONUS" ], "valid": false, "starting_trait": false, "mixed_effect": true, diff --git a/doc/JSON_FLAGS.md b/doc/JSON_FLAGS.md index 48da64112c85e..5297ad24f385e 100644 --- a/doc/JSON_FLAGS.md +++ b/doc/JSON_FLAGS.md @@ -420,7 +420,6 @@ Character flags can be `trait_id`, `json_flag_id` or `flag_id`. Some of these a - ```THERMOMETER``` You always know what temperature it is. - ```TINY``` Changes your size to `creature_size::tiny`. Checked first of the size category flags. - ```TREE_COMMUNION_PLUS``` Gain greatly enhanced effects from the Mycorrhizal Communion mutation. -- ```UNARMED_BONUS``` You get a bonus to unarmed bash and cut damage equal to unarmed_skill/2 up to 4. - ```WALK_UNDERWATER``` your stamina burn is not increased when you swim, emulating you walking on the water bottom. - ```WALL_CLING``` You can ascend/descend sheer cliffs as long as the tile above borders at least one wall. Chance to slip and fall each step. - ```WATCH``` You always know what time it is. diff --git a/doc/MUTATIONS.md b/doc/MUTATIONS.md index fb56438437975..0665518fbf2ea 100644 --- a/doc/MUTATIONS.md +++ b/doc/MUTATIONS.md @@ -234,7 +234,7 @@ Note that **all new traits that can be obtained through mutation must be purifia "processed_eocs": [ "eoc_id_1" ], // List of effect_on_conditions that attempt to activate every time (defined above) units of time. Time of 0 means every turn it processes. Processed when the mutation is active for activatable mutations and always for non-activatable ones. "deactivated_eocs": [ "eoc_id_1" ], // List of effect_on_conditions that attempt to activate when this mutation is successfully deactivated. "enchantments": [ "ench_id_1" ], // List of enchantments granted by this mutation. Can be either IDs or an inline definition of the enchantment (see MAGIC.md) - "flags": [ "UNARMED_BONUS" ], // List of flag_IDs and json_flag_IDs granted by the mutation. Note: trait_IDs can be set and generate no errors, but they're not actually "active". + "flags": [ "WALK_UNDERWATER" ], // List of flag_IDs and json_flag_IDs granted by the mutation. Note: trait_IDs can be set and generate no errors, but they're not actually "active". "moncams": [ [ "mon_player_blob", 16 ] ], // Monster cameras, ability to use friendly monster's from the list as additional source of vision. Max view distance is equal to monster's daytime vision. The number specifies the range at which it can "transmit" vision to the avatar. "override_look": { "id": "mon_cat_black", "tile_category": "monster" } // Change the character's appearance to another specified thing with a specified ID and tile category. Please ensure that the ID corresponds to the tile category. The valid tile category are "none", "vehicle_part", "terrain", "item", "furniture", "trap", "field", "lighting", "monster", "bullet", "hit_entity", "weather", "overmap_terrain", "map_extra", "overmap_note". } diff --git a/src/bodypart.cpp b/src/bodypart.cpp index e6f000e294d7c..c6ac999f9d3ef 100644 --- a/src/bodypart.cpp +++ b/src/bodypart.cpp @@ -602,6 +602,11 @@ bool body_part_type::has_limb_score( const limb_score_id &id ) const return limb_scores.count( id ); } +damage_instance body_part_type::unarmed_damage_instance() const +{ + return damage; +} + float body_part_type::unarmed_damage( const damage_type_id &dt ) const { return damage.type_damage( dt ); diff --git a/src/bodypart.h b/src/bodypart.h index bf2255e340f44..c23099837c38a 100644 --- a/src/bodypart.h +++ b/src/bodypart.h @@ -367,6 +367,7 @@ struct body_part_type { return bionic_slots_; } + damage_instance unarmed_damage_instance() const; float unarmed_damage( const damage_type_id &dt ) const; // return the total amount of unarmed damage this limb would do float total_unarmed_damage() const; diff --git a/src/character.cpp b/src/character.cpp index 6172005aa15b5..8b271cf20a3ae 100644 --- a/src/character.cpp +++ b/src/character.cpp @@ -2090,7 +2090,8 @@ std::set Character::get_limb_techs() const { std::set result; for( const bodypart_id &part : get_all_body_parts() ) { - if( !natural_attack_restricted_on( part ) ) { + const bodypart *bp = get_part( part ); + if( !bp->is_limb_overencumbered() && bp->get_hp_cur() > part->health_limit ) { std::set part_tech = get_part( part )->get_limb_techs(); result.insert( part_tech.begin(), part_tech.end() ); } diff --git a/src/character.h b/src/character.h index 648f71dda523e..98896c6aad871 100644 --- a/src/character.h +++ b/src/character.h @@ -1128,13 +1128,15 @@ class Character : public Creature, public visitable item_location best_shield(); /** Calculates melee weapon wear-and-tear through use, returns true if item is destroyed. */ bool handle_melee_wear( item_location shield, float wear_multiplier = 1.0f ); - /** Returns a random valid technique */ - matec_id pick_technique( Creature &t, const item_location &weap, - bool crit, bool dodge_counter, bool block_counter, const std::vector &blacklist = {} ); - // Houses the actual picking logic and returns the vector of eligable techniques - std::vector evaluate_techniques( Creature &t, const item_location &weap, - bool crit = false, bool dodge_counter = false, bool block_counter = false, - const std::vector &blacklist = {} ); + /** Returns a random technique/vector/contact area set from the possible techs */ + std::tuple pick_technique( + Creature &t, const item_location &weap, + bool crit, bool dodge_counter, bool block_counter, const std::vector &blacklist = {} ); + // Filter techniques, return a vector of tech/vector/contact area sets for pick_technique to choose from + std::vector> + evaluate_techniques( Creature &t, const item_location &weap, + bool crit = false, bool dodge_counter = false, bool block_counter = false, + const std::vector &blacklist = {} ); void perform_technique( const ma_technique &technique, Creature &t, damage_instance &di, int &move_cost, item_location &cur_weapon ); @@ -1204,10 +1206,11 @@ class Character : public Creature, public visitable // If average == true, adds expected values of random rolls instead of rolling. /** Adds all 3 types of physical damage to instance */ void roll_all_damage( bool crit, damage_instance &di, bool average, const item &weap, - const std::string &attack_vector, + const attack_vector_id &attack_vector, const sub_bodypart_str_id &contact, const Creature *target, const bodypart_id &bp ) const; void roll_damage( const damage_type_id &dt, bool crit, damage_instance &di, bool average, - const item &weap, const std::string &attack_vector, float crit_mod ) const; + const item &weap, const attack_vector_id &attack_vector, const sub_bodypart_str_id &contact, + float crit_mod ) const; /** Returns true if the player should be dead */ bool is_dead_state() const override; diff --git a/src/character_attire.cpp b/src/character_attire.cpp index fb08f333f1ad9..b4b3f1bc1a104 100644 --- a/src/character_attire.cpp +++ b/src/character_attire.cpp @@ -2251,6 +2251,21 @@ item *outfit::current_unarmed_weapon( const std::string &attack_vector ) return cur_weapon; } +item *outfit::current_unarmed_weapon( const sub_bodypart_str_id &contact_area ) +{ + item *cur_weapon = &null_item_reference(); + for( item &worn_item : worn ) { + bool covers = worn_item.covers( contact_area ); + // Uses enum layer_level to make distinction for top layer. + if( covers ) { + if( cur_weapon->is_null() || ( worn_item.get_layer() >= cur_weapon->get_layer() ) ) { + cur_weapon = &worn_item; + } + } + } + return cur_weapon; +} + void outfit::prepare_bodymap_info( bodygraph_info &info, const bodypart_id &bp, const std::set &sub_parts, const Character &person ) const { diff --git a/src/character_attire.h b/src/character_attire.h index 365dba47751f0..28bcc5964f8d8 100644 --- a/src/character_attire.h +++ b/src/character_attire.h @@ -113,6 +113,7 @@ class outfit item *best_shield(); // find the best clothing weapon when unarmed modifies item *current_unarmed_weapon( const std::string &attack_vector ); + item *current_unarmed_weapon( const sub_bodypart_str_id &contact_area ); item_location first_item_covering_bp( Character &guy, bodypart_id bp ); void inv_dump( std::vector &ret ); void inv_dump( std::vector &ret ) const; diff --git a/src/character_martial_arts.h b/src/character_martial_arts.h index ffc450248b636..a6069232db820 100644 --- a/src/character_martial_arts.h +++ b/src/character_martial_arts.h @@ -81,7 +81,11 @@ class character_martial_arts void ma_onkill_effects( Character &owner ); // Selects a valid attack vector - attack_vector_id choose_attack_vector( const Character &user, const matec_id &tech ) const; + std::optional> choose_attack_vector( + const Character &user, const matec_id &tech ) const; + // Calculate and return the damage of the given contact area for a given vector + damage_instance calculate_vector_damage( const Character &user, const attack_vector_id &vec, + const sub_bodypart_str_id &contact_area ) const; /** Returns an attack vector that the player can use */ std::string get_valid_attack_vector( const Character &user, const std::vector &attack_vectors ) const; diff --git a/src/martialarts.cpp b/src/martialarts.cpp index 8d6dc02502e83..933507ed4bc94 100644 --- a/src/martialarts.cpp +++ b/src/martialarts.cpp @@ -10,6 +10,7 @@ #include "bodypart.h" #include "character.h" +#include "character_attire.h" #include "character_martial_arts.h" #include "color.h" #include "condition.h" @@ -51,6 +52,8 @@ static const limb_score_id limb_score_block( "block" ); static const skill_id skill_unarmed( "unarmed" ); +static const sub_bodypart_str_id sub_body_part_sub_limb_debug( "sub_limb_debug" ); + static const weapon_category_id weapon_category_OTHER_INVALID_WEAP_CAT( "OTHER_INVALID_WEAP_CAT" ); @@ -89,13 +92,16 @@ void wip_attack_vector::reset() void wip_attack_vector::load( const JsonObject &jo, const std::string_view ) { mandatory( jo, was_loaded, "id", id ); + mandatory( jo, was_loaded, "name", name ); optional( jo, was_loaded, "weapon", weapon, false ); optional( jo, was_loaded, "limbs", limbs ); - optional( jo, was_loaded, "sub_limbs", sub_limbs ); - optional( jo, was_loaded, "limb_types", limb_types ); - optional( jo, was_loaded, "encumbrance_limit", encumbrance_limit ); - optional( jo, was_loaded, "bp_hp_limit", bp_hp_limit ); - + optional( jo, was_loaded, "strict_limb_definition", strict_limb_definition, false ); + optional( jo, was_loaded, "contact_area", contact_area ); + optional( jo, was_loaded, "armor_bonus", armor_bonus, true ); + optional( jo, was_loaded, "encumbrance_limit", encumbrance_limit, 100 ); + optional( jo, was_loaded, "bp_hp_limit", bp_hp_limit, 0 ); + optional( jo, was_loaded, "required_limb_flags", required_limb_flags ); + optional( jo, was_loaded, "forbidden_limb_flags", forbidden_limb_flags ); } template<> @@ -272,7 +278,6 @@ void ma_technique::load( const JsonObject &jo, const std::string &src ) optional( jo, was_loaded, "crit_tec", crit_tec, false ); optional( jo, was_loaded, "crit_ok", crit_ok, false ); - optional( jo, was_loaded, "crit_tec_id", crit_tec_id, tec_none ); optional( jo, was_loaded, "attack_override", attack_override, false ); optional( jo, was_loaded, "wall_adjacent", wall_adjacent, false ); optional( jo, was_loaded, "reach_tec", reach_tec, false ); @@ -608,6 +613,39 @@ void finalize_martial_arts() // bother us because ma_buff_effect_type does not have any members that can be sliced. effect_type::register_ma_buff_effect( new_eff ); } + // Iterate through every attack vector and substitute similar limbs (as long as they have similar sublimbs + for( wip_attack_vector vector : attack_vector_factory.get_all() ) { + // Check if this vector allows substitutions in the first place + if( vector.strict_limb_definition ) { + continue; + } + + // Begin by substituting similar subparts to ease filtering in the next step + std::vector similar_sbp; + for( const sub_bodypart_str_id &sbp : vector.contact_area ) { + for( const sub_bodypart_str_id &similar : sbp->similar_bodyparts ) { + similar_sbp.emplace_back( similar ); + } + } + + vector.contact_area.insert( vector.contact_area.end(), similar_sbp.begin(), similar_sbp.end() ); + + // We now have the actually-hitting sublimbs, substitute the main parts + // But discard any part where we'd be similar without a similar contact subpart + std::vector similar_bp; + for( const bodypart_str_id &bp : vector.limbs ) { + for( const bodypart_str_id &similar : bp->similar_bodyparts ) { + std::vector intersect; + std::set_intersection( similar->sub_parts.begin(), similar->sub_parts.end(), + vector.contact_area.begin(), vector.contact_area.end(), std::back_inserter( intersect ) ); + if( intersect.size() > 0 ) { + // We have a common element in our sublimb list and the contact area list + similar_bp.emplace_back( similar ); + } + } + } + vector.limbs.insert( vector.limbs.end(), similar_bp.begin(), similar_bp.end() ); + } } std::string martialart_difficulty( const matype_id &mstyle ) @@ -870,7 +908,6 @@ ma_technique::ma_technique() { crit_tec = false; crit_ok = false; - crit_tec_id = tec_none; // if not tec_none, use this tech instead when a crit procs defensive = false; side_switch = false; // moves the target behind user dummy = false; @@ -1427,109 +1464,149 @@ std::string character_martial_arts::get_valid_attack_vector( const Character &us return "NONE"; } -attack_vector_id character_martial_arts::choose_attack_vector( const Character &user, - const matec_id &tech ) const +std::optional> + character_martial_arts::choose_attack_vector( const Character &user, + const matec_id &tech ) const { - // Handle choosing the attack vector - // Deal with hard filters - DONE - // Consider expected damage? - // Weighted list based on damage? Hard priority? - // Return explicit bodypart id for unarmed stuff? - // - const std::vector anat = user.get_all_body_parts(); + // Use the simple weighted list to handle picking semi-randomly + attack_vector_id ret; weighted_float_list list; + std::pair return_set; + std::vector> storage; + const std::vector anat = user.get_all_body_parts(); + const bool armed = user.is_armed(); + martialart ma = style_selected.obj(); + bool valid_weapon = ma.weapon_valid( user.get_wielded_item() ); for( const attack_vector_id &vec : tech.obj().wip_attack_vectors ) { - wip_attack_vector tmp = vec.obj(); - bool found = false; + add_msg_debug( debugmode::DF_MELEE, "Evaluating vector %s for tech %s", vec->name, tech.c_str() ); float weight = 0.0f; // Early break for armed vectors - if( tmp.weapon && user.is_armed() ) { + if( vec->weapon && armed && valid_weapon ) { item *weapon = user.get_wielded_item().get_item(); - weight = weapon->average_dps( user ); - if( tmp.primary ) { - return vec; - } else { - list.add_or_replace( vec, weight ); - } + // Calculate weapon damage for weighting + // Store a dummy sublimb to show we're attacking with a weapon + weight = weapon->base_damage_melee().total_damage(); + list.add_or_replace( vec, weight ); + storage.emplace_back( std::make_pair( vec, sub_body_part_sub_limb_debug ) ); add_msg_debug( debugmode::DF_MELEE, "Weapon %s eligable for attack vector %s with weight %.1f", weapon->display_name(), vec.c_str(), weight ); - found = true; - break; - } - if( found ) { continue; } - // If we defined explicit bodyparts - for( const bodypart_id &bp : vec.obj().limbs ) { + // Smilar bodyparts get appended to the vector limb list in the finalization step + // So we just need to check if we have a limb, a contact area sublimb and tally up the damages + std::vector> calc_vector; + for( const bodypart_id &bp : vec->limbs ) { if( std::find( anat.begin(), anat.end(), bp ) != anat.end() ) { - const bodypart &part = *user.get_part( bp ); - if( ( 100 * part.get_hp_cur() / part.get_hp_max() ) > tmp.bp_hp_limit && - part.get_encumbrance_data().encumbrance < tmp.encumbrance_limit ) { - // Use the BP unarmed damage as the weight - weight = bp.obj().total_unarmed_damage(); - if( tmp.primary ) { - return vec; - } else { - list.add_or_replace( vec, weight ); + add_msg_debug( debugmode::DF_MELEE, "Evaluating limb %s for vector %s", bp->name, vec->name ); + // Filter on limb flags early + bool allowed = true; + for( const json_character_flag &req : vec->required_limb_flags ) { + if( !bp->has_flag( req ) ) { + add_msg_debug( debugmode::DF_MELEE, "Required limb flag %s not found on limb %s", req.c_str(), + bp->name ); + allowed = false; + break; } - add_msg_debug( debugmode::DF_MELEE, "Bodypart %s eligable for attack vector %s with weight %.1f", - bp.id().c_str(), - vec.c_str(), weight ); - found = true; - break; } - } - } - if( found ) { - continue; - } - // Sublimb check - for( const sub_bodypart_str_id &sbp : tmp.sub_limbs ) { - for( const bodypart_id &bp : anat ) { - if( std::find( bp.obj().sub_parts.begin(), bp.obj().sub_parts.end(), - sbp ) != bp.obj().sub_parts.end() ) { - list.add_or_replace( vec, weight ); - add_msg_debug( debugmode::DF_MELEE, - "Sub-bodypart %s eligable for attack vector %s with weight %.1f", sbp.c_str(), - vec.c_str(), weight ); - found = true; - break; + for( const json_character_flag &forb : vec->forbidden_limb_flags ) { + if( bp->has_flag( forb ) ) { + add_msg_debug( debugmode::DF_MELEE, "Forbidden limb flag %s found on limb %s", forb.c_str(), + bp->name ); + allowed = false; + break; + } + } + if( !allowed ) { + add_msg_debug( debugmode::DF_MELEE, "Limb %s disqualified for vector %s", bp->name, vec->name ); + continue; } - } - if( found ) { - break; - } - } - if( found ) { - continue; - } - // More flexible search for limb types - for( const body_part_type::type &type : tmp.limb_types ) { - std::vector limbs = user.get_all_body_parts_of_type( type ); - for( const bodypart_id &bp : limbs ) { const bodypart &part = *user.get_part( bp ); - if( ( 100 * part.get_hp_cur() / part.get_hp_max() ) > tmp.bp_hp_limit && - part.get_encumbrance_data().encumbrance < tmp.encumbrance_limit ) { - list.add_or_replace( vec, weight ); + if( ( 100 * part.get_hp_cur() / part.get_hp_max() ) > vec->bp_hp_limit && + part.get_encumbrance_data().encumbrance < vec->encumbrance_limit ) { + sub_bodypart_str_id current_contact; + for( const sub_bodypart_str_id &sbp : bp->sub_parts ) { + if( std::find( vec->contact_area.begin(), vec->contact_area.end(), + sbp ) != vec->contact_area.end() ) { + add_msg_debug( debugmode::DF_MELEE, "Contact area sbp %s on bodypart %s found", sbp->name, + bp->name ); + current_contact = sbp; + break; + } + } + // Go through the common function + float unarmed_damage = calculate_vector_damage( user, vec, current_contact ).total_damage(); + calc_vector.emplace_back( std::make_pair( current_contact, unarmed_damage ) ); add_msg_debug( debugmode::DF_MELEE, - "Bodypart %s eligable for attack vector %s (limb type filter) with weight %.1f", + "Bodypart %s eligable for attack vector %s weight %.1f (contact area %s)", bp.id().c_str(), - vec.c_str(), weight ); - found = true; - break; - + vec->name, weight, current_contact->name ); } } - if( found ) { + } + if( calc_vector.size() == 0 ) { + add_msg_debug( debugmode::DF_MELEE, "Vector %s found no eligable bodyparts, discarding", + vec->name ); + continue; + } + // Sort our calc_vector of sublimb/damage pairs + std::sort( calc_vector.begin(), + calc_vector.end(), []( const std::pair &a, + const std::pair &b ) { + return a.second < b.second; + } ); + int i = 0; + list.add( vec, calc_vector.rbegin()->second ); + storage.emplace_back( std::make_pair( vec, calc_vector.rbegin()->first ) ); + add_msg_debug( debugmode::DF_MELEE, + "Chose contact sublimb %s for vector %s with weight %.1f; %d stored vectors", + calc_vector.rbegin()->first->name, vec->name, calc_vector.rbegin()->second, storage.size() ); + } + if( !list.empty() ) { + ret = *list.pick(); + add_msg_debug( debugmode::DF_MELEE, "Picked vector %s for technique %s", ret.c_str(), + tech.c_str() ); + // Now find the contact data matching the winning vector + for( auto iterate = storage.begin(); iterate != storage.end(); ++iterate ) { + if( iterate->first == ret ) { + return_set = *iterate; + add_msg_debug( debugmode::DF_MELEE, "Return vector %s found in storage", return_set.first->name ); break; } } - if( found ) { - continue; + return return_set; + } + return std::nullopt; +} + +damage_instance character_martial_arts::calculate_vector_damage( const Character &user, + const attack_vector_id &vec, const sub_bodypart_str_id &contact_area ) const +{ + // Calculate unarmed damage for the given sublimb(s) + damage_instance ret; + // If we got this far the limb is not overencumbered + ret.add( contact_area->parent->unarmed_damage_instance() ); + add_msg_debug( debugmode::DF_MELEE, + "Unarmed damage of bodypart %s %.1f", contact_area->parent->name, + ret.total_damage() ); + ret.add( contact_area->unarmed_damage ); + add_msg_debug( debugmode::DF_MELEE, + "Unarmed damage of subpart %s %.1f, total damage %.1f", contact_area->parent->name, + contact_area->unarmed_damage.total_damage(), + ret.total_damage() ); + // Add any bonus from worn armor if the vector allows it + if( vec->armor_bonus ) { + outfit current_worn = user.worn; + item *unarmed_weapon = current_worn.current_unarmed_weapon( contact_area ); + if( unarmed_weapon != nullptr ) { + ret.add( unarmed_weapon->base_damage_melee() ); + add_msg_debug( debugmode::DF_MELEE, + "Unarmed weapon %s found, melee damage %.1f, new total damage %.1f", + unarmed_weapon->display_name(), unarmed_weapon->base_damage_melee().total_damage(), + ret.total_damage() ); } } - return *list.pick(); + return ret; } bool character_martial_arts::can_use_attack_vector( const Character &user, diff --git a/src/martialarts.h b/src/martialarts.h index 494fe583f6dd2..6287d833758fe 100644 --- a/src/martialarts.h +++ b/src/martialarts.h @@ -69,16 +69,21 @@ matype_id martial_art_learned_from( const itype & ); struct wip_attack_vector { attack_vector_id id; translation name; - // Used with a weapon, precludes most other checks + + // Used with a weapon, otherwise use unarmed damage calc bool weapon = false; - // If true the vector will always be preferentially used when eligable - bool primary = false; + // Explicit bodypart definitions std::vector limbs; - // Flexible bodypart type definition - std::vector limb_types; - // Explicit sublimb requirement - std::vector sub_limbs; + // If true no limb substitution step happens + bool strict_limb_definition = false; + // The actual contact area for unarmed damage calcs + std::vector contact_area; + // Do we care about armor damage bonuses + bool armor_bonus = true; + + cata::flat_set required_limb_flags; + cata::flat_set forbidden_limb_flags; // Encumbrance limit in absolute encumbrance int encumbrance_limit = 100; @@ -187,10 +192,6 @@ class ma_technique bool reach_ok = false; // possible to use during a reach attack bool attack_override = false; // The attack replaces the one it triggered off of - // performs the listed technique if this attack procs a crit. tec_none skips this behavior. - // requires crit_ok to be true - matec_id crit_tec_id = tec_none; - ma_requirements reqs; // What way is the technique delivered to the target? diff --git a/src/melee.cpp b/src/melee.cpp index 83650ed6bae14..e50d352a1ccb6 100644 --- a/src/melee.cpp +++ b/src/melee.cpp @@ -13,6 +13,7 @@ #include #include #include +#include #include #include @@ -73,6 +74,8 @@ static const anatomy_id anatomy_human_anatomy( "human_anatomy" ); +static const attack_vector_id attack_vector_base( "vector_base" ); + static const bionic_id bio_cqb( "bio_cqb" ); static const bionic_id bio_heat_absorb( "bio_heat_absorb" ); static const bionic_id bio_razors( "bio_razors" ); @@ -120,11 +123,8 @@ static const json_character_flag json_flag_GRAB( "GRAB" ); static const json_character_flag json_flag_GRAB_FILTER( "GRAB_FILTER" ); static const json_character_flag json_flag_HARDTOHIT( "HARDTOHIT" ); static const json_character_flag json_flag_HYPEROPIC( "HYPEROPIC" ); -static const json_character_flag json_flag_NEED_ACTIVE_TO_MELEE( "NEED_ACTIVE_TO_MELEE" ); static const json_character_flag json_flag_NULL( "NULL" ); static const json_character_flag json_flag_PSEUDOPOD_GRASP( "PSEUDOPOD_GRASP" ); -static const json_character_flag json_flag_UNARMED_BONUS( "UNARMED_BONUS" ); - static const limb_score_id limb_score_block( "block" ); static const limb_score_id limb_score_grip( "grip" ); static const limb_score_id limb_score_reaction( "reaction" ); @@ -440,7 +440,8 @@ std::string Character::get_miss_reason() } void Character::roll_all_damage( bool crit, damage_instance &di, bool average, - const item &weap, const std::string &attack_vector, const Creature *target, + const item &weap, const attack_vector_id &attack_vector, const sub_bodypart_str_id &contact, + const Creature *target, const bodypart_id &bp ) const { float crit_mod = 1.f; @@ -448,12 +449,12 @@ void Character::roll_all_damage( bool crit, damage_instance &di, bool average, crit_mod = target->get_crit_factor( bp ); } for( const damage_type &dt : damage_type::get_all() ) { - roll_damage( dt.id, crit, di, average, weap, attack_vector, crit_mod ); + roll_damage( dt.id, crit, di, average, weap, attack_vector, contact, crit_mod ); } } static void melee_train( Character &you, int lo, int hi, const item &weap, - const std::string &attack_vector ) + const attack_vector_id vector ) { you.practice( skill_melee, std::ceil( rng( lo, hi ) / 2.0 ), hi ); @@ -477,7 +478,7 @@ static void melee_train( Character &you, int lo, int hi, const item &weap, total = std::max( total, 1.f ); // Unarmed may deal cut, stab, and bash damage depending on the weapon - if( attack_vector != "WEAPON" ) { + if( !vector->weapon ) { you.practice( skill_unarmed, std::ceil( 1 * rng( lo, hi ) ), hi ); } else { for( const std::pair &dmg : dmg_vals ) { @@ -714,8 +715,7 @@ bool Character::melee_attack_abstract( Creature &t, bool allow_special, // Practice melee and relevant weapon skill (if any) except when using CQB bionic if( !has_active_bionic( bio_cqb ) && !t.is_hallucination() ) { - std::string attack_vector = cur_weapon ? "WEAPON" : "HAND"; - melee_train( *this, 2, std::min( 5, skill_training_cap ), cur_weap, attack_vector ); + melee_train( *this, 2, std::min( 5, skill_training_cap ), cur_weap, attack_vector_base ); } // Cap stumble penalty, heavy weapons are quite weak already @@ -741,53 +741,41 @@ bool Character::melee_attack_abstract( Creature &t, bool allow_special, const bool has_force_technique = !force_technique.str().empty(); - // Pick one or more special attacks - matec_id technique_id; + // Pick an attack + vector + contact area set + // tuple matec_id, attack_vector, std::vector ? + // We need the tech for tech data, chosen vector to decide on the weapon <-> just use sbp_dummy for weapon techs? + std::tuple attack; + + // Pick our attack + // Unarmed needs a defined technique if( has_force_technique ) { - technique_id = force_technique; + attack = std::make_tuple( force_technique, attack_vector_base, sub_body_part_sub_limb_debug ); } else if( allow_special ) { - technique_id = pick_technique( t, cur_weapon, critical_hit, false, false ); - if( critical_hit && technique_id.obj().crit_tec_id != tec_none ) { - technique_id = technique_id.obj().crit_tec_id; - } + attack = pick_technique( t, cur_weapon, critical_hit, false, false ); } else { - technique_id = tec_none; - } - - std::string attack_vector; - - // Failsafe for tec_none - if( technique_id == tec_none ) { - attack_vector = cur_weapon ? "WEAPON" : "HAND"; - } else { - attack_vector = martial_arts_data->get_valid_attack_vector( *this, - technique_id.obj().attack_vectors ); - - if( attack_vector == "NONE" ) { - std::vector shuffled_attack_vectors = technique_id.obj().attack_vectors_random; - std::shuffle( shuffled_attack_vectors.begin(), shuffled_attack_vectors.end(), rng_get_engine() ); - attack_vector = martial_arts_data->get_valid_attack_vector( *this, shuffled_attack_vectors ); - } + attack = std::make_tuple( tec_none, attack_vector_base, sub_body_part_sub_limb_debug ); } + // Unpack our data + matec_id attack_id; + attack_vector_id vector_id; + sub_bodypart_str_id contact_area; + std::tie( attack_id, vector_id, contact_area ) = attack; // If no weapon is selected, use highest layer of clothing for attack vector instead. - if( attack_vector != "WEAPON" ) { + if( contact_area != sub_body_part_sub_limb_debug ) { // todo: simplify this by using item_location everywhere // so only cur_weapon = worn.current_unarmed_weapon remains - item *worn_weap = worn.current_unarmed_weapon( attack_vector ); - cur_weapon = worn_weap ? item_location( *this, worn_weap ) : item_location(); - cur_weap = cur_weapon ? *cur_weapon : null_item_reference(); + // Check if our vector allows armor-derived damage + if( vector_id->armor_bonus ) { + item *worn_weap = worn.current_unarmed_weapon( contact_area ); + cur_weapon = worn_weap ? item_location( *this, worn_weap ) : item_location(); + cur_weap = cur_weapon ? *cur_weapon : null_item_reference(); + } } damage_instance d; - roll_all_damage( critical_hit, d, false, cur_weap, attack_vector, &t, target_bp ); + roll_all_damage( critical_hit, d, false, cur_weap, vector_id, contact_area, &t, target_bp ); - // your hits are not going to hurt very much if you can't use martial arts due to broken limbs - if( attack_vector == "HAND" && get_working_arm_count() < 1 ) { - technique_id = tec_none; - d.mult_damage( 0.1 ); - add_msg_if_player( m_bad, _( "Your arms are too damaged or encumbered to fight effectively!" ) ); - } // polearms and pikes (but not spears) do less damage to adjacent targets // In the case of a weapon like a glaive or a naginata, the wielder // lacks the room to build up momentum on a slash. @@ -809,7 +797,7 @@ bool Character::melee_attack_abstract( Creature &t, bool allow_special, d.mult_damage( 0.8 ); } - const ma_technique &technique = technique_id.obj(); + const ma_technique &technique = attack_id.obj(); // Handles effects as well; not done in melee_affect_* if( technique.id != tec_none ) { @@ -908,7 +896,7 @@ bool Character::melee_attack_abstract( Creature &t, bool allow_special, // Practice melee and relevant weapon skill (if any) except when using CQB bionic if( !has_active_bionic( bio_cqb ) && !t.is_hallucination() ) { - melee_train( *this, 5, std::min( 10, skill_training_cap ), cur_weap, attack_vector ); + melee_train( *this, 5, std::min( 10, skill_training_cap ), cur_weap, vector_id ); } // Treat monster as seen if we see it before or after the attack @@ -1284,11 +1272,11 @@ float Character::bonus_damage( bool random ) const static void roll_melee_damage_internal( const Character &u, const damage_type_id &dt, bool crit, damage_instance &di, bool average, const item &weap, - const std::string &attack_vector, float crit_mod ) + const attack_vector_id &attack_vector, const sub_bodypart_str_id &contact, float crit_mod ) { // FIXME: Hardcoded damage type float dmg = dt == damage_bash ? 0.f : u.mabuff_damage_bonus( dt ) + weap.damage_melee( dt ); - bool unarmed = attack_vector != "WEAPON"; + bool unarmed = !attack_vector->weapon; int arpen = 0; float skill = u.get_skill_level( unarmed ? skill_unarmed : dt->skill ); @@ -1297,82 +1285,41 @@ static void roll_melee_damage_internal( const Character &u, const damage_type_id skill = BIO_CQB_LEVEL; } - if( unarmed ) { - bool bp_unrestricted; - - if( attack_vector == "ARM" ) { - bp_unrestricted = !u.natural_attack_restricted_on( bodypart_id( "arm_l" ) ) || - ( !u.natural_attack_restricted_on( bodypart_id( "arm_r" ) ) ); - } else if( attack_vector == "ELBOW" ) { - bp_unrestricted = !u.natural_attack_restricted_on( sub_bodypart_id( "arm_elbow_l" ) ) || - ( !u.natural_attack_restricted_on( sub_bodypart_id( "arm_elbow_r" ) ) ); - } else if( attack_vector == "WRIST" ) { - bp_unrestricted = !u.natural_attack_restricted_on( sub_bodypart_id( "hand_wrist_l" ) ) || - ( !u.natural_attack_restricted_on( sub_bodypart_id( "hand_wrist_r" ) ) ); - } else if( attack_vector == "SHOULDER" ) { - bp_unrestricted = !u.natural_attack_restricted_on( sub_bodypart_id( "arm_shoulder_l" ) ) || - ( !u.natural_attack_restricted_on( sub_bodypart_id( "arm_shoulder_r" ) ) ); - } else if( attack_vector == "FOOT" ) { - bp_unrestricted = !u.natural_attack_restricted_on( bodypart_id( "foot_l" ) ) || - ( !u.natural_attack_restricted_on( bodypart_id( "foot_r" ) ) ); - } else if( attack_vector == "LOWER_LEG" ) { - bp_unrestricted = !u.natural_attack_restricted_on( sub_bodypart_id( "leg_lower_l" ) ) || - ( !u.natural_attack_restricted_on( sub_bodypart_id( "leg_lower_r" ) ) ); - } else if( attack_vector == "KNEE" ) { - bp_unrestricted = !u.natural_attack_restricted_on( sub_bodypart_id( "leg_knee_l" ) ) || - ( !u.natural_attack_restricted_on( sub_bodypart_id( "leg_knee_r" ) ) ); - } else if( attack_vector == "HIP" ) { - bp_unrestricted = !u.natural_attack_restricted_on( sub_bodypart_id( "leg_hip_l" ) ) || - ( !u.natural_attack_restricted_on( sub_bodypart_id( "leg_hip_r" ) ) ); - } else if( attack_vector == "HEAD" ) { - bp_unrestricted = !u.natural_attack_restricted_on( bodypart_id( "head" ) ); - } else if( attack_vector == "MOUTH" ) { - bp_unrestricted = !u.natural_attack_restricted_on( bodypart_id( "mouth" ) ); - } else if( attack_vector == "TORSO" ) { - bp_unrestricted = !u.natural_attack_restricted_on( bodypart_id( "torso" ) ); - } else { - bp_unrestricted = !u.natural_attack_restricted_on( bodypart_id( "hand_l" ) ) || - ( !u.natural_attack_restricted_on( bodypart_id( "hand_r" ) ) && weap.is_null() ); - } - - if( bp_unrestricted ) { - float extra_damage = 0.0f; - for( const trait_id &mut : u.get_mutations() ) { - if( mut->flags.count( json_flag_NEED_ACTIVE_TO_MELEE ) > 0 && !u.has_active_mutation( mut ) ) { - continue; - } - float unarmed_bonus = 0.0f; - int bonus_dmg = 0; - std::pair bonus_rand = { 0, 0 }; - if( mut->flags.count( json_flag_UNARMED_BONUS ) > 0 && bonus_dmg > 0 ) { - unarmed_bonus += std::min( u.get_skill_level( skill_unarmed ) / 2, 4.0f ); - } - extra_damage += bonus_dmg + unarmed_bonus; - extra_damage += average ? ( bonus_rand.first + bonus_rand.second ) / 2.0f : - rng( bonus_rand.first, bonus_rand.second ); - } - - // FIXME: Hardcoded damage type effect (stab) - if( attack_vector == "HAND" && dt == damage_stab && u.has_bionic( bio_razors ) ) { - extra_damage += 2; + // FIXME: Hardcoded damage type effects (bash) + if( dt == damage_bash ) { + if( u.has_trait( trait_KI_STRIKE ) && unarmed ) { + // Pure unarmed doubles the bonuses from unarmed skill + skill *= 2; + } + + // Drunken Master damage bonuses + if( u.has_trait( trait_DRUNKEN ) && u.has_effect( effect_drunk ) ) { + // Remember, a single drink gives 600 levels of "drunk" + int mindrunk = 0; + int maxdrunk = 0; + const time_duration drunk_dur = u.get_effect_dur( effect_drunk ); + if( unarmed ) { + mindrunk = drunk_dur / 1_hours; + maxdrunk = drunk_dur / 25_minutes; + } else { + mindrunk = drunk_dur / 90_minutes; + maxdrunk = drunk_dur / 40_minutes; } - dmg += extra_damage; + dmg += average ? ( mindrunk + maxdrunk ) * 0.5f : rng( mindrunk, maxdrunk ); } + } - float dam = 0.0f; - float ap = 0.0f; - for( const bodypart_id &bp : u.get_all_body_parts() ) { - if( bp->unarmed_bonus && !u.natural_attack_restricted_on( bp ) ) { - dam += bp->unarmed_damage( dt ); - ap += bp->unarmed_arpen( dt ); - } - } - dmg += dam; - arpen += ap; + if( unarmed && !u.natural_attack_restricted_on( contact ) ) { + // Add contact/parent damage bonuses to unarmed + dmg += contact->unarmed_damage.type_damage( dt ); + arpen += contact->unarmed_damage.type_arpen( dt ); + if( !u.natural_attack_restricted_on( contact->parent ) ) { + dmg += contact->parent->unarmed_damage( dt ); + arpen += contact->parent->unarmed_arpen( dt ); + } } - /** @ARM_STR increases bashing damage */ float stat_bonus = u.bonus_damage( !average ); stat_bonus += u.mabuff_damage_bonus( dt ); @@ -1454,7 +1401,8 @@ static void roll_melee_damage_internal( const Character &u, const damage_type_id } void Character::roll_damage( const damage_type_id &dt, bool crit, damage_instance &di, bool average, - const item &weap, const std::string &attack_vector, float crit_mod ) const + const item &weap, const attack_vector_id &attack_vector, const sub_bodypart_str_id &contact, + float crit_mod ) const { // For handling typical melee damage types (bash, cut, stab) if( dt->melee_only ) { @@ -1487,20 +1435,23 @@ void Character::roll_damage( const damage_type_id &dt, bool crit, damage_instanc di.add_damage( dt, other_dam, arpen, armor_mult, other_mul ); } } -matec_id Character::pick_technique( Creature &t, const item_location &weap, bool crit, - bool dodge_counter, bool block_counter, const std::vector &blacklist ) +std::tuple Character::pick_technique( + Creature &t, const item_location &weap, bool crit, + bool dodge_counter, bool block_counter, const std::vector &blacklist ) { - std::vector possible = evaluate_techniques( t, weap, crit, + std::vector> possible = + evaluate_techniques( t, weap, crit, dodge_counter, block_counter, blacklist ); return random_entry( possible, tec_none ); } -std::vector Character::evaluate_techniques( Creature &t, const item_location &weap, - bool crit, bool dodge_counter, bool block_counter, const std::vector &blacklist ) +std::vector> + Character::evaluate_techniques( Creature &t, const item_location &weap, + bool crit, bool dodge_counter, bool block_counter, const std::vector &blacklist ) { const std::vector all = martial_arts_data->get_all_techniques( weap, *this ); - std::vector possible; + std::vector>> possible; bool wall_adjacent = get_map().is_wall_adjacent( pos() ); // this could be more robust but for now it should work fine @@ -1624,26 +1575,25 @@ std::vector Character::evaluate_techniques( Creature &t, const item_lo continue; } - attack_vector_id wip = martial_arts_data->choose_attack_vector( *this, tec.id ); - - // Does the player have a functional attack vector to deliver the technique? - std::vector shuffled_attack_vectors = tec.attack_vectors_random; - std::shuffle( shuffled_attack_vectors.begin(), shuffled_attack_vectors.end(), rng_get_engine() ); - if( martial_arts_data->get_valid_attack_vector( *this, tec.attack_vectors ) == "NONE" && - martial_arts_data->get_valid_attack_vector( *this, shuffled_attack_vectors ) == "NONE" ) { - add_msg_debug( debugmode::DF_MELEE, "No valid attack vector found, attack discarded" ); - continue; - } + std::optional>> vector = + martial_arts_data->choose_attack_vector( *this, tec.id ); if( tec.is_valid_character( *this ) ) { - possible.push_back( tec.id ); - - //add weighted options into the list extra times, to increase their chance of being selected - if( tec.weighting > 1 ) { - for( int i = 1; i < tec.weighting; i++ ) { - possible.push_back( tec.id ); + // We made it this far, choose an actual vector if possible + vector = martial_arts_data->choose_attack_vector( *this, tec.id ); + if( vector ) { + possible.emplace_back( std::make_tuple( tec.id, vector->first, vector->second ) ); + //add weighted options into the list extra times, to increase their chance of being selected + if( tec.weighting > 1 ) { + for( int i = 1; i < tec.weighting; i++ ) { + possible.emplace_back( std::make_tuple( tec.id, vector->first, vector->second ) ); + } } + } else { + add_msg_debug( debugmode::DF_MELEE, "No valid attack vector found, attack discarded" ); + continue; } + } } diff --git a/src/subbodypart.cpp b/src/subbodypart.cpp index 8c69f4f1884dd..8a4732741470d 100644 --- a/src/subbodypart.cpp +++ b/src/subbodypart.cpp @@ -84,6 +84,7 @@ void sub_body_part_type::load( const JsonObject &jo, const std::string_view ) // defaults to self optional( jo, was_loaded, "locations_under", locations_under, { id } ); optional( jo, was_loaded, "similar_bodyparts", similar_bodyparts ); + optional( jo, was_loaded, "unarmed_damage", unarmed_damage ); } void sub_body_part_type::reset() diff --git a/src/subbodypart.h b/src/subbodypart.h index 94a86e62bb780..16cb6af3dd17a 100644 --- a/src/subbodypart.h +++ b/src/subbodypart.h @@ -10,6 +10,7 @@ #include #include +#include "damage.h" #include "enums.h" #include "flat_set.h" #include "int_id.h" @@ -75,6 +76,8 @@ struct sub_body_part_type { // These subparts act like this limb for armor coverage // TODO: Coverage/Encumbrance multiplier std::vector similar_bodyparts; + // Unarmed damage when this subpart is our contact area + damage_instance unarmed_damage; static void load_bp( const JsonObject &jo, const std::string &src ); From 4cc42591bb29a49b107d4f2036386f95b5536c12 Mon Sep 17 00:00:00 2001 From: Venera3 Date: Thu, 2 May 2024 09:38:06 +0200 Subject: [PATCH 03/16] Getting there --- data/json/attack_vectors.json | 12 ++--- data/json/items/armor/integrated.json | 4 +- data/json/mutations/mutation_techs.json | 1 - data/json/techniques.json | 18 ++----- .../Magiclysm/techniques_fantasy_species.json | 12 ----- data/mods/Xedra_Evolved/techniques.json | 3 -- doc/MARTIALART_JSON.md | 1 - src/character.cpp | 4 +- src/item.cpp | 15 ++++-- src/martialarts.cpp | 30 +++++------ src/martialarts.h | 1 - src/melee.cpp | 53 +++++++------------ src/talker_character.cpp | 5 +- 13 files changed, 60 insertions(+), 99 deletions(-) diff --git a/data/json/attack_vectors.json b/data/json/attack_vectors.json index 7b991c951554e..6e17d22a34de2 100644 --- a/data/json/attack_vectors.json +++ b/data/json/attack_vectors.json @@ -1,8 +1,9 @@ [ { "type": "attack_vector", - "id": "vector_base", + "id": "vector_null", "//": "Fallback weapon vector, unarmed attacks always go through techs", + "name": "base weapon vector", "weapon": true }, { @@ -17,12 +18,9 @@ "type": "attack_vector", "id": "test_punch", "name": "test punch", - "limbs": [ "hand_l", "hand_r", "foot_r", "foot_l", "eyes" ], - "contact_area": [ "hand_fingers_l", "hand_fingers_r", "foot_sole_l", "foot_sole_r", "eyes_left", "eyes_right" ], + "limbs": [ "hand_l", "hand_r" ], + "contact_area": [ "hand_fingers_l", "hand_fingers_r" ], "strict_limb_definition": false, - "armor_bonus": true, - "limb_count_min": 1, - "limb_count_max": 2, - "forbidden_limb_flags": [ "IGNORE_TEMP" ] + "armor_bonus": true } ] diff --git a/data/json/items/armor/integrated.json b/data/json/items/armor/integrated.json index 759bc5e7d509a..76743c9533c6a 100644 --- a/data/json/items/armor/integrated.json +++ b/data/json/items/armor/integrated.json @@ -1626,7 +1626,7 @@ "color": "white", "warmth": 5, "qualities": [ [ "CUT", 2 ], [ "BUTCHER", 4 ] ], - "techniques": [ "FANGS_BITE", "FANGS_BITE_NATURAL" ], + "techniques": [ "FANGS_BITE", "FANGS_BITE_NATURAL", "FANGS_BITE_CRIT" ], "flags": [ "INTEGRATED", "ALLOWS_NATURAL_ATTACKS", "UNBREAKABLE", "PERSONAL", "PADDED", "PROVIDES_TECHNIQUES" ], "armor": [ { @@ -1652,7 +1652,7 @@ "color": "white", "warmth": 5, "qualities": [ [ "CUT", 2 ], [ "BUTCHER", 4 ] ], - "techniques": [ "VAMPIRE_BITE", "VAMPIRE_BITE_NATURAL" ], + "techniques": [ "VAMPIRE_BITE", "VAMPIRE_BITE_NATURAL", "VAMPIRE_BITE_CRIT" ], "flags": [ "INTEGRATED", "ALLOWS_NATURAL_ATTACKS", "UNBREAKABLE", "PERSONAL", "PADDED", "PROVIDES_TECHNIQUES" ], "armor": [ { diff --git a/data/json/mutations/mutation_techs.json b/data/json/mutations/mutation_techs.json index 7cb362bde29a8..43ec14397975b 100644 --- a/data/json/mutations/mutation_techs.json +++ b/data/json/mutations/mutation_techs.json @@ -6,7 +6,6 @@ "messages": [ "You sting %s with your bug-laden tail", " stings %s with their bug-laden tail" ], "unarmed_allowed": true, "melee_allowed": true, - "attack_override": true, "weighting": 200, "crit_ok": true, "repeat_min": 1, diff --git a/data/json/techniques.json b/data/json/techniques.json index 9b57a279d907d..1441d95a45a98 100644 --- a/data/json/techniques.json +++ b/data/json/techniques.json @@ -3,13 +3,15 @@ "type": "technique", "id": "tec_none", "name": "Not a technique at all", - "wip_attack_vectors": [ "vector_base" ], + "wip_attack_vectors": [ "vector_null" ], "dummy": true }, { "type": "technique", "id": "tech_base_punch", "name": "Basic punch", + "crit_ok": true, + "unarmed_allowed": true, "messages": [ "You punch %s!", " punches %s!" ], "description": "Just a basic punch", "wip_attack_vectors": [ "test_punch" ] @@ -3500,9 +3502,6 @@ "unarmed_allowed": true, "weighting": -5, "reach_ok": false, - "attack_override": true, - "crit_ok": true, - "crit_tec_id": "FANGS_BITE_CRIT", "attack_vectors": [ "MOUTH" ], "condition": { "and": [ @@ -3539,9 +3538,6 @@ "unarmed_allowed": true, "weighting": -2, "reach_ok": false, - "attack_override": true, - "crit_ok": true, - "crit_tec_id": "FANGS_BITE_CRIT", "attack_vectors": [ "MOUTH" ], "condition": { "and": [ @@ -3578,7 +3574,6 @@ "messages": [ "You deliver a wicked bite to %s", " delivers a wicked bite to %s!" ], "unarmed_allowed": true, "reach_ok": false, - "attack_override": true, "crit_tec": true, "attack_vectors": [ "MOUTH" ], "tech_effects": [ @@ -3612,9 +3607,6 @@ "unarmed_allowed": true, "weighting": -5, "reach_ok": false, - "attack_override": true, - "crit_ok": true, - "crit_tec_id": "VAMPIRE_BITE_CRIT", "attack_vectors": [ "MOUTH" ], "condition": { "and": [ @@ -3651,9 +3643,6 @@ "unarmed_allowed": true, "weighting": -2, "reach_ok": false, - "attack_override": true, - "crit_ok": true, - "crit_tec_id": "VAMPIRE_BITE_CRIT", "attack_vectors": [ "MOUTH" ], "tech_effects": [ { @@ -3691,7 +3680,6 @@ "unarmed_allowed": true, "weighting": -4, "reach_ok": false, - "attack_override": true, "crit_tec": true, "tech_effects": [ { diff --git a/data/mods/Magiclysm/techniques_fantasy_species.json b/data/mods/Magiclysm/techniques_fantasy_species.json index 3f9e922d13314..6ec3868b8dc03 100644 --- a/data/mods/Magiclysm/techniques_fantasy_species.json +++ b/data/mods/Magiclysm/techniques_fantasy_species.json @@ -9,9 +9,6 @@ "unarmed_allowed": true, "weighting": -3, "reach_ok": false, - "attack_override": true, - "crit_ok": true, - "crit_tec_id": "FANGS_BITE_GOBLIN_CRIT", "attack_vectors": [ "MOUTH" ], "condition": { "and": [ { "not": { "npc_has_flag": "GRAB_FILTER" } }, { "not": { "u_has_effect": "GRAB" } } ] }, "//": "The bonuses below are based on the natural anatomy of a non-human creature and should not be used for mutant attack scaling.", @@ -35,9 +32,6 @@ "unarmed_allowed": true, "weighting": -2, "reach_ok": false, - "attack_override": true, - "crit_ok": true, - "crit_tec_id": "FANGS_BITE_GOBLIN_CRIT", "attack_vectors": [ "MOUTH" ], "condition": { "and": [ { "npc_has_flag": "GRAB_FILTER" }, { "u_has_flag": "GRAB" } ] }, "//": "The bonuses below are based on the natural anatomy of a non-human creature and should not be used for mutant attack scaling.", @@ -59,7 +53,6 @@ "messages": [ "You deliver a wicked bite to %s", " delivers a wicked bite to %s!" ], "unarmed_allowed": true, "reach_ok": false, - "attack_override": true, "crit_tec": true, "attack_vectors": [ "MOUTH" ], "flat_bonuses": [ @@ -84,9 +77,6 @@ "unarmed_allowed": true, "weighting": -2, "reach_ok": false, - "attack_override": true, - "crit_ok": true, - "crit_tec_id": "LIZARDFOLK_BITE_CRIT", "attack_vectors": [ "MOUTH" ], "//": "The bonuses below are based on the natural anatomy of a non-human creature and should not be used for mutant attack scaling.", "flat_bonuses": [ @@ -108,8 +98,6 @@ "unarmed_allowed": true, "weighting": -5, "reach_ok": false, - "attack_override": true, - "crit_ok": true, "crit_tec": true, "attack_vectors": [ "MOUTH" ], "condition": { "and": [ { "not": { "npc_has_flag": "GRAB_FILTER" } }, { "not": { "u_has_effect": "GRAB" } } ] }, diff --git a/data/mods/Xedra_Evolved/techniques.json b/data/mods/Xedra_Evolved/techniques.json index 3ee2fbfe83ad2..374689fdb1d58 100644 --- a/data/mods/Xedra_Evolved/techniques.json +++ b/data/mods/Xedra_Evolved/techniques.json @@ -9,7 +9,6 @@ ], "unarmed_allowed": true, "melee_allowed": true, - "attack_override": true, "weighting": 2, "crit_ok": true, "repeat_min": 1, @@ -29,7 +28,6 @@ ], "unarmed_allowed": true, "melee_allowed": true, - "attack_override": true, "weighting": 2, "crit_ok": true, "repeat_min": 1, @@ -51,7 +49,6 @@ ], "unarmed_allowed": true, "melee_allowed": true, - "attack_override": true, "weighting": 2, "crit_ok": true, "repeat_min": 1, diff --git a/doc/MARTIALART_JSON.md b/doc/MARTIALART_JSON.md index 8ecebf0d6d072..99370f3870fce 100644 --- a/doc/MARTIALART_JSON.md +++ b/doc/MARTIALART_JSON.md @@ -90,7 +90,6 @@ "crit_ok": true, // This technique works on both normal and critical hits "reach_tec": true, // This technique only works on a reach attack hit "reach_ok": true, // This technique works on both normal and reach attack hits - "attack_override": false, // This technique replaces the base attack it triggered on, nulling damage and movecost (instead using the tech's flat_bonuses), and counts as unarmed for the purposes of skill training and special melee effects "condition": "u_is_outside",// Optional (array of) dialog conditions the attack requires to trigger. Failing these will disqualify the tech from being selected "condition_desc": "Needs X",// Description string describing the conditions of this attack (since dialog conditions can't be automatically evaluated) "repeat_min": 1, // Technique's damage and any added effects are repeated rng(repeat_min, repeat_max) times. The target's armor and the effect's chances are applied for each repeat. diff --git a/src/character.cpp b/src/character.cpp index 8b271cf20a3ae..e8a8b699bf136 100644 --- a/src/character.cpp +++ b/src/character.cpp @@ -353,8 +353,6 @@ static const limb_score_id limb_score_night_vis( "night_vis" ); static const limb_score_id limb_score_reaction( "reaction" ); static const limb_score_id limb_score_vision( "vision" ); -const matec_id tec_none( "tec_none" ); - static const material_id material_budget_steel( "budget_steel" ); static const material_id material_ch_steel( "ch_steel" ); static const material_id material_flesh( "flesh" ); @@ -1974,7 +1972,7 @@ void Character::on_dodge( Creature *source, float difficulty, float training_lev // For adjacent attackers check for techniques usable upon successful dodge if( source && square_dist( pos(), source->pos() ) == 1 ) { - matec_id tec = pick_technique( *source, used_weapon(), false, true, false ); + matec_id tec = std::get<0>( pick_technique( *source, used_weapon(), false, true, false ) ); if( tec != tec_none && !is_dead_state() ) { if( get_stamina() < get_stamina_max() / 3 ) { diff --git a/src/item.cpp b/src/item.cpp index 2b217c4492bef..00abc322490ec 100644 --- a/src/item.cpp +++ b/src/item.cpp @@ -124,6 +124,8 @@ static const ammotype ammo_bolt( "bolt" ); static const ammotype ammo_money( "money" ); static const ammotype ammo_plutonium( "plutonium" ); +static const attack_vector_id attack_vector_null( "vector_null" ); + static const bionic_id bio_digestion( "bio_digestion" ); static const bodygraph_id bodygraph_full_body_iteminfo( "full_body_iteminfo" ); @@ -187,6 +189,7 @@ static const json_character_flag json_flag_PSYCHOPATH( "PSYCHOPATH" ); static const json_character_flag json_flag_SAPIOVORE( "SAPIOVORE" ); static const matec_id RAPID( "RAPID" ); +static const matec_id tec_none( "tec_none" ); static const material_id material_wool( "wool" ); @@ -2279,7 +2282,8 @@ double item::effective_dps( const Character &guy, Creature &mon ) const Creature *temp_mon = &mon; double subtotal_damage = 0; damage_instance base_damage; - guy.roll_all_damage( crit, base_damage, true, *this, "WEAPON", &mon, bp ); + guy.roll_all_damage( crit, base_damage, true, *this, attack_vector_null, + sub_body_part_sub_limb_debug, &mon, bp ); damage_instance dealt_damage = base_damage; // TODO: Modify DPS calculation to consider weakpoints. resistances r = resistances( *static_cast( temp_mon ) ); @@ -2304,7 +2308,8 @@ double item::effective_dps( const Character &guy, Creature &mon ) const if( has_technique( RAPID ) ) { Creature *temp_rs_mon = &mon; damage_instance rs_base_damage; - guy.roll_all_damage( crit, rs_base_damage, true, *this, "WEAPON", &mon, bp ); + guy.roll_all_damage( crit, rs_base_damage, true, *this, attack_vector_null, + sub_body_part_sub_limb_debug, &mon, bp ); damage_instance dealt_rs_damage = rs_base_damage; for( damage_unit &dmg_unit : dealt_rs_damage.damage_units ) { dmg_unit.damage_multiplier *= 0.66; @@ -5584,9 +5589,11 @@ void item::melee_combat_info( std::vector &info, const iteminfo_query ( !dmg_types.empty() || type->m_to_hit > 0 ) ) || debug_mode ) { bodypart_id bp = bodypart_id( "torso" ); damage_instance non_crit; - player_character.roll_all_damage( false, non_crit, true, *this, "WEAPON", nullptr, bp ); + player_character.roll_all_damage( false, non_crit, true, *this, attack_vector_null, + sub_body_part_sub_limb_debug, nullptr, bp ); damage_instance crit; - player_character.roll_all_damage( true, crit, true, *this, "WEAPON", nullptr, bp ); + player_character.roll_all_damage( true, crit, true, *this, attack_vector_null, + sub_body_part_sub_limb_debug, nullptr, bp ); int attack_cost = player_character.attack_speed( *this ); insert_separation_line( info ); if( parts->test( iteminfo_parts::DESCRIPTION_MELEEDMG ) ) { diff --git a/src/martialarts.cpp b/src/martialarts.cpp index 933507ed4bc94..370e8c87ceb15 100644 --- a/src/martialarts.cpp +++ b/src/martialarts.cpp @@ -37,8 +37,6 @@ #include "value_ptr.h" #include "weighted_list.h" -static const attack_vector_id attack_vector_null( "null" ); - static const bionic_id bio_armor_arms( "bio_armor_arms" ); static const bionic_id bio_armor_legs( "bio_armor_legs" ); static const bionic_id bio_cqb( "bio_cqb" ); @@ -52,8 +50,6 @@ static const limb_score_id limb_score_block( "block" ); static const skill_id skill_unarmed( "unarmed" ); -static const sub_bodypart_str_id sub_body_part_sub_limb_debug( "sub_limb_debug" ); - static const weapon_category_id weapon_category_OTHER_INVALID_WEAP_CAT( "OTHER_INVALID_WEAP_CAT" ); @@ -278,7 +274,6 @@ void ma_technique::load( const JsonObject &jo, const std::string &src ) optional( jo, was_loaded, "crit_tec", crit_tec, false ); optional( jo, was_loaded, "crit_ok", crit_ok, false ); - optional( jo, was_loaded, "attack_override", attack_override, false ); optional( jo, was_loaded, "wall_adjacent", wall_adjacent, false ); optional( jo, was_loaded, "reach_tec", reach_tec, false ); optional( jo, was_loaded, "reach_ok", reach_ok, false ); @@ -1540,7 +1535,7 @@ std::optional> add_msg_debug( debugmode::DF_MELEE, "Bodypart %s eligable for attack vector %s weight %.1f (contact area %s)", bp.id().c_str(), - vec->name, weight, current_contact->name ); + vec->name, unarmed_damage, current_contact->name ); } } } @@ -1585,15 +1580,20 @@ damage_instance character_martial_arts::calculate_vector_damage( const Character // Calculate unarmed damage for the given sublimb(s) damage_instance ret; // If we got this far the limb is not overencumbered - ret.add( contact_area->parent->unarmed_damage_instance() ); - add_msg_debug( debugmode::DF_MELEE, - "Unarmed damage of bodypart %s %.1f", contact_area->parent->name, - ret.total_damage() ); - ret.add( contact_area->unarmed_damage ); - add_msg_debug( debugmode::DF_MELEE, - "Unarmed damage of subpart %s %.1f, total damage %.1f", contact_area->parent->name, - contact_area->unarmed_damage.total_damage(), - ret.total_damage() ); + // But do filter on the flag to bring it in line with the actual damage calc + if( !user.natural_attack_restricted_on( contact_area->parent ) ) { + ret.add( contact_area->parent->unarmed_damage_instance() ); + add_msg_debug( debugmode::DF_MELEE, + "Unarmed damage of bodypart %s %.1f", contact_area->parent->name, + ret.total_damage() ); + } + if( !user.natural_attack_restricted_on( contact_area ) ) { + ret.add( contact_area->unarmed_damage ); + add_msg_debug( debugmode::DF_MELEE, + "Unarmed damage of subpart %s %.1f, total damage %.1f", contact_area->parent->name, + contact_area->unarmed_damage.total_damage(), + ret.total_damage() ); + } // Add any bonus from worn armor if the vector allows it if( vec->armor_bonus ) { outfit current_worn = user.worn; diff --git a/src/martialarts.h b/src/martialarts.h index 6287d833758fe..7daecb44f2b69 100644 --- a/src/martialarts.h +++ b/src/martialarts.h @@ -190,7 +190,6 @@ class ma_technique bool crit_ok = false; bool reach_tec = false; // only possible to use during a reach attack bool reach_ok = false; // possible to use during a reach attack - bool attack_override = false; // The attack replaces the one it triggered off of ma_requirements reqs; diff --git a/src/melee.cpp b/src/melee.cpp index e50d352a1ccb6..d18f1c283e7d1 100644 --- a/src/melee.cpp +++ b/src/melee.cpp @@ -74,7 +74,7 @@ static const anatomy_id anatomy_human_anatomy( "human_anatomy" ); -static const attack_vector_id attack_vector_base( "vector_base" ); +static const attack_vector_id attack_vector_null( "vector_null" ); static const bionic_id bio_cqb( "bio_cqb" ); static const bionic_id bio_heat_absorb( "bio_heat_absorb" ); @@ -715,7 +715,7 @@ bool Character::melee_attack_abstract( Creature &t, bool allow_special, // Practice melee and relevant weapon skill (if any) except when using CQB bionic if( !has_active_bionic( bio_cqb ) && !t.is_hallucination() ) { - melee_train( *this, 2, std::min( 5, skill_training_cap ), cur_weap, attack_vector_base ); + melee_train( *this, 2, std::min( 5, skill_training_cap ), cur_weap, attack_vector_null ); } // Cap stumble penalty, heavy weapons are quite weak already @@ -749,11 +749,11 @@ bool Character::melee_attack_abstract( Creature &t, bool allow_special, // Pick our attack // Unarmed needs a defined technique if( has_force_technique ) { - attack = std::make_tuple( force_technique, attack_vector_base, sub_body_part_sub_limb_debug ); + attack = std::make_tuple( force_technique, attack_vector_null, sub_body_part_sub_limb_debug ); } else if( allow_special ) { attack = pick_technique( t, cur_weapon, critical_hit, false, false ); } else { - attack = std::make_tuple( tec_none, attack_vector_base, sub_body_part_sub_limb_debug ); + attack = std::make_tuple( tec_none, attack_vector_null, sub_body_part_sub_limb_debug ); } // Unpack our data matec_id attack_id; @@ -820,11 +820,7 @@ bool Character::melee_attack_abstract( Creature &t, bool allow_special, std::string specialmsg; // Handles speed penalties to monster & us, etc if( !t.is_hallucination() ) { - if( technique.attack_override ) { - specialmsg = melee_special_effects( t, d, null_item_reference() ); - } else { - specialmsg = melee_special_effects( t, d, cur_weap ); - } + specialmsg = melee_special_effects( t, d, cur_weap ); } // gets overwritten with the dealt damage values @@ -1406,25 +1402,21 @@ void Character::roll_damage( const damage_type_id &dt, bool crit, damage_instanc { // For handling typical melee damage types (bash, cut, stab) if( dt->melee_only ) { - roll_melee_damage_internal( *this, dt, crit, di, average, weap, attack_vector, crit_mod ); + roll_melee_damage_internal( *this, dt, crit, di, average, weap, attack_vector, contact, crit_mod ); return; } - bool unarmed = attack_vector != "WEAPON"; - float other_dam = mabuff_damage_bonus( dt ) + weap.damage_melee( dt ); float arpen = 0.0f; - if( unarmed ) { - float dam = 0.0f; - float ap = 0.0f; - for( const bodypart_id &bp : get_all_body_parts() ) { - if( bp->unarmed_bonus && !natural_attack_restricted_on( bp ) ) { - dam += bp->unarmed_damage( dt ); - arpen += bp->unarmed_arpen( dt ); - } + bool unarmed = !attack_vector->weapon; + if( unarmed && !this->natural_attack_restricted_on( contact ) ) { + // Add contact/parent damage bonuses to unarmed + other_dam += contact->unarmed_damage.type_damage( dt ); + arpen += contact->unarmed_damage.type_arpen( dt ); + if( !this->natural_attack_restricted_on( contact->parent ) ) { + other_dam += contact->parent->unarmed_damage( dt ); + arpen += contact->parent->unarmed_arpen( dt ); } - other_dam += dam; - arpen += ap; } // No negative damage! @@ -1442,7 +1434,9 @@ std::tuple Character::pick_tech std::vector> possible = evaluate_techniques( t, weap, crit, dodge_counter, block_counter, blacklist ); - return random_entry( possible, tec_none ); + return random_entry( possible, + std::make_tuple( tec_none, attack_vector_null, + sub_body_part_sub_limb_debug ) ); } std::vector> Character::evaluate_techniques( Creature &t, const item_location &weap, @@ -1451,7 +1445,7 @@ std::vector> const std::vector all = martial_arts_data->get_all_techniques( weap, *this ); - std::vector>> possible; + std::vector> possible; bool wall_adjacent = get_map().is_wall_adjacent( pos() ); // this could be more robust but for now it should work fine @@ -1575,8 +1569,7 @@ std::vector> continue; } - std::optional>> vector = - martial_arts_data->choose_attack_vector( *this, tec.id ); + std::optional> vector; if( tec.is_valid_character( *this ) ) { // We made it this far, choose an actual vector if possible @@ -1759,12 +1752,6 @@ void Character::perform_technique( const ma_technique &technique, Creature &t, int rep = rng( technique.repeat_min, technique.repeat_max ); add_msg_debug( debugmode::DF_MELEE, "Tech repeats %d times", rep ); - // Keep the technique definitions shorter - if( technique.attack_override ) { - move_cost = 0; - di.clear(); - } - for( const damage_type &dt : damage_type::get_all() ) { float dam = technique.damage_bonus( *this, dt.id ); float arpen = technique.armor_penetration( *this, dt.id ); @@ -2210,7 +2197,7 @@ bool Character::block_hit( Creature *source, bodypart_id &bp_hit, damage_instanc martial_arts_data->ma_onblock_effects( *this ); // Check if we have any block counters - matec_id tec = pick_technique( *source, shield, false, false, true ); + matec_id tec = std::get<0>( pick_technique( *source, shield, false, false, true ) ); if( tec != tec_none && !is_dead_state() ) { int twenty_percent = std::round( ( 20 * weapon.type->mat_portion_total ) / 100.0f ); diff --git a/src/talker_character.cpp b/src/talker_character.cpp index b12146efc0b12..5fa81e2dde7c2 100644 --- a/src/talker_character.cpp +++ b/src/talker_character.cpp @@ -1215,8 +1215,9 @@ void talker_character::die() matec_id talker_character::get_random_technique( Creature &t, bool crit, bool dodge_counter, bool block_counter, const std::vector &blacklist ) const { - return me_chr->pick_technique( t, me_chr->used_weapon(), crit, dodge_counter, block_counter, - blacklist ); + return std::get<0>( me_chr->pick_technique( t, me_chr->used_weapon(), crit, dodge_counter, + block_counter, + blacklist ) ); } void talker_character::attack_target( Creature &t, bool allow_special, From 8273ee60be82ceec499731ccdd4c8f2bf0c0b56b Mon Sep 17 00:00:00 2001 From: Venera3 Date: Thu, 2 May 2024 09:42:45 +0200 Subject: [PATCH 04/16] oop --- src/melee.cpp | 27 +-------------------------- 1 file changed, 1 insertion(+), 26 deletions(-) diff --git a/src/melee.cpp b/src/melee.cpp index d18f1c283e7d1..fdfb70a212d5f 100644 --- a/src/melee.cpp +++ b/src/melee.cpp @@ -1280,32 +1280,7 @@ static void roll_melee_damage_internal( const Character &u, const damage_type_id if( u.has_active_bionic( bio_cqb ) ) { skill = BIO_CQB_LEVEL; } - - // FIXME: Hardcoded damage type effects (bash) - if( dt == damage_bash ) { - if( u.has_trait( trait_KI_STRIKE ) && unarmed ) { - // Pure unarmed doubles the bonuses from unarmed skill - skill *= 2; - } - - // Drunken Master damage bonuses - if( u.has_trait( trait_DRUNKEN ) && u.has_effect( effect_drunk ) ) { - // Remember, a single drink gives 600 levels of "drunk" - int mindrunk = 0; - int maxdrunk = 0; - const time_duration drunk_dur = u.get_effect_dur( effect_drunk ); - if( unarmed ) { - mindrunk = drunk_dur / 1_hours; - maxdrunk = drunk_dur / 25_minutes; - } else { - mindrunk = drunk_dur / 90_minutes; - maxdrunk = drunk_dur / 40_minutes; - } - - dmg += average ? ( mindrunk + maxdrunk ) * 0.5f : rng( mindrunk, maxdrunk ); - } - } - + if( unarmed && !u.natural_attack_restricted_on( contact ) ) { // Add contact/parent damage bonuses to unarmed dmg += contact->unarmed_damage.type_damage( dt ); From 86057c037b208afcb13d4a8adda1cab88864366e Mon Sep 17 00:00:00 2001 From: Venera3 Date: Thu, 2 May 2024 10:26:34 +0200 Subject: [PATCH 05/16] Deprecate, dewippify --- src/character_attire.cpp | 59 ------------------------------------ src/character_attire.h | 1 - src/character_martial_arts.h | 5 --- src/init.cpp | 2 +- src/martialarts.cpp | 56 ++++++---------------------------- src/martialarts.h | 6 ++-- src/type_id.h | 4 +-- 7 files changed, 14 insertions(+), 119 deletions(-) diff --git a/src/character_attire.cpp b/src/character_attire.cpp index b4b3f1bc1a104..464b8108a41a4 100644 --- a/src/character_attire.cpp +++ b/src/character_attire.cpp @@ -2192,65 +2192,6 @@ item *outfit::best_shield() return ret; } -item *outfit::current_unarmed_weapon( const std::string &attack_vector ) -{ - item *cur_weapon = &null_item_reference(); - - for( item &worn_item : worn ) { - bool covers = false; - - if( attack_vector == "HAND" || attack_vector == "GRAPPLE" || attack_vector == "THROW" ) { - covers = worn_item.covers( bodypart_id( "hand_l" ) ) && - worn_item.covers( bodypart_id( "hand_r" ) ); - } else if( attack_vector == "ARM" ) { - covers = worn_item.covers( bodypart_id( "arm_l" ) ) && - worn_item.covers( bodypart_id( "arm_r" ) ); - } else if( attack_vector == "ELBOW" ) { - covers = worn_item.covers( sub_bodypart_id( "arm_elbow_l" ) ) && - worn_item.covers( sub_bodypart_id( "arm_elbow_r" ) ); - } else if( attack_vector == "FINGERS" ) { - covers = worn_item.covers( sub_bodypart_id( "hand_fingers_l" ) ) && - worn_item.covers( sub_bodypart_id( "hand_fingers_r" ) ); - } else if( attack_vector == "WRIST" ) { - covers = worn_item.covers( sub_bodypart_id( "hand_wrist_l" ) ) && - worn_item.covers( sub_bodypart_id( "hand_wrist_r" ) ); - } else if( attack_vector == "PALM" ) { - covers = worn_item.covers( sub_bodypart_id( "hand_palm_l" ) ) && - worn_item.covers( sub_bodypart_id( "hand_palm_r" ) ); - } else if( attack_vector == "HAND_BACK" ) { - covers = worn_item.covers( sub_bodypart_id( "hand_back_l" ) ) && - worn_item.covers( sub_bodypart_id( "hand_back_r" ) ); - } else if( attack_vector == "SHOULDER" ) { - covers = worn_item.covers( sub_bodypart_id( "arm_shoulder_l" ) ) && - worn_item.covers( sub_bodypart_id( "arm_shoulder_r" ) ); - } else if( attack_vector == "FOOT" ) { - covers = worn_item.covers( bodypart_id( "foot_l" ) ) && - worn_item.covers( bodypart_id( "foot_r" ) ); - } else if( attack_vector == "LOWER_LEG" ) { - covers = worn_item.covers( sub_bodypart_id( "leg_lower_l" ) ) && - worn_item.covers( sub_bodypart_id( "leg_lower_r" ) ); - } else if( attack_vector == "KNEE" ) { - covers = worn_item.covers( sub_bodypart_id( "leg_knee_l" ) ) && - worn_item.covers( sub_bodypart_id( "leg_knee_r" ) ); - } else if( attack_vector == "HIP" ) { - covers = worn_item.covers( sub_bodypart_id( "leg_hip_l" ) ) && - worn_item.covers( sub_bodypart_id( "leg_hip_r" ) ); - } else if( attack_vector == "HEAD" ) { - covers = worn_item.covers( bodypart_id( "head" ) ); - } else if( attack_vector == "TORSO" ) { - covers = worn_item.covers( bodypart_id( "torso" ) ); - } - - // Uses enum layer_level to make distinction for top layer. - if( covers ) { - if( cur_weapon->is_null() || ( worn_item.get_layer() >= cur_weapon->get_layer() ) ) { - cur_weapon = &worn_item; - } - } - } - return cur_weapon; -} - item *outfit::current_unarmed_weapon( const sub_bodypart_str_id &contact_area ) { item *cur_weapon = &null_item_reference(); diff --git a/src/character_attire.h b/src/character_attire.h index 28bcc5964f8d8..68db0240e2a60 100644 --- a/src/character_attire.h +++ b/src/character_attire.h @@ -112,7 +112,6 @@ class outfit // get the best blocking value with the flag that allows worn. item *best_shield(); // find the best clothing weapon when unarmed modifies - item *current_unarmed_weapon( const std::string &attack_vector ); item *current_unarmed_weapon( const sub_bodypart_str_id &contact_area ); item_location first_item_covering_bp( Character &guy, bodypart_id bp ); void inv_dump( std::vector &ret ); diff --git a/src/character_martial_arts.h b/src/character_martial_arts.h index a6069232db820..95f36598e6151 100644 --- a/src/character_martial_arts.h +++ b/src/character_martial_arts.h @@ -86,11 +86,6 @@ class character_martial_arts // Calculate and return the damage of the given contact area for a given vector damage_instance calculate_vector_damage( const Character &user, const attack_vector_id &vec, const sub_bodypart_str_id &contact_area ) const; - /** Returns an attack vector that the player can use */ - std::string get_valid_attack_vector( const Character &user, - const std::vector &attack_vectors ) const; - /** Returns true if the player is able to use the given attack vector */ - bool can_use_attack_vector( const Character &user, const std::string &av ) const; /** Returns true if the player has the leg block technique available */ bool can_leg_block( const Character &owner ) const; /** Returns true if the player has the arm block technique available */ diff --git a/src/init.cpp b/src/init.cpp index 319c26141a747..c65e6be9b599c 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -407,7 +407,7 @@ void DynamicDataLoader::initialize() add( "weapon_category", &weapon_category::load_weapon_categories ); add( "martial_art", &load_martial_art ); add( "climbing_aid", &climbing_aid::load_climbing_aid ); - add( "attack_vector", &wip_attack_vector::load_attack_vectors ); + add( "attack_vector", &attack_vector::load_attack_vectors ); add( "effect_type", &load_effect_type ); add( "oter_id_migration", &overmap::load_oter_id_migration ); add( "overmap_terrain", &overmap_terrains::load ); diff --git a/src/martialarts.cpp b/src/martialarts.cpp index 370e8c87ceb15..d32d068e0c1d7 100644 --- a/src/martialarts.cpp +++ b/src/martialarts.cpp @@ -59,12 +59,12 @@ generic_factory weapon_category_factory( "weapon category" ); generic_factory ma_techniques( "martial art technique" ); generic_factory martialarts( "martial art style" ); generic_factory ma_buffs( "martial art buff" ); -generic_factory attack_vector_factory( "attack vector" ); +generic_factory attack_vector_factory( "attack vector" ); } // namespace /** @relates string_id */ template<> -const wip_attack_vector &string_id::obj() const +const attack_vector &string_id::obj() const { return attack_vector_factory.obj( *this ); } @@ -75,17 +75,17 @@ bool attack_vector_id::is_valid() const return attack_vector_factory.is_valid( *this ); } -void wip_attack_vector::load_attack_vectors( const JsonObject &jo, const std::string &src ) +void attack_vector::load_attack_vectors( const JsonObject &jo, const std::string &src ) { attack_vector_factory.load( jo, src ); } -void wip_attack_vector::reset() +void attack_vector::reset() { attack_vector_factory.reset(); } -void wip_attack_vector::load( const JsonObject &jo, const std::string_view ) +void attack_vector::load( const JsonObject &jo, const std::string_view ) { mandatory( jo, was_loaded, "id", id ); mandatory( jo, was_loaded, "name", name ); @@ -304,9 +304,6 @@ void ma_technique::load( const JsonObject &jo, const std::string &src ) optional( jo, was_loaded, "flags", flags, auto_flags_reader<> {} ); optional( jo, was_loaded, "tech_effects", tech_effects, tech_effect_reader{} ); - optional( jo, was_loaded, "attack_vectors", attack_vectors, {} ); - optional( jo, was_loaded, "attack_vectors_random", attack_vectors_random, {} ); - for( JsonValue jv : jo.get_array( "eocs" ) ) { eocs.push_back( effect_on_conditions::load_inline_eoc( jv, src ) ); } @@ -317,8 +314,8 @@ void ma_technique::load( const JsonObject &jo, const std::string &src ) has_condition = true; } - if( jo.has_array( "wip_attack_vectors" ) ) { - optional( jo, was_loaded, "wip_attack_vectors", wip_attack_vectors ); + if( jo.has_array( "attack_vectors" ) ) { + optional( jo, was_loaded, "attack_vectors", attack_vectors ); } reqs.load( jo, src ); bonuses.load( jo ); @@ -609,7 +606,7 @@ void finalize_martial_arts() effect_type::register_ma_buff_effect( new_eff ); } // Iterate through every attack vector and substitute similar limbs (as long as they have similar sublimbs - for( wip_attack_vector vector : attack_vector_factory.get_all() ) { + for( attack_vector vector : attack_vector_factory.get_all() ) { // Check if this vector allows substitutions in the first place if( vector.strict_limb_definition ) { continue; @@ -1447,18 +1444,6 @@ ma_technique character_martial_arts::get_miss_recovery( const Character &owner ) return get_valid_technique( owner, &ma_technique::miss_recovery ); } -std::string character_martial_arts::get_valid_attack_vector( const Character &user, - const std::vector &attack_vectors ) const -{ - for( auto av : attack_vectors ) { - if( can_use_attack_vector( user, av ) ) { - return av; - } - } - - return "NONE"; -} - std::optional> character_martial_arts::choose_attack_vector( const Character &user, const matec_id &tech ) const @@ -1472,7 +1457,7 @@ std::optional> const bool armed = user.is_armed(); martialart ma = style_selected.obj(); bool valid_weapon = ma.weapon_valid( user.get_wielded_item() ); - for( const attack_vector_id &vec : tech.obj().wip_attack_vectors ) { + for( const attack_vector_id &vec : tech.obj().attack_vectors ) { add_msg_debug( debugmode::DF_MELEE, "Evaluating vector %s for tech %s", vec->name, tech.c_str() ); float weight = 0.0f; // Early break for armed vectors @@ -1609,29 +1594,6 @@ damage_instance character_martial_arts::calculate_vector_damage( const Character return ret; } -bool character_martial_arts::can_use_attack_vector( const Character &user, - const std::string &av ) const -{ - martialart ma = style_selected.obj(); - bool valid_weapon = ma.weapon_valid( user.get_wielded_item() ); - int arm_r_hp = user.get_part_hp_cur( bodypart_id( "arm_r" ) ); - int arm_l_hp = user.get_part_hp_cur( bodypart_id( "arm_l" ) ); - int leg_r_hp = user.get_part_hp_cur( bodypart_id( "leg_r" ) ); - int leg_l_hp = user.get_part_hp_cur( bodypart_id( "leg_l" ) ); - bool healthy_arm = arm_r_hp > 0 || arm_l_hp > 0; - bool healthy_arms = arm_r_hp > 0 && arm_l_hp > 0; - bool healthy_legs = leg_r_hp > 0 && leg_l_hp > 0; - bool mouth_ok = ( av == "MOUTH" ) && !user.natural_attack_restricted_on( bodypart_id( "mouth" ) ); - bool always_ok = av == "HEAD" || av == "TORSO"; - bool weapon_ok = av == "WEAPON" && valid_weapon && healthy_arm; - bool arm_ok = ( av == "HAND" || av == "FINGER" || av == "WRIST" || av == "ARM" || av == "ELBOW" || - av == "HAND_BACK" || av == "PALM" || av == "SHOULDER" ) && healthy_arm; - bool arms_ok = ( av == "GRAPPLE" || av == "THROW" ) && healthy_arms; - bool legs_ok = ( av == "FOOT" || av == "LOWER_LEG" || av == "KNEE" || av == "HIP" ) && healthy_legs; - - return always_ok || weapon_ok || mouth_ok || arm_ok || arms_ok || legs_ok; -} - bool character_martial_arts::can_leg_block( const Character &owner ) const { const martialart &ma = style_selected.obj(); diff --git a/src/martialarts.h b/src/martialarts.h index 7daecb44f2b69..621531a2d9b10 100644 --- a/src/martialarts.h +++ b/src/martialarts.h @@ -66,7 +66,7 @@ class weapon_category matype_id martial_art_learned_from( const itype & ); -struct wip_attack_vector { +struct attack_vector { attack_vector_id id; translation name; @@ -194,9 +194,7 @@ class ma_technique ma_requirements reqs; // What way is the technique delivered to the target? - std::vector attack_vectors; // by priority - std::vector attack_vectors_random; // randomly - std::vector wip_attack_vectors; + std::vector attack_vectors; int repeat_min = 1; // Number of times the technique is repeated on a successful proc int repeat_max = 1; diff --git a/src/type_id.h b/src/type_id.h index ccbd4a267cd70..13685d8e419b9 100644 --- a/src/type_id.h +++ b/src/type_id.h @@ -23,8 +23,8 @@ struct ammo_effect; using ammo_effect_id = int_id; using ammo_effect_str_id = string_id; -struct wip_attack_vector; -using attack_vector_id = string_id; +struct attack_vector; +using attack_vector_id = string_id; struct bionic_data; using bionic_id = string_id; From ea46a5fd4b5b26125733acb9dd612ccb0d838c2d Mon Sep 17 00:00:00 2001 From: Venera3 Date: Fri, 3 May 2024 17:14:27 +0200 Subject: [PATCH 06/16] Aight clang, lemme have it --- data/json/attack_vectors.json | 104 +++- data/json/items/armor/integrated.json | 12 +- data/json/martialarts_fictional.json | 44 +- data/json/mutations/mutation_techs.json | 202 +++++++- data/json/techniques.json | 485 +++++------------- data/mods/MMA/techniques.json | 60 +-- data/mods/Magiclysm/items/integrated.json | 6 +- data/mods/Magiclysm/techniques.json | 2 +- .../Magiclysm/techniques_fantasy_species.json | 22 +- data/mods/TEST_DATA/martialarts.json | 6 +- data/mods/Xedra_Evolved/techniques.json | 4 +- doc/MARTIALART_JSON.md | 40 +- src/character.h | 9 +- src/martialarts.cpp | 31 +- src/martialarts.h | 3 +- src/melee.cpp | 258 +++++----- tests/martial_art_test.cpp | 46 +- 17 files changed, 711 insertions(+), 623 deletions(-) diff --git a/data/json/attack_vectors.json b/data/json/attack_vectors.json index 6e17d22a34de2..59adab90741a9 100644 --- a/data/json/attack_vectors.json +++ b/data/json/attack_vectors.json @@ -3,24 +3,114 @@ "type": "attack_vector", "id": "vector_null", "//": "Fallback weapon vector, unarmed attacks always go through techs", - "name": "base weapon vector", "weapon": true }, { "type": "attack_vector", "id": "test_test", - "name": "test debug tail", "limbs": [ "debug_tail" ], "encumbrance_limit": 33, "bp_hp_limit": 75 }, { "type": "attack_vector", - "id": "test_punch", - "name": "test punch", + "id": "vector_headbutt", + "limbs": [ "head" ], + "contact_area": [ "head_forehead" ], + "bp_hp_limit": 50 + }, + { + "type": "attack_vector", + "id": "vector_bite", + "limbs": [ "mouth" ], + "contact_area": [ "mouth_lips" ], + "encumbrance_limit": 1 + }, + { + "type": "attack_vector", + "id": "vector_punch", "limbs": [ "hand_l", "hand_r" ], - "contact_area": [ "hand_fingers_l", "hand_fingers_r" ], - "strict_limb_definition": false, - "armor_bonus": true + "contact_area": [ "hand_fingers_l", "hand_fingers_r" ] + }, + { + "type": "attack_vector", + "id": "vector_wrist", + "limbs": [ "hand_l", "hand_r" ], + "contact_area": [ "hand_wrist_l", "hand_wrist_r" ] + }, + { + "type": "attack_vector", + "id": "vector_palm", + "limbs": [ "hand_l", "hand_r" ], + "contact_area": [ "hand_palm_l", "hand_palm_r" ] + }, + { + "type": "attack_vector", + "id": "vector_grasp", + "limbs": [ "hand_l", "hand_r" ], + "contact_area": [ "hand_palm_l", "hand_palm_r" ], + "armor_bonus": false + }, + { + "type": "attack_vector", + "id": "vector_backhand", + "limbs": [ "hand_l", "hand_r" ], + "contact_area": [ "hand_back_l", "hand_back_r" ] + }, + { + "type": "attack_vector", + "id": "vector_shoulder", + "limbs": [ "arm_l", "arm_r" ], + "contact_area": [ "arm_shoulder_l", "arm_shoulder_r" ] + }, + { + "type": "attack_vector", + "id": "vector_arm", + "limbs": [ "arm_l", "arm_r" ], + "contact_area": [ "arm_upper_r", "arm_elbow_r", "arm_lower_r", "arm_upper_l", "arm_elbow_l", "arm_lower_l" ] + }, + { + "type": "attack_vector", + "id": "vector_arm_grapple", + "limbs": [ "arm_l", "arm_r" ], + "contact_area": [ "arm_upper_r", "arm_elbow_r", "arm_lower_r", "arm_upper_l", "arm_elbow_l", "arm_lower_l" ], + "armor_bonus": false + }, + { + "type": "attack_vector", + "id": "vector_elbow", + "limbs": [ "arm_l", "arm_r" ], + "contact_area": [ "arm_elbow_l", "arm_elbow_r" ] + }, + { + "type": "attack_vector", + "id": "vector_foot_toes", + "limbs": [ "foot_l", "foot_r" ], + "contact_area": [ "foot_toes_l", "foot_toes_r" ] + }, + { + "type": "attack_vector", + "id": "vector_foot_sole", + "limbs": [ "foot_l", "foot_r" ], + "contact_area": [ "foot_sole_l", "foot_sole_r" ] + }, + { + "type": "attack_vector", + "id": "vector_foot_heel", + "limbs": [ "foot_l", "foot_r" ], + "contact_area": [ "foot_heel_l", "foot_heel_r" ] + }, + { + "type": "attack_vector", + "id": "vector_knee", + "limbs": [ "leg_l", "leg_r" ], + "contact_area": [ "leg_knee_l", "leg_knee_r" ] + }, + { + "type": "attack_vector", + "id": "vector_shin", + "limbs": [ "leg_l", "leg_r" ], + "contact_area": [ "leg_lower_l", "leg_lower_r" ], + "bp_hp_limit": 50 } ] diff --git a/data/json/items/armor/integrated.json b/data/json/items/armor/integrated.json index 76743c9533c6a..640656277eced 100644 --- a/data/json/items/armor/integrated.json +++ b/data/json/items/armor/integrated.json @@ -1632,10 +1632,12 @@ { "material": [ { "type": "bone", "covered_by_mat": 100, "thickness": 3.2 } ], "covers": [ "mouth" ], - "coverage": 0, + "specifically_covers": [ "mouth_lips" ], + "coverage": 100, "encumbrance": 0 } - ] + ], + "melee_damage": { "stab": 9 } }, { "id": "integrated_vampire_fangs", @@ -1658,9 +1660,11 @@ { "material": [ { "type": "bone", "covered_by_mat": 100, "thickness": 3.2 } ], "covers": [ "mouth" ], - "coverage": 0, + "specifically_covers": [ "mouth_lips" ], + "coverage": 100, "encumbrance": 0 } - ] + ], + "melee_damage": { "stab": 15 } } ] diff --git a/data/json/martialarts_fictional.json b/data/json/martialarts_fictional.json index 9944da87a5737..912b18fd2d2c4 100644 --- a/data/json/martialarts_fictional.json +++ b/data/json/martialarts_fictional.json @@ -380,7 +380,7 @@ } ], "stun_dur": 1, - "attack_vectors": [ "HAND" ] + "attack_vectors": [ "vector_grasp" ] }, { "type": "technique", @@ -389,7 +389,7 @@ "messages": [ "You make an efficient strike against %s!", " makes an efficient strike against %s!" ], "melee_allowed": true, "mult_bonuses": [ { "stat": "movecost", "scale": 0.8 } ], - "attack_vectors": [ "WEAPON" ] + "attack_vectors": [ "vector_null" ] }, { "type": "technique", @@ -399,7 +399,7 @@ "unarmed_allowed": true, "crit_ok": true, "mult_bonuses": [ { "stat": "movecost", "scale": 0.8 } ], - "attack_vectors": [ "HAND" ] + "attack_vectors": [ "vector_punch" ] }, { "type": "technique", @@ -415,7 +415,7 @@ { "stat": "damage", "type": "stab", "scale": 1.5 } ], "stun_dur": 1, - "attack_vectors": [ "WEAPON" ] + "attack_vectors": [ "vector_null" ] }, { "type": "technique", @@ -429,7 +429,7 @@ "condition": { "not": { "npc_has_effect": "downed" } }, "condition_desc": "* Only works on a non-downed target", "down_dur": 2, - "attack_vectors": [ "FOOT" ] + "attack_vectors": [ "vector_foot_toes" ] }, { "type": "technique", @@ -442,7 +442,7 @@ "weighting": 2, "crit_tec": true, "aoe": "wide", - "attack_vectors": [ "WEAPON" ] + "attack_vectors": [ "vector_null" ] }, { "type": "technique", @@ -456,7 +456,7 @@ { "stat": "damage", "type": "cut", "scale": 0.66 }, { "stat": "damage", "type": "stab", "scale": 0.66 } ], - "attack_vectors": [ "HAND" ] + "attack_vectors": [ "vector_punch" ] }, { "type": "technique", @@ -467,7 +467,7 @@ "unarmed_allowed": true, "crit_ok": true, "mult_bonuses": [ { "stat": "damage", "type": "bash", "scale": 1.2 } ], - "attack_vectors": [ "FOOT" ] + "attack_vectors": [ "vector_foot_toes" ] }, { "type": "technique", @@ -496,7 +496,7 @@ "knockback_dist": 1, "knockback_spread": 1, "mult_bonuses": [ { "stat": "damage", "type": "bash", "scale": 2.5 } ], - "attack_vectors": [ "TORSO" ] + "attack_vectors": [ "vector_shoulder" ] }, { "type": "technique", @@ -521,7 +521,7 @@ { "stat": "damage", "type": "cut", "scale": 0.66 }, { "stat": "damage", "type": "stab", "scale": 0.66 } ], - "attack_vectors": [ "HAND" ] + "attack_vectors": [ "vector_wrist" ] }, { "type": "technique", @@ -533,7 +533,7 @@ "crit_tec": true, "stun_dur": 2, "mult_bonuses": [ { "stat": "damage", "type": "bash", "scale": 1.25 } ], - "attack_vectors": [ "HAND" ] + "attack_vectors": [ "vector_punch" ] }, { "type": "technique", @@ -546,7 +546,7 @@ "condition_desc": "Requires a stunned target", "weighting": 2, "mult_bonuses": [ { "stat": "damage", "type": "bash", "scale": 1.5 } ], - "attack_vectors": [ "HAND" ] + "attack_vectors": [ "vector_punch" ] }, { "type": "technique", @@ -560,7 +560,7 @@ "weighting": 2, "crit_tec": true, "mult_bonuses": [ { "stat": "damage", "type": "bash", "scale": 2.0 } ], - "attack_vectors": [ "HAND" ] + "attack_vectors": [ "vector_punch" ] }, { "type": "technique", @@ -569,7 +569,7 @@ "messages": [ "You roundhouse kick %s!", " roundhouse kicks %s!" ], "unarmed_allowed": true, "mult_bonuses": [ { "stat": "damage", "type": "bash", "scale": 1.2 } ], - "attack_vectors": [ "FOOT" ] + "attack_vectors": [ "vector_foot_heel" ] }, { "type": "technique", @@ -582,7 +582,7 @@ "knockback_dist": 3, "mult_bonuses": [ { "stat": "damage", "type": "bash", "scale": 2.0 } ], "messages": [ "Your Stinger Kick sends %s flying!", "'s Stinger Kick sends %s flying!" ], - "attack_vectors": [ "FOOT" ] + "attack_vectors": [ "vector_foot_sole" ] }, { "type": "technique", @@ -594,7 +594,7 @@ "required_buffs_all": [ "buff_scorpion_onmove" ], "stun_dur": 1, "mult_bonuses": [ { "stat": "damage", "type": "bash", "scale": 1.25 } ], - "attack_vectors": [ "FOOT" ] + "attack_vectors": [ "vector_foot_toes" ] }, { "type": "technique", @@ -605,7 +605,7 @@ "crit_ok": true, "unarmed_allowed": true, "mult_bonuses": [ { "stat": "damage", "type": "bash", "scale": 1.25 } ], - "attack_vectors": [ "HAND" ] + "attack_vectors": [ "vector_punch" ] }, { "type": "technique", @@ -617,7 +617,7 @@ "crit_tec": true, "stun_dur": 1, "mult_bonuses": [ { "stat": "damage", "type": "bash", "scale": 1.4 } ], - "attack_vectors": [ "ARM" ] + "attack_vectors": [ "vector_arm" ] }, { "type": "technique", @@ -632,7 +632,7 @@ "condition_desc": "* Only works on a non-downed target", "down_dur": 2, "mult_bonuses": [ { "stat": "movecost", "scale": 0.5 } ], - "attack_vectors": [ "HAND" ] + "attack_vectors": [ "vector_grasp" ] }, { "type": "technique", @@ -656,7 +656,7 @@ { "stat": "damage", "type": "cut", "scale": 0.66 }, { "stat": "damage", "type": "stab", "scale": 0.66 } ], - "attack_vectors": [ "HAND" ] + "attack_vectors": [ "vector_punch" ] }, { "type": "technique", @@ -670,7 +670,7 @@ "condition_desc": "* Only works on a non-downed target", "down_dur": 1, "mult_bonuses": [ { "stat": "movecost", "scale": 0.5 } ], - "attack_vectors": [ "PALM" ] + "attack_vectors": [ "vector_palm" ] }, { "type": "technique", @@ -689,6 +689,6 @@ "message": "The weapon of %s has been forced out ot their hands!" } ], - "attack_vectors": [ "HAND" ] + "attack_vectors": [ "vector_grasp" ] } ] diff --git a/data/json/mutations/mutation_techs.json b/data/json/mutations/mutation_techs.json index 43ec14397975b..36d19691e9eab 100644 --- a/data/json/mutations/mutation_techs.json +++ b/data/json/mutations/mutation_techs.json @@ -28,7 +28,205 @@ }, { "id": "downed", "chance": 50, "duration": 2, "message": "You bonk the %s real good", "req_flag": "DREAMY" } ], - "wip_attack_vectors": [ "test_test" ], - "attack_vectors": [ "HAND" ] + "attack_vectors": [ "test_test" ] + }, + { + "type": "technique", + "id": "FANGS_BITE", + "name": "Fang Bite", + "melee_allowed": true, + "messages": [ "You bite %s", " bites %s!" ], + "unarmed_allowed": true, + "weighting": -5, + "reach_ok": false, + "attack_vectors": [ "vector_bite" ], + "condition": { + "and": [ + { "not": { "u_has_effect": "natural_stance" } }, + { "and": [ { "not": { "npc_has_flag": "GRAB_FILTER" } }, { "not": { "u_has_effect": "GRAB" } } ] } + ] + }, + "tech_effects": [ + { + "id": "anticoagulant_draculin", + "chance": 70, + "duration": 3000, + "on_damage": true, + "message": "Saliva glistens across %s's wound!", + "req_flag": "DRACULIN_VENOM" + } + ], + "flat_bonuses": [ + { "stat": "damage", "type": "stab", "scaling-stat": "unarmed", "scale": 1.0 }, + { "stat": "damage", "type": "bash", "scaling-stat": "str", "scale": 0.75 }, + { "stat": "damage", "type": "bash", "scaling-stat": "unarmed", "scale": 0.06 }, + { "stat": "movecost", "scaling-stat": "melee", "scale": -1.25 }, + { "stat": "movecost", "scaling-stat": "dex", "scale": -0.5 } + ] + }, + { + "type": "technique", + "id": "FANGS_BITE_NATURAL", + "name": "Fang Bite", + "melee_allowed": true, + "messages": [ "You bite %s", " bites %s!" ], + "unarmed_allowed": true, + "weighting": -2, + "reach_ok": false, + "attack_vectors": [ "vector_bite" ], + "condition": { + "and": [ + { + "or": [ { "u_has_effect": "natural_stance" }, { "and": [ { "npc_has_flag": "GRAB_FILTER" }, { "u_has_flag": "GRAB" } ] } ] + } + ] + }, + "tech_effects": [ + { + "id": "anticoagulant_draculin", + "chance": 70, + "duration": 3000, + "on_damage": true, + "message": "Saliva glistens across %s's wound!", + "req_flag": "DRACULIN_VENOM" + } + ], + "flat_bonuses": [ + { "stat": "damage", "type": "stab", "scaling-stat": "unarmed", "scale": 1.0 }, + { "stat": "damage", "type": "bash", "scaling-stat": "str", "scale": 0.75 }, + { "stat": "damage", "type": "bash", "scaling-stat": "unarmed", "scale": 0.06 }, + { "stat": "movecost", "scaling-stat": "melee", "scale": -1.25 }, + { "stat": "movecost", "scaling-stat": "dex", "scale": -0.5 } + ] + }, + { + "type": "technique", + "id": "FANGS_BITE_CRIT", + "name": "Critical Fang Bite", + "melee_allowed": true, + "messages": [ "You deliver a wicked bite to %s", " delivers a wicked bite to %s!" ], + "unarmed_allowed": true, + "reach_ok": false, + "crit_tec": true, + "attack_vectors": [ "vector_bite" ], + "tech_effects": [ + { + "id": "anticoagulant_draculin", + "chance": 90, + "duration": 6000, + "on_damage": true, + "message": "Saliva glistens across %s's wound!", + "req_flag": "DRACULIN_VENOM" + } + ], + "flat_bonuses": [ + { "stat": "damage", "type": "stab", "scaling-stat": "unarmed", "scale": 4.4 }, + { "stat": "damage", "type": "bash", "scaling-stat": "str", "scale": 0.75 }, + { "stat": "damage", "type": "bash", "scaling-stat": "unarmed", "scale": 0.24 }, + { "stat": "arpen", "type": "bash", "scaling-stat": "unar,ed", "scale": 1 }, + { "stat": "arpen", "type": "stab", "scaling-stat": "unarmed", "scale": 1 }, + { "stat": "movecost", "scaling-stat": "melee", "scale": -1.25 }, + { "stat": "movecost", "scaling-stat": "dex", "scale": -0.5 } + ] + }, + { + "type": "technique", + "id": "VAMPIRE_BITE", + "name": "Vampire Bite", + "melee_allowed": true, + "messages": [ "You bite %s", " bites %s!" ], + "unarmed_allowed": true, + "weighting": -5, + "reach_ok": false, + "attack_vectors": [ "vector_bite" ], + "condition": { + "and": [ + { "not": { "u_has_effect": "natural_stance" } }, + { "and": [ { "not": { "npc_has_flag": "GRAB_FILTER" } }, { "not": { "u_has_effect": "GRAB" } } ] } + ] + }, + "tech_effects": [ + { + "id": "anticoagulant_draculin", + "chance": 90, + "duration": 3000, + "on_damage": true, + "message": "Saliva glistens across %s's wound!", + "req_flag": "DRACULIN_VENOM" + } + ], + "flat_bonuses": [ + { "stat": "damage", "type": "stab", "scaling-stat": "unarmed", "scale": 1.0 }, + { "stat": "damage", "type": "bash", "scaling-stat": "str", "scale": 0.75 }, + { "stat": "damage", "type": "bash", "scaling-stat": "unarmed", "scale": 0.06 }, + { "stat": "movecost", "scaling-stat": "melee", "scale": -1.25 }, + { "stat": "movecost", "scaling-stat": "dex", "scale": -0.5 } + ] + }, + { + "type": "technique", + "id": "VAMPIRE_BITE_NATURAL", + "name": "Vampire Bite", + "melee_allowed": true, + "messages": [ "You bite %s", " bites %s!" ], + "unarmed_allowed": true, + "weighting": -2, + "reach_ok": false, + "attack_vectors": [ "vector_bite" ], + "tech_effects": [ + { + "id": "anticoagulant_draculin", + "chance": 90, + "duration": 3000, + "on_damage": true, + "message": "Saliva glistens across %s's wound!", + "req_flag": "DRACULIN_VENOM" + } + ], + "condition": { + "and": [ + { + "or": [ { "u_has_effect": "natural_stance" }, { "and": [ { "npc_has_flag": "GRAB_FILTER" }, { "u_has_flag": "GRAB" } ] } ] + } + ] + }, + "flat_bonuses": [ + { "stat": "damage", "type": "stab", "scaling-stat": "unarmed", "scale": 1.0 }, + { "stat": "damage", "type": "bash", "scaling-stat": "str", "scale": 0.75 }, + { "stat": "damage", "type": "bash", "scaling-stat": "unarmed", "scale": 0.06 }, + { "stat": "movecost", "scaling-stat": "melee", "scale": -1.25 }, + { "stat": "movecost", "scaling-stat": "dex", "scale": -0.5 } + ] + }, + { + "type": "technique", + "id": "VAMPIRE_BITE_CRIT", + "name": "Critical Vampire Bite", + "melee_allowed": true, + "messages": [ "You sink your fangs deep into %s!", " sinks their fangs deep into %s!" ], + "unarmed_allowed": true, + "weighting": -4, + "reach_ok": false, + "crit_tec": true, + "tech_effects": [ + { + "id": "anticoagulant_draculin", + "chance": 100, + "duration": 6000, + "on_damage": true, + "message": "Saliva glistens across %s's wound!", + "req_flag": "DRACULIN_VENOM" + } + ], + "attack_vectors": [ "vector_bite" ], + "flat_bonuses": [ + { "stat": "damage", "type": "stab", "scaling-stat": "unarmed", "scale": 4.4 }, + { "stat": "damage", "type": "bash", "scaling-stat": "str", "scale": 0.75 }, + { "stat": "damage", "type": "bash", "scaling-stat": "unarmed", "scale": 0.24 }, + { "stat": "arpen", "type": "bash", "scaling-stat": "unarmed", "scale": 1 }, + { "stat": "arpen", "type": "stab", "scaling-stat": "unarmed", "scale": 1 }, + { "stat": "movecost", "scaling-stat": "melee", "scale": -1.25 }, + { "stat": "movecost", "scaling-stat": "dex", "scale": -0.5 } + ] } ] diff --git a/data/json/techniques.json b/data/json/techniques.json index 1441d95a45a98..3454c82d5757a 100644 --- a/data/json/techniques.json +++ b/data/json/techniques.json @@ -3,7 +3,7 @@ "type": "technique", "id": "tec_none", "name": "Not a technique at all", - "wip_attack_vectors": [ "vector_null" ], + "attack_vectors": [ "vector_null" ], "dummy": true }, { @@ -14,7 +14,7 @@ "unarmed_allowed": true, "messages": [ "You punch %s!", " punches %s!" ], "description": "Just a basic punch", - "wip_attack_vectors": [ "test_punch" ] + "attack_vectors": [ "vector_punch" ] }, { "type": "technique", @@ -60,7 +60,7 @@ "message": "The weapon of %s has been forced out of their hands!" } ], - "attack_vectors": [ "HAND" ] + "attack_vectors": [ "vector_grasp" ] }, { "type": "technique", @@ -79,7 +79,7 @@ "messages": [ "You swing through %s and everyone nearby", " swings through %s and everyone nearby!" ], "aoe": "spin", "description": "Attack adjacent enemies, crit only, min 4 melee", - "attack_vectors": [ "WEAPON" ] + "attack_vectors": [ "vector_null" ] }, { "type": "technique", @@ -92,7 +92,7 @@ "messages": [ "You swing in a wide arc through %s", " swings in a wide arc through %s!" ], "aoe": "wide", "description": "Attack in a wide arc, crit only, min 3 melee", - "attack_vectors": [ "WEAPON" ] + "attack_vectors": [ "vector_null" ] }, { "type": "technique", @@ -104,7 +104,7 @@ "messages": [ "You pierce straight through %s", " pierces through %s!" ], "aoe": "impale", "description": "Attack target and another one behind it, crit only, min 4 melee", - "attack_vectors": [ "WEAPON" ] + "attack_vectors": [ "vector_null" ] }, { "type": "technique", @@ -158,7 +158,7 @@ "condition_desc": "* Only works on a non-stunned mundane target of similar or smaller size, may fail on enemies grabbing you", "messages": [ "You send %s reeling", " sends %s reeling!" ], "description": "Stun 1 turn, knockback 1 tile, crit only, min 2 melee", - "attack_vectors": [ "WEAPON" ] + "attack_vectors": [ "vector_null" ] }, { "type": "technique", @@ -174,7 +174,7 @@ ], "messages": [ "You quickly strike %s", " quickly strikes %s!" ], "description": "50% moves, 66% damage, min 2 melee", - "attack_vectors": [ "WEAPON" ] + "attack_vectors": [ "vector_null" ] }, { "type": "technique", @@ -189,7 +189,7 @@ "Snicker-snack! slices through %s like a hot knife through butter!" ], "description": "Cut damage multiply by 99, crit only", - "attack_vectors": [ "WEAPON" ] + "attack_vectors": [ "vector_null" ] }, { "type": "technique", @@ -211,7 +211,7 @@ "stun_dur": 2, "messages": [ "You wrap up %s", " wraps up %s!" ], "description": "Stun 2 turns, min 4 melee", - "attack_vectors": [ "WEAPON" ] + "attack_vectors": [ "vector_null" ] }, { "type": "technique", @@ -233,7 +233,7 @@ "down_dur": 2, "messages": [ "You sweep %s", " sweeps %s!" ], "description": "Down 2 turns, min 3 melee", - "attack_vectors": [ "WEAPON" ] + "attack_vectors": [ "vector_null" ] }, { "type": "technique", @@ -254,7 +254,7 @@ ], "messages": [ "With a loud crack the piston extends, sending %s reeling", " sends %s reeling!" ], "description": "Stun 1 turn, knockback 2 tile, crit only, requires ammo", - "attack_vectors": [ "WEAPON" ] + "attack_vectors": [ "vector_null" ] }, { "type": "technique", @@ -275,7 +275,7 @@ "messages": [ "You precisely hit %s", " precisely hits %s!" ], "stun_dur": 2, "description": "Stun 2 turns, crit only, min 3 melee", - "attack_vectors": [ "WEAPON" ] + "attack_vectors": [ "vector_null" ] }, { "type": "technique", @@ -294,7 +294,7 @@ "message": "The weapon of %s has been forced out ot their hands!" } ], - "attack_vectors": [ "WEAPON" ] + "attack_vectors": [ "vector_null" ] }, { "type": "technique", @@ -334,7 +334,7 @@ "condition_desc": "* Only works on a non-stunned humanoid target of similar or smaller size", "messages": [ "You jab deftly at %s!", " jabs deftly at %s!" ], "stun_dur": 2, - "attack_vectors": [ "WEAPON" ] + "attack_vectors": [ "vector_null" ] }, { "type": "technique", @@ -376,7 +376,7 @@ "down_dur": 1, "knockback_dist": 1, "mult_bonuses": [ { "stat": "movecost", "scale": 0.7 } ], - "attack_vectors": [ "THROW" ] + "attack_vectors": [ "vector_grasp" ] }, { "type": "technique", @@ -417,7 +417,7 @@ "down_dur": 1, "knockback_dist": 1, "mult_bonuses": [ { "stat": "movecost", "scale": 0.7 } ], - "attack_vectors": [ "THROW" ] + "attack_vectors": [ "vector_grasp" ] }, { "type": "technique", @@ -469,7 +469,7 @@ "down_dur": 1, "knockback_dist": 1, "mult_bonuses": [ { "stat": "movecost", "scale": 0.7 } ], - "attack_vectors": [ "THROW" ] + "attack_vectors": [ "vector_grasp" ] }, { "type": "technique", @@ -521,7 +521,7 @@ "down_dur": 1, "knockback_dist": 1, "mult_bonuses": [ { "stat": "movecost", "scale": 0.7 } ], - "attack_vectors": [ "THROW" ] + "attack_vectors": [ "vector_grasp" ] }, { "type": "technique", @@ -545,7 +545,7 @@ "skill_requirements": [ { "name": "melee", "level": 1 } ], "melee_allowed": true, "mult_bonuses": [ { "stat": "damage", "type": "bash", "scale": 1.2 } ], - "attack_vectors": [ "WEAPON" ] + "attack_vectors": [ "vector_null" ] }, { "type": "technique", @@ -572,7 +572,7 @@ "condition_desc": "* Only works on a non-downed humanoid target of similar or smaller size incapable of flight", "down_dur": 1, "mult_bonuses": [ { "stat": "movecost", "scale": 0.8 } ], - "attack_vectors": [ "WEAPON" ] + "attack_vectors": [ "vector_null" ] }, { "type": "technique", @@ -598,7 +598,7 @@ "condition_desc": "* Only works on a non-stunned target of similar or smaller size", "stun_dur": 1, "mult_bonuses": [ { "stat": "damage", "type": "bash", "scale": 1.4 } ], - "attack_vectors": [ "WEAPON" ] + "attack_vectors": [ "vector_null" ] }, { "type": "technique", @@ -611,7 +611,7 @@ "weighting": 2, "crit_ok": true, "mult_bonuses": [ { "stat": "movecost", "scale": 0.75 }, { "stat": "damage", "type": "bash", "scale": 1.5 } ], - "attack_vectors": [ "WEAPON" ] + "attack_vectors": [ "vector_null" ] }, { "type": "technique", @@ -642,7 +642,7 @@ "message": "The weapon of %s has been forced out of their hands!" } ], - "attack_vectors": [ "WEAPON" ] + "attack_vectors": [ "vector_null" ] }, { "type": "technique", @@ -659,7 +659,7 @@ { "stat": "damage", "type": "stab", "scale": 1.1 }, { "stat": "movecost", "scale": 1.2 } ], - "attack_vectors": [ "WEAPON" ] + "attack_vectors": [ "vector_null" ] }, { "type": "technique", @@ -676,7 +676,7 @@ { "stat": "damage", "type": "cut", "scale": 0.66 }, { "stat": "damage", "type": "stab", "scale": 0.66 } ], - "attack_vectors": [ "WEAPON" ] + "attack_vectors": [ "vector_null" ] }, { "type": "technique", @@ -685,7 +685,7 @@ "messages": [ "You throw a heavy cross at %s", " throws a cross at %s!" ], "unarmed_allowed": true, "mult_bonuses": [ { "stat": "damage", "type": "bash", "scale": 1.2 } ], - "attack_vectors": [ "HAND" ] + "attack_vectors": [ "vector_punch" ] }, { "type": "technique", @@ -712,7 +712,7 @@ "condition_desc": "* Only works on a non-downed humanoid target of similar or smaller size incapable of flight", "down_dur": 1, "mult_bonuses": [ { "stat": "damage", "type": "bash", "scale": 1.5 } ], - "attack_vectors": [ "HAND" ] + "attack_vectors": [ "vector_punch" ] }, { "type": "technique", @@ -727,7 +727,7 @@ { "stat": "damage", "type": "cut", "scale": 0.66 }, { "stat": "damage", "type": "stab", "scale": 0.66 } ], - "attack_vectors": [ "HAND" ] + "attack_vectors": [ "vector_punch" ] }, { "type": "technique", @@ -758,7 +758,7 @@ "condition_desc": "* Only works on a non-stunned target of similar size incapable of flight", "stun_dur": 1, "mult_bonuses": [ { "stat": "damage", "type": "bash", "scale": 1.4 } ], - "attack_vectors": [ "HAND" ] + "attack_vectors": [ "vector_punch" ] }, { "type": "technique", @@ -823,7 +823,7 @@ "message": "The weapon of %s has been forced out of their hands!" } ], - "attack_vectors": [ "WEAPON" ] + "attack_vectors": [ "vector_null" ] }, { "type": "technique", @@ -842,7 +842,7 @@ "message": "The weapon of %s has been forced out of their hands!" } ], - "attack_vectors": [ "HAND" ] + "attack_vectors": [ "vector_grasp" ] }, { "type": "technique", @@ -864,7 +864,7 @@ ] }, "condition_desc": "* Only works on a non-downed and non-stunned target of similar or smaller size incapable of flight", - "attack_vectors": [ "HAND" ] + "attack_vectors": [ "vector_punch" ] }, { "type": "technique", @@ -884,7 +884,7 @@ }, "condition_desc": "* Only works on a non-downed humanoid target of similar size incapable of flight", "down_dur": 1, - "attack_vectors": [ "HAND" ] + "attack_vectors": [ "vector_shin" ] }, { "type": "technique", @@ -907,7 +907,7 @@ { "stat": "damage", "type": "cut", "scale": 1.2 }, { "stat": "damage", "type": "stab", "scale": 1.2 } ], - "attack_vectors": [ "FOOT" ] + "attack_vectors": [ "vector_foot_sole" ] }, { "type": "technique", @@ -922,7 +922,7 @@ { "stat": "damage", "type": "cut", "scale": 1.4 }, { "stat": "damage", "type": "stab", "scale": 1.4 } ], - "attack_vectors": [ "FOOT" ] + "attack_vectors": [ "vector_foot_sole" ] }, { "type": "technique", @@ -950,7 +950,7 @@ { "stat": "damage", "type": "cut", "scale": 1.2 }, { "stat": "damage", "type": "stab", "scale": 1.2 } ], - "attack_vectors": [ "FOOT" ] + "attack_vectors": [ "vector_foot_toes" ] }, { "type": "technique", @@ -971,7 +971,7 @@ { "stat": "damage", "type": "cut", "scale": 1.4 }, { "stat": "damage", "type": "stab", "scale": 1.4 } ], - "attack_vectors": [ "FOOT" ] + "attack_vectors": [ "vector_foot_sole" ] }, { "type": "technique", @@ -1006,7 +1006,7 @@ { "stat": "damage", "type": "cut", "scale": 1.25 }, { "stat": "damage", "type": "stab", "scale": 1.25 } ], - "attack_vectors": [ "FOOT" ] + "attack_vectors": [ "vector_foot_toes" ] }, { "type": "technique", @@ -1062,7 +1062,7 @@ { "stat": "damage", "type": "cut", "scale": 1.5 }, { "stat": "damage", "type": "stab", "scale": 1.5 } ], - "attack_vectors": [ "WRIST" ] + "attack_vectors": [ "vector_wrist" ] }, { "type": "technique", @@ -1071,7 +1071,7 @@ "unarmed_allowed": true, "mult_bonuses": [ { "stat": "damage", "type": "bash", "scale": 1.2 } ], "messages": [ "You lash out at %s with a Dragon Claw", " lashes out at %s with a Dragon Claw!" ], - "attack_vectors": [ "HAND" ] + "attack_vectors": [ "vector_punch" ] }, { "type": "technique", @@ -1097,7 +1097,7 @@ { "stat": "damage", "type": "cut", "scale": 1.4 }, { "stat": "damage", "type": "stab", "scale": 1.4 } ], - "attack_vectors": [ "FOOT" ] + "attack_vectors": [ "vector_foot_heel" ] }, { "type": "technique", @@ -1115,7 +1115,7 @@ { "stat": "damage", "type": "cut", "scale": 1.5 }, { "stat": "damage", "type": "stab", "scale": 1.5 } ], - "attack_vectors": [ "HAND" ] + "attack_vectors": [ "vector_punch" ] }, { "type": "technique", @@ -1125,7 +1125,7 @@ "skill_requirements": [ { "name": "melee", "level": 4 } ], "melee_allowed": true, "mult_bonuses": [ { "stat": "movecost", "scale": 0.6 } ], - "attack_vectors": [ "WEAPON" ] + "attack_vectors": [ "vector_null" ] }, { "type": "technique", @@ -1135,7 +1135,7 @@ "skill_requirements": [ { "name": "melee", "level": 2 } ], "melee_allowed": true, "mult_bonuses": [ { "stat": "movecost", "scale": 0.75 } ], - "attack_vectors": [ "WEAPON" ] + "attack_vectors": [ "vector_null" ] }, { "type": "technique", @@ -1144,7 +1144,7 @@ "messages": [ "You snap out at %s", " snaps quickly at %s!" ], "melee_allowed": true, "mult_bonuses": [ { "stat": "movecost", "scale": 0.8 } ], - "attack_vectors": [ "WEAPON" ] + "attack_vectors": [ "vector_null" ] }, { "type": "technique", @@ -1161,7 +1161,7 @@ { "stat": "damage", "type": "cut", "scale": 1.5 }, { "stat": "damage", "type": "stab", "scale": 1.5 } ], - "attack_vectors": [ "WEAPON" ] + "attack_vectors": [ "vector_null" ] }, { "type": "technique", @@ -1198,7 +1198,7 @@ "condition_desc": "* Only works on a non-stunned mundane target of similar or smaller size", "stun_dur": 1, "mult_bonuses": [ { "stat": "movecost", "scale": 0.8 } ], - "attack_vectors": [ "WEAPON" ] + "attack_vectors": [ "vector_null" ] }, { "type": "technique", @@ -1219,7 +1219,7 @@ }, "condition_desc": "* Only works on a non-downed humanoid target of similar or smaller size incapable of flight", "down_dur": 1, - "attack_vectors": [ "WEAPON" ] + "attack_vectors": [ "vector_null" ] }, { "type": "technique", @@ -1240,7 +1240,7 @@ "melee_allowed": true, "crit_ok": true, "mult_bonuses": [ { "stat": "movecost", "scale": 0.8 } ], - "attack_vectors": [ "WEAPON" ] + "attack_vectors": [ "vector_null" ] }, { "type": "technique", @@ -1257,7 +1257,7 @@ { "stat": "damage", "type": "cut", "scale": 1.25 }, { "stat": "damage", "type": "stab", "scale": 1.25 } ], - "attack_vectors": [ "WEAPON" ] + "attack_vectors": [ "vector_null" ] }, { "type": "technique", @@ -1274,7 +1274,7 @@ { "stat": "damage", "type": "cut", "scale": 1.25 }, { "stat": "damage", "type": "stab", "scale": 1.25 } ], - "attack_vectors": [ "WEAPON" ] + "attack_vectors": [ "vector_null" ] }, { "type": "technique", @@ -1318,7 +1318,7 @@ { "stat": "damage", "type": "cut", "scale": 1.2 }, { "stat": "damage", "type": "stab", "scale": 1.2 } ], - "attack_vectors": [ "WEAPON" ] + "attack_vectors": [ "vector_null" ] }, { "type": "technique", @@ -1347,7 +1347,7 @@ { "stat": "damage", "type": "cut", "scale": 0.5 }, { "stat": "damage", "type": "stab", "scale": 0.5 } ], - "attack_vectors": [ "WEAPON" ] + "attack_vectors": [ "vector_null" ] }, { "type": "technique", @@ -1363,7 +1363,7 @@ { "stat": "damage", "type": "cut", "scale": 1.1 }, { "stat": "damage", "type": "stab", "scale": 1.1 } ], - "attack_vectors": [ "WEAPON" ] + "attack_vectors": [ "vector_null" ] }, { "type": "technique", @@ -1413,7 +1413,7 @@ { "stat": "damage", "type": "cut", "scale": 0.5 }, { "stat": "damage", "type": "stab", "scale": 0.5 } ], - "attack_vectors": [ "WEAPON" ] + "attack_vectors": [ "vector_null" ] }, { "type": "technique", @@ -1432,7 +1432,7 @@ { "stat": "damage", "type": "cut", "scale": 1.5 }, { "stat": "damage", "type": "stab", "scale": 1.5 } ], - "attack_vectors": [ "WEAPON" ] + "attack_vectors": [ "vector_null" ] }, { "type": "technique", @@ -1454,7 +1454,7 @@ "condition_desc": "* Only works on a non-downed humanoid target of identical size incapable of flight", "down_dur": 1, "mult_bonuses": [ { "stat": "damage", "type": "bash", "scale": 1.25 } ], - "attack_vectors": [ "THROW" ] + "attack_vectors": [ "vector_grasp" ] }, { "type": "technique", @@ -1486,7 +1486,7 @@ "condition_desc": "* Only works on a non-downed humanoid target of identical size incapable of flight", "down_dur": 1, "mult_bonuses": [ { "stat": "damage", "type": "bash", "scale": 1.25 } ], - "attack_vectors": [ "THROW" ] + "attack_vectors": [ "vector_grasp" ] }, { "type": "technique", @@ -1511,7 +1511,7 @@ "down_dur": 1, "stun_dur": 1, "mult_bonuses": [ { "stat": "damage", "type": "bash", "scale": 1.5 } ], - "attack_vectors": [ "THROW" ] + "attack_vectors": [ "vector_grasp" ] }, { "type": "technique", @@ -1566,7 +1566,7 @@ { "stat": "damage", "type": "cut", "scale": 1.4 }, { "stat": "damage", "type": "stab", "scale": 1.4 } ], - "attack_vectors": [ "HAND" ] + "attack_vectors": [ "vector_wrist" ] }, { "type": "technique", @@ -1581,7 +1581,7 @@ { "stat": "damage", "type": "cut", "scale": 0.66 }, { "stat": "damage", "type": "stab", "scale": 0.66 } ], - "attack_vectors": [ "HAND_BACK" ] + "attack_vectors": [ "vector_backhand" ] }, { "type": "technique", @@ -1595,7 +1595,7 @@ { "stat": "damage", "type": "cut", "scale": 1.2 }, { "stat": "damage", "type": "stab", "scale": 1.2 } ], - "attack_vectors": [ "FOOT" ] + "attack_vectors": [ "vector_foot_toes" ] }, { "type": "technique", @@ -1606,7 +1606,7 @@ "melee_allowed": true, "weapon_categories_allowed": "QUARTERSTAVES", "mult_bonuses": [ { "stat": "movecost", "scale": 0.9 } ], - "attack_vectors": [ "WEAPON" ] + "attack_vectors": [ "vector_null" ] }, { "type": "technique", @@ -1633,7 +1633,7 @@ { "stat": "damage", "type": "cut", "scale": 1.1 }, { "stat": "damage", "type": "stab", "scale": 1.1 } ], - "attack_vectors": [ "WEAPON" ] + "attack_vectors": [ "vector_null" ] }, { "type": "technique", @@ -1648,7 +1648,7 @@ { "stat": "damage", "type": "cut", "scale": 0.66 }, { "stat": "damage", "type": "stab", "scale": 0.66 } ], - "attack_vectors": [ "HAND" ] + "attack_vectors": [ "vector_punch" ] }, { "type": "technique", @@ -1661,7 +1661,7 @@ { "stat": "damage", "type": "cut", "scale": 1.2 }, { "stat": "damage", "type": "stab", "scale": 1.2 } ], - "attack_vectors": [ "HAND" ] + "attack_vectors": [ "vector_punch" ] }, { "type": "technique", @@ -1693,7 +1693,7 @@ }, "condition_desc": "* Only works on a target of similar or smaller size, may fail on enemies grabbing you", "knockback_dist": 1, - "attack_vectors": [ "FOOT" ] + "attack_vectors": [ "vector_foot_sole" ] }, { "type": "technique", @@ -1742,7 +1742,7 @@ "condition_desc": "* Only works on a non-stunned target of similar size incapable of flight", "stun_dur": 1, "mult_bonuses": [ { "stat": "damage", "type": "bash", "scale": 1.4 } ], - "attack_vectors": [ "HAND" ] + "attack_vectors": [ "vector_punch" ] }, { "type": "technique", @@ -1758,7 +1758,7 @@ { "stat": "damage", "type": "cut", "scale": 1.4 }, { "stat": "damage", "type": "stab", "scale": 1.4 } ], - "attack_vectors": [ "FOOT" ] + "attack_vectors": [ "vector_foot_toes" ] }, { "type": "technique", @@ -1774,7 +1774,7 @@ { "stat": "damage", "type": "cut", "scale": 0.66 }, { "stat": "damage", "type": "stab", "scale": 0.66 } ], - "attack_vectors": [ "WEAPON", "HAND" ] + "attack_vectors": [ "vector_null", "vector_punch" ] }, { "type": "technique", @@ -1795,7 +1795,7 @@ "message": "The weapon of %s has been forced out of their hands!" } ], - "attack_vectors": [ "WEAPON", "HAND" ] + "attack_vectors": [ "vector_null", "vector_grasp" ] }, { "type": "technique", @@ -1833,8 +1833,7 @@ }, "condition_desc": "* Only works on a non-stunned mundane target of similar or smaller size", "stun_dur": 1, - "attack_vectors": [ "WEAPON" ], - "attack_vectors_random": [ "HAND", "FINGERS", "FOOT", "ELBOW", "KNEE", "LOWER_LEG", "HEAD" ] + "attack_vectors": [ "vector_null", "vector_headbutt", "vector_punch", "vector_elbow", "vector_foot_toes", "vector_shin", "vector_knee" ] }, { "type": "technique", @@ -1856,7 +1855,7 @@ }, "condition_desc": "* Only works on a non-downed humanoid target of similar or smaller size incapable of flight", "down_dur": 1, - "attack_vectors": [ "THROW" ] + "attack_vectors": [ "vector_grasp" ] }, { "type": "technique", @@ -1888,7 +1887,7 @@ ], "flat_bonuses": [ { "stat": "arpen", "type": "bash", "scaling-stat": "str", "scale": 1.0 } ], "mult_bonuses": [ { "stat": "damage", "type": "bash", "scale": 2.0 } ], - "attack_vectors": [ "GRAPPLE" ] + "attack_vectors": [ "vector_arm_grapple" ] }, { "type": "technique", @@ -1946,7 +1945,7 @@ "condition_desc": "* Only works on a non-stunned mundane target of similar or smaller size", "stun_dur": 1, "mult_bonuses": [ { "stat": "movecost", "scale": 0.5 } ], - "attack_vectors": [ "HAND" ] + "attack_vectors": [ "vector_punch" ] }, { "type": "technique", @@ -1960,7 +1959,7 @@ { "stat": "damage", "type": "cut", "scale": 0.66 }, { "stat": "damage", "type": "stab", "scale": 0.66 } ], - "attack_vectors": [ "HAND" ] + "attack_vectors": [ "vector_punch" ] }, { "type": "technique", @@ -1970,7 +1969,7 @@ "unarmed_allowed": true, "crit_tec": true, "mult_bonuses": [ { "stat": "movecost", "scale": 0.8 }, { "stat": "damage", "type": "bash", "scale": 1.3 } ], - "attack_vectors": [ "HAND" ] + "attack_vectors": [ "vector_punch" ] }, { "type": "technique", @@ -1991,7 +1990,7 @@ "condition_desc": "* Only works on a non-downed humanoid target of identical or smaller size incapable of flight", "down_dur": 1, "mult_bonuses": [ { "stat": "movecost", "scale": 0.8 }, { "stat": "damage", "type": "bash", "scale": 2.0 } ], - "attack_vectors": [ "HAND" ] + "attack_vectors": [ "vector_punch" ] }, { "type": "technique", @@ -2017,7 +2016,7 @@ }, "condition_desc": "* Only works on a non-downed humanoid target of similar or smaller size incapable of flight", "down_dur": 2, - "attack_vectors": [ "WEAPON" ] + "attack_vectors": [ "vector_null" ] }, { "type": "technique", @@ -2034,7 +2033,7 @@ { "stat": "arpen", "type": "stab", "scaling-stat": "str", "scale": 1.5 } ], "mult_bonuses": [ { "stat": "damage", "type": "bash", "scale": 0 } ], - "attack_vectors": [ "WEAPON" ] + "attack_vectors": [ "vector_null" ] }, { "type": "technique", @@ -2062,7 +2061,7 @@ { "stat": "damage", "type": "cut", "scale": 0.66 }, { "stat": "damage", "type": "stab", "scale": 0.66 } ], - "attack_vectors": [ "HAND" ] + "attack_vectors": [ "vector_punch" ] }, { "type": "technique", @@ -2075,7 +2074,7 @@ { "stat": "damage", "type": "cut", "scale": 1.2 }, { "stat": "damage", "type": "stab", "scale": 1.2 } ], - "attack_vectors": [ "HAND" ] + "attack_vectors": [ "vector_punch" ] }, { "type": "technique", @@ -2087,7 +2086,7 @@ "crit_tec": true, "flat_bonuses": [ { "stat": "damage", "type": "cut", "scale": 2 } ], "mult_bonuses": [ { "stat": "movecost", "scale": 0.66 } ], - "attack_vectors": [ "ELBOW" ] + "attack_vectors": [ "vector_elbow" ] }, { "type": "technique", @@ -2102,7 +2101,7 @@ { "stat": "damage", "type": "cut", "scale": 1.4 }, { "stat": "damage", "type": "stab", "scale": 1.4 } ], - "attack_vectors_random": [ "LOWER_LEG", "FOOT" ] + "attack_vectors": [ "vector_shin", "vector_foot_toes" ] }, { "type": "technique", @@ -2134,7 +2133,7 @@ }, "condition_desc": "* Only works on a target of similar or smaller size, may fail on enemies grabbing you", "knockback_dist": 1, - "attack_vectors": [ "FOOT" ] + "attack_vectors": [ "vector_foot_sole" ] }, { "type": "technique", @@ -2171,7 +2170,7 @@ }, "condition_desc": "* Only works on a non-stunned mundane target of similar or smaller size", "mult_bonuses": [ { "stat": "damage", "type": "bash", "scale": 1.35 } ], - "attack_vectors": [ "KNEE" ] + "attack_vectors": [ "vector_knee" ] }, { "type": "technique", @@ -2196,8 +2195,7 @@ "unarmed_allowed": true, "melee_allowed": true, "mult_bonuses": [ { "stat": "movecost", "scale": 0.8 } ], - "attack_vectors": [ "WEAPON" ], - "attack_vectors_random": [ "HAND", "ELBOW", "LOWER_LEG", "FOOT" ] + "attack_vectors": [ "vector_null", "vector_punch", "vector_elbow", "vector_foot_toes", "vector_shin" ] }, { "type": "technique", @@ -2210,8 +2208,7 @@ "required_buffs_all": [ "buff_ninjutsu_onattack" ], "crit_tec": true, "mult_bonuses": [ { "stat": "movecost", "scale": 0.8 } ], - "attack_vectors": [ "WEAPON" ], - "attack_vectors_random": [ "HAND", "ELBOW", "LOWER_LEG", "FOOT" ] + "attack_vectors": [ "vector_null", "vector_punch", "vector_elbow", "vector_foot_toes", "vector_shin" ] }, { "type": "technique", @@ -2228,7 +2225,7 @@ { "stat": "damage", "type": "cut", "scale": 1.3 }, { "stat": "damage", "type": "stab", "scale": 1.3 } ], - "attack_vectors": [ "WEAPON" ] + "attack_vectors": [ "vector_null" ] }, { "type": "technique", @@ -2251,7 +2248,7 @@ "down_dur": 2, "weighting": 3, "mult_bonuses": [ { "stat": "damage", "type": "bash", "scale": 2.0 } ], - "attack_vectors": [ "THROW" ] + "attack_vectors": [ "vector_grasp" ] }, { "type": "technique", @@ -2275,7 +2272,7 @@ "down_dur": 2, "weighting": 3, "mult_bonuses": [ { "stat": "damage", "type": "bash", "scale": 2.0 } ], - "attack_vectors": [ "THROW" ] + "attack_vectors": [ "vector_grasp" ] }, { "type": "technique", @@ -2300,7 +2297,7 @@ "condition_desc": "* Only works on a non-downed humanoid target of similar or smaller size incapable of flight", "down_dur": 2, "weighting": 2, - "attack_vectors": [ "WEAPON" ] + "attack_vectors": [ "vector_null" ] }, { "type": "technique", @@ -2341,8 +2338,7 @@ "stun_dur": 2, "disarms": true, "tech_effects": [ { "id": "disarmed", "chance": 100, "duration": 400, "on_damage": true, "message": "The hand of %s is forced open!" } ], - "attack_vectors": [ "WEAPON" ], - "attack_vectors_random": [ "HAND", "ELBOW", "LOWER_LEG", "FOOT" ] + "attack_vectors": [ "vector_null", "vector_punch", "vector_elbow", "vector_foot_heel", "vector_shin" ] }, { "type": "technique", @@ -2356,7 +2352,7 @@ { "stat": "damage", "type": "bash", "scale": 2.0 }, { "stat": "damage", "type": "cut", "scale": 2.0 } ], - "attack_vectors": [ "WEAPON" ] + "attack_vectors": [ "vector_null" ] }, { "type": "technique", @@ -2375,7 +2371,7 @@ }, "condition_desc": "* Only works on a non-downed humanoid target of similar or smaller size incapable of flight", "down_dur": 1, - "attack_vectors": [ "WEAPON" ] + "attack_vectors": [ "vector_null" ] }, { "type": "technique", @@ -2416,7 +2412,7 @@ { "stat": "damage", "type": "cut", "scale": 1.5 }, { "stat": "damage", "type": "stab", "scale": 1.5 } ], - "attack_vectors": [ "WEAPON" ] + "attack_vectors": [ "vector_null" ] }, { "type": "technique", @@ -2458,7 +2454,7 @@ { "stat": "damage", "type": "bash", "scale": 1.5 }, { "stat": "damage", "type": "cut", "scale": 1.5 } ], - "attack_vectors": [ "WEAPON" ] + "attack_vectors": [ "vector_null" ] }, { "type": "technique", @@ -2482,7 +2478,7 @@ { "stat": "damage", "type": "cut", "scale": 1.2 }, { "stat": "damage", "type": "stab", "scale": 1.2 } ], - "attack_vectors": [ "HAND" ] + "attack_vectors": [ "vector_punch" ] }, { "type": "technique", @@ -2504,7 +2500,7 @@ }, "condition_desc": "* Only works on a non-downed humanoid target of similar or smaller size incapable of flight", "down_dur": 2, - "attack_vectors": [ "FOOT" ] + "attack_vectors": [ "vector_foot_sole" ] }, { "type": "technique", @@ -2527,7 +2523,7 @@ "condition_desc": "* Only works on a downed target", "weighting": 3, "mult_bonuses": [ { "stat": "movecost", "scale": 0.5 } ], - "attack_vectors": [ "KNEE" ] + "attack_vectors": [ "vector_knee" ] }, { "type": "technique", @@ -2544,7 +2540,7 @@ "tech_effects": [ { "id": "disarmed", "chance": 100, "duration": 400, "on_damage": true, "message": "The hand of %s is forced open!" } ], "flat_bonuses": [ { "stat": "arpen", "type": "bash", "scaling-stat": "str", "scale": 1.0 } ], "mult_bonuses": [ { "stat": "damage", "type": "bash", "scale": 1.25 } ], - "attack_vectors": [ "GRAPPLE" ] + "attack_vectors": [ "vector_arm_grapple" ] }, { "type": "technique", @@ -2586,7 +2582,7 @@ }, "condition_desc": "* Only works on a non-downed humanoid target of similar or smaller size incapable of flight, may fail on targets grabbing you", "mult_bonuses": [ { "stat": "damage", "type": "bash", "scale": 2.0 } ], - "attack_vectors": [ "THROW" ] + "attack_vectors": [ "vector_grasp" ] }, { "type": "technique", @@ -2606,7 +2602,7 @@ }, "condition_desc": "* Only works on a non-downed humanoid target of similar or smaller size incapable of flight", "down_dur": 2, - "attack_vectors": [ "WEAPON" ] + "attack_vectors": [ "vector_null" ] }, { "type": "technique", @@ -2624,7 +2620,7 @@ { "stat": "damage", "type": "cut", "scale": 1.33 }, { "stat": "damage", "type": "stab", "scale": 1.33 } ], - "attack_vectors": [ "WEAPON" ] + "attack_vectors": [ "vector_null" ] }, { "type": "technique", @@ -2660,7 +2656,7 @@ }, "condition_desc": "* Only works on a non-stunned mundane target of similar or smaller size", "stun_dur": 2, - "attack_vectors": [ "WEAPON" ] + "attack_vectors": [ "vector_null" ] }, { "type": "technique", @@ -2678,7 +2674,7 @@ { "stat": "damage", "type": "cut", "scale": 1.33 }, { "stat": "damage", "type": "stab", "scale": 1.33 } ], - "attack_vectors": [ "WEAPON" ] + "attack_vectors": [ "vector_null" ] }, { "type": "technique", @@ -2693,7 +2689,7 @@ { "stat": "arpen", "type": "stab", "scaling-stat": "per", "scale": 0.5 } ], "mult_bonuses": [ { "stat": "movecost", "scale": 0.8 } ], - "attack_vectors": [ "HAND" ] + "attack_vectors": [ "vector_punch" ] }, { "type": "technique", @@ -2756,7 +2752,7 @@ { "stat": "arpen", "type": "stab", "scaling-stat": "per", "scale": 1.0 } ], "mult_bonuses": [ { "stat": "damage", "type": "bash", "scale": 1.5 } ], - "attack_vectors": [ "HAND" ] + "attack_vectors": [ "vector_punch" ] }, { "type": "technique", @@ -2793,7 +2789,7 @@ { "stat": "damage", "type": "cut", "scale": 0.5 }, { "stat": "damage", "type": "stab", "scale": 0.5 } ], - "attack_vectors": [ "WEAPON" ] + "attack_vectors": [ "vector_null" ] }, { "type": "technique", @@ -2818,7 +2814,7 @@ { "stat": "damage", "type": "cut", "scale": 0.5 }, { "stat": "damage", "type": "stab", "scale": 0.5 } ], - "attack_vectors": [ "WEAPON" ] + "attack_vectors": [ "vector_null" ] }, { "type": "technique", @@ -2834,7 +2830,7 @@ { "stat": "damage", "type": "cut", "scale": 0.66 }, { "stat": "damage", "type": "stab", "scale": 0.66 } ], - "attack_vectors": [ "WEAPON" ] + "attack_vectors": [ "vector_null" ] }, { "type": "technique", @@ -2856,7 +2852,7 @@ "weighting": 2, "take_weapon": true, "mult_bonuses": [ { "stat": "damage", "type": "bash", "scale": 0.5 } ], - "attack_vectors": [ "HAND" ] + "attack_vectors": [ "vector_grasp" ] }, { "type": "technique", @@ -2904,7 +2900,7 @@ { "stat": "damage", "type": "cut", "scale": 1.5 }, { "stat": "damage", "type": "stab", "scale": 1.5 } ], - "attack_vectors": [ "FOOT" ] + "attack_vectors": [ "vector_foot_heel" ] }, { "type": "technique", @@ -2936,7 +2932,7 @@ }, "condition_desc": "* Only works on a target of similar or smaller size, may fail on enemies grabbing you", "knockback_dist": 1, - "attack_vectors": [ "FOOT" ] + "attack_vectors": [ "vector_foot_sole" ] }, { "type": "technique", @@ -2957,7 +2953,7 @@ }, "condition_desc": "* Only works on a non-downed humanoid target of similar or smaller size incapable of flight", "down_dur": 1, - "attack_vectors_random": [ "FOOT", "LOWER_LEG" ] + "attack_vectors": [ "vector_foot_toes", "vector_shin" ] }, { "type": "technique", @@ -2971,7 +2967,7 @@ { "stat": "damage", "type": "cut", "scale": 1.2 }, { "stat": "damage", "type": "stab", "scale": 1.2 } ], - "attack_vectors": [ "FOOT" ] + "attack_vectors": [ "vector_foot_toes" ] }, { "type": "technique", @@ -3001,7 +2997,7 @@ "message": "The weapon of %s has been forced out of their hands!" } ], - "attack_vectors": [ "HAND" ] + "attack_vectors": [ "vector_grasp" ] }, { "type": "technique", @@ -3039,7 +3035,7 @@ { "stat": "damage", "type": "cut", "scale": 1.5 }, { "stat": "damage", "type": "stab", "scale": 1.5 } ], - "attack_vectors": [ "PALM" ] + "attack_vectors": [ "vector_palm" ] }, { "type": "technique", @@ -3066,7 +3062,7 @@ { "stat": "damage", "type": "cut", "scale": 1.2 }, { "stat": "damage", "type": "stab", "scale": 1.2 } ], - "attack_vectors": [ "THROW" ] + "attack_vectors": [ "vector_grasp" ] }, { "type": "technique", @@ -3125,7 +3121,7 @@ { "stat": "damage", "type": "cut", "scale": 2.0 }, { "stat": "damage", "type": "stab", "scale": 2.0 } ], - "attack_vectors": [ "PALM" ] + "attack_vectors": [ "vector_palm" ] }, { "type": "technique", @@ -3157,7 +3153,7 @@ "condition_desc": "* Only works on a non-downed humanoid target of similar or smaller size incapable of flight", "down_dur": 1, "mult_bonuses": [ { "stat": "damage", "type": "bash", "scale": 1.25 } ], - "attack_vectors": [ "THROW" ] + "attack_vectors": [ "vector_grasp" ] }, { "type": "technique", @@ -3177,7 +3173,7 @@ { "stat": "damage", "type": "cut", "scale": 1.25 }, { "stat": "damage", "type": "stab", "scale": 1.25 } ], - "attack_vectors": [ "HAND" ] + "attack_vectors": [ "vector_palm" ] }, { "type": "technique", @@ -3218,7 +3214,7 @@ { "stat": "damage", "type": "cut", "scale": 1.25 }, { "stat": "damage", "type": "stab", "scale": 1.25 } ], - "attack_vectors": [ "HAND" ] + "attack_vectors": [ "vector_punch" ] }, { "type": "technique", @@ -3235,7 +3231,7 @@ { "stat": "damage", "type": "cut", "scale": 1.25 }, { "stat": "damage", "type": "stab", "scale": 1.25 } ], - "attack_vectors": [ "HAND" ] + "attack_vectors": [ "vector_punch" ] }, { "type": "technique", @@ -3248,7 +3244,7 @@ { "stat": "damage", "type": "cut", "scale": 1.1 }, { "stat": "damage", "type": "stab", "scale": 1.1 } ], - "attack_vectors": [ "HAND" ] + "attack_vectors": [ "vector_punch" ] }, { "type": "technique", @@ -3288,7 +3284,7 @@ { "stat": "damage", "type": "cut", "scale": 1.1 }, { "stat": "damage", "type": "stab", "scale": 1.1 } ], - "attack_vectors": [ "HAND" ] + "attack_vectors": [ "vector_punch" ] }, { "type": "technique", @@ -3313,7 +3309,7 @@ { "stat": "damage", "type": "cut", "scale": 1.2 }, { "stat": "damage", "type": "stab", "scale": 1.2 } ], - "attack_vectors": [ "HAND" ] + "attack_vectors": [ "vector_punch" ] }, { "type": "technique", @@ -3358,7 +3354,7 @@ { "stat": "damage", "type": "cut", "scale": 1.2 }, { "stat": "damage", "type": "stab", "scale": 1.2 } ], - "attack_vectors": [ "HAND" ] + "attack_vectors": [ "vector_punch" ] }, { "type": "technique", @@ -3392,7 +3388,7 @@ { "stat": "damage", "type": "cut", "scale": 1.2 }, { "stat": "damage", "type": "stab", "scale": 1.2 } ], - "attack_vectors": [ "HAND" ] + "attack_vectors": [ "vector_punch" ] }, { "type": "technique", @@ -3420,7 +3416,7 @@ { "stat": "damage", "type": "cut", "scale": 1.3 }, { "stat": "damage", "type": "stab", "scale": 1.3 } ], - "attack_vectors_random": [ "HAND", "FOOT" ] + "attack_vectors": [ "vector_punch", "vector_foot_toes" ] }, { "type": "technique", @@ -3435,7 +3431,7 @@ ], "messages": [ "You quickly strike %s!", " quickly strikes %s!" ], "description": "50% moves, 66% damage", - "attack_vectors": [ "WEAPON" ] + "attack_vectors": [ "vector_null" ] }, { "type": "technique", @@ -3449,7 +3445,7 @@ ], "flat_bonuses": [ { "stat": "movecost", "scale": 100 }, { "stat": "movecost", "scaling-stat": "str", "scale": 10 } ], "messages": [ "You slowly strike %s!", " slowly strikes %s!" ], - "attack_vectors": [ "HAND" ] + "attack_vectors": [ "vector_punch" ] }, { "type": "technique", @@ -3470,7 +3466,7 @@ ], "crit_tec": true, "messages": [ "You phase-strike %s!", " phase-strikes %s!" ], - "attack_vectors": [ "WEAPON", "HAND" ] + "attack_vectors": [ "vector_null", "vector_punch" ] }, { "type": "technique", @@ -3491,217 +3487,6 @@ ] } ], - "attack_vectors": [ "WEAPON", "HAND" ] - }, - { - "type": "technique", - "id": "FANGS_BITE", - "name": "Fang Bite", - "melee_allowed": true, - "messages": [ "You bite %s", " bites %s!" ], - "unarmed_allowed": true, - "weighting": -5, - "reach_ok": false, - "attack_vectors": [ "MOUTH" ], - "condition": { - "and": [ - { "not": { "u_has_effect": "natural_stance" } }, - { "and": [ { "not": { "npc_has_flag": "GRAB_FILTER" } }, { "not": { "u_has_effect": "GRAB" } } ] } - ] - }, - "tech_effects": [ - { - "id": "anticoagulant_draculin", - "chance": 70, - "duration": 3000, - "on_damage": true, - "message": "Saliva glistens across %s's wound!", - "req_flag": "DRACULIN_VENOM" - } - ], - "flat_bonuses": [ - { "stat": "damage", "type": "stab", "scale": 9 }, - { "stat": "damage", "type": "stab", "scaling-stat": "unarmed", "scale": 1.0 }, - { "stat": "damage", "type": "bash", "scaling-stat": "str", "scale": 0.75 }, - { "stat": "damage", "type": "bash", "scaling-stat": "unarmed", "scale": 0.06 }, - { "stat": "movecost", "scale": 75 }, - { "stat": "movecost", "scaling-stat": "melee", "scale": -1.25 }, - { "stat": "movecost", "scaling-stat": "dex", "scale": -0.5 } - ] - }, - { - "type": "technique", - "id": "FANGS_BITE_NATURAL", - "name": "Fang Bite", - "melee_allowed": true, - "messages": [ "You bite %s", " bites %s!" ], - "unarmed_allowed": true, - "weighting": -2, - "reach_ok": false, - "attack_vectors": [ "MOUTH" ], - "condition": { - "and": [ - { - "or": [ { "u_has_effect": "natural_stance" }, { "and": [ { "npc_has_flag": "GRAB_FILTER" }, { "u_has_flag": "GRAB" } ] } ] - } - ] - }, - "tech_effects": [ - { - "id": "anticoagulant_draculin", - "chance": 70, - "duration": 3000, - "on_damage": true, - "message": "Saliva glistens across %s's wound!", - "req_flag": "DRACULIN_VENOM" - } - ], - "flat_bonuses": [ - { "stat": "damage", "type": "stab", "scale": 9 }, - { "stat": "damage", "type": "stab", "scaling-stat": "unarmed", "scale": 1.0 }, - { "stat": "damage", "type": "bash", "scaling-stat": "str", "scale": 0.75 }, - { "stat": "damage", "type": "bash", "scaling-stat": "unarmed", "scale": 0.06 }, - { "stat": "movecost", "scale": 75 }, - { "stat": "movecost", "scaling-stat": "melee", "scale": -1.25 }, - { "stat": "movecost", "scaling-stat": "dex", "scale": -0.5 } - ] - }, - { - "type": "technique", - "id": "FANGS_BITE_CRIT", - "name": "Critical Fang Bite", - "melee_allowed": true, - "messages": [ "You deliver a wicked bite to %s", " delivers a wicked bite to %s!" ], - "unarmed_allowed": true, - "reach_ok": false, - "crit_tec": true, - "attack_vectors": [ "MOUTH" ], - "tech_effects": [ - { - "id": "anticoagulant_draculin", - "chance": 90, - "duration": 6000, - "on_damage": true, - "message": "Saliva glistens across %s's wound!", - "req_flag": "DRACULIN_VENOM" - } - ], - "flat_bonuses": [ - { "stat": "damage", "type": "stab", "scale": 9 }, - { "stat": "damage", "type": "stab", "scaling-stat": "unarmed", "scale": 4.4 }, - { "stat": "damage", "type": "bash", "scaling-stat": "str", "scale": 0.75 }, - { "stat": "damage", "type": "bash", "scaling-stat": "unarmed", "scale": 0.24 }, - { "stat": "arpen", "type": "bash", "scaling-stat": "unar,ed", "scale": 1 }, - { "stat": "arpen", "type": "stab", "scaling-stat": "unarmed", "scale": 1 }, - { "stat": "movecost", "scale": 75 }, - { "stat": "movecost", "scaling-stat": "melee", "scale": -1.25 }, - { "stat": "movecost", "scaling-stat": "dex", "scale": -0.5 } - ] - }, - { - "type": "technique", - "id": "VAMPIRE_BITE", - "name": "Vampire Bite", - "melee_allowed": true, - "messages": [ "You bite %s", " bites %s!" ], - "unarmed_allowed": true, - "weighting": -5, - "reach_ok": false, - "attack_vectors": [ "MOUTH" ], - "condition": { - "and": [ - { "not": { "u_has_effect": "natural_stance" } }, - { "and": [ { "not": { "npc_has_flag": "GRAB_FILTER" } }, { "not": { "u_has_effect": "GRAB" } } ] } - ] - }, - "tech_effects": [ - { - "id": "anticoagulant_draculin", - "chance": 90, - "duration": 3000, - "on_damage": true, - "message": "Saliva glistens across %s's wound!", - "req_flag": "DRACULIN_VENOM" - } - ], - "flat_bonuses": [ - { "stat": "damage", "type": "stab", "scale": 15 }, - { "stat": "damage", "type": "stab", "scaling-stat": "unarmed", "scale": 1.0 }, - { "stat": "damage", "type": "bash", "scaling-stat": "str", "scale": 0.75 }, - { "stat": "damage", "type": "bash", "scaling-stat": "unarmed", "scale": 0.06 }, - { "stat": "movecost", "scale": 75 }, - { "stat": "movecost", "scaling-stat": "melee", "scale": -1.25 }, - { "stat": "movecost", "scaling-stat": "dex", "scale": -0.5 } - ] - }, - { - "type": "technique", - "id": "VAMPIRE_BITE_NATURAL", - "name": "Vampire Bite", - "melee_allowed": true, - "messages": [ "You bite %s", " bites %s!" ], - "unarmed_allowed": true, - "weighting": -2, - "reach_ok": false, - "attack_vectors": [ "MOUTH" ], - "tech_effects": [ - { - "id": "anticoagulant_draculin", - "chance": 90, - "duration": 3000, - "on_damage": true, - "message": "Saliva glistens across %s's wound!", - "req_flag": "DRACULIN_VENOM" - } - ], - "condition": { - "and": [ - { - "or": [ { "u_has_effect": "natural_stance" }, { "and": [ { "npc_has_flag": "GRAB_FILTER" }, { "u_has_flag": "GRAB" } ] } ] - } - ] - }, - "flat_bonuses": [ - { "stat": "damage", "type": "stab", "scale": 15 }, - { "stat": "damage", "type": "stab", "scaling-stat": "unarmed", "scale": 1.0 }, - { "stat": "damage", "type": "bash", "scaling-stat": "str", "scale": 0.75 }, - { "stat": "damage", "type": "bash", "scaling-stat": "unarmed", "scale": 0.06 }, - { "stat": "movecost", "scale": 75 }, - { "stat": "movecost", "scaling-stat": "melee", "scale": -1.25 }, - { "stat": "movecost", "scaling-stat": "dex", "scale": -0.5 } - ] - }, - { - "type": "technique", - "id": "VAMPIRE_BITE_CRIT", - "name": "Critical Vampire Bite", - "melee_allowed": true, - "messages": [ "You sink your fangs deep into %s!", " sinks their fangs deep into %s!" ], - "unarmed_allowed": true, - "weighting": -4, - "reach_ok": false, - "crit_tec": true, - "tech_effects": [ - { - "id": "anticoagulant_draculin", - "chance": 100, - "duration": 6000, - "on_damage": true, - "message": "Saliva glistens across %s's wound!", - "req_flag": "DRACULIN_VENOM" - } - ], - "attack_vectors": [ "MOUTH" ], - "flat_bonuses": [ - { "stat": "damage", "type": "stab", "scale": 15 }, - { "stat": "damage", "type": "stab", "scaling-stat": "unarmed", "scale": 4.4 }, - { "stat": "damage", "type": "bash", "scaling-stat": "str", "scale": 0.75 }, - { "stat": "damage", "type": "bash", "scaling-stat": "unarmed", "scale": 0.24 }, - { "stat": "arpen", "type": "bash", "scaling-stat": "unarmed", "scale": 1 }, - { "stat": "arpen", "type": "stab", "scaling-stat": "unarmed", "scale": 1 }, - { "stat": "movecost", "scale": 75 }, - { "stat": "movecost", "scaling-stat": "melee", "scale": -1.25 }, - { "stat": "movecost", "scaling-stat": "dex", "scale": -0.5 } - ] + "attack_vectors": [ "vector_null", "vector_wrist" ] } ] diff --git a/data/mods/MMA/techniques.json b/data/mods/MMA/techniques.json index beca51c0fb43c..6cf652b9b9c1e 100644 --- a/data/mods/MMA/techniques.json +++ b/data/mods/MMA/techniques.json @@ -6,7 +6,7 @@ "messages": [ "You unleash a fiery attack against %s!", " unleash a fiery attack against %s!" ], "melee_allowed": true, "flat_bonuses": [ { "stat": "damage", "type": "heat", "scale": 3.0 } ], - "attack_vectors": [ "WEAPON" ] + "attack_vectors": [ "vector_null" ] }, { "type": "technique", @@ -17,7 +17,7 @@ "melee_allowed": true, "crit_tec": true, "flat_bonuses": [ { "stat": "damage", "type": "heat", "scale": 7.0 } ], - "attack_vectors": [ "WEAPON" ] + "attack_vectors": [ "vector_null" ] }, { "type": "technique", @@ -29,7 +29,7 @@ "crit_ok": true, "aoe": "impale", "flat_bonuses": [ { "stat": "damage", "type": "heat", "scale": 7.0 } ], - "attack_vectors": [ "WEAPON" ] + "attack_vectors": [ "vector_null" ] }, { "type": "technique", @@ -44,7 +44,7 @@ "crit_tec": true, "aoe": "spin", "flat_bonuses": [ { "stat": "damage", "type": "heat", "scale": 10.0 } ], - "attack_vectors": [ "WEAPON" ] + "attack_vectors": [ "vector_null" ] }, { "type": "technique", @@ -54,7 +54,7 @@ "skill_requirements": [ { "name": "melee", "level": 2 } ], "melee_allowed": true, "aoe": "wide", - "attack_vectors": [ "WEAPON" ] + "attack_vectors": [ "vector_null" ] }, { "type": "technique", @@ -64,7 +64,7 @@ "skill_requirements": [ { "name": "melee", "level": 1 } ], "melee_allowed": true, "flat_bonuses": [ { "stat": "damage", "type": "stab", "scaling-stat": "int", "scale": 0.5 } ], - "attack_vectors": [ "WEAPON" ] + "attack_vectors": [ "vector_null" ] }, { "type": "technique", @@ -75,7 +75,7 @@ "melee_allowed": true, "crit_tec": true, "flat_bonuses": [ { "stat": "damage", "type": "stab", "scaling-stat": "int", "scale": 1.0 } ], - "attack_vectors": [ "WEAPON" ] + "attack_vectors": [ "vector_null" ] }, { "type": "technique", @@ -90,7 +90,7 @@ "required_buffs_any": [ "mma_buff_hylian_onpause", "mma_buff_hylian_spin" ], "crit_ok": true, "aoe": "spin", - "attack_vectors": [ "WEAPON" ] + "attack_vectors": [ "vector_null" ] }, { "type": "technique", @@ -105,7 +105,7 @@ "required_buffs_any": [ "mma_buff_hylian_onpause", "mma_buff_hylian_spin" ], "crit_ok": true, "aoe": "wide", - "attack_vectors": [ "WEAPON" ] + "attack_vectors": [ "vector_null" ] }, { "type": "technique", @@ -123,7 +123,7 @@ "message": "The weapon of %s has been forced out ot their hands!" } ], - "attack_vectors": [ "WEAPON" ] + "attack_vectors": [ "vector_null" ] }, { "type": "technique", @@ -134,7 +134,7 @@ "melee_allowed": true, "defensive": true, "miss_recovery": true, - "attack_vectors": [ "WEAPON" ] + "attack_vectors": [ "vector_null" ] }, { "type": "technique", @@ -175,7 +175,7 @@ { "stat": "damage", "type": "cut", "scale": 1.25 }, { "stat": "damage", "type": "stab", "scale": 1.25 } ], - "attack_vectors": [ "WEAPON" ] + "attack_vectors": [ "vector_null" ] }, { "type": "technique", @@ -216,7 +216,7 @@ { "stat": "damage", "type": "cut", "scale": 1.4 }, { "stat": "damage", "type": "stab", "scale": 1.4 } ], - "attack_vectors": [ "WEAPON" ] + "attack_vectors": [ "vector_null" ] }, { "type": "technique", @@ -229,7 +229,7 @@ "skill_requirements": [ { "name": "melee", "level": 3 } ], "melee_allowed": true, "aoe": "wide", - "attack_vectors": [ "WEAPON" ] + "attack_vectors": [ "vector_null" ] }, { "type": "technique", @@ -245,7 +245,7 @@ { "stat": "damage", "type": "cut", "scale": 1.25 }, { "stat": "damage", "type": "stab", "scale": 1.25 } ], - "attack_vectors": [ "WEAPON" ] + "attack_vectors": [ "vector_null" ] }, { "type": "technique", @@ -260,7 +260,7 @@ { "stat": "damage", "type": "cut", "scale": 1.2 }, { "stat": "damage", "type": "stab", "scale": 1.2 } ], - "attack_vectors": [ "WEAPON", "FOOT" ] + "attack_vectors": [ "vector_null", "vector_foot_heel" ] }, { "type": "technique", @@ -309,7 +309,7 @@ { "stat": "damage", "type": "cut", "scale": 1.5 }, { "stat": "damage", "type": "stab", "scale": 1.5 } ], - "attack_vectors": [ "WEAPON", "HAND" ] + "attack_vectors": [ "vector_null", "vector_punch" ] }, { "type": "technique", @@ -326,7 +326,7 @@ { "stat": "damage", "type": "cut", "scale": 0.66 }, { "stat": "damage", "type": "stab", "scale": 0.66 } ], - "attack_vectors": [ "WEAPON", "HAND" ] + "attack_vectors": [ "vector_null", "vector_punch" ] }, { "type": "technique", @@ -341,7 +341,7 @@ { "stat": "damage", "type": "cut", "scale": 2.0 }, { "stat": "damage", "type": "stab", "scale": 2.0 } ], - "attack_vectors": [ "FOOT" ] + "attack_vectors": [ "vector_foot_sole" ] }, { "type": "technique", @@ -356,7 +356,7 @@ { "stat": "arpen", "type": "cut", "scaling-stat": "str", "scale": 1.5 }, { "stat": "arpen", "type": "stab", "scaling-stat": "str", "scale": 1.5 } ], - "attack_vectors": [ "ARM" ] + "attack_vectors": [ "vector_arm" ] }, { "type": "technique", @@ -367,7 +367,7 @@ "unarmed_allowed": true, "crit_ok": true, "flat_bonuses": [ { "stat": "hit", "scale": 5.0 } ], - "attack_vectors_random": [ "HAND", "FOOT", "HEAD", "TORSO", "HEAD" ] + "attack_vectors": [ "vector_punch", "vector_foot_toes", "vector_headbutt", "vector_shoulder" ] }, { "type": "technique", @@ -387,7 +387,7 @@ }, "condition_desc": "* Only works on a non-downed humanoid target of similar or smaller size incapable of flight", "down_dur": 1, - "attack_vectors_random": [ "FOOT", "LOWER_LEG" ] + "attack_vectors": [ "vector_foot_toes", "vector_shin" ] }, { "type": "technique", @@ -424,7 +424,7 @@ }, "condition_desc": "* Only works on a non-stunned mundane target of similar or smaller size", "stun_dur": 1, - "attack_vectors": [ "WEAPON", "HAND" ] + "attack_vectors": [ "vector_null", "vector_punch" ] }, { "type": "technique", @@ -465,7 +465,7 @@ { "stat": "damage", "type": "cut", "scale": 1.33 }, { "stat": "damage", "type": "stab", "scale": 1.33 } ], - "attack_vectors": [ "THROW" ] + "attack_vectors": [ "vector_grasp" ] }, { "type": "technique", @@ -508,7 +508,7 @@ { "stat": "damage", "type": "cut", "scale": 1.5 }, { "stat": "damage", "type": "stab", "scale": 1.5 } ], - "attack_vectors": [ "THROW" ] + "attack_vectors": [ "vector_grasp" ] }, { "type": "technique", @@ -528,7 +528,7 @@ "message": "The weapon of %s has been forced out ot their hands!" } ], - "attack_vectors": [ "WEAPON" ] + "attack_vectors": [ "vector_null" ] }, { "type": "technique", @@ -539,7 +539,7 @@ "melee_allowed": true, "crit_ok": true, "aoe": "wide", - "attack_vectors": [ "WEAPON" ] + "attack_vectors": [ "vector_null" ] }, { "type": "technique", @@ -557,7 +557,7 @@ { "stat": "arpen", "type": "cut", "scaling-stat": "str", "scale": 0.5 }, { "stat": "arpen", "type": "stab", "scaling-stat": "str", "scale": 0.5 } ], - "attack_vectors": [ "WEAPON", "HAND" ] + "attack_vectors": [ "vector_null", "vector_wrist" ] }, { "type": "technique", @@ -594,7 +594,7 @@ }, "condition_desc": "* Only works on a non-stunned mundane target of similar or smaller size", "stun_dur": 1, - "attack_vectors": [ "WEAPON", "HAND" ] + "attack_vectors": [ "vector_null", "vector_wrist" ] }, { "type": "technique", @@ -614,7 +614,7 @@ { "stat": "damage", "type": "cut", "scale": 1.3 }, { "stat": "damage", "type": "stab", "scale": 1.3 } ], - "attack_vectors": [ "WEAPON", "HAND" ] + "attack_vectors": [ "vector_null", "vector_punch" ] }, { "type": "technique", diff --git a/data/mods/Magiclysm/items/integrated.json b/data/mods/Magiclysm/items/integrated.json index 90c5fc6955102..25ccfa3e50ca0 100644 --- a/data/mods/Magiclysm/items/integrated.json +++ b/data/mods/Magiclysm/items/integrated.json @@ -6,7 +6,8 @@ "category": "armor", "name": { "str_sp": "goblin teeth" }, "description": "A set of goblin teeth, small but needle-sharp.", - "techniques": [ "FANGS_BITE_GOBLIN", "FANGS_BITE_GOBLIN_GRAB" ] + "techniques": [ "FANGS_BITE_GOBLIN", "FANGS_BITE_GOBLIN_GRAB", "FANGS_BITE_GOBLIN_CRIT" ], + "melee_damage": { "stab": 6 } }, { "id": "integrated_lizardfolk_teeth", @@ -15,6 +16,7 @@ "category": "armor", "name": { "str_sp": "lizardfolk teeth" }, "description": "A set of lizardfolk teeth, made for biting chunks of out their prey.", - "techniques": [ "FANGS_BITE_LIZARDFOLK" ] + "techniques": [ "FANGS_BITE_LIZARDFOLK", "LIZARDFOLK_BITE_CRIT" ], + "melee_damage": { "stab": 20 } } ] diff --git a/data/mods/Magiclysm/techniques.json b/data/mods/Magiclysm/techniques.json index afe9463d6cfaa..58daf4b452c24 100644 --- a/data/mods/Magiclysm/techniques.json +++ b/data/mods/Magiclysm/techniques.json @@ -13,6 +13,6 @@ "Snicker-snack! slices through %s like a hot knife slices through butter." ], "description": "Cut damage multiply by 2, crit only", - "attack_vectors": [ "WEAPON" ] + "attack_vectors": [ "vector_null" ] } ] diff --git a/data/mods/Magiclysm/techniques_fantasy_species.json b/data/mods/Magiclysm/techniques_fantasy_species.json index 6ec3868b8dc03..6b0927beb4b63 100644 --- a/data/mods/Magiclysm/techniques_fantasy_species.json +++ b/data/mods/Magiclysm/techniques_fantasy_species.json @@ -9,15 +9,13 @@ "unarmed_allowed": true, "weighting": -3, "reach_ok": false, - "attack_vectors": [ "MOUTH" ], + "attack_vectors": [ "vector_bite" ], "condition": { "and": [ { "not": { "npc_has_flag": "GRAB_FILTER" } }, { "not": { "u_has_effect": "GRAB" } } ] }, "//": "The bonuses below are based on the natural anatomy of a non-human creature and should not be used for mutant attack scaling.", "flat_bonuses": [ - { "stat": "damage", "type": "stab", "scale": 6 }, { "stat": "damage", "type": "stab", "scaling-stat": "unarmed", "scale": 1.1 }, { "stat": "damage", "type": "bash", "scaling-stat": "str", "scale": 0.8 }, { "stat": "damage", "type": "bash", "scaling-stat": "unarmed", "scale": 0.065 }, - { "stat": "movecost", "scale": 75 }, { "stat": "movecost", "scaling-stat": "melee", "scale": -1.25 }, { "stat": "movecost", "scaling-stat": "dex", "scale": -0.5 } ] @@ -32,15 +30,13 @@ "unarmed_allowed": true, "weighting": -2, "reach_ok": false, - "attack_vectors": [ "MOUTH" ], + "attack_vectors": [ "vector_bite" ], "condition": { "and": [ { "npc_has_flag": "GRAB_FILTER" }, { "u_has_flag": "GRAB" } ] }, "//": "The bonuses below are based on the natural anatomy of a non-human creature and should not be used for mutant attack scaling.", "flat_bonuses": [ - { "stat": "damage", "type": "stab", "scale": 6 }, { "stat": "damage", "type": "stab", "scaling-stat": "unarmed", "scale": 1.1 }, { "stat": "damage", "type": "bash", "scaling-stat": "str", "scale": 0.8 }, { "stat": "damage", "type": "bash", "scaling-stat": "unarmed", "scale": 0.065 }, - { "stat": "movecost", "scale": 75 }, { "stat": "movecost", "scaling-stat": "melee", "scale": -1.25 }, { "stat": "movecost", "scaling-stat": "dex", "scale": -0.5 } ] @@ -54,15 +50,13 @@ "unarmed_allowed": true, "reach_ok": false, "crit_tec": true, - "attack_vectors": [ "MOUTH" ], + "attack_vectors": [ "vector_bite" ], "flat_bonuses": [ - { "stat": "damage", "type": "stab", "scale": 6 }, { "stat": "damage", "type": "stab", "scaling-stat": "unarmed", "scale": 4.4 }, { "stat": "damage", "type": "bash", "scaling-stat": "str", "scale": 0.75 }, { "stat": "damage", "type": "bash", "scaling-stat": "unarmed", "scale": 0.24 }, { "stat": "arpen", "type": "bash", "scaling-stat": "unarmed", "scale": 1 }, { "stat": "arpen", "type": "stab", "scaling-stat": "unarmed", "scale": 1 }, - { "stat": "movecost", "scale": 75 }, { "stat": "movecost", "scaling-stat": "melee", "scale": -1.25 }, { "stat": "movecost", "scaling-stat": "dex", "scale": -0.5 } ] @@ -77,14 +71,12 @@ "unarmed_allowed": true, "weighting": -2, "reach_ok": false, - "attack_vectors": [ "MOUTH" ], + "attack_vectors": [ "vector_bite" ], "//": "The bonuses below are based on the natural anatomy of a non-human creature and should not be used for mutant attack scaling.", "flat_bonuses": [ - { "stat": "damage", "type": "stab", "scale": 20 }, { "stat": "damage", "type": "stab", "scaling-stat": "unarmed", "scale": 1.1 }, { "stat": "damage", "type": "bash", "scaling-stat": "str", "scale": 0.8 }, { "stat": "damage", "type": "bash", "scaling-stat": "unarmed", "scale": 0.065 }, - { "stat": "movecost", "scale": 75 }, { "stat": "movecost", "scaling-stat": "melee", "scale": -1.25 }, { "stat": "movecost", "scaling-stat": "dex", "scale": -0.5 } ] @@ -96,20 +88,18 @@ "melee_allowed": true, "messages": [ "You viciously tear into %s", " viciously tears %s!" ], "unarmed_allowed": true, - "weighting": -5, + "weighting": -2, "reach_ok": false, "crit_tec": true, - "attack_vectors": [ "MOUTH" ], + "attack_vectors": [ "vector_bite" ], "condition": { "and": [ { "not": { "npc_has_flag": "GRAB_FILTER" } }, { "not": { "u_has_effect": "GRAB" } } ] }, "//": "The bonuses below are based on the natural anatomy of a non-human creature and should not be used for mutant attack scaling.", "flat_bonuses": [ - { "stat": "damage", "type": "stab", "scale": 20 }, { "stat": "damage", "type": "stab", "scaling-stat": "unarmed", "scale": 4.4 }, { "stat": "damage", "type": "bash", "scaling-stat": "str", "scale": 0.75 }, { "stat": "damage", "type": "bash", "scaling-stat": "unarmed", "scale": 0.24 }, { "stat": "arpen", "type": "bash", "scaling-stat": "unarmed", "scale": 1 }, { "stat": "arpen", "type": "stab", "scaling-stat": "unarmed", "scale": 1 }, - { "stat": "movecost", "scale": 100 }, { "stat": "movecost", "scaling-stat": "melee", "scale": -1.25 }, { "stat": "movecost", "scaling-stat": "dex", "scale": -0.5 } ] diff --git a/data/mods/TEST_DATA/martialarts.json b/data/mods/TEST_DATA/martialarts.json index a3725d7c0535b..6c69e517e9b92 100644 --- a/data/mods/TEST_DATA/martialarts.json +++ b/data/mods/TEST_DATA/martialarts.json @@ -25,7 +25,7 @@ }, "down_dur": 2, "messages": [ "You see if you can sweep %s", " sees if they can sweep %s" ], - "attack_vectors": [ "FOOT" ] + "attack_vectors": [ "vector_foot_toes" ] }, { "type": "technique", @@ -59,7 +59,7 @@ }, "stun_dur": 1, "weapon_categories_allowed": [ "MEDIUM_SWORDS" ], - "attack_vectors": [ "WEAPON" ] + "attack_vectors": [ "vector_null" ] }, { "type": "technique", @@ -91,7 +91,7 @@ }, "knockback_dist": 2, "weapon_categories_allowed": [ "MACES" ], - "attack_vectors": [ "WEAPON" ] + "attack_vectors": [ "vector_null" ] }, { "type": "martial_art", diff --git a/data/mods/Xedra_Evolved/techniques.json b/data/mods/Xedra_Evolved/techniques.json index 374689fdb1d58..9110a7a481d19 100644 --- a/data/mods/Xedra_Evolved/techniques.json +++ b/data/mods/Xedra_Evolved/techniques.json @@ -74,7 +74,7 @@ "condition_desc": "* Only works on target of similar or bigger size", "messages": [ "You send %s reeling!", " sends %s reeling!" ], "description": "Down 4 turns, knockback 3 tiles, 250% damage, 140% move, crit only, min 3 melee", - "attack_vectors": [ "WEAPON", "HAND" ] + "attack_vectors": [ "vector_null", "vector_punch" ] }, { "type": "technique", @@ -93,6 +93,6 @@ "In an instant melts %s and those nearby with a sprawling attack!" ], "description": "150% damage, 120% move, attack multiple enemies, crit only, min 4 melee", - "attack_vectors": [ "WEAPON" ] + "attack_vectors": [ "vector_null" ] } ] diff --git a/doc/MARTIALART_JSON.md b/doc/MARTIALART_JSON.md index 99370f3870fce..3ea3f8548ac5e 100644 --- a/doc/MARTIALART_JSON.md +++ b/doc/MARTIALART_JSON.md @@ -65,12 +65,11 @@ ### Techniques -```C++ +```JSON { "id": "tec_debug_arpen", // Unique ID. Must be one continuous word "name": "phasing strike", // In-game name displayed - "attack_vectors": [ "WEAPON", "HAND" ] // What attack vector would be used for this technique; this field is order dependend, meaning in this example the game will try to use WEAPON first, and, if not possible, reject it and will use HAND instead; For more info see Attack vectors below - "attack_vectors_random": [ "FOOT", "HEAD", "TORSO", "HEAD", "HEAD" ] // same as attack_vectors, but has no priority, and pick random vector from the list; it is used only if all choises from attack_vectors are rejected + "attack_vectors": [ "vector_1", "vector_2" ], // What attack vector would be used for this technique; For more info see Attack vectors below "unarmed_allowed": true, // Can an unarmed character use this technique "weapon_categories_allowed": [ "BLADES", "KNIVES" ], // Restrict technique to only these categories of weapons. If omitted, all weapon categories are allowed. "melee_allowed": true, // Means that ANY melee weapon can be used, NOT just the martial art's weapons @@ -122,28 +121,27 @@ ### Attack vectors -Attack vector is a way for game to separate which techniques could be used by character, and which could not - it would be odd to see player is unable to kick because their arm is broken +Attack vectors define which (sub)bodypart is used for the attack in question, allow filtering of eligable bodyparts and apply the relevant worn armor's unarmed damage to the attack. Note for the (sub)part to apply its unarmed damage it needs unrestricted natural attacks. +```JSON +[ + { + "type": "attack_vector", // Always attack_vector + "id": "vector_hand", // ID + "limbs": [ "hand_l", "hand_r" ], // List of bodyparts used in this attack (relevant for HP/encumbrance/flag filtering) + "contact_area": [ "hand_fingers_l", "hand_fingers_r" ], // List of subbodyparts that can be used as a strike surface in the attack using the sbp's armor or intrinsic unarmed damage + "strict_limb_definition": false, // Bool, default false. When true *only* the bodyparts defined above are used for the vector, otherwise similar bodyparts can be used as long as both the contact area and the defined limb are similar, see JSON_INFO.md/Bodyparts for bodypart similarity + "armor_bonus": true, // Bool, default true, defines if the vector takes the unarmed damage bonus of the armor worn on the contact area into account + "required_limb_flags": [ "foo", "bar" ], // List of character flags required for the bodypart to be eligable for this vector + "forbidden_limb_flags": [ "foo", "bar" ], // List of character flags that disqualify a limb from being usable by this vector + "encumbrance_limit": 15, // Int, default 100, encumbrance of the limb above this will disqualify it from this vector + "bp_hp_limit": 75 // Int, default 10, percent of bodypart limb HP necessary for the limbs to qualify for this vector. For minor (non-main) bodyparts the corresponding main part HP is taken into account. + } +] +``` List of attack vectors is currently hardcoded, and contain: -- `HAND` - Any technique that hits with any part of the hand (backhand, jab, hammer fist). Can be used as long as at least one hand/arm limb is not broken. -- `FINGERS` - Any technique that hits with the fingers (eye gouge, spearhand). Can be used as long as at least one hand/arm limb is not broken. -- `PALM` - Any technique that hits with the palm of the hand(palm strike). Can be used as long as at least one hand/arm limb is not broken. -- `HAND_BACK` - Any technique that hits with the back of the hand(backfist, backhand slap). Can be used as long as at least one hand/arm limb is not broken. -- `WRIST` - Any technique that hits with the wrist (crane strike). Can be used as long as at least one hand/arm limb is not broken. -- `ARM` - Any technique that hits with the arm itself (clothesline). Can be used as long as at least one hand/arm limb is not broken. -- `ELBOW` - Any technique that hits with an elbow (elbow strike). Can be used as long as at least one hand/arm limb is not broken. -- `SHOULDER` - Any technique that hits with the upper part of the arm (shoulder check). Can be used as long as at least one hand/arm limb is not broken. -- `FOOT` - Any technique that hits with any part of the foot (roundhouse kick, foot stomp, heel drop). Can be used only if both legs are not broken. You need one functional leg to perform the attack and another functional leg to balance on. -- `LOWER_LEG` - Any technique that hits with shin (Muay Thai kicks). Can be used only if both legs are not broken. You need one functional leg to perform the attack and another functional leg to balance on. -- `KNEE` - Any technique that hits with the knee (knee bash). Can be used only if both legs are not broken. You need one functional leg to perform the attack and another functional leg to balance on. -- `HIP` - Any technique that hits with the hips or buttocks (Peach Bomber, R. Mika's Flying Peach). Can be used only if both legs are not broken. You need one functional leg to perform the attack and another functional leg to balance on. -- `TORSO` - Any technique that hits with the center mass of the user's body (flying body splash, throwing yourself at an enemy). Can always be used because if your Torso is broken, you are dead. // Shouldn't it requre both legs? can't really use a whole body if legs are broken, no way to deliver the momentum ain't it? -- `HEAD` - Any technique that hits with the user's head such as a headbutt. Can always be used because if your Head is broken, you are dead. - `WEAPON` - Any technique the requires a held item to perform (see any weapon style). Can be used if the user is holding a valid style weapon for their martial art and at least one hand/arm is not broken. -- `THROW` - Any technique that forcefully moves an opponent (judo throws, suplex). Can be used only if both hands/arms are not broken. -- `GRAPPLE` - Any technique that maintains contact with an opponent and squeezes (chock, headlock), bends (Krav Maga's Arm Breaker), or twists (arm twist) some part of the opponent. Can be used only if both hands/arms are not broken. -- `MOUTH` - A technique that uses the mouth to bite or spit on an opponent. Can be used only if the mouth is not covered by anything not flagged with ALLOWS_NATURAL_ATTACKS. ### Tech effects ```C++ diff --git a/src/character.h b/src/character.h index 98896c6aad871..470e13f338d0c 100644 --- a/src/character.h +++ b/src/character.h @@ -1132,11 +1132,10 @@ class Character : public Creature, public visitable std::tuple pick_technique( Creature &t, const item_location &weap, bool crit, bool dodge_counter, bool block_counter, const std::vector &blacklist = {} ); - // Filter techniques, return a vector of tech/vector/contact area sets for pick_technique to choose from - std::vector> - evaluate_techniques( Creature &t, const item_location &weap, - bool crit = false, bool dodge_counter = false, bool block_counter = false, - const std::vector &blacklist = {} ); + // Filter techniques per tech, return a tech/vector/sublimb set + std::optional> + evaluate_technique( const matec_id &tec_id, Creature &t, const item_location &weap, + bool crit = false, bool dodge_counter = false, bool block_counter = false ); void perform_technique( const ma_technique &technique, Creature &t, damage_instance &di, int &move_cost, item_location &cur_weapon ); diff --git a/src/martialarts.cpp b/src/martialarts.cpp index d32d068e0c1d7..4dbecb3304150 100644 --- a/src/martialarts.cpp +++ b/src/martialarts.cpp @@ -88,14 +88,13 @@ void attack_vector::reset() void attack_vector::load( const JsonObject &jo, const std::string_view ) { mandatory( jo, was_loaded, "id", id ); - mandatory( jo, was_loaded, "name", name ); optional( jo, was_loaded, "weapon", weapon, false ); optional( jo, was_loaded, "limbs", limbs ); optional( jo, was_loaded, "strict_limb_definition", strict_limb_definition, false ); optional( jo, was_loaded, "contact_area", contact_area ); optional( jo, was_loaded, "armor_bonus", armor_bonus, true ); optional( jo, was_loaded, "encumbrance_limit", encumbrance_limit, 100 ); - optional( jo, was_loaded, "bp_hp_limit", bp_hp_limit, 0 ); + optional( jo, was_loaded, "bp_hp_limit", bp_hp_limit, 10 ); optional( jo, was_loaded, "required_limb_flags", required_limb_flags ); optional( jo, was_loaded, "forbidden_limb_flags", forbidden_limb_flags ); } @@ -1458,7 +1457,7 @@ std::optional> martialart ma = style_selected.obj(); bool valid_weapon = ma.weapon_valid( user.get_wielded_item() ); for( const attack_vector_id &vec : tech.obj().attack_vectors ) { - add_msg_debug( debugmode::DF_MELEE, "Evaluating vector %s for tech %s", vec->name, tech.c_str() ); + add_msg_debug( debugmode::DF_MELEE, "Evaluating vector %s for tech %s", vec.c_str(), tech.c_str() ); float weight = 0.0f; // Early break for armed vectors if( vec->weapon && armed && valid_weapon ) { @@ -1478,7 +1477,7 @@ std::optional> std::vector> calc_vector; for( const bodypart_id &bp : vec->limbs ) { if( std::find( anat.begin(), anat.end(), bp ) != anat.end() ) { - add_msg_debug( debugmode::DF_MELEE, "Evaluating limb %s for vector %s", bp->name, vec->name ); + add_msg_debug( debugmode::DF_MELEE, "Evaluating limb %s for vector %s", bp->name, vec.c_str() ); // Filter on limb flags early bool allowed = true; for( const json_character_flag &req : vec->required_limb_flags ) { @@ -1498,12 +1497,17 @@ std::optional> } } if( !allowed ) { - add_msg_debug( debugmode::DF_MELEE, "Limb %s disqualified for vector %s", bp->name, vec->name ); + add_msg_debug( debugmode::DF_MELEE, "Limb %s disqualified for vector %s", bp->name, vec.c_str() ); continue; } - const bodypart &part = *user.get_part( bp ); - if( ( 100 * part.get_hp_cur() / part.get_hp_max() ) > vec->bp_hp_limit && - part.get_encumbrance_data().encumbrance < vec->encumbrance_limit ) { + + // TODO: move this from being a special case to the default + int bp_hp_cur = bp->main_part == bp.id() ? user.get_part_hp_cur( bp ) : user.get_part_hp_cur( + bp->main_part ); + int bp_hp_max = bp->main_part == bp.id() ? user.get_part_hp_max( bp ) : user.get_part_hp_max( + bp->main_part ); + if( ( 100 * bp_hp_cur / bp_hp_max ) > vec->bp_hp_limit && + user.get_part_encumbrance_data( bp ).encumbrance < vec->encumbrance_limit ) { sub_bodypart_str_id current_contact; for( const sub_bodypart_str_id &sbp : bp->sub_parts ) { if( std::find( vec->contact_area.begin(), vec->contact_area.end(), @@ -1516,17 +1520,20 @@ std::optional> } // Go through the common function float unarmed_damage = calculate_vector_damage( user, vec, current_contact ).total_damage(); + if( unarmed_damage == 0.0f ) { + unarmed_damage++; + } calc_vector.emplace_back( std::make_pair( current_contact, unarmed_damage ) ); add_msg_debug( debugmode::DF_MELEE, "Bodypart %s eligable for attack vector %s weight %.1f (contact area %s)", bp.id().c_str(), - vec->name, unarmed_damage, current_contact->name ); + vec.c_str(), unarmed_damage, current_contact->name ); } } } if( calc_vector.size() == 0 ) { add_msg_debug( debugmode::DF_MELEE, "Vector %s found no eligable bodyparts, discarding", - vec->name ); + vec.c_str() ); continue; } // Sort our calc_vector of sublimb/damage pairs @@ -1540,7 +1547,7 @@ std::optional> storage.emplace_back( std::make_pair( vec, calc_vector.rbegin()->first ) ); add_msg_debug( debugmode::DF_MELEE, "Chose contact sublimb %s for vector %s with weight %.1f; %d stored vectors", - calc_vector.rbegin()->first->name, vec->name, calc_vector.rbegin()->second, storage.size() ); + calc_vector.rbegin()->first->name, vec.c_str(), calc_vector.rbegin()->second, storage.size() ); } if( !list.empty() ) { ret = *list.pick(); @@ -1550,7 +1557,7 @@ std::optional> for( auto iterate = storage.begin(); iterate != storage.end(); ++iterate ) { if( iterate->first == ret ) { return_set = *iterate; - add_msg_debug( debugmode::DF_MELEE, "Return vector %s found in storage", return_set.first->name ); + add_msg_debug( debugmode::DF_MELEE, "Return vector %s found in storage", return_set.first.c_str() ); break; } } diff --git a/src/martialarts.h b/src/martialarts.h index 621531a2d9b10..b2af8d3917c6d 100644 --- a/src/martialarts.h +++ b/src/martialarts.h @@ -68,7 +68,6 @@ matype_id martial_art_learned_from( const itype & ); struct attack_vector { attack_vector_id id; - translation name; // Used with a weapon, otherwise use unarmed damage calc bool weapon = false; @@ -88,7 +87,7 @@ struct attack_vector { // Encumbrance limit in absolute encumbrance int encumbrance_limit = 100; // Percent of bodypart HP required - int bp_hp_limit = 100; + int bp_hp_limit = 10; bool was_loaded = false; diff --git a/src/melee.cpp b/src/melee.cpp index fdfb70a212d5f..ec251eb840729 100644 --- a/src/melee.cpp +++ b/src/melee.cpp @@ -1280,7 +1280,7 @@ static void roll_melee_damage_internal( const Character &u, const damage_type_id if( u.has_active_bionic( bio_cqb ) ) { skill = BIO_CQB_LEVEL; } - + if( unarmed && !u.natural_attack_restricted_on( contact ) ) { // Add contact/parent damage bonuses to unarmed dmg += contact->unarmed_damage.type_damage( dt ); @@ -1406,166 +1406,170 @@ std::tuple Character::pick_tech Creature &t, const item_location &weap, bool crit, bool dodge_counter, bool block_counter, const std::vector &blacklist ) { - std::vector> possible = - evaluate_techniques( t, weap, crit, - dodge_counter, block_counter, blacklist ); - return random_entry( possible, - std::make_tuple( tec_none, attack_vector_null, - sub_body_part_sub_limb_debug ) ); -} -std::vector> - Character::evaluate_techniques( Creature &t, const item_location &weap, - bool crit, bool dodge_counter, bool block_counter, const std::vector &blacklist ) -{ - const std::vector all = martial_arts_data->get_all_techniques( weap, *this ); std::vector> possible; - bool wall_adjacent = get_map().is_wall_adjacent( pos() ); - // this could be more robust but for now it should work fine - bool is_loaded = weap && weap->is_magazine_full(); - - // first add non-aoe tecs for( const matec_id &tec_id : all ) { - const ma_technique &tec = tec_id.obj(); - add_msg_debug( debugmode::DF_MELEE, "Evaluating technique %s", tec.name ); + add_msg_debug( debugmode::DF_MELEE, "Evaluating technique %s", tec_id->name ); - // skip techniques on the blacklist if( find( blacklist.begin(), blacklist.end(), tec_id ) != blacklist.end() ) { add_msg_debug( debugmode::DF_MELEE, "Technique is on the blacklist, discarded" ); continue; } - // ignore "dummy" techniques like WBLOCK_1 - if( tec.dummy ) { - add_msg_debug( debugmode::DF_MELEE, "Dummy technique, attack discarded" ); - continue; + auto tec = evaluate_technique( tec_id, t, weap, crit, + dodge_counter, block_counter ); + if( tec ) { + possible.push_back( tec.value() ); + if( tec_id->weighting > 1 ) { + for( int i = 1; i < tec_id->weighting; i++ ) { + possible.push_back( tec.value() ); + add_msg_debug( debugmode::DF_MELEE, "Adding technique %s to the tech list (%d)", tec_id->name, i ); + } + } } + } - // skip defensive techniques - if( tec.defensive ) { - add_msg_debug( debugmode::DF_MELEE, "Defensive technique, attack discarded" ); - continue; - } + return random_entry( possible, + std::make_tuple( tec_none, attack_vector_null, + sub_body_part_sub_limb_debug ) ); +} +std::optional> + Character::evaluate_technique( const matec_id &tec_id, Creature &t, const item_location &weap, + bool crit, bool dodge_counter, bool block_counter ) +{ + std::optional> ret; - // Ignore this technique if we fail the doalog conditions - if( tec.has_condition ) { - dialogue d( get_talker_for( this ), get_talker_for( t ) ); - if( !tec.condition( d ) ) { - add_msg_debug( debugmode::DF_MELEE, "Conditionas failed, attack discarded" ); - continue; - } - } + // this could be more robust but for now it should work fine + bool is_loaded = weap && weap->is_magazine_full(); - // skip wall adjacent techniques if not next to a wall - if( tec.wall_adjacent && !wall_adjacent ) { - add_msg_debug( debugmode::DF_MELEE, "No adjacent walls found, attack discarded" ); - continue; - } + // first add non-aoe tecs - // skip non reach ok techniques if reach attacking - if( !( tec.reach_ok || tec.reach_tec ) && reach_attacking ) { - add_msg_debug( debugmode::DF_MELEE, "Not usable with reach attack, attack discarded" ); - continue; - } + // ignore "dummy" techniques like WBLOCK_1 + if( tec_id->dummy ) { + add_msg_debug( debugmode::DF_MELEE, "Dummy technique, attack discarded" ); + return std::nullopt; + } - // skip reach techniques if not reach attacking - if( tec.reach_tec && !reach_attacking ) { - add_msg_debug( debugmode::DF_MELEE, "Only usable with reach attack, attack discarded" ); - continue; - } + // skip defensive techniques + if( tec_id->defensive ) { + add_msg_debug( debugmode::DF_MELEE, "Defensive technique, attack discarded" ); + return std::nullopt; + } - // skip dodge counter techniques if it's not a dodge count, and vice versa - if( dodge_counter != tec.dodge_counter ) { - add_msg_debug( debugmode::DF_MELEE, "Not a dodge counter, attack discarded" ); - continue; - } - // likewise for block counters - if( block_counter != tec.block_counter ) { - add_msg_debug( debugmode::DF_MELEE, "Not a block counter, attack discarded" ); - continue; + // Ignore this technique if we fail the dialog conditions + if( tec_id->has_condition ) { + dialogue d( get_talker_for( this ), get_talker_for( t ) ); + if( !tec_id->condition( d ) ) { + add_msg_debug( debugmode::DF_MELEE, "Conditionals failed, attack discarded" ); + return std::nullopt; } + } - // Don't counter if it would exhaust moves. - if( tec.block_counter || tec.dodge_counter ) { - item &used_weap = used_weapon() ? *used_weapon() : null_item_reference(); - float move_cost = attack_speed( used_weap ); - move_cost *= tec.move_cost_multiplier( *this ); - move_cost += tec.move_cost_penalty( *this ); - float move_mult = exertion_adjusted_move_multiplier( EXTRA_EXERCISE ); - move_cost *= ( 1.0f / move_mult ); - if( get_moves() + get_speed() - move_cost < 0 ) { - add_msg_debug( debugmode::DF_MELEE, - "Counter technique would exhaust remaining moves, attack discarded" ); - continue; - } - } + // skip wall adjacent techniques if not next to a wall + if( tec_id->wall_adjacent && !get_map().is_wall_adjacent( pos() ) ) { + add_msg_debug( debugmode::DF_MELEE, "No adjacent walls found, attack discarded" ); + return std::nullopt; + } - // if critical then select only from critical tecs - // but allow the technique if its crit ok - if( !tec.crit_ok && ( crit != tec.crit_tec ) ) { - add_msg_debug( debugmode::DF_MELEE, "Attack is%s critical, attack discarded", crit ? "" : "n't" ); - continue; - } + // skip non reach ok techniques if reach attacking + if( !( tec_id->reach_ok || tec_id->reach_tec ) && reach_attacking ) { + add_msg_debug( debugmode::DF_MELEE, "Not usable with reach attack, attack discarded" ); + return std::nullopt; + } - // if the technique needs a loaded weapon and it isn't loaded skip it - if( tec.needs_ammo && !is_loaded ) { - add_msg_debug( debugmode::DF_MELEE, "No ammo, attack discarded" ); - continue; - } + // skip reach techniques if not reach attacking + if( tec_id->reach_tec && !reach_attacking ) { + add_msg_debug( debugmode::DF_MELEE, "Only usable with reach attack, attack discarded" ); + return std::nullopt; + } + + // skip dodge counter techniques if it's not a dodge count, and vice versa + if( dodge_counter != tec_id->dodge_counter ) { + add_msg_debug( debugmode::DF_MELEE, "%s a dodge counter, discarded", + dodge_counter ? "Looking for" : + "Attack is" ); + return std::nullopt; + } + // likewise for block counters + if( block_counter != tec_id->block_counter ) { + add_msg_debug( debugmode::DF_MELEE, "%s a block counter, attack discarded", + block_counter ? "Looking for" : "Attack is" ); + return std::nullopt; + } - // don't apply disarming techniques to someone without a weapon - // TODO: these are the stat requirements for tec_disarm - // dice( dex_cur + get_skill_level("unarmed"), 8) > - // dice(p->dex_cur + p->get_skill_level("melee"), 10)) - if( tec.disarms && !t.has_weapon() ) { + // Don't counter if it would exhaust moves. + if( tec_id->block_counter || tec_id->dodge_counter ) { + item &used_weap = used_weapon() ? *used_weapon() : null_item_reference(); + float move_cost = attack_speed( used_weap ); + move_cost *= tec_id->move_cost_multiplier( *this ); + move_cost += tec_id->move_cost_penalty( *this ); + float move_mult = exertion_adjusted_move_multiplier( EXTRA_EXERCISE ); + move_cost *= ( 1.0f / move_mult ); + if( get_moves() + get_speed() - move_cost < 0 ) { add_msg_debug( debugmode::DF_MELEE, - "Disarming technique against unarmed opponent, attack discarded" ); - continue; + "Counter technique would exhaust remaining moves, attack discarded" ); + return std::nullopt; } + } - if( tec.take_weapon && ( has_weapon() || !t.has_weapon() ) ) { - add_msg_debug( debugmode::DF_MELEE, "Weapon-taking technique %s, attack discarded", - has_weapon() ? "while armed" : "against an unarmed opponent" ); - continue; - } + // if critical then select only from critical tecs + // but allow the technique if its crit ok + if( !tec_id->crit_ok && ( crit != tec_id->crit_tec ) ) { + add_msg_debug( debugmode::DF_MELEE, "Attack is%s critical, attack discarded", crit ? "" : "n't" ); + return std::nullopt; + } - // if aoe, check if there are valid targets - if( !tec.aoe.empty() && !valid_aoe_technique( t, tec ) ) { - add_msg_debug( debugmode::DF_MELEE, "AoE technique witout valid AoE targets, attack discarded" ); - continue; - } + // if the technique needs a loaded weapon and it isn't loaded skip it + if( tec_id->needs_ammo && !is_loaded ) { + add_msg_debug( debugmode::DF_MELEE, "No ammo, attack discarded" ); + return std::nullopt; + } - // If we have negative weighting then roll to see if it's valid this time - if( tec.weighting < 0 && !one_in( std::abs( tec.weighting ) ) ) { - add_msg_debug( debugmode::DF_MELEE, - "Negative technique weighting failed weight roll, attack discarded" ); - continue; - } + // don't apply disarming techniques to someone without a weapon + // TODO: these are the stat requirements for tec_disarm + // dice( dex_cur + get_skill_level("unarmed"), 8) > + // dice(p->dex_cur + p->get_skill_level("melee"), 10)) + if( tec_id->disarms && !t.has_weapon() ) { + add_msg_debug( debugmode::DF_MELEE, + "Disarming technique against unarmed opponent, attack discarded" ); + return std::nullopt; + } - std::optional> vector; + if( tec_id->take_weapon && ( has_weapon() || !t.has_weapon() ) ) { + add_msg_debug( debugmode::DF_MELEE, "Weapon-taking technique %s, attack discarded", + has_weapon() ? "while armed" : "against an unarmed opponent" ); + return std::nullopt; + } - if( tec.is_valid_character( *this ) ) { - // We made it this far, choose an actual vector if possible - vector = martial_arts_data->choose_attack_vector( *this, tec.id ); - if( vector ) { - possible.emplace_back( std::make_tuple( tec.id, vector->first, vector->second ) ); - //add weighted options into the list extra times, to increase their chance of being selected - if( tec.weighting > 1 ) { - for( int i = 1; i < tec.weighting; i++ ) { - possible.emplace_back( std::make_tuple( tec.id, vector->first, vector->second ) ); - } - } - } else { - add_msg_debug( debugmode::DF_MELEE, "No valid attack vector found, attack discarded" ); - continue; - } + // if aoe, check if there are valid targets + if( !tec_id->aoe.empty() && !valid_aoe_technique( t, tec_id.obj() ) ) { + add_msg_debug( debugmode::DF_MELEE, "AoE technique witout valid AoE targets, attack discarded" ); + return std::nullopt; + } + // If we have negative weighting then roll to see if it's valid this time + if( tec_id->weighting < 0 && !one_in( std::abs( tec_id->weighting ) ) ) { + add_msg_debug( debugmode::DF_MELEE, + "Negative technique weighting failed weight roll, attack discarded" ); + return std::nullopt; + } + + std::optional> vector; + + if( tec_id->is_valid_character( *this ) ) { + // We made it this far, choose an actual vector if possible + vector = martial_arts_data->choose_attack_vector( *this, tec_id ); + if( vector ) { + return std::make_tuple( tec_id, vector->first, vector->second ); } + } else { + add_msg_debug( debugmode::DF_MELEE, "No valid attack vector found, attack discarded" ); + return std::nullopt; } - return possible; + return std::nullopt; } bool Character::valid_aoe_technique( Creature &t, const ma_technique &technique ) diff --git a/tests/martial_art_test.cpp b/tests/martial_art_test.cpp index af1a617914a65..0736d80ac728b 100644 --- a/tests/martial_art_test.cpp +++ b/tests/martial_art_test.cpp @@ -1,3 +1,5 @@ +#include + #include "catch/catch.hpp" #include "player_helpers.h" @@ -94,22 +96,24 @@ TEST_CASE( "Martial_art_technique_conditionals", "[martial_arts]" ) monster &target_1 = spawn_test_monster( "mon_zombie_fat", target_1_pos ); monster &target_2 = spawn_test_monster( "mon_zombie_hulk", target_2_pos ); monster &target_3 = spawn_test_monster( "mon_blob", target_3_pos ); - std::vector tech_1 = dude.evaluate_techniques( target_1, dude.used_weapon() ); // test sweeping a zombie (succeed) REQUIRE( target_1.get_size() == 3 ); - CHECK( tech_1.size() == 1 ); - CHECK( std::find( tech_1.begin(), tech_1.end(), tec ) != tech_1.end() ); + CHECK( dude.evaluate_technique( tec, target_1, dude.used_weapon(), false, false, + false ).has_value() ); // Being downed disables the attack target_1.add_effect( effect_downed, 1_days ); - CHECK( dude.evaluate_techniques( target_1, dude.used_weapon() ).empty() ); + CHECK( !dude.evaluate_technique( tec, target_1, dude.used_weapon(), false, false, + false ).has_value() ); // test sweeping a big zomb (fail) REQUIRE( target_2.get_size() == 5 ); - CHECK( dude.evaluate_techniques( target_2, dude.used_weapon() ).empty() ); + CHECK( !dude.evaluate_technique( tec, target_2, dude.used_weapon(), false, false, + false ).has_value() ); // test sweeping a slime (fail) REQUIRE( target_3.get_size() == 3 ); REQUIRE( target_3.type->bodytype == "blob" ); - CHECK( dude.evaluate_techniques( target_3, dude.used_weapon() ).empty() ); + CHECK( !dude.evaluate_technique( tec, target_3, dude.used_weapon(), false, false, + false ).has_value() ); } SECTION( "Test stun" ) { @@ -119,22 +123,26 @@ TEST_CASE( "Martial_art_technique_conditionals", "[martial_arts]" ) monster &target_3 = spawn_test_monster( "mon_blob", target_3_pos ); item weap( itype_sword_crude ); dude.wield( weap ); + // test stunning a feral (succeed) - std::vector tech_1 = dude.evaluate_techniques( target_1, dude.used_weapon() ); REQUIRE( target_1.get_size() == 3 ); REQUIRE( !target_1.type->in_species( species_ZOMBIE ) ); - CHECK( std::find( tech_1.begin(), tech_1.end(), tec ) != tech_1.end() ); - // Being downed disables the attack + CHECK( dude.evaluate_technique( tec, target_1, dude.used_weapon(), false, false, + false ).has_value() ); + // Being stunned disables the attack target_1.add_effect( effect_stunned, 1_days ); - CHECK( dude.evaluate_techniques( target_1, dude.used_weapon() ).empty() ); + CHECK( !dude.evaluate_technique( tec, target_1, dude.used_weapon(), false, false, + false ).has_value() ); // test stunning a zombie (fail) REQUIRE( target_2.get_size() == 3 ); REQUIRE( target_2.type->in_species( species_ZOMBIE ) ); - CHECK( dude.evaluate_techniques( target_2, dude.used_weapon() ).empty() ); + CHECK( !dude.evaluate_technique( tec, target_2, dude.used_weapon(), false, false, + false ).has_value() ); // test stunning a slime (fail) REQUIRE( target_3.get_size() == 3 ); REQUIRE( target_3.type->in_species( species_SLIME ) ); - CHECK( dude.evaluate_techniques( target_3, dude.used_weapon() ).empty() ); + CHECK( !dude.evaluate_technique( tec, target_3, dude.used_weapon(), false, false, + false ).has_value() ); } SECTION( "Test knockback" ) { const matec_id &tec = *test_style_ma1->techniques.find( test_tech_condition_knockback ); @@ -143,21 +151,25 @@ TEST_CASE( "Martial_art_technique_conditionals", "[martial_arts]" ) monster &target_3 = spawn_test_monster( "mon_test_tech_grabber", target_3_pos ); item weap( itype_club_wooden ); dude.wield( weap ); + // test throwing a feral (succeed) - std::vector tech_1 = dude.evaluate_techniques( target_1, dude.used_weapon() ); REQUIRE( target_1.get_size() == 3 ); - CHECK( std::find( tech_1.begin(), tech_1.end(), tec ) != tech_1.end() ); + CHECK( dude.evaluate_technique( tec, target_1, dude.used_weapon(), false, false, + false ).has_value() ); // Being downed disables the attack target_1.add_effect( effect_downed, 1_days ); - CHECK( dude.evaluate_techniques( target_1, dude.used_weapon() ).empty() ); + CHECK( !dude.evaluate_technique( tec, target_1, dude.used_weapon(), false, false, + false ).has_value() ); // test throwing a large target (fail) REQUIRE( target_2.get_size() == 5 ); - CHECK( dude.evaluate_techniques( target_2, dude.used_weapon() ).empty() ); + CHECK( !dude.evaluate_technique( tec, target_2, dude.used_weapon(), false, false, + false ).has_value() ); // test throwing a monster grabbing you (succeed) dude.add_effect( effect_grabbed, 1_days ); target_3.add_effect( effect_grabbing, 1_days ); REQUIRE( target_3.get_size() == 3 ); REQUIRE( target_3.get_grab_strength() == 0 ); - CHECK( dude.evaluate_techniques( target_3, dude.used_weapon() ).size() == 1 ); + CHECK( dude.evaluate_technique( tec, target_3, dude.used_weapon(), false, false, + false ).has_value() ); } } From c235f54548986fe7d688f3d05cc5b7ea33928ee5 Mon Sep 17 00:00:00 2001 From: Venera3 Date: Fri, 3 May 2024 21:23:17 +0200 Subject: [PATCH 07/16] Getting there --- data/json/body_parts.json | 3 +-- doc/MARTIALART_JSON.md | 5 +---- src/item.cpp | 11 +++++------ src/martialarts.cpp | 35 +++++++++++++++++------------------ src/melee.cpp | 22 +++++++++------------- 5 files changed, 33 insertions(+), 43 deletions(-) diff --git a/data/json/body_parts.json b/data/json/body_parts.json index 14a5fdd28ddc2..a008ccfb9b8e1 100644 --- a/data/json/body_parts.json +++ b/data/json/body_parts.json @@ -551,7 +551,6 @@ "side": "left", "legacy_id": "HAND_L", "techniques": [ "tech_base_punch" ], - "unarmed_damage": [ { "damage_type": "bash", "amount": 10 } ], "stylish_bonus": 0.5, "hot_morale_mod": 0.5, "cold_morale_mod": 0.5, @@ -583,7 +582,7 @@ "limb_scores": [ [ "grip", 0.5 ], [ "manip", 0.5, 1.0 ], [ "swim", 0.15 ] ], "side": "right", "legacy_id": "HAND_R", - "unarmed_damage": [ { "damage_type": "bash", "amount": 15 } ], + "techniques": [ "tech_base_punch" ], "stylish_bonus": 0.5, "hot_morale_mod": 0.5, "cold_morale_mod": 0.5, diff --git a/doc/MARTIALART_JSON.md b/doc/MARTIALART_JSON.md index 3ea3f8548ac5e..2d6aebcd15a4d 100644 --- a/doc/MARTIALART_JSON.md +++ b/doc/MARTIALART_JSON.md @@ -82,7 +82,7 @@ "forbidden_buffs_all": [ "eskrima_hit_buff" ], // This technique is forbidden if all of the named buffs are active "req_flags": [ "" ], // List of item flags the used weapon needs to be eligible for the technique "required_char_flags": [ "" ], // List of "character" (bionic, trait, effect or bodypart) flags the character needs to be able to use this technique - "required_char_flags_all": [ ""], // This technique requires all of the listed character flags to trigger + "required_char_flags_all": [ "" ], // This technique requires all of the listed character flags to trigger "forbidden_char_flags": [ "" ], // List of character flags disabling this technique "needs_ammo": true, // Technique works only if weapon is loaded; Consume 1 charge per attack "crit_tec": true, // This technique only works on a critical hit @@ -139,9 +139,6 @@ Attack vectors define which (sub)bodypart is used for the attack in question, al } ] ``` -List of attack vectors is currently hardcoded, and contain: - -- `WEAPON` - Any technique the requires a held item to perform (see any weapon style). Can be used if the user is holding a valid style weapon for their martial art and at least one hand/arm is not broken. ### Tech effects ```C++ diff --git a/src/item.cpp b/src/item.cpp index 00abc322490ec..90a86eae9566c 100644 --- a/src/item.cpp +++ b/src/item.cpp @@ -124,7 +124,7 @@ static const ammotype ammo_bolt( "bolt" ); static const ammotype ammo_money( "money" ); static const ammotype ammo_plutonium( "plutonium" ); -static const attack_vector_id attack_vector_null( "vector_null" ); +static const attack_vector_id attack_vector_vector_null( "vector_null" ); static const bionic_id bio_digestion( "bio_digestion" ); @@ -189,7 +189,6 @@ static const json_character_flag json_flag_PSYCHOPATH( "PSYCHOPATH" ); static const json_character_flag json_flag_SAPIOVORE( "SAPIOVORE" ); static const matec_id RAPID( "RAPID" ); -static const matec_id tec_none( "tec_none" ); static const material_id material_wool( "wool" ); @@ -2282,7 +2281,7 @@ double item::effective_dps( const Character &guy, Creature &mon ) const Creature *temp_mon = &mon; double subtotal_damage = 0; damage_instance base_damage; - guy.roll_all_damage( crit, base_damage, true, *this, attack_vector_null, + guy.roll_all_damage( crit, base_damage, true, *this, attack_vector_vector_null, sub_body_part_sub_limb_debug, &mon, bp ); damage_instance dealt_damage = base_damage; // TODO: Modify DPS calculation to consider weakpoints. @@ -2308,7 +2307,7 @@ double item::effective_dps( const Character &guy, Creature &mon ) const if( has_technique( RAPID ) ) { Creature *temp_rs_mon = &mon; damage_instance rs_base_damage; - guy.roll_all_damage( crit, rs_base_damage, true, *this, attack_vector_null, + guy.roll_all_damage( crit, rs_base_damage, true, *this, attack_vector_vector_null, sub_body_part_sub_limb_debug, &mon, bp ); damage_instance dealt_rs_damage = rs_base_damage; for( damage_unit &dmg_unit : dealt_rs_damage.damage_units ) { @@ -5589,10 +5588,10 @@ void item::melee_combat_info( std::vector &info, const iteminfo_query ( !dmg_types.empty() || type->m_to_hit > 0 ) ) || debug_mode ) { bodypart_id bp = bodypart_id( "torso" ); damage_instance non_crit; - player_character.roll_all_damage( false, non_crit, true, *this, attack_vector_null, + player_character.roll_all_damage( false, non_crit, true, *this, attack_vector_vector_null, sub_body_part_sub_limb_debug, nullptr, bp ); damage_instance crit; - player_character.roll_all_damage( true, crit, true, *this, attack_vector_null, + player_character.roll_all_damage( true, crit, true, *this, attack_vector_vector_null, sub_body_part_sub_limb_debug, nullptr, bp ); int attack_cost = player_character.attack_speed( *this ); insert_separation_line( info ); diff --git a/src/martialarts.cpp b/src/martialarts.cpp index 4dbecb3304150..d7c52b79f1422 100644 --- a/src/martialarts.cpp +++ b/src/martialarts.cpp @@ -629,7 +629,7 @@ void finalize_martial_arts() std::vector intersect; std::set_intersection( similar->sub_parts.begin(), similar->sub_parts.end(), vector.contact_area.begin(), vector.contact_area.end(), std::back_inserter( intersect ) ); - if( intersect.size() > 0 ) { + if( !intersect.empty() ) { // We have a common element in our sublimb list and the contact area list similar_bp.emplace_back( similar ); } @@ -711,10 +711,7 @@ bool ma_requirements::is_valid_character( const Character &u ) const bool valid_melee = !strictly_unarmed && ( forced_unarmed || melee_ok ); if( !valid_unarmed && !valid_melee ) { - return false; - } - - if( wall_adjacent && !get_map().is_wall_adjacent( u.pos() ) ) { + add_msg_debug( debugmode::DF_MELEE, "Weapon/technique conflict, attack discarded" ); return false; } @@ -1466,7 +1463,7 @@ std::optional> // Store a dummy sublimb to show we're attacking with a weapon weight = weapon->base_damage_melee().total_damage(); list.add_or_replace( vec, weight ); - storage.emplace_back( std::make_pair( vec, sub_body_part_sub_limb_debug ) ); + storage.emplace_back( vec, sub_body_part_sub_limb_debug ); add_msg_debug( debugmode::DF_MELEE, "Weapon %s eligable for attack vector %s with weight %.1f", weapon->display_name(), vec.c_str(), weight ); @@ -1475,7 +1472,8 @@ std::optional> // Smilar bodyparts get appended to the vector limb list in the finalization step // So we just need to check if we have a limb, a contact area sublimb and tally up the damages std::vector> calc_vector; - for( const bodypart_id &bp : vec->limbs ) { + for( const bodypart_id &bp_id : vec->limbs ) { + const bodypart_str_id &bp = bp_id.id(); if( std::find( anat.begin(), anat.end(), bp ) != anat.end() ) { add_msg_debug( debugmode::DF_MELEE, "Evaluating limb %s for vector %s", bp->name, vec.c_str() ); // Filter on limb flags early @@ -1502,9 +1500,9 @@ std::optional> } // TODO: move this from being a special case to the default - int bp_hp_cur = bp->main_part == bp.id() ? user.get_part_hp_cur( bp ) : user.get_part_hp_cur( + int bp_hp_cur = bp->main_part == bp ? user.get_part_hp_cur( bp ) : user.get_part_hp_cur( bp->main_part ); - int bp_hp_max = bp->main_part == bp.id() ? user.get_part_hp_max( bp ) : user.get_part_hp_max( + int bp_hp_max = bp->main_part == bp ? user.get_part_hp_max( bp ) : user.get_part_hp_max( bp->main_part ); if( ( 100 * bp_hp_cur / bp_hp_max ) > vec->bp_hp_limit && user.get_part_encumbrance_data( bp ).encumbrance < vec->encumbrance_limit ) { @@ -1518,15 +1516,16 @@ std::optional> break; } } - // Go through the common function + float unarmed_damage = calculate_vector_damage( user, vec, current_contact ).total_damage(); - if( unarmed_damage == 0.0f ) { - unarmed_damage++; + if( unarmed_damage <= 0.0f ) { + // Give extra/damage-less vectors a base chance to be chosen + unarmed_damage = 1.0f; } calc_vector.emplace_back( std::make_pair( current_contact, unarmed_damage ) ); add_msg_debug( debugmode::DF_MELEE, "Bodypart %s eligable for attack vector %s weight %.1f (contact area %s)", - bp.id().c_str(), + bp.c_str(), vec.c_str(), unarmed_damage, current_contact->name ); } } @@ -1544,9 +1543,9 @@ std::optional> } ); int i = 0; list.add( vec, calc_vector.rbegin()->second ); - storage.emplace_back( std::make_pair( vec, calc_vector.rbegin()->first ) ); + storage.emplace_back( vec, calc_vector.rbegin()->first ); add_msg_debug( debugmode::DF_MELEE, - "Chose contact sublimb %s for vector %s with weight %.1f; %d stored vectors", + "Chose contact sublimb %s for vector %s with weight %.1f; %d stored vectors", calc_vector.rbegin()->first->name, vec.c_str(), calc_vector.rbegin()->second, storage.size() ); } if( !list.empty() ) { @@ -1554,9 +1553,9 @@ std::optional> add_msg_debug( debugmode::DF_MELEE, "Picked vector %s for technique %s", ret.c_str(), tech.c_str() ); // Now find the contact data matching the winning vector - for( auto iterate = storage.begin(); iterate != storage.end(); ++iterate ) { - if( iterate->first == ret ) { - return_set = *iterate; + for( auto &iterate : storage ) { + if( iterate.first == ret ) { + return_set = iterate; add_msg_debug( debugmode::DF_MELEE, "Return vector %s found in storage", return_set.first.c_str() ); break; } diff --git a/src/melee.cpp b/src/melee.cpp index ec251eb840729..8c565412af8b0 100644 --- a/src/melee.cpp +++ b/src/melee.cpp @@ -74,11 +74,10 @@ static const anatomy_id anatomy_human_anatomy( "human_anatomy" ); -static const attack_vector_id attack_vector_null( "vector_null" ); +static const attack_vector_id attack_vector_vector_null( "vector_null" ); static const bionic_id bio_cqb( "bio_cqb" ); static const bionic_id bio_heat_absorb( "bio_heat_absorb" ); -static const bionic_id bio_razors( "bio_razors" ); static const bionic_id bio_shock( "bio_shock" ); static const character_modifier_id @@ -715,7 +714,7 @@ bool Character::melee_attack_abstract( Creature &t, bool allow_special, // Practice melee and relevant weapon skill (if any) except when using CQB bionic if( !has_active_bionic( bio_cqb ) && !t.is_hallucination() ) { - melee_train( *this, 2, std::min( 5, skill_training_cap ), cur_weap, attack_vector_null ); + melee_train( *this, 2, std::min( 5, skill_training_cap ), cur_weap, attack_vector_vector_null ); } // Cap stumble penalty, heavy weapons are quite weak already @@ -749,11 +748,12 @@ bool Character::melee_attack_abstract( Creature &t, bool allow_special, // Pick our attack // Unarmed needs a defined technique if( has_force_technique ) { - attack = std::make_tuple( force_technique, attack_vector_null, sub_body_part_sub_limb_debug ); + attack = std::make_tuple( force_technique, attack_vector_vector_null, + sub_body_part_sub_limb_debug ); } else if( allow_special ) { attack = pick_technique( t, cur_weapon, critical_hit, false, false ); } else { - attack = std::make_tuple( tec_none, attack_vector_null, sub_body_part_sub_limb_debug ); + attack = std::make_tuple( tec_none, attack_vector_vector_null, sub_body_part_sub_limb_debug ); } // Unpack our data matec_id attack_id; @@ -1432,20 +1432,16 @@ std::tuple Character::pick_tech } return random_entry( possible, - std::make_tuple( tec_none, attack_vector_null, + std::make_tuple( tec_none, attack_vector_vector_null, sub_body_part_sub_limb_debug ) ); } std::optional> Character::evaluate_technique( const matec_id &tec_id, Creature &t, const item_location &weap, bool crit, bool dodge_counter, bool block_counter ) { - std::optional> ret; - // this could be more robust but for now it should work fine bool is_loaded = weap && weap->is_magazine_full(); - // first add non-aoe tecs - // ignore "dummy" techniques like WBLOCK_1 if( tec_id->dummy ) { add_msg_debug( debugmode::DF_MELEE, "Dummy technique, attack discarded" ); @@ -1563,10 +1559,10 @@ std::optional> vector = martial_arts_data->choose_attack_vector( *this, tec_id ); if( vector ) { return std::make_tuple( tec_id, vector->first, vector->second ); + } else { + add_msg_debug( debugmode::DF_MELEE, "No valid attack vector found, attack discarded" ); + return std::nullopt; } - } else { - add_msg_debug( debugmode::DF_MELEE, "No valid attack vector found, attack discarded" ); - return std::nullopt; } return std::nullopt; From 21d3781b916039e9d8b7120873b5cf0d1c86da43 Mon Sep 17 00:00:00 2001 From: Venera3 Date: Sat, 4 May 2024 12:53:21 +0200 Subject: [PATCH 08/16] Limb type restrictions --- data/json/attack_vectors.json | 19 +++++++++++++------ data/json/body_parts.json | 4 ++++ data/json/techniques.json | 25 +++++++++++++++++++++++-- doc/MARTIALART_JSON.md | 4 +++- src/martialarts.cpp | 22 ++++++++++++++++++++++ src/martialarts.h | 4 +++- src/melee.cpp | 2 ++ 7 files changed, 70 insertions(+), 10 deletions(-) diff --git a/data/json/attack_vectors.json b/data/json/attack_vectors.json index 59adab90741a9..8928cf53fd311 100644 --- a/data/json/attack_vectors.json +++ b/data/json/attack_vectors.json @@ -49,6 +49,7 @@ "id": "vector_grasp", "limbs": [ "hand_l", "hand_r" ], "contact_area": [ "hand_palm_l", "hand_palm_r" ], + "limb_req": [ [ "arm", 2 ] ], "armor_bonus": false }, { @@ -74,7 +75,8 @@ "id": "vector_arm_grapple", "limbs": [ "arm_l", "arm_r" ], "contact_area": [ "arm_upper_r", "arm_elbow_r", "arm_lower_r", "arm_upper_l", "arm_elbow_l", "arm_lower_l" ], - "armor_bonus": false + "armor_bonus": false, + "limb_req": [ [ "arm", 2 ] ] }, { "type": "attack_vector", @@ -86,31 +88,36 @@ "type": "attack_vector", "id": "vector_foot_toes", "limbs": [ "foot_l", "foot_r" ], - "contact_area": [ "foot_toes_l", "foot_toes_r" ] + "contact_area": [ "foot_toes_l", "foot_toes_r" ], + "limb_req": [ [ "leg", 2 ] ] }, { "type": "attack_vector", "id": "vector_foot_sole", "limbs": [ "foot_l", "foot_r" ], - "contact_area": [ "foot_sole_l", "foot_sole_r" ] + "contact_area": [ "foot_sole_l", "foot_sole_r" ], + "limb_req": [ [ "leg", 2 ] ] }, { "type": "attack_vector", "id": "vector_foot_heel", "limbs": [ "foot_l", "foot_r" ], - "contact_area": [ "foot_heel_l", "foot_heel_r" ] + "contact_area": [ "foot_heel_l", "foot_heel_r" ], + "limb_req": [ [ "leg", 2 ] ] }, { "type": "attack_vector", "id": "vector_knee", "limbs": [ "leg_l", "leg_r" ], - "contact_area": [ "leg_knee_l", "leg_knee_r" ] + "contact_area": [ "leg_knee_l", "leg_knee_r" ], + "limb_req": [ [ "leg", 2 ] ] }, { "type": "attack_vector", "id": "vector_shin", "limbs": [ "leg_l", "leg_r" ], "contact_area": [ "leg_lower_l", "leg_lower_r" ], - "bp_hp_limit": 50 + "bp_hp_limit": 50, + "limb_req": [ [ "leg", 2 ] ] } ] diff --git a/data/json/body_parts.json b/data/json/body_parts.json index a008ccfb9b8e1..e0cc5b1781739 100644 --- a/data/json/body_parts.json +++ b/data/json/body_parts.json @@ -228,6 +228,7 @@ "base_hp": 60, "drench_capacity": 700, "smash_message": "You smash the %s with a firm headbutt.", + "techniques": [ "tech_base_headbutt" ], "smash_efficiency": 0.25, "bionic_slots": 18, "flags": [ "LIMB_UPPER" ], @@ -811,6 +812,7 @@ "base_hp": 60, "drench_capacity": 300, "smash_message": "You kick down the %s, smashing it.", + "techniques": [ "tech_base_kick" ], "bionic_slots": 7, "sub_parts": [ "foot_sole_l", "foot_arch_l", "foot_toes_l", "foot_ankle_l", "foot_heel_l" ], "flags": [ "LIMB_LOWER" ], @@ -844,6 +846,8 @@ "base_hp": 60, "drench_capacity": 300, "smash_message": "You kick down the %s, smashing it.", + "technique_encumbrance_limit": 40, + "techniques": [ "tech_base_kick" ], "bionic_slots": 7, "sub_parts": [ "foot_sole_r", "foot_arch_r", "foot_toes_r", "foot_ankle_r", "foot_heel_r" ], "flags": [ "LIMB_LOWER" ], diff --git a/data/json/techniques.json b/data/json/techniques.json index 3454c82d5757a..4e5fad23b5f03 100644 --- a/data/json/techniques.json +++ b/data/json/techniques.json @@ -6,16 +6,37 @@ "attack_vectors": [ "vector_null" ], "dummy": true }, + { + "type": "technique", + "id": "tech_base_headbutt", + "name": "Headbutt", + "crit_ok": true, + "unarmed_allowed": true, + "messages": [ "You headbutt %s!", " headbutts %s!" ], + "description": "A basic headbutt", + "skill_requirements": [ { "name": "unarmed", "level": 3 } ], + "attack_vectors": [ "vector_headbutt" ] + }, { "type": "technique", "id": "tech_base_punch", - "name": "Basic punch", + "name": "Punch", "crit_ok": true, "unarmed_allowed": true, "messages": [ "You punch %s!", " punches %s!" ], - "description": "Just a basic punch", + "description": "A basic punch", "attack_vectors": [ "vector_punch" ] }, + { + "type": "technique", + "id": "tech_base_kick", + "name": "Kick", + "crit_ok": true, + "unarmed_allowed": true, + "messages": [ "You kick %s!", " kicks %s!" ], + "description": "A basic kick", + "attack_vectors": [ "vector_foot_toes", "vector_knee", "vector_shin" ] + }, { "type": "technique", "id": "WBLOCK_1", diff --git a/doc/MARTIALART_JSON.md b/doc/MARTIALART_JSON.md index 2d6aebcd15a4d..5fede45d49bde 100644 --- a/doc/MARTIALART_JSON.md +++ b/doc/MARTIALART_JSON.md @@ -135,7 +135,9 @@ Attack vectors define which (sub)bodypart is used for the attack in question, al "required_limb_flags": [ "foo", "bar" ], // List of character flags required for the bodypart to be eligable for this vector "forbidden_limb_flags": [ "foo", "bar" ], // List of character flags that disqualify a limb from being usable by this vector "encumbrance_limit": 15, // Int, default 100, encumbrance of the limb above this will disqualify it from this vector - "bp_hp_limit": 75 // Int, default 10, percent of bodypart limb HP necessary for the limbs to qualify for this vector. For minor (non-main) bodyparts the corresponding main part HP is taken into account. + "bp_hp_limit": 75 , // Int, default 10, percent of bodypart limb HP necessary for the limbs to qualify for this vector. For minor (non-main) bodyparts the corresponding main part HP is taken into account. + "limb_req": [ [ "arm", 2] ] // Array of pairs. Limb type requirements for this vector. The character must have this many limbs of the given type above the limb's health limit (See JSON_INFO.md:Bodyparts). Requirements must all be met. + } ] ``` diff --git a/src/martialarts.cpp b/src/martialarts.cpp index d7c52b79f1422..c0c549160b4c8 100644 --- a/src/martialarts.cpp +++ b/src/martialarts.cpp @@ -92,6 +92,7 @@ void attack_vector::load( const JsonObject &jo, const std::string_view ) optional( jo, was_loaded, "limbs", limbs ); optional( jo, was_loaded, "strict_limb_definition", strict_limb_definition, false ); optional( jo, was_loaded, "contact_area", contact_area ); + optional( jo, was_loaded, "limb_req", limb_req ); optional( jo, was_loaded, "armor_bonus", armor_bonus, true ); optional( jo, was_loaded, "encumbrance_limit", encumbrance_limit, 100 ); optional( jo, was_loaded, "bp_hp_limit", bp_hp_limit, 10 ); @@ -1469,6 +1470,27 @@ std::optional> vec.c_str(), weight ); continue; } + // Check if we have the required limbs + bool reqs = true; + for( const std::pair &req : vec->limb_req ) { + int count = 0; + add_msg_debug( debugmode::DF_MELEE, "Checking limb requirements" ); + for( const bodypart_id &bp : user.get_all_body_parts_of_type( req.first ) ) { + if( user.get_part_hp_cur( bp ) > bp->health_limit ) { + count++; + } + } + if( count < req.second ) { + add_msg_debug( debugmode::DF_MELEE, "%d matching limbs found from %d req, vector discarded", count, + req.second ); + reqs = false; + break; + } + } + if( !reqs ) { + continue; + } + // Smilar bodyparts get appended to the vector limb list in the finalization step // So we just need to check if we have a limb, a contact area sublimb and tally up the damages std::vector> calc_vector; diff --git a/src/martialarts.h b/src/martialarts.h index b2af8d3917c6d..c7a73c9995706 100644 --- a/src/martialarts.h +++ b/src/martialarts.h @@ -26,7 +26,7 @@ class effect; class item; struct itype; -extern const matec_id tec_none; +static const matec_id tec_none( "tec_none" ); class weapon_category { @@ -78,6 +78,8 @@ struct attack_vector { bool strict_limb_definition = false; // The actual contact area for unarmed damage calcs std::vector contact_area; + // If we have any bodypart count restrictions + std::vector> limb_req; // Do we care about armor damage bonuses bool armor_bonus = true; diff --git a/src/melee.cpp b/src/melee.cpp index 8c565412af8b0..0952dce28feee 100644 --- a/src/melee.cpp +++ b/src/melee.cpp @@ -770,6 +770,8 @@ bool Character::melee_attack_abstract( Creature &t, bool allow_special, item *worn_weap = worn.current_unarmed_weapon( contact_area ); cur_weapon = worn_weap ? item_location( *this, worn_weap ) : item_location(); cur_weap = cur_weapon ? *cur_weapon : null_item_reference(); + add_msg_debug( debugmode::DF_MELEE, "Vector allows armor damage calculation, chosen weapon %s", + cur_weap.display_name() ); } } From 8e5d3383bb0c1ab8971790c66eede3544d66f0fe Mon Sep 17 00:00:00 2001 From: Venera3 Date: Sat, 4 May 2024 13:18:42 +0200 Subject: [PATCH 09/16] BB --- src/martialarts.cpp | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/src/martialarts.cpp b/src/martialarts.cpp index c0c549160b4c8..40d76d236e03a 100644 --- a/src/martialarts.cpp +++ b/src/martialarts.cpp @@ -1474,14 +1474,14 @@ std::optional> bool reqs = true; for( const std::pair &req : vec->limb_req ) { int count = 0; - add_msg_debug( debugmode::DF_MELEE, "Checking limb requirements" ); for( const bodypart_id &bp : user.get_all_body_parts_of_type( req.first ) ) { if( user.get_part_hp_cur( bp ) > bp->health_limit ) { count++; } } if( count < req.second ) { - add_msg_debug( debugmode::DF_MELEE, "%d matching limbs found from %d req, vector discarded", count, + add_msg_debug( debugmode::DF_MELEE, + "Limb type requirements: %d matching limbs found from %d req, vector discarded", count, req.second ); reqs = false; break; @@ -1494,15 +1494,16 @@ std::optional> // Smilar bodyparts get appended to the vector limb list in the finalization step // So we just need to check if we have a limb, a contact area sublimb and tally up the damages std::vector> calc_vector; - for( const bodypart_id &bp_id : vec->limbs ) { - const bodypart_str_id &bp = bp_id.id(); + for( const bodypart_str_id &bp : vec->limbs ) { + //const bodypart_str_id &bp = bp_id.id(); if( std::find( anat.begin(), anat.end(), bp ) != anat.end() ) { add_msg_debug( debugmode::DF_MELEE, "Evaluating limb %s for vector %s", bp->name, vec.c_str() ); // Filter on limb flags early bool allowed = true; for( const json_character_flag &req : vec->required_limb_flags ) { if( !bp->has_flag( req ) ) { - add_msg_debug( debugmode::DF_MELEE, "Required limb flag %s not found on limb %s", req.c_str(), + add_msg_debug( debugmode::DF_MELEE, "Required limb flag %s not found on limb %s, limb discarded", + req.c_str(), bp->name ); allowed = false; break; @@ -1510,14 +1511,14 @@ std::optional> } for( const json_character_flag &forb : vec->forbidden_limb_flags ) { if( bp->has_flag( forb ) ) { - add_msg_debug( debugmode::DF_MELEE, "Forbidden limb flag %s found on limb %s", forb.c_str(), + add_msg_debug( debugmode::DF_MELEE, "Forbidden limb flag %s found on limb %s, limb discarded", + forb.c_str(), bp->name ); allowed = false; break; } } if( !allowed ) { - add_msg_debug( debugmode::DF_MELEE, "Limb %s disqualified for vector %s", bp->name, vec.c_str() ); continue; } @@ -1532,8 +1533,6 @@ std::optional> for( const sub_bodypart_str_id &sbp : bp->sub_parts ) { if( std::find( vec->contact_area.begin(), vec->contact_area.end(), sbp ) != vec->contact_area.end() ) { - add_msg_debug( debugmode::DF_MELEE, "Contact area sbp %s on bodypart %s found", sbp->name, - bp->name ); current_contact = sbp; break; } @@ -1552,7 +1551,7 @@ std::optional> } } } - if( calc_vector.size() == 0 ) { + if( calc_vector.empty() ) { add_msg_debug( debugmode::DF_MELEE, "Vector %s found no eligable bodyparts, discarding", vec.c_str() ); continue; @@ -1563,7 +1562,6 @@ std::optional> const std::pair &b ) { return a.second < b.second; } ); - int i = 0; list.add( vec, calc_vector.rbegin()->second ); storage.emplace_back( vec, calc_vector.rbegin()->first ); add_msg_debug( debugmode::DF_MELEE, @@ -1578,7 +1576,6 @@ std::optional> for( auto &iterate : storage ) { if( iterate.first == ret ) { return_set = iterate; - add_msg_debug( debugmode::DF_MELEE, "Return vector %s found in storage", return_set.first.c_str() ); break; } } From b1120adff37f903f275ccd1084c93419e81e6cbe Mon Sep 17 00:00:00 2001 From: Venera3 Date: Sat, 4 May 2024 16:48:34 +0200 Subject: [PATCH 10/16] Actually working substitutions --- .../Limb_WIP/mutations/mutation_limbs.json | 1 + .../Limb_WIP/mutations/mutation_techs.json | 13 +++++++ .../Limb_WIP/mutations/mutation_vectors.json | 8 +++++ src/martialarts.cpp | 36 ++++++++----------- src/martialarts.h | 2 +- 5 files changed, 38 insertions(+), 22 deletions(-) create mode 100644 data/mods/Limb_WIP/mutations/mutation_techs.json create mode 100644 data/mods/Limb_WIP/mutations/mutation_vectors.json diff --git a/data/mods/Limb_WIP/mutations/mutation_limbs.json b/data/mods/Limb_WIP/mutations/mutation_limbs.json index 7beceb51e8516..c9ccd484b6bf1 100644 --- a/data/mods/Limb_WIP/mutations/mutation_limbs.json +++ b/data/mods/Limb_WIP/mutations/mutation_limbs.json @@ -358,6 +358,7 @@ [ "crawl", 0.2 ] ], "smash_message": "You buffet the %s with your wing.", + "techniques": [ "tech_wing_slap" ], "side": "left", "base_hp": 45, "sub_parts": [ diff --git a/data/mods/Limb_WIP/mutations/mutation_techs.json b/data/mods/Limb_WIP/mutations/mutation_techs.json new file mode 100644 index 0000000000000..318842aec9aec --- /dev/null +++ b/data/mods/Limb_WIP/mutations/mutation_techs.json @@ -0,0 +1,13 @@ +[ + { + "type": "technique", + "id": "tech_wing_slap", + "name": "Wing Slap", + "crit_ok": true, + "unarmed_allowed": true, + "messages": [ "You slap %s with your wings", " slaps %s with their wings" ], + "description": "A slap of the wings.", + "attack_vectors": [ "vector_wing_slap" ], + "flat_bonuses": [ { "stat": "movecost", "scale": 20 } ] + } +] diff --git a/data/mods/Limb_WIP/mutations/mutation_vectors.json b/data/mods/Limb_WIP/mutations/mutation_vectors.json new file mode 100644 index 0000000000000..316b415a11cc0 --- /dev/null +++ b/data/mods/Limb_WIP/mutations/mutation_vectors.json @@ -0,0 +1,8 @@ +[ + { + "type": "attack_vector", + "id": "vector_wing_slap", + "limbs": [ "arm_wing_bird_l", "arm_wing_bird_r" ], + "contact_area": [ "arm_wing_bird_lower_r", "arm_wing_bird_lower_l" ] + } +] diff --git a/src/martialarts.cpp b/src/martialarts.cpp index 40d76d236e03a..e89c01aaefbd5 100644 --- a/src/martialarts.cpp +++ b/src/martialarts.cpp @@ -605,14 +605,23 @@ void finalize_martial_arts() // bother us because ma_buff_effect_type does not have any members that can be sliced. effect_type::register_ma_buff_effect( new_eff ); } - // Iterate through every attack vector and substitute similar limbs (as long as they have similar sublimbs - for( attack_vector vector : attack_vector_factory.get_all() ) { + attack_vector_factory.finalize(); + for( const attack_vector &vector : attack_vector_factory.get_all() ) { // Check if this vector allows substitutions in the first place if( vector.strict_limb_definition ) { continue; } + // Add similar parts + // The vector needs both a limb and a contact area, so we can substitute safely + std::vector similar_bp; + for( const bodypart_str_id &bp : vector.limbs ) { + for( const bodypart_str_id &similar : bp->similar_bodyparts ) { + similar_bp.emplace_back( similar ); + } + } + const_cast( vector ).limbs.insert( vector.limbs.end(), similar_bp.begin(), + similar_bp.end() ); - // Begin by substituting similar subparts to ease filtering in the next step std::vector similar_sbp; for( const sub_bodypart_str_id &sbp : vector.contact_area ) { for( const sub_bodypart_str_id &similar : sbp->similar_bodyparts ) { @@ -620,23 +629,8 @@ void finalize_martial_arts() } } - vector.contact_area.insert( vector.contact_area.end(), similar_sbp.begin(), similar_sbp.end() ); - - // We now have the actually-hitting sublimbs, substitute the main parts - // But discard any part where we'd be similar without a similar contact subpart - std::vector similar_bp; - for( const bodypart_str_id &bp : vector.limbs ) { - for( const bodypart_str_id &similar : bp->similar_bodyparts ) { - std::vector intersect; - std::set_intersection( similar->sub_parts.begin(), similar->sub_parts.end(), - vector.contact_area.begin(), vector.contact_area.end(), std::back_inserter( intersect ) ); - if( !intersect.empty() ) { - // We have a common element in our sublimb list and the contact area list - similar_bp.emplace_back( similar ); - } - } - } - vector.limbs.insert( vector.limbs.end(), similar_bp.begin(), similar_bp.end() ); + const_cast( vector ).contact_area.insert( vector.contact_area.end(), + similar_sbp.begin(), similar_sbp.end() ); } } @@ -1496,7 +1490,7 @@ std::optional> std::vector> calc_vector; for( const bodypart_str_id &bp : vec->limbs ) { //const bodypart_str_id &bp = bp_id.id(); - if( std::find( anat.begin(), anat.end(), bp ) != anat.end() ) { + if( std::find( anat.begin(), anat.end(), bp.id() ) != anat.end() ) { add_msg_debug( debugmode::DF_MELEE, "Evaluating limb %s for vector %s", bp->name, vec.c_str() ); // Filter on limb flags early bool allowed = true; diff --git a/src/martialarts.h b/src/martialarts.h index c7a73c9995706..549094bedb662 100644 --- a/src/martialarts.h +++ b/src/martialarts.h @@ -26,7 +26,7 @@ class effect; class item; struct itype; -static const matec_id tec_none( "tec_none" ); +const matec_id tec_none( "tec_none" ); class weapon_category { From 5e01a69110e252d857b1d86dbebffe94d4c916d1 Mon Sep 17 00:00:00 2001 From: Venera3 Date: Sat, 4 May 2024 19:28:33 +0200 Subject: [PATCH 11/16] Tests --- data/json/body_parts.json | 1 + data/mods/TEST_DATA/body_parts.json | 19 ++++++++++ data/mods/TEST_DATA/martialarts.json | 41 ++++++++++++++++++++- tests/martial_art_test.cpp | 54 ++++++++++++++++++++++++++++ 4 files changed, 114 insertions(+), 1 deletion(-) diff --git a/data/json/body_parts.json b/data/json/body_parts.json index e0cc5b1781739..b9a2a56a6d967 100644 --- a/data/json/body_parts.json +++ b/data/json/body_parts.json @@ -876,6 +876,7 @@ "accusative": { "ctxt": "bodypart_accusative", "str": "debug tail" }, "hp_bar_ui_text": "DBG TAIL", "base_hp": 20, + "health_limit": 5, "flags": [ "ALWAYS_BLOCK", "NONSTANDARD_BLOCK", "MEND_LIMB", "WALL_CLING" ], "armor": { "bash": 10 }, "unarmed_damage": [ { "damage_type": "acid", "amount": 10 }, { "damage_type": "bullet", "amount": 5, "armor_penetration": 100 } ], diff --git a/data/mods/TEST_DATA/body_parts.json b/data/mods/TEST_DATA/body_parts.json index f16e14b5e573d..558a0c80320b7 100644 --- a/data/mods/TEST_DATA/body_parts.json +++ b/data/mods/TEST_DATA/body_parts.json @@ -1,4 +1,23 @@ [ + { + "type": "body_part", + "id": "mouth", + "copy-from": "mouth", + "name": "mouth", + "similar_bodyparts": [ "test_corvid_beak" ], + "limb_type": "mouth", + "limb_scores": [ [ "breathing", 1.0 ], [ "manip", 0.05, 0.2 ] ] + }, + { + "type": "sub_body_part", + "id": "mouth_lips", + "copy-from": "mouth_lips", + "similar_bodyparts": [ "sub_limb_test_corvid_beak" ], + "opposite": "mouth_lips", + "side": 0, + "name": "lips", + "name_multiple": "lips" + }, { "type": "body_part", "id": "test_arm_l", diff --git a/data/mods/TEST_DATA/martialarts.json b/data/mods/TEST_DATA/martialarts.json index 6c69e517e9b92..a3d19b7d88b95 100644 --- a/data/mods/TEST_DATA/martialarts.json +++ b/data/mods/TEST_DATA/martialarts.json @@ -1,4 +1,35 @@ [ + { + "type": "technique", + "id": "test_vector_tech_1", + "name": "Test Vectors One", + "//": "Fails because of missing limbs of type other", + "unarmed_allowed": true, + "messages": [ "You tail-slap %s", " tail-slaps %s" ], + "attack_vectors": [ "test_tail_req" ] + }, + { + "type": "technique", + "id": "test_vector_tech_2", + "name": "Test Vectors Two", + "//": "Beaks get substituted, succeeds on birdperson", + "unarmed_allowed": true, + "messages": [ "You peck the %s", " pecks the %s" ], + "attack_vectors": [ "test_beak" ] + }, + { + "type": "attack_vector", + "id": "test_tail_req", + "limbs": [ "hand_l" ], + "contact_area": [ "hand_fingers_l" ], + "limb_req": [ [ "tail", 1 ] ] + }, + { + "type": "attack_vector", + "id": "test_beak", + "limbs": [ "mouth" ], + "contact_area": [ "mouth_lips" ] + }, { "type": "technique", "id": "test_technique", @@ -121,7 +152,15 @@ "weapon_categories_allowed": "KNIVES" } ], - "techniques": [ "test_technique", "test_tech_condition_sweep", "test_tech_condition_stun", "test_tech_condition_knockback" ], + "techniques": [ + "test_technique", + "test_tech_condition_sweep", + "test_tech_condition_stun", + "test_tech_condition_knockback", + "test_vector_tech_1", + "test_vector_tech_2", + "test_vector_tech_3" + ], "weapon_category": [ "TEST_CAT1" ] } ] diff --git a/tests/martial_art_test.cpp b/tests/martial_art_test.cpp index 0736d80ac728b..839a04820ef5f 100644 --- a/tests/martial_art_test.cpp +++ b/tests/martial_art_test.cpp @@ -10,11 +10,14 @@ #include "mtype.h" #include "npc.h" +static const bodypart_str_id body_part_debug_tail( "debug_tail" ); static const efftype_id effect_downed( "downed" ); static const efftype_id effect_grabbed( "grabbed" ); static const efftype_id effect_grabbing( "grabbing" ); static const efftype_id effect_stunned( "stunned" ); +static const enchantment_id enchantment_ENCH_TEST_BIRD_PARTS( "ENCH_TEST_BIRD_PARTS" ); + static const itype_id itype_club_wooden( "club_wooden" ); static const itype_id itype_sword_crude( "sword_crude" ); static const itype_id itype_test_weapon1( "test_weapon1" ); @@ -24,11 +27,15 @@ static const matec_id test_tech_condition_knockback( "test_tech_condition_knockb static const matec_id test_tech_condition_stun( "test_tech_condition_stun" ); static const matec_id test_tech_condition_sweep( "test_tech_condition_sweep" ); static const matec_id test_technique( "test_technique" ); +static const matec_id test_vector_tech_1( "test_vector_tech_1" ); +static const matec_id test_vector_tech_2( "test_vector_tech_2" ); static const matype_id test_style_ma1( "test_style_ma1" ); static const species_id species_SLIME( "SLIME" ); static const species_id species_ZOMBIE( "ZOMBIE" ); +static const trait_id trait_DEBUG_TAIL( "DEBUG_TAIL" ); + static constexpr tripoint dude_pos( HALF_MAPSIZE_X, HALF_MAPSIZE_Y, 0 ); TEST_CASE( "martial_arts", "[martial_arts]" ) @@ -80,6 +87,53 @@ TEST_CASE( "Martial_art_required_weapon_categories", "[martial_arts]" ) } } +TEST_CASE( "Attack_vector_test", "[martial_arts][limb]" ) +{ + clear_map(); + standard_npc dude( "TestCharacter", dude_pos, {}, 0, 8, 8, 8, 8 ); + clear_character( dude ); + dude.martial_arts_data->add_martialart( test_style_ma1 ); + dude.martial_arts_data->set_style( test_style_ma1, false ); + monster &target_1 = spawn_test_monster( "mon_zombie_fat", dude_pos + tripoint_east ); + SECTION( "Limb requirements" ) { + const matec_id &tec = *test_style_ma1->techniques.find( test_vector_tech_1 ); + REQUIRE( dude.get_all_body_parts_of_type( body_part_type::type::tail ).empty() ); + // Can't trigger the tech without a tail + CHECK( !dude.evaluate_technique( tec, target_1, dude.used_weapon(), false, false, + false ) ); + // Grow a tail, suddenly we can use it + dude.toggle_trait( trait_DEBUG_TAIL ); + REQUIRE( !dude.get_all_body_parts_of_type( body_part_type::type::tail ).empty() ); + CHECK( dude.evaluate_technique( tec, target_1, dude.used_weapon(), false, false, + false ) ); + // Unless our tail breaks + REQUIRE( body_part_debug_tail->health_limit == 5 ); + dude.set_part_hp_cur( body_part_debug_tail, 1 ); + CHECK( !dude.evaluate_technique( tec, target_1, dude.used_weapon(), false, false, + false ) ); + dude.set_part_hp_cur( body_part_debug_tail, 20 ); + CHECK( dude.evaluate_technique( tec, target_1, dude.used_weapon(), false, false, + false ) ); + // Lose our vector limb + dude.enchantment_cache->force_add( *enchantment_ENCH_TEST_BIRD_PARTS, dude ); + dude.recalculate_bodyparts(); + REQUIRE( !dude.has_part( body_part_hand_l ) ); + CHECK( !dude.evaluate_technique( tec, target_1, dude.used_weapon(), false, false, + false ) ); + } + SECTION( "Limb substitution" ) { + const matec_id &tec2 = *test_style_ma1->techniques.find( test_vector_tech_2 ); + // Succeed before changing + CHECK( dude.evaluate_technique( tec2, target_1, dude.used_weapon(), false, false, + false ) ); + // Beak substitution lets us succeed afterwards + dude.enchantment_cache->force_add( *enchantment_ENCH_TEST_BIRD_PARTS, dude ); + dude.recalculate_bodyparts(); + CHECK( dude.evaluate_technique( tec2, target_1, dude.used_weapon(), false, false, + false ) ); + } +} + TEST_CASE( "Martial_art_technique_conditionals", "[martial_arts]" ) { clear_map(); From 4d57299a445290c0c2aafa076ba5d9ee12902425 Mon Sep 17 00:00:00 2001 From: Venera3 Date: Sun, 5 May 2024 08:00:40 +0200 Subject: [PATCH 12/16] Tests, clang, magiclysm oopsies --- data/mods/Magiclysm/mutations/mutations.json | 1 + data/mods/Magiclysm/traits/temporary_demon_traits.json | 1 + data/mods/TEST_DATA/martialarts.json | 3 +-- src/martialarts.cpp | 2 +- 4 files changed, 4 insertions(+), 3 deletions(-) diff --git a/data/mods/Magiclysm/mutations/mutations.json b/data/mods/Magiclysm/mutations/mutations.json index 130dc1241c8bd..3e7d2e811ad2f 100644 --- a/data/mods/Magiclysm/mutations/mutations.json +++ b/data/mods/Magiclysm/mutations/mutations.json @@ -788,6 +788,7 @@ "ugliness": 3, "mixed_effect": true, "restricts_gear": [ "hand_l", "hand_r" ], + "integrated_armor": [ "integrated_dragon_talons_black" ], "destroys_gear": true, "description": "All of your fingers have developed into huge scaled talons. They prevent wearing gloves, but you can use them to powerful effect in melee combat and you appear to have uncovered some secrets about black dragons.", "types": [ "CLAWS" ], diff --git a/data/mods/Magiclysm/traits/temporary_demon_traits.json b/data/mods/Magiclysm/traits/temporary_demon_traits.json index c4b947fd7c0a1..02fc154a9a612 100644 --- a/data/mods/Magiclysm/traits/temporary_demon_traits.json +++ b/data/mods/Magiclysm/traits/temporary_demon_traits.json @@ -73,6 +73,7 @@ "starting_trait": false, "mixed_effect": true, "restricts_gear": [ "hand_l", "hand_r" ], + "ntegrated_armor": [ "integrated_demon_claws" ], "description": "Your index fingers have grown into huge talons. After a bit of practice, you find that this does not affect your dexterity, but allows for a deadly unarmed attack. They also prevent wearing gloves.", "types": [ "CLAWS" ], "cancels": [ "ARM_TENTACLES", "ARM_TENTACLES_4", "ARM_TENTACLES_8" ] diff --git a/data/mods/TEST_DATA/martialarts.json b/data/mods/TEST_DATA/martialarts.json index a3d19b7d88b95..67bd63cebd708 100644 --- a/data/mods/TEST_DATA/martialarts.json +++ b/data/mods/TEST_DATA/martialarts.json @@ -158,8 +158,7 @@ "test_tech_condition_stun", "test_tech_condition_knockback", "test_vector_tech_1", - "test_vector_tech_2", - "test_vector_tech_3" + "test_vector_tech_2" ], "weapon_category": [ "TEST_CAT1" ] } diff --git a/src/martialarts.cpp b/src/martialarts.cpp index e89c01aaefbd5..538fcf27b817d 100644 --- a/src/martialarts.cpp +++ b/src/martialarts.cpp @@ -1537,7 +1537,7 @@ std::optional> // Give extra/damage-less vectors a base chance to be chosen unarmed_damage = 1.0f; } - calc_vector.emplace_back( std::make_pair( current_contact, unarmed_damage ) ); + calc_vector.emplace_back( current_contact, unarmed_damage ); add_msg_debug( debugmode::DF_MELEE, "Bodypart %s eligable for attack vector %s weight %.1f (contact area %s)", bp.c_str(), From 5828711a9886ea2d8b399c41a7a7a6dd483df49a Mon Sep 17 00:00:00 2001 From: Venera3 Date: Mon, 6 May 2024 18:13:40 +0200 Subject: [PATCH 13/16] Tests --- data/mods/TEST_DATA/martialarts.json | 2 ++ tests/martial_art_test.cpp | 41 +++++++++++++++++++--------- 2 files changed, 30 insertions(+), 13 deletions(-) diff --git a/data/mods/TEST_DATA/martialarts.json b/data/mods/TEST_DATA/martialarts.json index 67bd63cebd708..71967a31743ce 100644 --- a/data/mods/TEST_DATA/martialarts.json +++ b/data/mods/TEST_DATA/martialarts.json @@ -22,6 +22,8 @@ "id": "test_tail_req", "limbs": [ "hand_l" ], "contact_area": [ "hand_fingers_l" ], + "bp_hp_limit": 50, + "encumbrance_limit": 10, "limb_req": [ [ "tail", 1 ] ] }, { diff --git a/tests/martial_art_test.cpp b/tests/martial_art_test.cpp index 839a04820ef5f..2bbfee2bb5af9 100644 --- a/tests/martial_art_test.cpp +++ b/tests/martial_art_test.cpp @@ -95,15 +95,17 @@ TEST_CASE( "Attack_vector_test", "[martial_arts][limb]" ) dude.martial_arts_data->add_martialart( test_style_ma1 ); dude.martial_arts_data->set_style( test_style_ma1, false ); monster &target_1 = spawn_test_monster( "mon_zombie_fat", dude_pos + tripoint_east ); + const matec_id &tec = *test_style_ma1->techniques.find( test_vector_tech_1 ); + const matec_id &tec2 = *test_style_ma1->techniques.find( test_vector_tech_2 ); + REQUIRE( !dude.evaluate_technique( tec, target_1, dude.used_weapon(), false, false, + false ) ); + REQUIRE( dude.evaluate_technique( tec2, target_1, dude.used_weapon(), false, false, + false ) ); + REQUIRE( dude.get_all_body_parts_of_type( body_part_type::type::tail ).empty() ); + SECTION( "Limb requirements" ) { - const matec_id &tec = *test_style_ma1->techniques.find( test_vector_tech_1 ); - REQUIRE( dude.get_all_body_parts_of_type( body_part_type::type::tail ).empty() ); - // Can't trigger the tech without a tail - CHECK( !dude.evaluate_technique( tec, target_1, dude.used_weapon(), false, false, - false ) ); // Grow a tail, suddenly we can use it dude.toggle_trait( trait_DEBUG_TAIL ); - REQUIRE( !dude.get_all_body_parts_of_type( body_part_type::type::tail ).empty() ); CHECK( dude.evaluate_technique( tec, target_1, dude.used_weapon(), false, false, false ) ); // Unless our tail breaks @@ -111,22 +113,35 @@ TEST_CASE( "Attack_vector_test", "[martial_arts][limb]" ) dude.set_part_hp_cur( body_part_debug_tail, 1 ); CHECK( !dude.evaluate_technique( tec, target_1, dude.used_weapon(), false, false, false ) ); - dude.set_part_hp_cur( body_part_debug_tail, 20 ); + } + SECTION( "Missing contact" ) { + // Lose our vector limb + dude.toggle_trait( trait_DEBUG_TAIL ); CHECK( dude.evaluate_technique( tec, target_1, dude.used_weapon(), false, false, false ) ); - // Lose our vector limb dude.enchantment_cache->force_add( *enchantment_ENCH_TEST_BIRD_PARTS, dude ); dude.recalculate_bodyparts(); REQUIRE( !dude.has_part( body_part_hand_l ) ); CHECK( !dude.evaluate_technique( tec, target_1, dude.used_weapon(), false, false, false ) ); } - SECTION( "Limb substitution" ) { - const matec_id &tec2 = *test_style_ma1->techniques.find( test_vector_tech_2 ); - // Succeed before changing - CHECK( dude.evaluate_technique( tec2, target_1, dude.used_weapon(), false, false, + SECTION( "Limb HP" ) { + dude.toggle_trait( trait_DEBUG_TAIL ); + CHECK( dude.evaluate_technique( tec, target_1, dude.used_weapon(), false, false, false ) ); - // Beak substitution lets us succeed afterwards + // Check bp hp limit (hands don't have HP so we check arms) + dude.set_part_hp_cur( body_part_arm_l, 1 ); + CHECK( !dude.evaluate_technique( tec, target_1, dude.used_weapon(), false, false, + false ) ); + } + SECTION( "Encumbrance" ) { + REQUIRE( dude.get_all_body_parts_of_type( body_part_type::type::tail ).empty() ); + item test_eoc_armor_suit( "test_eoc_armor_suit" ); + REQUIRE( dude.wear_item( test_eoc_armor_suit, false ) ); + CHECK( !dude.evaluate_technique( tec, target_1, dude.used_weapon(), false, false, + false ) ); + } + SECTION( "Limb substitution" ) { dude.enchantment_cache->force_add( *enchantment_ENCH_TEST_BIRD_PARTS, dude ); dude.recalculate_bodyparts(); CHECK( dude.evaluate_technique( tec2, target_1, dude.used_weapon(), false, false, From dd27203ba1d732b8431d01f68a1ae76253fd7cb3 Mon Sep 17 00:00:00 2001 From: Venera3 Date: Mon, 6 May 2024 20:25:34 +0200 Subject: [PATCH 14/16] ntegrated is def a word, shutup --- data/mods/Magiclysm/traits/temporary_demon_traits.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/mods/Magiclysm/traits/temporary_demon_traits.json b/data/mods/Magiclysm/traits/temporary_demon_traits.json index 02fc154a9a612..16561b3d3f73f 100644 --- a/data/mods/Magiclysm/traits/temporary_demon_traits.json +++ b/data/mods/Magiclysm/traits/temporary_demon_traits.json @@ -73,7 +73,7 @@ "starting_trait": false, "mixed_effect": true, "restricts_gear": [ "hand_l", "hand_r" ], - "ntegrated_armor": [ "integrated_demon_claws" ], + "integrated_armor": [ "integrated_demon_claws" ], "description": "Your index fingers have grown into huge talons. After a bit of practice, you find that this does not affect your dexterity, but allows for a deadly unarmed attack. They also prevent wearing gloves.", "types": [ "CLAWS" ], "cancels": [ "ARM_TENTACLES", "ARM_TENTACLES_4", "ARM_TENTACLES_8" ] From 5aaaf233635cb95b9cdb74f592242cac392bbca7 Mon Sep 17 00:00:00 2001 From: Venera3 Date: Tue, 7 May 2024 09:06:25 +0200 Subject: [PATCH 15/16] Mandatorify --- src/martialarts.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/martialarts.cpp b/src/martialarts.cpp index 538fcf27b817d..48bcd9d0882ad 100644 --- a/src/martialarts.cpp +++ b/src/martialarts.cpp @@ -314,9 +314,7 @@ void ma_technique::load( const JsonObject &jo, const std::string &src ) has_condition = true; } - if( jo.has_array( "attack_vectors" ) ) { - optional( jo, was_loaded, "attack_vectors", attack_vectors ); - } + mandatory( jo, was_loaded, "attack_vectors", attack_vectors ); reqs.load( jo, src ); bonuses.load( jo ); } From bd5666f0f774e2a58888eae1538c0be1c7111fe1 Mon Sep 17 00:00:00 2001 From: Venera3 Date: Tue, 7 May 2024 16:01:47 +0200 Subject: [PATCH 16/16] Fine, no mandatory You get verified for that, mister --- src/martialarts.cpp | 14 +++++++++++++- src/martialarts.h | 2 ++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/src/martialarts.cpp b/src/martialarts.cpp index 48bcd9d0882ad..3caf4fdb94f02 100644 --- a/src/martialarts.cpp +++ b/src/martialarts.cpp @@ -314,11 +314,23 @@ void ma_technique::load( const JsonObject &jo, const std::string &src ) has_condition = true; } - mandatory( jo, was_loaded, "attack_vectors", attack_vectors ); + optional( jo, was_loaded, "attack_vectors", attack_vectors ); reqs.load( jo, src ); bonuses.load( jo ); } +void ma_technique::verify_ma_techniques() +{ + ma_techniques.check(); +} + +void ma_technique::check() const +{ + if( attack_vectors.empty() && !dummy && !defensive && !grab_break && !miss_recovery ) { + debugmsg( "MA technique %s is missing an attack vector", id.c_str() ); + } +} + // Not implemented on purpose (martialart objects have no integer id) // int_id string_id::id() const; diff --git a/src/martialarts.h b/src/martialarts.h index 549094bedb662..8b98a41383c83 100644 --- a/src/martialarts.h +++ b/src/martialarts.h @@ -164,6 +164,8 @@ class ma_technique ma_technique(); void load( const JsonObject &jo, const std::string &src ); + static void verify_ma_techniques(); + void check() const; matec_id id; std::vector> src;