1 #include "npc_class.h"
2
3 #include <algorithm>
4 #include <array>
5 #include <cstddef>
6 #include <iterator>
7 #include <list>
8 #include <set>
9 #include <string>
10 #include <utility>
11
12 #include "debug.h"
13 #include "generic_factory.h"
14 #include "item_group.h"
15 #include "json.h"
16 #include "mutation.h"
17 #include "rng.h"
18 #include "skill.h"
19 #include "trait_group.h"
20
21 static const std::array<npc_class_id, 19> legacy_ids = {{
22 npc_class_id( "NC_NONE" ),
23 npc_class_id( "NC_EVAC_SHOPKEEP" ), // Found in the Evacuation Center, unique, has more goods than he should be able to carry
24 npc_class_id( "NC_SHOPKEEP" ), // Found in towns. Stays in his shop mostly.
25 npc_class_id( "NC_HACKER" ), // Weak in combat but has hacking skills and equipment
26 npc_class_id( "NC_CYBORG" ), // Broken Cyborg rescued from a lab
27 npc_class_id( "NC_DOCTOR" ), // Found in towns, or roaming. Stays in the clinic.
28 npc_class_id( "NC_TRADER" ), // Roaming trader, journeying between towns.
29 npc_class_id( "NC_NINJA" ), // Specializes in unarmed combat, carries few items
30 npc_class_id( "NC_COWBOY" ), // Gunslinger and survivalist
31 npc_class_id( "NC_SCIENTIST" ), // Uses intelligence-based skills and high-tech items
32 npc_class_id( "NC_BOUNTY_HUNTER" ), // Resourceful and well-armored
33 npc_class_id( "NC_THUG" ), // Moderate melee skills and poor equipment
34 npc_class_id( "NC_SCAVENGER" ), // Good with pistols light weapons
35 npc_class_id( "NC_ARSONIST" ), // Evacuation Center, restocks Molotovs and anarchist type stuff
36 npc_class_id( "NC_HUNTER" ), // Survivor type good with bow or rifle
37 npc_class_id( "NC_SOLDIER" ), // Well equipped and trained combatant, good with rifles and melee
38 npc_class_id( "NC_BARTENDER" ), // Stocks alcohol
39 npc_class_id( "NC_JUNK_SHOPKEEP" ), // Stocks wide range of items...
40 npc_class_id( "NC_HALLU" ) // Hallucinatory NPCs
41 }
42 };
43
44 const npc_class_id NC_NONE( "NC_NONE" );
45 const npc_class_id NC_EVAC_SHOPKEEP( "NC_EVAC_SHOPKEEP" );
46 const npc_class_id NC_SHOPKEEP( "NC_SHOPKEEP" );
47 const npc_class_id NC_HACKER( "NC_HACKER" );
48 const npc_class_id NC_CYBORG( "NC_CYBORG" );
49 const npc_class_id NC_DOCTOR( "NC_DOCTOR" );
50 const npc_class_id NC_TRADER( "NC_TRADER" );
51 const npc_class_id NC_NINJA( "NC_NINJA" );
52 const npc_class_id NC_COWBOY( "NC_COWBOY" );
53 const npc_class_id NC_SCIENTIST( "NC_SCIENTIST" );
54 const npc_class_id NC_BOUNTY_HUNTER( "NC_BOUNTY_HUNTER" );
55 const npc_class_id NC_THUG( "NC_THUG" );
56 const npc_class_id NC_SCAVENGER( "NC_SCAVENGER" );
57 const npc_class_id NC_ARSONIST( "NC_ARSONIST" );
58 const npc_class_id NC_HUNTER( "NC_HUNTER" );
59 const npc_class_id NC_SOLDIER( "NC_SOLDIER" );
60 const npc_class_id NC_BARTENDER( "NC_BARTENDER" );
61 const npc_class_id NC_JUNK_SHOPKEEP( "NC_JUNK_SHOPKEEP" );
62 const npc_class_id NC_HALLU( "NC_HALLU" );
63
64 static generic_factory<npc_class> npc_class_factory( "npc_class" );
65
66 /** @relates string_id */
67 template<>
obj() const68 const npc_class &string_id<npc_class>::obj() const
69 {
70 return npc_class_factory.obj( *this );
71 }
72
73 /** @relates string_id */
74 template<>
is_valid() const75 bool string_id<npc_class>::is_valid() const
76 {
77 return npc_class_factory.is_valid( *this );
78 }
79
npc_class()80 npc_class::npc_class() : id( NC_NONE )
81 {
82 }
83
load_npc_class(const JsonObject & jo,const std::string & src)84 void npc_class::load_npc_class( const JsonObject &jo, const std::string &src )
85 {
86 npc_class_factory.load( jo, src );
87 }
88
reset_npc_classes()89 void npc_class::reset_npc_classes()
90 {
91 npc_class_factory.reset();
92 }
93
94 // Copies the value under the key "ALL" to all unassigned skills
95 template <typename T>
apply_all_to_unassigned(T & skills)96 void apply_all_to_unassigned( T &skills )
97 {
98 auto iter = std::find_if( skills.begin(), skills.end(),
99 []( decltype( *begin( skills ) ) &pr ) {
100 return pr.first.str() == "ALL";
101 } );
102
103 if( iter != skills.end() ) {
104 distribution dis = iter->second;
105 skills.erase( iter );
106 for( const auto &sk : Skill::skills ) {
107 if( skills.count( sk.ident() ) == 0 ) {
108 skills[ sk.ident() ] = dis;
109 }
110 }
111 }
112 }
113
finalize_all()114 void npc_class::finalize_all()
115 {
116 for( const npc_class &cl_const : npc_class_factory.get_all() ) {
117 auto &cl = const_cast<npc_class &>( cl_const );
118 apply_all_to_unassigned( cl.skills );
119 apply_all_to_unassigned( cl.bonus_skills );
120
121 for( const auto &pr : cl.bonus_skills ) {
122 if( cl.skills.count( pr.first ) == 0 ) {
123 cl.skills[ pr.first ] = pr.second;
124 } else {
125 cl.skills[ pr.first ] = cl.skills[ pr.first ] + pr.second;
126 }
127 }
128 }
129 }
130
check_consistency()131 void npc_class::check_consistency()
132 {
133 for( const auto &legacy : legacy_ids ) {
134 if( !npc_class_factory.is_valid( legacy ) ) {
135 debugmsg( "Missing legacy npc class %s", legacy.c_str() );
136 }
137 }
138
139 for( const npc_class &cl : npc_class_factory.get_all() ) {
140 if( !item_group::group_is_defined( cl.shopkeeper_item_group ) ) {
141 debugmsg( "Missing shopkeeper item group %s", cl.shopkeeper_item_group.c_str() );
142 }
143
144 if( !cl.worn_override.is_empty() && !item_group::group_is_defined( cl.worn_override ) ) {
145 debugmsg( "Missing worn override item group %s", cl.worn_override.c_str() );
146 }
147
148 if( !cl.carry_override.is_empty() && !item_group::group_is_defined( cl.carry_override ) ) {
149 debugmsg( "Missing carry override item group %s", cl.carry_override.c_str() );
150 }
151
152 if( !cl.weapon_override.is_empty() && !item_group::group_is_defined( cl.weapon_override ) ) {
153 debugmsg( "Missing weapon override item group %s", cl.weapon_override.c_str() );
154 }
155
156 for( const auto &pr : cl.skills ) {
157 if( !pr.first.is_valid() ) {
158 debugmsg( "Invalid skill %s", pr.first.c_str() );
159 }
160 }
161
162 if( !cl.traits.is_valid() ) {
163 debugmsg( "Trait group %s is undefined", cl.traits.c_str() );
164 }
165 }
166 }
167
load_distribution(const JsonObject & jo)168 static distribution load_distribution( const JsonObject &jo )
169 {
170 if( jo.has_float( "constant" ) ) {
171 return distribution::constant( jo.get_float( "constant" ) );
172 }
173
174 if( jo.has_float( "one_in" ) ) {
175 return distribution::one_in( jo.get_float( "one_in" ) );
176 }
177
178 if( jo.has_array( "dice" ) ) {
179 JsonArray jarr = jo.get_array( "dice" );
180 return distribution::dice_roll( jarr.get_int( 0 ), jarr.get_int( 1 ) );
181 }
182
183 if( jo.has_array( "rng" ) ) {
184 JsonArray jarr = jo.get_array( "rng" );
185 return distribution::rng_roll( jarr.get_int( 0 ), jarr.get_int( 1 ) );
186 }
187
188 if( jo.has_array( "sum" ) ) {
189 JsonArray jarr = jo.get_array( "sum" );
190 JsonObject obj = jarr.next_object();
191 distribution ret = load_distribution( obj );
192 while( jarr.has_more() ) {
193 obj = jarr.next_object();
194 ret = ret + load_distribution( obj );
195 }
196
197 return ret;
198 }
199
200 if( jo.has_array( "mul" ) ) {
201 JsonArray jarr = jo.get_array( "mul" );
202 JsonObject obj = jarr.next_object();
203 distribution ret = load_distribution( obj );
204 while( jarr.has_more() ) {
205 obj = jarr.next_object();
206 ret = ret * load_distribution( obj );
207 }
208
209 return ret;
210 }
211
212 jo.throw_error( "Invalid distribution" );
213 }
214
load_distribution(const JsonObject & jo,const std::string & name)215 static distribution load_distribution( const JsonObject &jo, const std::string &name )
216 {
217 if( !jo.has_member( name ) ) {
218 return distribution();
219 }
220
221 if( jo.has_float( name ) ) {
222 return distribution::constant( jo.get_float( name ) );
223 }
224
225 if( jo.has_object( name ) ) {
226 JsonObject obj = jo.get_object( name );
227 return load_distribution( obj );
228 }
229
230 jo.throw_error( "Invalid distribution type", name );
231 }
232
load(const JsonObject & jo,const std::string &)233 void npc_class::load( const JsonObject &jo, const std::string & )
234 {
235 mandatory( jo, was_loaded, "name", name );
236 mandatory( jo, was_loaded, "job_description", job_description );
237
238 optional( jo, was_loaded, "common", common, true );
239 bonus_str = load_distribution( jo, "bonus_str" );
240 bonus_dex = load_distribution( jo, "bonus_dex" );
241 bonus_int = load_distribution( jo, "bonus_int" );
242 bonus_per = load_distribution( jo, "bonus_per" );
243
244 optional( jo, was_loaded, "shopkeeper_item_group", shopkeeper_item_group,
245 item_group_id( "EMPTY_GROUP" ) );
246 optional( jo, was_loaded, "worn_override", worn_override );
247 optional( jo, was_loaded, "carry_override", carry_override );
248 optional( jo, was_loaded, "weapon_override", weapon_override );
249
250 if( jo.has_member( "traits" ) ) {
251 traits = trait_group::load_trait_group( jo.get_member( "traits" ), "collection" );
252 }
253
254 if( jo.has_array( "spells" ) ) {
255 for( JsonObject subobj : jo.get_array( "spells" ) ) {
256 const int level = subobj.get_int( "level" );
257 const spell_id sp = spell_id( subobj.get_string( "id" ) );
258 _starting_spells.emplace( sp, level );
259 }
260 }
261
262 optional( jo, was_loaded, "proficiencies", _starting_proficiencies );
263 /* Mutation rounds can be specified as follows:
264 * "mutation_rounds": {
265 * "ANY" : { "constant": 1 },
266 * "INSECT" : { "rng": [1, 3] }
267 * }
268 */
269 if( jo.has_object( "mutation_rounds" ) ) {
270 const std::map<mutation_category_id, mutation_category_trait> &mutation_categories =
271 mutation_category_trait::get_all();
272 for( const JsonMember member : jo.get_object( "mutation_rounds" ) ) {
273 const mutation_category_id mutation( member.name() );
274 const auto category_match = [&mutation]( const
275 std::pair<const mutation_category_id, mutation_category_trait>
276 &p ) {
277 return p.second.id == mutation;
278 };
279 if( std::find_if( mutation_categories.begin(), mutation_categories.end(),
280 category_match ) == mutation_categories.end() ) {
281 debugmsg( "Unrecognized mutation category %s", mutation.str() );
282 continue;
283 }
284 JsonObject distrib = member.get_object();
285 mutation_rounds[mutation] = load_distribution( distrib );
286 }
287 }
288
289 if( jo.has_array( "skills" ) ) {
290 for( JsonObject skill_obj : jo.get_array( "skills" ) ) {
291 auto skill_ids = skill_obj.get_tags( "skill" );
292 if( skill_obj.has_object( "level" ) ) {
293 const distribution dis = load_distribution( skill_obj, "level" );
294 for( const auto &sid : skill_ids ) {
295 skills[ skill_id( sid ) ] = dis;
296 }
297 } else {
298 const distribution dis = load_distribution( skill_obj, "bonus" );
299 for( const auto &sid : skill_ids ) {
300 bonus_skills[ skill_id( sid ) ] = dis;
301 }
302 }
303 }
304 }
305
306 if( jo.has_array( "bionics" ) ) {
307 for( JsonObject bionic_obj : jo.get_array( "bionics" ) ) {
308 auto bionic_ids = bionic_obj.get_tags( "id" );
309 int chance = bionic_obj.get_int( "chance" );
310 for( const auto &bid : bionic_ids ) {
311 bionic_list[ bionic_id( bid )] = chance;
312 }
313 }
314 }
315 }
316
from_legacy_int(int i)317 const npc_class_id &npc_class::from_legacy_int( int i )
318 {
319 if( i < 0 || static_cast<size_t>( i ) >= legacy_ids.size() ) {
320 debugmsg( "Invalid legacy class id: %d", i );
321 return npc_class_id::NULL_ID();
322 }
323
324 return legacy_ids[ i ];
325 }
326
get_all()327 const std::vector<npc_class> &npc_class::get_all()
328 {
329 return npc_class_factory.get_all();
330 }
331
random_common()332 const npc_class_id &npc_class::random_common()
333 {
334 std::list<const npc_class_id *> common_classes;
335 for( const auto &pr : npc_class_factory.get_all() ) {
336 if( pr.common ) {
337 common_classes.push_back( &pr.id );
338 }
339 }
340
341 if( common_classes.empty() || one_in( common_classes.size() ) ) {
342 return NC_NONE;
343 }
344
345 return *random_entry( common_classes );
346 }
347
get_name() const348 std::string npc_class::get_name() const
349 {
350 return name.translated();
351 }
352
get_job_description() const353 std::string npc_class::get_job_description() const
354 {
355 return job_description.translated();
356 }
357
get_shopkeeper_items() const358 const item_group_id &npc_class::get_shopkeeper_items() const
359 {
360 return shopkeeper_item_group;
361 }
362
roll_strength() const363 int npc_class::roll_strength() const
364 {
365 return dice( 4, 3 ) + bonus_str.roll();
366 }
367
roll_dexterity() const368 int npc_class::roll_dexterity() const
369 {
370 return dice( 4, 3 ) + bonus_dex.roll();
371 }
372
roll_intelligence() const373 int npc_class::roll_intelligence() const
374 {
375 return dice( 4, 3 ) + bonus_int.roll();
376 }
377
roll_perception() const378 int npc_class::roll_perception() const
379 {
380 return dice( 4, 3 ) + bonus_per.roll();
381 }
382
roll_skill(const skill_id & sid) const383 int npc_class::roll_skill( const skill_id &sid ) const
384 {
385 const auto &iter = skills.find( sid );
386 if( iter == skills.end() ) {
387 return 0;
388 }
389
390 return std::max<int>( 0, iter->second.roll() );
391 }
392
distribution()393 distribution::distribution()
394 {
395 generator_function = []() {
396 return 0.0f;
397 };
398 }
399
distribution(const distribution & d)400 distribution::distribution( const distribution &d )
401 {
402 generator_function = d.generator_function;
403 }
404
distribution(const std::function<float ()> & gen)405 distribution::distribution( const std::function<float()> &gen )
406 {
407 generator_function = gen;
408 }
409
roll() const410 float distribution::roll() const
411 {
412 return generator_function();
413 }
414
constant(float val)415 distribution distribution::constant( float val )
416 {
417 return distribution( [val]() {
418 return val;
419 } );
420 }
421
one_in(float in)422 distribution distribution::one_in( float in )
423 {
424 if( in <= 1.0f ) {
425 debugmsg( "Invalid one_in: %.2f", in );
426 return distribution();
427 }
428
429 return distribution( [in]() {
430 return x_in_y( 1, in );
431 } );
432 }
433
rng_roll(int from,int to)434 distribution distribution::rng_roll( int from, int to )
435 {
436 return distribution( [from, to]() -> float {
437 return rng( from, to );
438 } );
439 }
440
dice_roll(int sides,int size)441 distribution distribution::dice_roll( int sides, int size )
442 {
443 if( sides < 1 || size < 1 ) {
444 debugmsg( "Invalid dice: %d sides, %d sizes", sides, size );
445 return distribution();
446 }
447
448 return distribution( [sides, size]() -> float {
449 return dice( sides, size );
450 } );
451 }
452
operator +(const distribution & other) const453 distribution distribution::operator+( const distribution &other ) const
454 {
455 auto my_fun = generator_function;
456 auto other_fun = other.generator_function;
457 return distribution( [my_fun, other_fun]() {
458 return my_fun() + other_fun();
459 } );
460 }
461
operator *(const distribution & other) const462 distribution distribution::operator*( const distribution &other ) const
463 {
464 auto my_fun = generator_function;
465 auto other_fun = other.generator_function;
466 return distribution( [my_fun, other_fun]() {
467 return my_fun() * other_fun();
468 } );
469 }
470
471 distribution &distribution::operator=( const distribution &other ) = default;
472