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