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