1 #include "martialarts.h"
2 
3 #include <algorithm>
4 #include <cstdlib>
5 #include <functional>
6 #include <iterator>
7 #include <map>
8 #include <memory>
9 #include <string>
10 #include <utility>
11 
12 #include "bodypart.h"
13 #include "character.h"
14 #include "character_martial_arts.h"
15 #include "color.h"
16 #include "cursesdef.h"
17 #include "damage.h"
18 #include "debug.h"
19 #include "effect.h"
20 #include "enums.h"
21 #include "game_constants.h"
22 #include "generic_factory.h"
23 #include "input.h"
24 #include "item.h"
25 #include "itype.h"
26 #include "json.h"
27 #include "map.h"
28 #include "output.h"
29 #include "pimpl.h"
30 #include "point.h"
31 #include "skill.h"
32 #include "string_formatter.h"
33 #include "translations.h"
34 #include "ui_manager.h"
35 #include "value_ptr.h"
36 
37 static const skill_id skill_unarmed( "unarmed" );
38 
39 static const bionic_id bio_armor_arms( "bio_armor_arms" );
40 static const bionic_id bio_armor_legs( "bio_armor_legs" );
41 static const bionic_id bio_cqb( "bio_cqb" );
42 
43 static const flag_id json_flag_UNARMED_WEAPON( "UNARMED_WEAPON" );
44 
45 namespace
46 {
47 generic_factory<ma_technique> ma_techniques( "martial art technique" );
48 generic_factory<martialart> martialarts( "martial art style" );
49 generic_factory<ma_buff> ma_buffs( "martial art buff" );
50 } // namespace
51 
martial_art_learned_from(const itype & type)52 matype_id martial_art_learned_from( const itype &type )
53 {
54     if( !type.can_use( "MA_MANUAL" ) ) {
55         return {};
56     }
57 
58     if( !type.book || type.book->martial_art.is_null() ) {
59         debugmsg( "Item '%s' which claims to teach a martial art is missing martial_art",
60                   type.get_id().str() );
61         return {};
62     }
63 
64     return type.book->martial_art;
65 }
66 
load_technique(const JsonObject & jo,const std::string & src)67 void load_technique( const JsonObject &jo, const std::string &src )
68 {
69     ma_techniques.load( jo, src );
70 }
71 
72 // To avoid adding empty entries
73 template <typename Container>
add_if_exists(const JsonObject & jo,Container & cont,bool was_loaded,const std::string & json_key,const typename Container::key_type & id)74 void add_if_exists( const JsonObject &jo, Container &cont, bool was_loaded,
75                     const std::string &json_key, const typename Container::key_type &id )
76 {
77     if( jo.has_member( json_key ) ) {
78         mandatory( jo, was_loaded, json_key, cont[id] );
79     }
80 }
81 
82 class ma_skill_reader : public generic_typed_reader<ma_skill_reader>
83 {
84     public:
get_next(JsonIn & jin) const85         std::pair<skill_id, int> get_next( JsonIn &jin ) const {
86             JsonObject jo = jin.get_object();
87             return std::pair<skill_id, int>( skill_id( jo.get_string( "name" ) ), jo.get_int( "level" ) );
88         }
89         template<typename C>
erase_next(JsonIn & jin,C & container) const90         void erase_next( JsonIn &jin, C &container ) const {
91             const skill_id id = skill_id( jin.get_string() );
92             reader_detail::handler<C>().erase_if( container, [&id]( const std::pair<skill_id, int> &e ) {
93                 return e.first == id;
94             } );
95         }
96 };
97 
98 class ma_weapon_damage_reader : public generic_typed_reader<ma_weapon_damage_reader>
99 {
100     public:
101         std::map<std::string, damage_type> dt_map = get_dt_map();
102 
get_next(JsonIn & jin) const103         std::pair<damage_type, int> get_next( JsonIn &jin ) const {
104             JsonObject jo = jin.get_object();
105             std::string type = jo.get_string( "type" );
106             const auto iter = get_dt_map().find( type );
107             if( iter == get_dt_map().end() ) {
108                 jo.throw_error( "Invalid damage type" );
109             }
110             const damage_type dt = iter->second;
111             return std::pair<damage_type, int>( dt, jo.get_int( "min" ) );
112         }
113         template<typename C>
erase_next(JsonIn & jin,C & container) const114         void erase_next( JsonIn &jin, C &container ) const {
115             JsonObject jo = jin.get_object();
116             std::string type = jo.get_string( "type" );
117             const auto iter = get_dt_map().find( type );
118             if( iter == get_dt_map().end() ) {
119                 jo.throw_error( "Invalid damage type" );
120             }
121             damage_type id = iter->second;
122             reader_detail::handler<C>().erase_if( container, [&id]( const std::pair<damage_type, int> &e ) {
123                 return e.first == id;
124             } );
125         }
126 };
127 
load(const JsonObject & jo,const std::string &)128 void ma_requirements::load( const JsonObject &jo, const std::string & )
129 {
130     optional( jo, was_loaded, "unarmed_allowed", unarmed_allowed, false );
131     optional( jo, was_loaded, "melee_allowed", melee_allowed, false );
132     optional( jo, was_loaded, "unarmed_weapons_allowed", unarmed_weapons_allowed, true );
133     optional( jo, was_loaded, "strictly_unarmed", strictly_unarmed, false );
134     optional( jo, was_loaded, "wall_adjacent", wall_adjacent, false );
135 
136     optional( jo, was_loaded, "req_buffs", req_buffs, auto_flags_reader<mabuff_id> {} );
137     optional( jo, was_loaded, "req_flags", req_flags, auto_flags_reader<flag_id> {} );
138 
139     optional( jo, was_loaded, "skill_requirements", min_skill, ma_skill_reader {} );
140     optional( jo, was_loaded, "weapon_damage_requirements", min_damage, ma_weapon_damage_reader {} );
141 }
142 
load(const JsonObject & jo,const std::string & src)143 void ma_technique::load( const JsonObject &jo, const std::string &src )
144 {
145     mandatory( jo, was_loaded, "name", name );
146     optional( jo, was_loaded, "description", description, translation() );
147 
148     if( jo.has_member( "messages" ) ) {
149         JsonArray jsarr = jo.get_array( "messages" );
150         jsarr.read( 0, avatar_message );
151         jsarr.read( 1, npc_message );
152     }
153 
154     optional( jo, was_loaded, "crit_tec", crit_tec, false );
155     optional( jo, was_loaded, "crit_ok", crit_ok, false );
156     optional( jo, was_loaded, "downed_target", downed_target, false );
157     optional( jo, was_loaded, "stunned_target", stunned_target, false );
158     optional( jo, was_loaded, "wall_adjacent", wall_adjacent, false );
159     optional( jo, was_loaded, "human_target", human_target, false );
160 
161     optional( jo, was_loaded, "defensive", defensive, false );
162     optional( jo, was_loaded, "disarms", disarms, false );
163     optional( jo, was_loaded, "take_weapon", take_weapon, false );
164     optional( jo, was_loaded, "side_switch", side_switch, false );
165     optional( jo, was_loaded, "dummy", dummy, false );
166     optional( jo, was_loaded, "dodge_counter", dodge_counter, false );
167     optional( jo, was_loaded, "block_counter", block_counter, false );
168     optional( jo, was_loaded, "miss_recovery", miss_recovery, false );
169     optional( jo, was_loaded, "grab_break", grab_break, false );
170 
171     optional( jo, was_loaded, "weighting", weighting, 1 );
172 
173     optional( jo, was_loaded, "down_dur", down_dur, 0 );
174     optional( jo, was_loaded, "stun_dur", stun_dur, 0 );
175     optional( jo, was_loaded, "knockback_dist", knockback_dist, 0 );
176     optional( jo, was_loaded, "knockback_spread", knockback_spread, 0 );
177     optional( jo, was_loaded, "powerful_knockback", powerful_knockback, false );
178     optional( jo, was_loaded, "knockback_follow", knockback_follow, false );
179 
180     optional( jo, was_loaded, "aoe", aoe, "" );
181     optional( jo, was_loaded, "flags", flags, auto_flags_reader<> {} );
182 
183     reqs.load( jo, src );
184     bonuses.load( jo );
185 }
186 
187 // Not implemented on purpose (martialart objects have no integer id)
188 // int_id<T> string_id<mabuff>::id() const;
189 
190 /** @relates string_id */
191 template<>
obj() const192 const ma_technique &string_id<ma_technique>::obj() const
193 {
194     return ma_techniques.obj( *this );
195 }
196 
197 /** @relates string_id */
198 template<>
is_valid() const199 bool string_id<ma_technique>::is_valid() const
200 {
201     return ma_techniques.is_valid( *this );
202 }
203 
load(const JsonObject & jo,const std::string & src)204 void ma_buff::load( const JsonObject &jo, const std::string &src )
205 {
206     mandatory( jo, was_loaded, "name", name );
207     mandatory( jo, was_loaded, "description", description );
208 
209     optional( jo, was_loaded, "buff_duration", buff_duration, 2_turns );
210     optional( jo, was_loaded, "max_stacks", max_stacks, 1 );
211 
212     optional( jo, was_loaded, "bonus_dodges", dodges_bonus, 0 );
213     optional( jo, was_loaded, "bonus_blocks", blocks_bonus, 0 );
214 
215     optional( jo, was_loaded, "quiet", quiet, false );
216     optional( jo, was_loaded, "throw_immune", throw_immune, false );
217     optional( jo, was_loaded, "stealthy", stealthy, false );
218 
219     reqs.load( jo, src );
220     bonuses.load( jo );
221 }
222 
223 // Not implemented on purpose (martialart objects have no integer id)
224 // int_id<T> string_id<mabuff>::id() const;
225 
226 /** @relates string_id */
227 template<>
obj() const228 const ma_buff &string_id<ma_buff>::obj() const
229 {
230     return ma_buffs.obj( *this );
231 }
232 
233 /** @relates string_id */
234 template<>
is_valid() const235 bool string_id<ma_buff>::is_valid() const
236 {
237     return ma_buffs.is_valid( *this );
238 }
239 
load_martial_art(const JsonObject & jo,const std::string & src)240 void load_martial_art( const JsonObject &jo, const std::string &src )
241 {
242     martialarts.load( jo, src );
243 }
244 
245 class ma_buff_reader : public generic_typed_reader<ma_buff_reader>
246 {
247     public:
get_next(JsonIn & jin) const248         mabuff_id get_next( JsonIn &jin ) const {
249             if( jin.test_string() ) {
250                 return mabuff_id( jin.get_string() );
251             }
252             JsonObject jsobj = jin.get_object();
253             ma_buffs.load( jsobj, "" );
254             return mabuff_id( jsobj.get_string( "id" ) );
255         }
256 };
257 
load(const JsonObject & jo,const std::string &)258 void martialart::load( const JsonObject &jo, const std::string & )
259 {
260     mandatory( jo, was_loaded, "name", name );
261     mandatory( jo, was_loaded, "description", description );
262     mandatory( jo, was_loaded, "initiate", initiate );
263     for( JsonArray skillArray : jo.get_array( "autolearn" ) ) {
264         std::string skill_name = skillArray.get_string( 0 );
265         int skill_level = 0;
266         std::string skill_level_string = skillArray.get_string( 1 );
267         skill_level = stoi( skill_level_string );
268         autolearn_skills.emplace_back( skill_name, skill_level );
269     }
270     optional( jo, was_loaded, "primary_skill", primary_skill, skill_id( "unarmed" ) );
271     optional( jo, was_loaded, "learn_difficulty", learn_difficulty );
272 
273     optional( jo, was_loaded, "static_buffs", static_buffs, ma_buff_reader{} );
274     optional( jo, was_loaded, "onmove_buffs", onmove_buffs, ma_buff_reader{} );
275     optional( jo, was_loaded, "onpause_buffs", onpause_buffs, ma_buff_reader{} );
276     optional( jo, was_loaded, "onhit_buffs", onhit_buffs, ma_buff_reader{} );
277     optional( jo, was_loaded, "onattack_buffs", onattack_buffs, ma_buff_reader{} );
278     optional( jo, was_loaded, "ondodge_buffs", ondodge_buffs, ma_buff_reader{} );
279     optional( jo, was_loaded, "onblock_buffs", onblock_buffs, ma_buff_reader{} );
280     optional( jo, was_loaded, "ongethit_buffs", ongethit_buffs, ma_buff_reader{} );
281     optional( jo, was_loaded, "onmiss_buffs", onmiss_buffs, ma_buff_reader{} );
282     optional( jo, was_loaded, "oncrit_buffs", oncrit_buffs, ma_buff_reader{} );
283     optional( jo, was_loaded, "onkill_buffs", onkill_buffs, ma_buff_reader{} );
284 
285     optional( jo, was_loaded, "techniques", techniques, auto_flags_reader<matec_id> {} );
286     optional( jo, was_loaded, "weapons", weapons, auto_flags_reader<itype_id> {} );
287 
288     optional( jo, was_loaded, "strictly_melee", strictly_melee, false );
289     optional( jo, was_loaded, "strictly_unarmed", strictly_unarmed, false );
290     optional( jo, was_loaded, "allow_melee", allow_melee, false );
291     optional( jo, was_loaded, "force_unarmed", force_unarmed, false );
292 
293     optional( jo, was_loaded, "leg_block", leg_block, 99 );
294     optional( jo, was_loaded, "arm_block", arm_block, 99 );
295 
296     optional( jo, was_loaded, "arm_block_with_bio_armor_arms", arm_block_with_bio_armor_arms, false );
297     optional( jo, was_loaded, "leg_block_with_bio_armor_legs", leg_block_with_bio_armor_legs, false );
298 }
299 
300 // Not implemented on purpose (martialart objects have no integer id)
301 // int_id<T> string_id<martialart>::id() const;
302 
303 /** @relates string_id */
304 template<>
obj() const305 const martialart &string_id<martialart>::obj() const
306 {
307     return martialarts.obj( *this );
308 }
309 
310 /** @relates string_id */
311 template<>
is_valid() const312 bool string_id<martialart>::is_valid() const
313 {
314     return martialarts.is_valid( *this );
315 }
316 
all_martialart_types()317 std::vector<matype_id> all_martialart_types()
318 {
319     std::vector<matype_id> result;
320     for( const auto &ma : martialarts.get_all() ) {
321         result.push_back( ma.id );
322     }
323     return result;
324 }
325 
autolearn_martialart_types()326 std::vector<matype_id> autolearn_martialart_types()
327 {
328     std::vector<matype_id> result;
329     for( const auto &ma : martialarts.get_all() ) {
330         if( ma.autolearn_skills.empty() ) {
331             continue;
332         }
333         result.push_back( ma.id );
334     }
335     return result;
336 }
337 
check(const ma_requirements & req,const std::string & display_text)338 static void check( const ma_requirements &req, const std::string &display_text )
339 {
340     for( const mabuff_id &r : req.req_buffs ) {
341         if( !r.is_valid() ) {
342             debugmsg( "ma buff %s of %s does not exist", r.c_str(), display_text );
343         }
344     }
345 }
346 
check_martialarts()347 void check_martialarts()
348 {
349     for( const auto &ma : martialarts.get_all() ) {
350         for( auto technique = ma.techniques.cbegin();
351              technique != ma.techniques.cend(); ++technique ) {
352             if( !technique->is_valid() ) {
353                 debugmsg( "Technique with id %s in style %s doesn't exist.",
354                           technique->c_str(), ma.name );
355             }
356         }
357         for( auto weapon = ma.weapons.cbegin();
358              weapon != ma.weapons.cend(); ++weapon ) {
359             if( !item::type_is_defined( *weapon ) ) {
360                 debugmsg( "Weapon %s in style %s doesn't exist.",
361                           weapon->c_str(), ma.name );
362             }
363         }
364     }
365     for( const auto &t : ma_techniques.get_all() ) {
366         ::check( t.reqs, string_format( "technique %s", t.id.c_str() ) );
367     }
368     for( const auto &b : ma_buffs.get_all() ) {
369         ::check( b.reqs, string_format( "buff %s", b.id.c_str() ) );
370     }
371 }
372 
373 /**
374  * This is a wrapper class to get access to the protected members of effect_type, it creates
375  * the @ref effect_type that is used to store a @ref ma_buff in the creatures effect map.
376  * Note: this class must not contain any new members, it will be converted to a plain
377  * effect_type later and that would slice the new members of.
378  */
379 class ma_buff_effect_type : public effect_type
380 {
381     public:
ma_buff_effect_type(const ma_buff & buff)382         explicit ma_buff_effect_type( const ma_buff &buff ) {
383             id = buff.get_effect_id();
384             max_intensity = buff.max_stacks;
385             // add_effect add the duration to an existing effect, but it must never be
386             // above buff_duration, this keeps the old ma_buff behavior
387             max_duration = buff.buff_duration;
388             dur_add_perc = 100;
389             // each add_effect call increases the intensity by 1
390             int_add_val = 1;
391             // effect intensity increases by -1 each turn.
392             int_decay_step = -1;
393             int_decay_tick = 1;
394             int_dur_factor = 0_turns;
395             name.push_back( buff.name );
396             desc.push_back( buff.description );
397             rating = e_good;
398         }
399 };
400 
finalize_martial_arts()401 void finalize_martial_arts()
402 {
403     // This adds an effect type for each ma_buff, so we can later refer to it and don't need a
404     // redundant definition of those effects in json.
405     for( const auto &buff : ma_buffs.get_all() ) {
406         const ma_buff_effect_type new_eff( buff );
407         // Note the slicing here: new_eff is converted to a plain effect_type, but this doesn't
408         // bother us because ma_buff_effect_type does not have any members that can be sliced.
409         effect_type::register_ma_buff_effect( new_eff );
410     }
411 }
412 
martialart_difficulty(const matype_id & mstyle)413 std::string martialart_difficulty( const matype_id &mstyle )
414 {
415     std::string diff;
416     if( mstyle->learn_difficulty <= 2 ) {
417         diff = _( "easy" );
418     } else if( mstyle->learn_difficulty <= 4 ) {
419         diff = _( "moderately hard" );
420     } else if( mstyle->learn_difficulty <= 6 ) {
421         diff = _( "hard" );
422     } else if( mstyle->learn_difficulty <= 8 ) {
423         diff = _( "very hard" );
424     } else {
425         diff = _( "extremely hard" );
426     }
427     return diff;
428 }
429 
clear_techniques_and_martial_arts()430 void clear_techniques_and_martial_arts()
431 {
432     martialarts.reset();
433     ma_buffs.reset();
434     ma_techniques.reset();
435 }
436 
is_valid_character(const Character & u) const437 bool ma_requirements::is_valid_character( const Character &u ) const
438 {
439     for( const mabuff_id &buff_id : req_buffs ) {
440         if( !u.has_mabuff( buff_id ) ) {
441             return false;
442         }
443     }
444 
445     //A technique is valid if it applies to unarmed strikes, if it applies generally
446     //to all weapons (such as Ninjutsu sneak attacks or innate weapon techniques like RAPID)
447     //or if the weapon is flagged as being compatible with the style. Some techniques have
448     //further restrictions on required weapon properties (is_valid_weapon).
449     bool cqb = u.has_active_bionic( bio_cqb );
450     // There are 4 different cases of "armedness":
451     // Truly unarmed, unarmed weapon, style-allowed weapon, generic weapon
452     bool melee_style = u.martial_arts_data->selected_strictly_melee();
453     bool is_armed = u.is_armed();
454     bool unarmed_weapon = is_armed && u.used_weapon().has_flag( json_flag_UNARMED_WEAPON );
455     bool forced_unarmed = u.martial_arts_data->selected_force_unarmed();
456     bool weapon_ok = is_valid_weapon( u.weapon );
457     bool style_weapon = u.martial_arts_data->selected_has_weapon( u.weapon.typeId() );
458     bool all_weapons = u.martial_arts_data->selected_allow_melee();
459 
460     bool unarmed_ok = !is_armed || ( unarmed_weapon && unarmed_weapons_allowed );
461     bool melee_ok = melee_allowed && weapon_ok && ( style_weapon || all_weapons );
462 
463     bool valid_unarmed = !melee_style && unarmed_allowed && unarmed_ok;
464     bool valid_melee = !strictly_unarmed && ( forced_unarmed || melee_ok );
465 
466     if( !valid_unarmed && !valid_melee ) {
467         return false;
468     }
469 
470     if( wall_adjacent && !get_map().is_wall_adjacent( u.pos() ) ) {
471         return false;
472     }
473 
474     for( const auto &pr : min_skill ) {
475         if( ( cqb ? 5 : u.get_skill_level( pr.first ) ) < pr.second ) {
476             return false;
477         }
478     }
479 
480     return true;
481 }
482 
is_valid_weapon(const item & i) const483 bool ma_requirements::is_valid_weapon( const item &i ) const
484 {
485     for( const flag_id &flag : req_flags ) {
486         if( !i.has_flag( flag ) ) {
487             return false;
488         }
489     }
490     for( const auto &pr : min_damage ) {
491         if( i.damage_melee( pr.first ) < pr.second ) {
492             return false;
493         }
494     }
495 
496     return true;
497 }
498 
get_description(bool buff) const499 std::string ma_requirements::get_description( bool buff ) const
500 {
501     std::string dump;
502 
503     if( std::any_of( min_skill.begin(), min_skill.end(), []( const std::pair<skill_id, int> &pr ) {
504     return pr.second > 0;
505 } ) ) {
506         dump += string_format( _( "<bold>%s required: </bold>" ),
507                                ngettext( "Skill", "Skills", min_skill.size() ) );
508 
509         dump += enumerate_as_string( min_skill.begin(),
510         min_skill.end(), []( const std::pair<skill_id, int>  &pr ) {
511             Character &u = get_player_character();
512             int player_skill = u.get_skill_level( skill_id( pr.first ) );
513             if( u.has_active_bionic( bio_cqb ) ) {
514                 player_skill = BIO_CQB_LEVEL;
515             }
516             return string_format( "%s: <stat>%d</stat>/<stat>%d</stat>", pr.first->name(), player_skill,
517                                   pr.second );
518         }, enumeration_conjunction::none ) + "\n";
519     }
520 
521     if( std::any_of( min_damage.begin(),
522     min_damage.end(), []( const std::pair<damage_type, int>  &pr ) {
523     return pr.second > 0;
524 } ) ) {
525         dump += ngettext( "<bold>Damage type required: </bold>",
526                           "<bold>Damage types required: </bold>", min_damage.size() );
527 
528         dump += enumerate_as_string( min_damage.begin(),
529         min_damage.end(), []( const std::pair<damage_type, int>  &pr ) {
530             return string_format( _( "%s: <stat>%d</stat>" ), name_by_dt( pr.first ), pr.second );
531         }, enumeration_conjunction::none ) + "\n";
532     }
533 
534     if( !req_buffs.empty() ) {
535         dump += _( "<bold>Requires:</bold> " );
536 
537         dump += enumerate_as_string( req_buffs.begin(), req_buffs.end(), []( const mabuff_id & bid ) {
538             return bid->name.translated();
539         }, enumeration_conjunction::none ) + "\n";
540     }
541 
542     const std::string type = buff ? _( "activate" ) : _( "be used" );
543 
544     if( unarmed_allowed && melee_allowed ) {
545         dump += string_format( _( "* Can %s while <info>armed</info> or <info>unarmed</info>" ),
546                                type ) + "\n";
547         if( unarmed_weapons_allowed ) {
548             dump += string_format( _( "* Can %s while using <info>any unarmed weapon</info>" ),
549                                    type ) + "\n";
550         }
551     } else if( unarmed_allowed ) {
552         dump += string_format( _( "* Can <info>only</info> %s while <info>unarmed</info>" ),
553                                type ) + "\n";
554         if( unarmed_weapons_allowed ) {
555             dump += string_format( _( "* Can %s while using <info>any unarmed weapon</info>" ),
556                                    type ) + "\n";
557         }
558     } else if( melee_allowed ) {
559         dump += string_format( _( "* Can <info>only</info> %s while <info>armed</info>" ),
560                                type ) + "\n";
561     }
562 
563     if( wall_adjacent ) {
564         dump += string_format( _( "* Can %s while <info>near</info> to a <info>wall</info>" ),
565                                type ) + "\n";
566     }
567 
568     return dump;
569 }
570 
ma_technique()571 ma_technique::ma_technique()
572 {
573     crit_tec = false;
574     crit_ok = false;
575     defensive = false;
576     side_switch = false; // moves the target behind user
577     dummy = false;
578 
579     down_dur = 0;
580     stun_dur = 0;
581     knockback_dist = 0;
582     knockback_spread = 0; // adding randomness to knockback, like tec_throw
583     powerful_knockback = false;
584     knockback_follow = false; // player follows the knocked-back party into their former tile
585 
586     // offensive
587     disarms = false; // like tec_disarm
588     take_weapon = false; // disarms and equips weapon if hands are free
589     dodge_counter = false; // like tec_grab
590     block_counter = false; // like tec_counter
591 
592     // conditional
593     downed_target = false;    // only works on downed enemies
594     stunned_target = false;   // only works on stunned enemies
595     wall_adjacent = false;    // only works near a wall
596     human_target = false;     // only works on humanoid enemies
597 
598     miss_recovery = false; // allows free recovery from misses, like tec_feint
599     grab_break = false; // allows grab_breaks, like tec_break
600 }
601 
is_valid_character(const Character & u) const602 bool ma_technique::is_valid_character( const Character &u ) const
603 {
604     return reqs.is_valid_character( u );
605 }
606 
ma_buff()607 ma_buff::ma_buff()
608     : buff_duration( 1_turns )
609 {
610     max_stacks = 1; // total number of stacks this buff can have
611 
612     dodges_bonus = 0; // extra dodges, like karate
613     blocks_bonus = 0; // extra blocks, like karate
614 
615     throw_immune = false;
616 
617 }
618 
get_effect_id() const619 efftype_id ma_buff::get_effect_id() const
620 {
621     return efftype_id( std::string( "mabuff:" ) + id.str() );
622 }
623 
from_effect(const effect & eff)624 const ma_buff *ma_buff::from_effect( const effect &eff )
625 {
626     const std::string &id = eff.get_effect_type()->id.str();
627     // Same as in get_effect_id!
628     if( id.compare( 0, 7, "mabuff:" ) != 0 ) {
629         return nullptr;
630     }
631     return &mabuff_id( id.substr( 7 ) ).obj();
632 }
633 
apply_buff(Character & u) const634 void ma_buff::apply_buff( Character &u ) const
635 {
636     u.add_effect( get_effect_id(), time_duration::from_turns( buff_duration ) );
637 }
638 
is_valid_character(const Character & u) const639 bool ma_buff::is_valid_character( const Character &u ) const
640 {
641     return reqs.is_valid_character( u );
642 }
643 
apply_character(Character & u) const644 void ma_buff::apply_character( Character &u ) const
645 {
646     u.mod_num_dodges_bonus( dodges_bonus );
647     u.set_num_blocks_bonus( u.get_num_blocks_bonus() + blocks_bonus );
648 }
649 
hit_bonus(const Character & u) const650 int ma_buff::hit_bonus( const Character &u ) const
651 {
652     return bonuses.get_flat( u, affected_stat::HIT );
653 }
critical_hit_chance_bonus(const Character & u) const654 int ma_buff::critical_hit_chance_bonus( const Character &u ) const
655 {
656     return bonuses.get_flat( u, affected_stat::CRITICAL_HIT_CHANCE );
657 }
dodge_bonus(const Character & u) const658 int ma_buff::dodge_bonus( const Character &u ) const
659 {
660     return bonuses.get_flat( u, affected_stat::DODGE );
661 }
block_bonus(const Character & u) const662 int ma_buff::block_bonus( const Character &u ) const
663 {
664     return bonuses.get_flat( u, affected_stat::BLOCK );
665 }
block_effectiveness_bonus(const Character & u) const666 int ma_buff::block_effectiveness_bonus( const Character &u ) const
667 {
668     return bonuses.get_flat( u, affected_stat::BLOCK_EFFECTIVENESS );
669 }
speed_bonus(const Character & u) const670 int ma_buff::speed_bonus( const Character &u ) const
671 {
672     return bonuses.get_flat( u, affected_stat::SPEED );
673 }
armor_bonus(const Character & guy,damage_type dt) const674 int ma_buff::armor_bonus( const Character &guy, damage_type dt ) const
675 {
676     return bonuses.get_flat( guy, affected_stat::ARMOR, dt );
677 }
damage_bonus(const Character & u,damage_type dt) const678 float ma_buff::damage_bonus( const Character &u, damage_type dt ) const
679 {
680     return bonuses.get_flat( u, affected_stat::DAMAGE, dt );
681 }
damage_mult(const Character & u,damage_type dt) const682 float ma_buff::damage_mult( const Character &u, damage_type dt ) const
683 {
684     return bonuses.get_mult( u, affected_stat::DAMAGE, dt );
685 }
is_throw_immune() const686 bool ma_buff::is_throw_immune() const
687 {
688     return throw_immune;
689 }
is_quiet() const690 bool ma_buff::is_quiet() const
691 {
692     return quiet;
693 }
is_stealthy() const694 bool ma_buff::is_stealthy() const
695 {
696     return stealthy;
697 }
698 
can_melee() const699 bool ma_buff::can_melee() const
700 {
701     return melee_allowed;
702 }
703 
get_description(bool passive) const704 std::string ma_buff::get_description( bool passive ) const
705 {
706     std::string dump;
707     dump += string_format( _( "<bold>Buff technique:</bold> %s" ), name ) + "\n";
708 
709     std::string temp = bonuses.get_description();
710     if( !temp.empty() ) {
711         dump += string_format( _( "<bold>%s:</bold> " ),
712                                ngettext( "Bonus", "Bonus/stack", max_stacks ) ) + "\n" + temp;
713     }
714 
715     dump += reqs.get_description( true );
716 
717     if( max_stacks > 1 ) {
718         dump += string_format( _( "* Will <info>stack</info> up to <stat>%d</stat> times" ),
719                                max_stacks ) + "\n";
720     }
721 
722     const int turns = to_turns<int>( buff_duration );
723     if( !passive && turns ) {
724         dump += string_format( _( "* Will <info>last</info> for <stat>%d %s</stat>" ),
725                                turns, ngettext( "turn", "turns", turns ) ) + "\n";
726     }
727 
728     if( dodges_bonus > 0 ) {
729         dump += string_format( _( "* Will give a <good>+%s</good> bonus to <info>dodge</info>%s" ),
730                                dodges_bonus, ngettext( " for the stack", " per stack", max_stacks ) ) + "\n";
731     } else if( dodges_bonus < 0 ) {
732         dump += string_format( _( "* Will give a <bad>%s</bad> penalty to <info>dodge</info>%s" ),
733                                dodges_bonus, ngettext( " for the stack", " per stack", max_stacks ) ) + "\n";
734     }
735 
736     if( blocks_bonus > 0 ) {
737         dump += string_format( _( "* Will give a <good>+%s</good> bonus to <info>block</info>%s" ),
738                                blocks_bonus, ngettext( " for the stack", " per stack", max_stacks ) ) + "\n";
739     } else if( blocks_bonus < 0 ) {
740         dump += string_format( _( "* Will give a <bad>%s</bad> penalty to <info>block</info>%s" ),
741                                blocks_bonus, ngettext( " for the stack", " per stack", max_stacks ) ) + "\n";
742     }
743 
744     if( quiet ) {
745         dump += _( "* Attacks will be completely <info>silent</info>" ) + std::string( "\n" );
746     }
747 
748     if( stealthy ) {
749         dump += _( "* Movement will make <info>less noise</info>" ) + std::string( "\n" );
750     }
751 
752     return dump;
753 }
754 
martialart()755 martialart::martialart()
756 {
757     leg_block = -1;
758     arm_block = -1;
759 }
760 
761 // simultaneously check and add all buffs. this is so that buffs that have
762 // buff dependencies added by the same event trigger correctly
simultaneous_add(Character & u,const std::vector<mabuff_id> & buffs)763 static void simultaneous_add( Character &u, const std::vector<mabuff_id> &buffs )
764 {
765     std::vector<const ma_buff *> buffer; // hey get it because it's for buffs????
766     for( const mabuff_id &buffid : buffs ) {
767         const ma_buff &buff = buffid.obj();
768         if( buff.is_valid_character( u ) ) {
769             buffer.push_back( &buff );
770         }
771     }
772     for( auto &elem : buffer ) {
773         elem->apply_buff( u );
774     }
775 }
776 
apply_static_buffs(Character & u) const777 void martialart::apply_static_buffs( Character &u ) const
778 {
779     simultaneous_add( u, static_buffs );
780 }
781 
apply_onmove_buffs(Character & u) const782 void martialart::apply_onmove_buffs( Character &u ) const
783 {
784     simultaneous_add( u, onmove_buffs );
785 }
786 
apply_onpause_buffs(Character & u) const787 void martialart::apply_onpause_buffs( Character &u ) const
788 {
789     simultaneous_add( u, onpause_buffs );
790 }
791 
apply_onhit_buffs(Character & u) const792 void martialart::apply_onhit_buffs( Character &u ) const
793 {
794     simultaneous_add( u, onhit_buffs );
795 }
796 
apply_onattack_buffs(Character & u) const797 void martialart::apply_onattack_buffs( Character &u ) const
798 {
799     simultaneous_add( u, onattack_buffs );
800 }
801 
apply_ondodge_buffs(Character & u) const802 void martialart::apply_ondodge_buffs( Character &u ) const
803 {
804     simultaneous_add( u, ondodge_buffs );
805 }
806 
apply_onblock_buffs(Character & u) const807 void martialart::apply_onblock_buffs( Character &u ) const
808 {
809     simultaneous_add( u, onblock_buffs );
810 }
811 
apply_ongethit_buffs(Character & u) const812 void martialart::apply_ongethit_buffs( Character &u ) const
813 {
814     simultaneous_add( u, ongethit_buffs );
815 }
816 
apply_onmiss_buffs(Character & u) const817 void martialart::apply_onmiss_buffs( Character &u ) const
818 {
819     simultaneous_add( u, onmiss_buffs );
820 }
821 
apply_oncrit_buffs(Character & u) const822 void martialart::apply_oncrit_buffs( Character &u ) const
823 {
824     simultaneous_add( u, oncrit_buffs );
825 }
826 
apply_onkill_buffs(Character & u) const827 void martialart::apply_onkill_buffs( Character &u ) const
828 {
829     simultaneous_add( u, onkill_buffs );
830 }
831 
has_technique(const Character & u,const matec_id & tec_id) const832 bool martialart::has_technique( const Character &u, const matec_id &tec_id ) const
833 {
834     for( const matec_id &elem : techniques ) {
835         const ma_technique &tec = elem.obj();
836         if( tec.is_valid_character( u ) && tec.id == tec_id ) {
837             return true;
838         }
839     }
840     return false;
841 }
842 
has_weapon(const itype_id & itt) const843 bool martialart::has_weapon( const itype_id &itt ) const
844 {
845     return weapons.count( itt ) > 0;
846 }
847 
weapon_valid(const item & it) const848 bool martialart::weapon_valid( const item &it ) const
849 {
850     if( allow_melee ) {
851         return true;
852     }
853 
854     if( it.is_null() && !strictly_melee ) {
855         return true;
856     }
857 
858     if( has_weapon( it.typeId() ) ) {
859         return true;
860     }
861 
862     if( !strictly_unarmed && !strictly_melee && !it.is_null() &&
863         it.has_flag( json_flag_UNARMED_WEAPON ) ) {
864         return true;
865     }
866 
867     return false;
868 }
869 
get_initiate_avatar_message() const870 std::string martialart::get_initiate_avatar_message() const
871 {
872     return initiate[0].translated();
873 }
874 
get_initiate_npc_message() const875 std::string martialart::get_initiate_npc_message() const
876 {
877     return initiate[1].translated();
878 }
879 // Player stuff
880 
881 // technique
get_all_techniques(const item & weap) const882 std::vector<matec_id> character_martial_arts::get_all_techniques( const item &weap ) const
883 {
884     std::vector<matec_id> tecs;
885     // Grab individual item techniques
886     const auto &weapon_techs = weap.get_techniques();
887     tecs.insert( tecs.end(), weapon_techs.begin(), weapon_techs.end() );
888     // and martial art techniques
889     const auto &style = style_selected.obj();
890     tecs.insert( tecs.end(), style.techniques.begin(), style.techniques.end() );
891 
892     return tecs;
893 }
894 
895 // defensive technique-related
has_miss_recovery_tec(const item & weap) const896 bool character_martial_arts::has_miss_recovery_tec( const item &weap ) const
897 {
898     for( const matec_id &technique : get_all_techniques( weap ) ) {
899         if( technique->miss_recovery ) {
900             return true;
901         }
902     }
903     return false;
904 }
905 
get_miss_recovery_tec(const item & weap) const906 ma_technique character_martial_arts::get_miss_recovery_tec( const item &weap ) const
907 {
908     ma_technique tech;
909     for( const matec_id &technique : get_all_techniques( weap ) ) {
910         if( technique->miss_recovery ) {
911             tech = technique.obj();
912             break;
913         }
914     }
915 
916     return tech;
917 }
918 
919 // This one isn't used with a weapon
has_grab_break_tec() const920 bool character_martial_arts::has_grab_break_tec() const
921 {
922     for( const matec_id &technique : get_all_techniques( item() ) ) {
923         if( technique->grab_break ) {
924             return true;
925         }
926     }
927     return false;
928 }
929 
get_grab_break_tec(const item & weap) const930 ma_technique character_martial_arts::get_grab_break_tec( const item &weap ) const
931 {
932     ma_technique tec;
933     for( const matec_id &technique : get_all_techniques( weap ) ) {
934         if( technique->grab_break ) {
935             tec = technique.obj();
936             break;
937         }
938     }
939     return tec;
940 }
941 
can_grab_break(const item & weap) const942 bool Character::can_grab_break( const item &weap ) const
943 {
944     if( !has_grab_break_tec() ) {
945         return false;
946     }
947 
948     ma_technique tec;
949     for( const matec_id &technique : martial_arts_data->get_all_techniques( weap ) ) {
950         if( technique->grab_break ) {
951             tec = technique.obj();
952             if( tec.is_valid_character( *this ) ) {
953                 return true;
954             }
955         }
956     }
957 
958     return false;
959 }
960 
can_miss_recovery(const item & weap) const961 bool Character::can_miss_recovery( const item &weap ) const
962 {
963     if( !martial_arts_data->has_miss_recovery_tec( weap ) ) {
964         return false;
965     }
966 
967     ma_technique tec = martial_arts_data->get_miss_recovery_tec( weap );
968 
969     return tec.is_valid_character( *this );
970 }
971 
can_leg_block(const Character & owner) const972 bool character_martial_arts::can_leg_block( const Character &owner ) const
973 {
974     const martialart &ma = style_selected.obj();
975     ///\EFFECT_UNARMED increases ability to perform leg block
976     int unarmed_skill = owner.has_active_bionic( bio_cqb ) ? 5 : owner.get_skill_level(
977                             skill_unarmed );
978 
979     // Success conditions.
980     if( owner.get_working_leg_count() >= 1 ) {
981         if( unarmed_skill >= ma.leg_block ) {
982             return true;
983         } else if( ma.leg_block_with_bio_armor_legs && owner.has_bionic( bio_armor_legs ) ) {
984             return true;
985         }
986     }
987     // if not above, can't block.
988     return false;
989 }
990 
can_arm_block(const Character & owner) const991 bool character_martial_arts::can_arm_block( const Character &owner ) const
992 {
993     const martialart &ma = style_selected.obj();
994     ///\EFFECT_UNARMED increases ability to perform arm block
995     int unarmed_skill = owner.has_active_bionic( bio_cqb ) ? 5 : owner.get_skill_level(
996                             skill_unarmed );
997 
998     // Success conditions.
999     if( !owner.is_limb_broken( bodypart_id( "arm_l" ) ) ||
1000         !owner.is_limb_broken( bodypart_id( "arm_r" ) ) ) {
1001         if( unarmed_skill >= ma.arm_block ) {
1002             return true;
1003         } else if( ma.arm_block_with_bio_armor_arms && owner.has_bionic( bio_armor_arms ) ) {
1004             return true;
1005         }
1006     }
1007     // if not above, can't block.
1008     return false;
1009 }
1010 
can_limb_block(const Character & owner) const1011 bool character_martial_arts::can_limb_block( const Character &owner ) const
1012 {
1013     return can_arm_block( owner ) || can_leg_block( owner );
1014 }
1015 
is_force_unarmed() const1016 bool character_martial_arts::is_force_unarmed() const
1017 {
1018     return style_selected->force_unarmed;
1019 }
1020 
1021 // event handlers
ma_static_effects(Character & owner)1022 void character_martial_arts::ma_static_effects( Character &owner )
1023 {
1024     style_selected->apply_static_buffs( owner );
1025 }
ma_onmove_effects(Character & owner)1026 void character_martial_arts::ma_onmove_effects( Character &owner )
1027 {
1028     style_selected->apply_onmove_buffs( owner );
1029 }
ma_onpause_effects(Character & owner)1030 void character_martial_arts::ma_onpause_effects( Character &owner )
1031 {
1032     style_selected->apply_onpause_buffs( owner );
1033 }
ma_onhit_effects(Character & owner)1034 void character_martial_arts::ma_onhit_effects( Character &owner )
1035 {
1036     style_selected->apply_onhit_buffs( owner );
1037 }
ma_onattack_effects(Character & owner)1038 void character_martial_arts::ma_onattack_effects( Character &owner )
1039 {
1040     style_selected->apply_onattack_buffs( owner );
1041 }
ma_ondodge_effects(Character & owner)1042 void character_martial_arts::ma_ondodge_effects( Character &owner )
1043 {
1044     style_selected->apply_ondodge_buffs( owner );
1045 }
ma_onblock_effects(Character & owner)1046 void character_martial_arts::ma_onblock_effects( Character &owner )
1047 {
1048     style_selected->apply_onblock_buffs( owner );
1049 }
ma_ongethit_effects(Character & owner)1050 void character_martial_arts::ma_ongethit_effects( Character &owner )
1051 {
1052     style_selected->apply_ongethit_buffs( owner );
1053 }
ma_onmiss_effects(Character & owner)1054 void character_martial_arts::ma_onmiss_effects( Character &owner )
1055 {
1056     style_selected->apply_onmiss_buffs( owner );
1057 }
ma_oncrit_effects(Character & owner)1058 void character_martial_arts::ma_oncrit_effects( Character &owner )
1059 {
1060     style_selected->apply_oncrit_buffs( owner );
1061 }
ma_onkill_effects(Character & owner)1062 void character_martial_arts::ma_onkill_effects( Character &owner )
1063 {
1064     style_selected->apply_onkill_buffs( owner );
1065 }
1066 
1067 template<typename C, typename F>
accumulate_ma_buff_effects(const C & container,F f)1068 static void accumulate_ma_buff_effects( const C &container, F f )
1069 {
1070     for( auto &elem : container ) {
1071         for( auto &eff : elem.second ) {
1072             if( auto buff = ma_buff::from_effect( eff.second ) ) {
1073                 f( *buff, eff.second );
1074             }
1075         }
1076     }
1077 }
1078 
1079 template<typename C, typename F>
search_ma_buff_effect(const C & container,F f)1080 static bool search_ma_buff_effect( const C &container, F f )
1081 {
1082     for( auto &elem : container ) {
1083         for( auto &eff : elem.second ) {
1084             if( auto buff = ma_buff::from_effect( eff.second ) ) {
1085                 if( f( *buff, eff.second ) ) {
1086                     return true;
1087                 }
1088             }
1089         }
1090     }
1091     return false;
1092 }
1093 
1094 // bonuses
mabuff_tohit_bonus() const1095 float Character::mabuff_tohit_bonus() const
1096 {
1097     float ret = 0.0f;
1098     accumulate_ma_buff_effects( *effects, [&ret, this]( const ma_buff & b, const effect & ) {
1099         ret += b.hit_bonus( *this );
1100     } );
1101     return ret;
1102 }
mabuff_critical_hit_chance_bonus() const1103 float Character::mabuff_critical_hit_chance_bonus() const
1104 {
1105     float ret = 0.0f;
1106     accumulate_ma_buff_effects( *effects, [&ret, this]( const ma_buff & b, const effect & d ) {
1107         ret += d.get_intensity() * b.critical_hit_chance_bonus( *this );
1108     } );
1109     return ret;
1110 }
mabuff_dodge_bonus() const1111 float Character::mabuff_dodge_bonus() const
1112 {
1113     float ret = 0.0f;
1114     accumulate_ma_buff_effects( *effects, [&ret, this]( const ma_buff & b, const effect & d ) {
1115         ret += d.get_intensity() * b.dodge_bonus( *this );
1116     } );
1117     return ret;
1118 }
mabuff_block_bonus() const1119 int Character::mabuff_block_bonus() const
1120 {
1121     int ret = 0;
1122     accumulate_ma_buff_effects( *effects, [&ret, this]( const ma_buff & b, const effect & d ) {
1123         ret += d.get_intensity() * b.block_bonus( *this );
1124     } );
1125     return ret;
1126 }
mabuff_block_effectiveness_bonus() const1127 int Character::mabuff_block_effectiveness_bonus( ) const
1128 {
1129     int ret = 0;
1130     accumulate_ma_buff_effects( *effects, [&ret, this]( const ma_buff & b, const effect & d ) {
1131         ret += d.get_intensity() * b.block_effectiveness_bonus( *this );
1132     } );
1133     return ret;
1134 }
mabuff_speed_bonus() const1135 int Character::mabuff_speed_bonus() const
1136 {
1137     int ret = 0;
1138     accumulate_ma_buff_effects( *effects, [&ret, this]( const ma_buff & b, const effect & d ) {
1139         ret += d.get_intensity() * b.speed_bonus( *this );
1140     } );
1141     return ret;
1142 }
mabuff_armor_bonus(damage_type type) const1143 int Character::mabuff_armor_bonus( damage_type type ) const
1144 {
1145     int ret = 0;
1146     accumulate_ma_buff_effects( *effects, [&ret, type, this]( const ma_buff & b, const effect & d ) {
1147         ret += d.get_intensity() * b.armor_bonus( *this, type );
1148     } );
1149     return ret;
1150 }
mabuff_damage_mult(damage_type type) const1151 float Character::mabuff_damage_mult( damage_type type ) const
1152 {
1153     float ret = 1.f;
1154     accumulate_ma_buff_effects( *effects, [&ret, type, this]( const ma_buff & b, const effect & d ) {
1155         // This is correct, so that a 20% buff (1.2) plus a 20% buff (1.2)
1156         // becomes 1.4 instead of 2.4 (which would be a 240% buff)
1157         ret *= d.get_intensity() * ( b.damage_mult( *this, type ) - 1 ) + 1;
1158     } );
1159     return ret;
1160 }
mabuff_damage_bonus(damage_type type) const1161 int Character::mabuff_damage_bonus( damage_type type ) const
1162 {
1163     int ret = 0;
1164     accumulate_ma_buff_effects( *effects, [&ret, type, this]( const ma_buff & b, const effect & d ) {
1165         ret += d.get_intensity() * b.damage_bonus( *this, type );
1166     } );
1167     return ret;
1168 }
mabuff_attack_cost_penalty() const1169 int Character::mabuff_attack_cost_penalty() const
1170 {
1171     int ret = 0;
1172     accumulate_ma_buff_effects( *effects, [&ret, this]( const ma_buff & b, const effect & d ) {
1173         ret += d.get_intensity() * b.bonuses.get_flat( *this, affected_stat::MOVE_COST );
1174     } );
1175     return ret;
1176 }
mabuff_attack_cost_mult() const1177 float Character::mabuff_attack_cost_mult() const
1178 {
1179     float ret = 1.0f;
1180     accumulate_ma_buff_effects( *effects, [&ret, this]( const ma_buff & b, const effect & d ) {
1181         // This is correct, so that a 20% buff (1.2) plus a 20% buff (1.2)
1182         // becomes 1.4 instead of 2.4 (which would be a 240% buff)
1183         ret *= d.get_intensity() * ( b.bonuses.get_mult( *this,
1184                                      affected_stat::MOVE_COST ) - 1 ) + 1;
1185     } );
1186     return ret;
1187 }
1188 
is_throw_immune() const1189 bool Character::is_throw_immune() const
1190 {
1191     return search_ma_buff_effect( *effects, []( const ma_buff & b, const effect & ) {
1192         return b.is_throw_immune();
1193     } );
1194 }
is_quiet() const1195 bool Character::is_quiet() const
1196 {
1197     return search_ma_buff_effect( *effects, []( const ma_buff & b, const effect & ) {
1198         return b.is_quiet();
1199     } );
1200 }
is_stealthy() const1201 bool Character::is_stealthy() const
1202 {
1203     return search_ma_buff_effect( *effects, []( const ma_buff & b, const effect & ) {
1204         return b.is_stealthy();
1205     } );
1206 }
1207 
can_melee() const1208 bool Character::can_melee() const
1209 {
1210     return search_ma_buff_effect( *effects, []( const ma_buff & b, const effect & ) {
1211         return b.can_melee();
1212     } );
1213 }
1214 
has_mabuff(const mabuff_id & id) const1215 bool Character::has_mabuff( const mabuff_id &id ) const
1216 {
1217     return search_ma_buff_effect( *effects, [&id]( const ma_buff & b, const effect & ) {
1218         return b.id == id;
1219     } );
1220 }
1221 
has_grab_break_tec() const1222 bool Character::has_grab_break_tec() const
1223 {
1224     return martial_arts_data->has_grab_break_tec();
1225 }
1226 
has_martialart(const matype_id & ma) const1227 bool character_martial_arts::has_martialart( const matype_id &ma ) const
1228 {
1229     return std::find( ma_styles.begin(), ma_styles.end(), ma ) != ma_styles.end();
1230 }
1231 
add_martialart(const matype_id & ma_id)1232 void character_martial_arts::add_martialart( const matype_id &ma_id )
1233 {
1234     if( has_martialart( ma_id ) ) {
1235         return;
1236     }
1237     ma_styles.emplace_back( ma_id );
1238 }
1239 
can_autolearn(const matype_id & ma_id) const1240 bool Character::can_autolearn( const matype_id &ma_id ) const
1241 {
1242     if( ma_id.obj().autolearn_skills.empty() ) {
1243         return false;
1244     }
1245 
1246     for( const std::pair<std::string, int> &elem : ma_id.obj().autolearn_skills ) {
1247         const skill_id skill_req( elem.first );
1248         const int required_level = elem.second;
1249 
1250         if( required_level > get_skill_level( skill_req ) ) {
1251             return false;
1252         }
1253     }
1254 
1255     return true;
1256 }
1257 
martialart_use_message(const Character & owner) const1258 void character_martial_arts::martialart_use_message( const Character &owner ) const
1259 {
1260     martialart ma = style_selected.obj();
1261     if( ma.force_unarmed || ma.weapon_valid( owner.weapon ) ) {
1262         owner.add_msg_if_player( m_info, "%s", ma.get_initiate_avatar_message() );
1263     } else if( ma.strictly_melee && !owner.is_armed() ) {
1264         owner.add_msg_if_player( m_bad, _( "%s cannot be used unarmed." ), ma.name );
1265     } else if( ma.strictly_unarmed && owner.is_armed() ) {
1266         owner.add_msg_if_player( m_bad, _( "%s cannot be used with weapons." ), ma.name );
1267     } else {
1268         owner.add_msg_if_player( m_bad, _( "The %1$s is not a valid %2$s weapon." ), owner.weapon.tname( 1,
1269                                  false ), ma.name );
1270     }
1271 }
1272 
damage_bonus(const Character & u,damage_type type) const1273 float ma_technique::damage_bonus( const Character &u, damage_type type ) const
1274 {
1275     return bonuses.get_flat( u, affected_stat::DAMAGE, type );
1276 }
1277 
damage_multiplier(const Character & u,damage_type type) const1278 float ma_technique::damage_multiplier( const Character &u, damage_type type ) const
1279 {
1280     return bonuses.get_mult( u, affected_stat::DAMAGE, type );
1281 }
1282 
move_cost_multiplier(const Character & u) const1283 float ma_technique::move_cost_multiplier( const Character &u ) const
1284 {
1285     return bonuses.get_mult( u, affected_stat::MOVE_COST );
1286 }
1287 
move_cost_penalty(const Character & u) const1288 float ma_technique::move_cost_penalty( const Character &u ) const
1289 {
1290     return bonuses.get_flat( u, affected_stat::MOVE_COST );
1291 }
1292 
armor_penetration(const Character & u,damage_type type) const1293 float ma_technique::armor_penetration( const Character &u, damage_type type ) const
1294 {
1295     return bonuses.get_flat( u, affected_stat::ARMOR_PENETRATION, type );
1296 }
1297 
get_description() const1298 std::string ma_technique::get_description() const
1299 {
1300     std::string dump;
1301     std::string type;
1302 
1303     if( block_counter ) {
1304         type = _( "Block Counter" );
1305     } else if( dodge_counter ) {
1306         type = _( "Dodge Counter" );
1307     } else if( miss_recovery ) {
1308         type = _( "Miss Recovery" );
1309     } else if( grab_break ) {
1310         type = _( "Grab Break" );
1311     } else if( defensive ) {
1312         type = _( "Defensive" );
1313     } else {
1314         type = _( "Offensive" );
1315     }
1316 
1317     dump += string_format( _( "<bold>Type:</bold> %s" ), type ) + "\n";
1318 
1319     std::string temp = bonuses.get_description();
1320     if( !temp.empty() ) {
1321         dump += _( "<bold>Bonus:</bold> " ) + std::string( "\n" ) + temp;
1322     }
1323 
1324     dump += reqs.get_description();
1325 
1326     if( weighting > 1 ) {
1327         dump += string_format( _( "* <info>Greater chance</info> to activate: <stat>+%s%%</stat>" ),
1328                                ( 100 * ( weighting - 1 ) ) ) + "\n";
1329     } else if( weighting < -1 ) {
1330         dump += string_format( _( "* <info>Lower chance</info> to activate: <stat>1/%s</stat>" ),
1331                                std::abs( weighting ) ) + "\n";
1332     }
1333 
1334     if( crit_ok ) {
1335         dump += _( "* Can activate on a <info>normal</info> or a <info>crit</info> hit" ) +
1336                 std::string( "\n" );
1337     } else if( crit_tec ) {
1338         dump += _( "* Will only activate on a <info>crit</info>" ) + std::string( "\n" );
1339     }
1340 
1341     if( side_switch ) {
1342         dump += _( "* Moves target <info>behind</info> you" ) + std::string( "\n" );
1343     }
1344 
1345     if( wall_adjacent ) {
1346         dump += _( "* Will only activate while <info>near</info> to a <info>wall</info>" ) +
1347                 std::string( "\n" );
1348     }
1349 
1350     if( downed_target ) {
1351         dump += _( "* Only works on a <info>downed</info> target" ) + std::string( "\n" );
1352     }
1353 
1354     if( stunned_target ) {
1355         dump += _( "* Only works on a <info>stunned</info> target" ) + std::string( "\n" );
1356     }
1357 
1358     if( human_target ) {
1359         dump += _( "* Only works on a <info>humanoid</info> target" ) + std::string( "\n" );
1360     }
1361 
1362     if( powerful_knockback ) {
1363         dump += _( "* Causes extra damage on <info>knockback collision</info>." ) + std::string( "\n" );
1364     }
1365 
1366     if( dodge_counter ) {
1367         dump += _( "* Will <info>counterattack</info> when you <info>dodge</info>" ) + std::string( "\n" );
1368     }
1369 
1370     if( block_counter ) {
1371         dump += _( "* Will <info>counterattack</info> when you <info>block</info>" ) + std::string( "\n" );
1372     }
1373 
1374     if( miss_recovery ) {
1375         dump += _( "* Will grant <info>free recovery</info> from a <info>miss</info>" ) +
1376                 std::string( "\n" );
1377     }
1378 
1379     if( grab_break ) {
1380         dump += _( "* Will <info>break</info> a <info>grab</info>" ) + std::string( "\n" );
1381     }
1382 
1383     if( aoe == "wide" ) {
1384         dump += _( "* Will attack in a <info>wide arc</info> in front of you" ) + std::string( "\n" );
1385 
1386     } else if( aoe == "spin" ) {
1387         dump += _( "* Will attack <info>adjacent</info> enemies around you" ) + std::string( "\n" );
1388 
1389     } else if( aoe == "impale" ) {
1390         dump += _( "* Will <info>attack</info> your target and another <info>one behind</info> it" ) +
1391                 std::string( "\n" );
1392     }
1393 
1394     if( knockback_dist ) {
1395         dump += string_format( _( "* Will <info>knock back</info> enemies <stat>%d %s</stat>" ),
1396                                knockback_dist, ngettext( "tile", "tiles", knockback_dist ) ) + "\n";
1397     }
1398 
1399     if( knockback_follow ) {
1400         dump += _( "* Will <info>follow</info> enemies after knockback." ) + std::string( "\n" );
1401     }
1402 
1403     if( down_dur ) {
1404         dump += string_format( _( "* Will <info>down</info> enemies for <stat>%d %s</stat>" ),
1405                                down_dur, ngettext( "turn", "turns", down_dur ) ) + "\n";
1406     }
1407 
1408     if( stun_dur ) {
1409         dump += string_format( _( "* Will <info>stun</info> target for <stat>%d %s</stat>" ),
1410                                stun_dur, ngettext( "turn", "turns", stun_dur ) ) + "\n";
1411     }
1412 
1413     if( disarms ) {
1414         dump += _( "* Will <info>disarm</info> the target" ) + std::string( "\n" );
1415     }
1416 
1417     if( take_weapon ) {
1418         dump += _( "* Will <info>disarm</info> the target and <info>take their weapon</info>" ) +
1419                 std::string( "\n" );
1420     }
1421 
1422     return dump;
1423 }
1424 
key(const input_context & ctxt,const input_event & event,int entnum,uilist *)1425 bool ma_style_callback::key( const input_context &ctxt, const input_event &event, int entnum,
1426                              uilist * )
1427 {
1428     const std::string &action = ctxt.input_to_action( event );
1429     if( action != "SHOW_DESCRIPTION" ) {
1430         return false;
1431     }
1432     matype_id style_selected;
1433     const size_t index = entnum;
1434     if( index >= offset && index - offset < styles.size() ) {
1435         style_selected = styles[index - offset];
1436     }
1437     if( !style_selected.str().empty() ) {
1438         const martialart &ma = style_selected.obj();
1439 
1440         std::string buffer;
1441 
1442         if( ma.force_unarmed ) {
1443             buffer += _( "<bold>This style forces you to use unarmed strikes, even if wielding a weapon.</bold>" );
1444             buffer += "\n";
1445         } else if( ma.allow_melee ) {
1446             buffer += _( "<bold>This style can be used with all weapons.</bold>" );
1447             buffer += "\n";
1448         } else if( ma.strictly_melee ) {
1449             buffer += _( "<bold>This is an armed combat style.</bold>" );
1450             buffer += "\n";
1451         }
1452 
1453         buffer += "--\n";
1454 
1455         if( ma.arm_block_with_bio_armor_arms || ma.arm_block != 99 ||
1456             ma.leg_block_with_bio_armor_legs || ma.leg_block != 99 ) {
1457             Character &u = get_player_character();
1458             int unarmed_skill =  u.get_skill_level( skill_unarmed );
1459             if( u.has_active_bionic( bio_cqb ) ) {
1460                 unarmed_skill = BIO_CQB_LEVEL;
1461             }
1462             if( ma.arm_block_with_bio_armor_arms ) {
1463                 buffer += _( "You can <info>arm block</info> by installing the <info>Arms Alloy Plating CBM</info>" );
1464                 buffer += "\n";
1465             } else if( ma.arm_block != 99 ) {
1466                 buffer += string_format(
1467                               _( "You can <info>arm block</info> at <info>unarmed combat:</info> <stat>%s</stat>/<stat>%s</stat>" ),
1468                               unarmed_skill, ma.arm_block ) + "\n";
1469             }
1470 
1471             if( ma.leg_block_with_bio_armor_legs ) {
1472                 buffer += _( "You can <info>leg block</info> by installing the <info>Legs Alloy Plating CBM</info>" );
1473                 buffer += "\n";
1474             } else if( ma.leg_block != 99 ) {
1475                 buffer += string_format(
1476                               _( "You can <info>leg block</info> at <info>unarmed combat:</info> <stat>%s</stat>/<stat>%s</stat>" ),
1477                               unarmed_skill, ma.leg_block );
1478                 buffer += "\n";
1479             }
1480             buffer += "--\n";
1481         }
1482 
1483         auto buff_desc = [&]( const std::string & title, const std::vector<mabuff_id> &buffs,
1484         bool passive = false ) {
1485             if( !buffs.empty() ) {
1486                 buffer += string_format( _( "<header>%s buffs:</header>" ), title );
1487                 for( const auto &buff : buffs ) {
1488                     buffer += "\n" + buff->get_description( passive );
1489                 }
1490                 buffer += "--\n";
1491             }
1492         };
1493 
1494         buff_desc( _( "Passive" ), ma.static_buffs, true );
1495         buff_desc( _( "Move" ), ma.onmove_buffs );
1496         buff_desc( _( "Pause" ), ma.onpause_buffs );
1497         buff_desc( _( "Hit" ), ma.onhit_buffs );
1498         buff_desc( _( "Miss" ), ma.onmiss_buffs );
1499         buff_desc( _( "Attack" ), ma.onattack_buffs );
1500         buff_desc( _( "Crit" ), ma.oncrit_buffs );
1501         buff_desc( _( "Kill" ), ma.onkill_buffs );
1502         buff_desc( _( "Dodge" ), ma.ondodge_buffs );
1503         buff_desc( _( "Block" ), ma.onblock_buffs );
1504         buff_desc( _( "Get hit" ), ma.ongethit_buffs );
1505 
1506         for( const auto &tech : ma.techniques ) {
1507             buffer += string_format( _( "<header>Technique:</header> <bold>%s</bold>   " ),
1508                                      tech.obj().name ) + "\n";
1509             buffer += tech.obj().get_description() + "--\n";
1510         }
1511 
1512         if( !ma.weapons.empty() ) {
1513             std::vector<std::string> weapons;
1514             std::transform( ma.weapons.begin(), ma.weapons.end(),
1515             std::back_inserter( weapons ), []( const itype_id & wid )-> std::string {
1516                 // Colorize wielded weapon and move it to the front of the list
1517                 Character &player_character = get_player_character();
1518                 if( item::nname( wid ) == player_character.weapon.display_name() )
1519                 {
1520                     return colorize( item::nname( wid ) + _( " (wielded)" ), c_light_cyan );
1521                 } else
1522                 {
1523                     return item::nname( wid );
1524                 } } );
1525             // Sorting alphabetically makes it easier to find a specific weapon
1526             std::sort( weapons.begin(), weapons.end(), localized_compare );
1527             // This removes duplicate names (e.g. a real weapon and a replica sharing the same name) from the weapon list.
1528             auto last = std::unique( weapons.begin(), weapons.end() );
1529             weapons.erase( last, weapons.end() );
1530 
1531             buffer += ngettext( "<bold>Weapon:</bold>", "<bold>Weapons:</bold>",
1532                                 weapons.size() ) + std::string( " " );
1533             buffer += enumerate_as_string( weapons );
1534         }
1535 
1536         catacurses::window w;
1537 
1538         const std::string text = replace_colors( buffer );
1539         int width = 0;
1540         int height = 0;
1541         int iLines = 0;
1542         int selected = 0;
1543 
1544         ui_adaptor ui;
1545         ui.on_screen_resize( [&]( ui_adaptor & ui ) {
1546             w = catacurses::newwin( FULL_SCREEN_HEIGHT, FULL_SCREEN_WIDTH,
1547                                     point( TERMX > FULL_SCREEN_WIDTH ? ( TERMX - FULL_SCREEN_WIDTH ) / 2 : 0,
1548                                            TERMY > FULL_SCREEN_HEIGHT ? ( TERMY - FULL_SCREEN_HEIGHT ) / 2 : 0 ) );
1549 
1550             width = FULL_SCREEN_WIDTH - 4;
1551             height = FULL_SCREEN_HEIGHT - 2;
1552 
1553             const auto vFolded = foldstring( text, width );
1554             iLines = vFolded.size();
1555 
1556             if( iLines < height ) {
1557                 selected = 0;
1558             } else if( selected >= iLines - height ) {
1559                 selected = iLines - height;
1560             }
1561 
1562             ui.position_from_window( w );
1563         } );
1564         ui.mark_resize();
1565 
1566         input_context ict;
1567         ict.register_action( "UP" );
1568         ict.register_action( "DOWN" );
1569         ict.register_action( "PAGE_UP" );
1570         ict.register_action( "PAGE_DOWN" );
1571         ict.register_action( "QUIT" );
1572         ict.register_action( "HELP_KEYBINDINGS" );
1573 
1574         ui.on_redraw( [&]( const ui_adaptor & ) {
1575             werase( w );
1576             fold_and_print_from( w, point( 2, 1 ), width, selected, c_light_gray, text );
1577             draw_border( w, BORDER_COLOR, string_format( _( " Style: %s " ), ma.name ) );
1578             draw_scrollbar( w, selected, height, iLines, point_south, BORDER_COLOR, true );
1579             wnoutrefresh( w );
1580         } );
1581 
1582         do {
1583             if( selected < 0 ) {
1584                 selected = 0;
1585             } else if( iLines < height ) {
1586                 selected = 0;
1587             } else if( selected >= iLines - height ) {
1588                 selected = iLines - height;
1589             }
1590 
1591             ui_manager::redraw();
1592             const int scroll_lines = catacurses::getmaxy( w ) - 4;
1593             std::string action = ict.handle_input();
1594 
1595             if( action == "QUIT" ) {
1596                 break;
1597             } else if( action == "DOWN" ) {
1598                 selected++;
1599             } else if( action == "UP" ) {
1600                 selected--;
1601             } else if( action == "PAGE_DOWN" ) {
1602                 selected += scroll_lines;
1603             } else if( action == "PAGE_UP" ) {
1604                 selected -= scroll_lines;
1605             }
1606         } while( true );
1607     }
1608     return true;
1609 }
1610