1 #include "skill.h"
2 
3 #include <algorithm>
4 #include <array>
5 #include <cstddef>
6 #include <iterator>
7 #include <utility>
8 
9 #include "cata_utility.h"
10 #include "debug.h"
11 #include "item.h"
12 #include "json.h"
13 #include "options.h"
14 #include "recipe.h"
15 #include "rng.h"
16 #include "translations.h"
17 
18 // TODO: a map, for Barry's sake make this a map.
19 std::vector<Skill> Skill::skills;
20 std::map<skill_id, Skill> Skill::contextual_skills;
21 
22 std::vector<SkillDisplayType> SkillDisplayType::skillTypes;
23 
24 static const Skill invalid_skill;
25 static const SkillDisplayType invalid_skill_type;
26 
27 /** @relates string_id */
28 template<>
obj() const29 const Skill &string_id<Skill>::obj() const
30 {
31     for( const Skill &skill : Skill::skills ) {
32         if( skill.ident() == *this ) {
33             return skill;
34         }
35     }
36 
37     const auto iter = Skill::contextual_skills.find( *this );
38     if( iter != Skill::contextual_skills.end() ) {
39         return iter->second;
40     }
41 
42     return invalid_skill;
43 }
44 
45 /** @relates string_id */
46 template<>
is_valid() const47 bool string_id<Skill>::is_valid() const
48 {
49     return &obj() != &invalid_skill;
50 }
51 
Skill()52 Skill::Skill() : Skill( skill_id::NULL_ID(), to_translation( "nothing" ),
53                             to_translation( "The zen-most skill there is." ),
54                             std::set<std::string> {}, skill_displayType_id::NULL_ID() )
55 {
56 }
57 
Skill(const skill_id & ident,const translation & name,const translation & description,const std::set<std::string> & tags,skill_displayType_id display_type)58 Skill::Skill( const skill_id &ident, const translation &name, const translation &description,
59               const std::set<std::string> &tags, skill_displayType_id display_type )
60     : _ident( ident ), _name( name ), _description( description ), _tags( tags ),
61       _display_type( display_type )
62 {
63 }
64 
get_skills_sorted_by(std::function<bool (const Skill &,const Skill &)> pred)65 std::vector<const Skill *> Skill::get_skills_sorted_by(
66     std::function<bool ( const Skill &, const Skill & )> pred )
67 {
68     std::vector<const Skill *> result;
69     result.reserve( skills.size() );
70 
71     for( const Skill &sk : skills ) {
72         if( !sk.obsolete() ) {
73             result.push_back( &sk );
74         }
75     }
76 
77     std::sort( begin( result ), end( result ), [&]( const Skill * lhs, const Skill * rhs ) {
78         return pred( *lhs, *rhs );
79     } );
80 
81     return result;
82 }
83 
reset()84 void Skill::reset()
85 {
86     skills.clear();
87     contextual_skills.clear();
88 }
89 
load_skill(const JsonObject & jsobj)90 void Skill::load_skill( const JsonObject &jsobj )
91 {
92     // TEMPORARY until 0.G: Remove "ident" support
93     skill_id ident = skill_id( jsobj.has_string( "ident" ) ? jsobj.get_string( "ident" ) :
94                                jsobj.get_string( "id" ) );
95     skills.erase( std::remove_if( begin( skills ), end( skills ), [&]( const Skill & s ) {
96         return s._ident == ident;
97     } ), end( skills ) );
98 
99     translation name;
100     jsobj.read( "name", name );
101     translation desc;
102     jsobj.read( "description", desc );
103     std::unordered_map<std::string, int> companion_skill_practice;
104     for( JsonObject jo_csp : jsobj.get_array( "companion_skill_practice" ) ) {
105         companion_skill_practice.emplace( jo_csp.get_string( "skill" ), jo_csp.get_int( "weight" ) );
106     }
107     time_info_t time_to_attack;
108     if( jsobj.has_object( "time_to_attack" ) ) {
109         JsonObject jso_tta = jsobj.get_object( "time_to_attack" );
110         jso_tta.read( "min_time", time_to_attack.min_time );
111         jso_tta.read( "base_time", time_to_attack.base_time );
112         jso_tta.read( "time_reduction_per_level", time_to_attack.time_reduction_per_level );
113     }
114     skill_displayType_id display_type = skill_displayType_id( jsobj.get_string( "display_category" ) );
115     Skill sk( ident, name, desc, jsobj.get_tags( "tags" ), display_type );
116 
117     sk._time_to_attack = time_to_attack;
118     sk._companion_combat_rank_factor = jsobj.get_int( "companion_combat_rank_factor", 0 );
119     sk._companion_survival_rank_factor = jsobj.get_int( "companion_survival_rank_factor", 0 );
120     sk._companion_industry_rank_factor = jsobj.get_int( "companion_industry_rank_factor", 0 );
121     sk._companion_skill_practice = companion_skill_practice;
122     sk._obsolete = jsobj.get_bool( "obsolete", false );
123 
124     if( sk.is_contextual_skill() ) {
125         contextual_skills[sk.ident()] = sk;
126     } else {
127         skills.push_back( sk );
128     }
129 }
130 
SkillDisplayType()131 SkillDisplayType::SkillDisplayType() : SkillDisplayType( skill_displayType_id::NULL_ID(),
132             to_translation( "invalid" ) )
133 {
134 }
135 
SkillDisplayType(const skill_displayType_id & ident,const translation & display_string)136 SkillDisplayType::SkillDisplayType( const skill_displayType_id &ident,
137                                     const translation &display_string )
138     : _ident( ident ), _display_string( display_string )
139 {
140 }
141 
load(const JsonObject & jsobj)142 void SkillDisplayType::load( const JsonObject &jsobj )
143 {
144     // TEMPORARY until 0.G: Remove "ident" support
145     skill_displayType_id ident = skill_displayType_id(
146                                      jsobj.has_string( "ident" ) ? jsobj.get_string( "ident" ) :
147                                      jsobj.get_string( "id" ) );
148     skillTypes.erase( std::remove_if( begin( skillTypes ),
149     end( skillTypes ), [&]( const SkillDisplayType & s ) {
150         return s._ident == ident;
151     } ), end( skillTypes ) );
152 
153     translation display_string;
154     jsobj.read( "display_string", display_string );
155     const SkillDisplayType sk( ident, display_string );
156     skillTypes.push_back( sk );
157 }
158 
get_skill_type(const skill_displayType_id & id)159 const SkillDisplayType &SkillDisplayType::get_skill_type( const skill_displayType_id &id )
160 {
161     for( auto &i : skillTypes ) {
162         if( i._ident == id ) {
163             return i;
164         }
165     }
166     return invalid_skill_type;
167 }
168 
from_legacy_int(const int legacy_id)169 skill_id Skill::from_legacy_int( const int legacy_id )
170 {
171     static const std::array<skill_id, 28> legacy_skills = { {
172             skill_id::NULL_ID(), skill_id( "dodge" ), skill_id( "melee" ), skill_id( "unarmed" ),
173             skill_id( "bashing" ), skill_id( "cutting" ), skill_id( "stabbing" ), skill_id( "throw" ),
174             skill_id( "gun" ), skill_id( "pistol" ), skill_id( "shotgun" ), skill_id( "smg" ),
175             skill_id( "rifle" ), skill_id( "archery" ), skill_id( "launcher" ), skill_id( "mechanics" ),
176             skill_id( "electronics" ), skill_id( "cooking" ), skill_id( "tailor" ), skill_id::NULL_ID(),
177             skill_id( "firstaid" ), skill_id( "speech" ), skill_id( "computer" ),
178             skill_id( "survival" ), skill_id( "traps" ), skill_id( "swimming" ), skill_id( "driving" ),
179         }
180     };
181     if( static_cast<size_t>( legacy_id ) < legacy_skills.size() ) {
182         return legacy_skills[legacy_id];
183     }
184     debugmsg( "legacy skill id %d is invalid", legacy_id );
185     return skills.front().ident(); // return a non-null id because callers might not expect a null-id
186 }
187 
random_skill()188 skill_id Skill::random_skill()
189 {
190     return random_entry_ref( skills ).ident();
191 }
192 
193 // used for the pacifist trait
is_combat_skill() const194 bool Skill::is_combat_skill() const
195 {
196     static const std::string combat_skill( "combat_skill" );
197     return _tags.count( combat_skill ) > 0;
198 }
199 
is_contextual_skill() const200 bool Skill::is_contextual_skill() const
201 {
202     static const std::string contextual_skill( "contextual_skill" );
203     return _tags.count( contextual_skill ) > 0;
204 }
205 
train(int amount,bool skip_scaling)206 void SkillLevel::train( int amount, bool skip_scaling )
207 {
208     // Working off rust to regain levels goes twice as fast as reaching levels in the first place
209     if( _level < _highestLevel ) {
210         amount *= 2;
211     }
212 
213     if( skip_scaling ) {
214         _exercise += amount;
215     } else {
216         const double scaling = get_option<float>( "SKILL_TRAINING_SPEED" );
217         if( scaling > 0.0 ) {
218             _exercise += roll_remainder( amount * scaling );
219         }
220     }
221 
222     if( _exercise >= 100 * 100 * ( _level + 1 ) * ( _level + 1 ) ) {
223         _exercise = 0;
224         ++_level;
225         if( _level > _highestLevel ) {
226             _highestLevel = _level;
227         }
228     }
229 }
230 
231 namespace
232 {
rustRate(int level)233 time_duration rustRate( int level )
234 {
235     // for n = [0, 7]
236     //
237     // 2^18
238     // -------
239     // 2^(18-n)
240 
241     unsigned const n = clamp( level, 0, 7 );
242     return time_duration::from_turns( 1 << ( 18 - n ) );
243 }
244 } //namespace
245 
isRusting() const246 bool SkillLevel::isRusting() const
247 {
248     return get_option<std::string>( "SKILL_RUST" ) != "off" && ( _level > 0 ) &&
249            calendar::turn - _lastPracticed > rustRate( _level );
250 }
251 
rust(bool charged_bio_mem,int character_rate)252 bool SkillLevel::rust( bool charged_bio_mem, int character_rate )
253 {
254     const time_duration delta = calendar::turn - _lastPracticed;
255     const float char_rate = character_rate / 100.0f;
256     const time_duration skill_rate = rustRate( _level );
257     if( to_turns<int>( skill_rate ) * char_rate <= 0 || delta <= 0_turns ||
258         delta % ( skill_rate * char_rate ) != 0_turns ) {
259         return false;
260     }
261 
262     if( charged_bio_mem ) {
263         return one_in( 5 );
264     }
265 
266     _exercise -= _level * 100;
267     const std::string &rust_type = get_option<std::string>( "SKILL_RUST" );
268     if( _exercise < 0 ) {
269         if( rust_type == "vanilla" || rust_type == "int" ) {
270             _exercise = ( 100 * 100 * _level * _level ) - 1;
271             --_level;
272         } else {
273             _exercise = 0;
274         }
275     }
276 
277     return false;
278 }
279 
practice()280 void SkillLevel::practice()
281 {
282     _lastPracticed = calendar::turn;
283 }
284 
readBook(int minimumGain,int maximumGain,int maximumLevel)285 void SkillLevel::readBook( int minimumGain, int maximumGain, int maximumLevel )
286 {
287     if( _level < maximumLevel || maximumLevel < 0 ) {
288         train( ( _level + 1 ) * rng( minimumGain, maximumGain ) * 100 );
289     }
290 
291     practice();
292 }
293 
can_train() const294 bool SkillLevel::can_train() const
295 {
296     return get_option<float>( "SKILL_TRAINING_SPEED" ) > 0.0;
297 }
298 
get_skill_level_object(const skill_id & ident) const299 const SkillLevel &SkillLevelMap::get_skill_level_object( const skill_id &ident ) const
300 {
301     static const SkillLevel null_skill{};
302 
303     if( ident && ident->is_contextual_skill() ) {
304         debugmsg( "Skill \"%s\" is context-dependent.  It cannot be assigned.", ident.str() );
305         return null_skill;
306     }
307 
308     const auto iter = find( ident );
309 
310     if( iter != end() ) {
311         return iter->second;
312     }
313 
314     return null_skill;
315 }
316 
get_skill_level_object(const skill_id & ident)317 SkillLevel &SkillLevelMap::get_skill_level_object( const skill_id &ident )
318 {
319     static SkillLevel null_skill;
320 
321     if( ident && ident->is_contextual_skill() ) {
322         debugmsg( "Skill \"%s\" is context-dependent.  It cannot be assigned.", ident.str() );
323         return null_skill;
324     }
325 
326     return ( *this )[ident];
327 }
328 
mod_skill_level(const skill_id & ident,int delta)329 void SkillLevelMap::mod_skill_level( const skill_id &ident, int delta )
330 {
331     SkillLevel &obj = get_skill_level_object( ident );
332     obj.level( obj.level() + delta );
333 }
334 
get_skill_level(const skill_id & ident) const335 int SkillLevelMap::get_skill_level( const skill_id &ident ) const
336 {
337     return get_skill_level_object( ident ).level();
338 }
339 
get_skill_level(const skill_id & ident,const item & context) const340 int SkillLevelMap::get_skill_level( const skill_id &ident, const item &context ) const
341 {
342     const auto id = context.is_null() ? ident : context.contextualize_skill( ident );
343     return get_skill_level( id );
344 }
345 
meets_skill_requirements(const std::map<skill_id,int> & req) const346 bool SkillLevelMap::meets_skill_requirements( const std::map<skill_id, int> &req ) const
347 {
348     return meets_skill_requirements( req, item() );
349 }
350 
meets_skill_requirements(const std::map<skill_id,int> & req,const item & context) const351 bool SkillLevelMap::meets_skill_requirements( const std::map<skill_id, int> &req,
352         const item &context ) const
353 {
354     return std::all_of( req.begin(), req.end(),
355     [this, &context]( const std::pair<skill_id, int> &pr ) {
356         return get_skill_level( pr.first, context ) >= pr.second;
357     } );
358 }
359 
compare_skill_requirements(const std::map<skill_id,int> & req) const360 std::map<skill_id, int> SkillLevelMap::compare_skill_requirements(
361     const std::map<skill_id, int> &req ) const
362 {
363     return compare_skill_requirements( req, item() );
364 }
365 
compare_skill_requirements(const std::map<skill_id,int> & req,const item & context) const366 std::map<skill_id, int> SkillLevelMap::compare_skill_requirements(
367     const std::map<skill_id, int> &req, const item &context ) const
368 {
369     std::map<skill_id, int> res;
370 
371     for( const auto &elem : req ) {
372         const int diff = get_skill_level( elem.first, context ) - elem.second;
373         if( diff != 0 ) {
374             res[elem.first] = diff;
375         }
376     }
377 
378     return res;
379 }
380 
exceeds_recipe_requirements(const recipe & rec) const381 int SkillLevelMap::exceeds_recipe_requirements( const recipe &rec ) const
382 {
383     int over = rec.skill_used ? get_skill_level( rec.skill_used ) - rec.difficulty : 0;
384     for( const auto &elem : compare_skill_requirements( rec.required_skills ) ) {
385         over = std::min( over, elem.second );
386     }
387     return over;
388 }
389 
has_recipe_requirements(const recipe & rec) const390 bool SkillLevelMap::has_recipe_requirements( const recipe &rec ) const
391 {
392     return exceeds_recipe_requirements( rec ) >= 0;
393 }
394 
395 // Actually take the difference in social skill between the two parties involved
396 // Caps at 200% when you are 5 levels ahead, int comparison is handled in npctalk.cpp
price_adjustment(int barter_skill)397 double price_adjustment( int barter_skill )
398 {
399     if( barter_skill <= 0 ) {
400         return 1.0;
401     }
402     if( barter_skill >= 5 ) {
403         return 2.0;
404     }
405     switch( barter_skill ) {
406         case 1:
407             return 1.05;
408         case 2:
409             return 1.15;
410         case 3:
411             return 1.30;
412         case 4:
413             return 1.65;
414         default:
415             // Should never occur
416             return 1.0;
417     }
418 }
419