1 #include "profession.h"
2 
3 #include <algorithm>
4 #include <cmath>
5 #include <functional>
6 #include <iterator>
7 #include <map>
8 #include <memory>
9 
10 #include "addiction.h"
11 #include "avatar.h"
12 #include "calendar.h"
13 #include "debug.h"
14 #include "flag.h"
15 #include "generic_factory.h"
16 #include "item.h"
17 #include "item_contents.h"
18 #include "item_group.h"
19 #include "itype.h"
20 #include "json.h"
21 #include "magic.h"
22 #include "options.h"
23 #include "pimpl.h"
24 #include "player.h"
25 #include "pldata.h"
26 #include "translations.h"
27 #include "type_id.h"
28 #include "visitable.h"
29 
30 namespace
31 {
32 generic_factory<profession> all_profs( "profession" );
33 const string_id<profession> generic_profession_id( "unemployed" );
34 } // namespace
35 
36 static class json_item_substitution
37 {
38     public:
39         void reset();
40         void load( const JsonObject &jo );
41         void check_consistency();
42 
43     private:
44         struct trait_requirements {
45             explicit trait_requirements( const JsonObject &obj );
46             trait_requirements() = default;
47             std::vector<trait_id> present;
48             std::vector<trait_id> absent;
49             bool meets_condition( const std::vector<trait_id> &traits ) const;
50         };
51         struct substitution {
52             trait_requirements trait_reqs;
53             struct info {
54                 explicit info( const JsonValue &value );
55                 info() = default;
56                 itype_id new_item;
57                 double ratio = 1.0; // new charges / old charges
58             };
59             std::vector<info> infos;
60         };
61         std::map<itype_id, std::vector<substitution>> substitutions;
62         std::vector<std::pair<item_group_id, trait_requirements>> bonuses;
63     public:
64         std::vector<item> get_bonus_items( const std::vector<trait_id> &traits ) const;
65         std::vector<item> get_substitution( const item &it, const std::vector<trait_id> &traits ) const;
66 } item_substitutions;
67 
68 /** @relates string_id */
69 template<>
obj() const70 const profession &string_id<profession>::obj() const
71 {
72     return all_profs.obj( *this );
73 }
74 
75 /** @relates string_id */
76 template<>
is_valid() const77 bool string_id<profession>::is_valid() const
78 {
79     return all_profs.is_valid( *this );
80 }
81 
profession()82 profession::profession()
83     : _name_male( no_translation( "null" ) ),
84       _name_female( no_translation( "null" ) ),
85       _description_male( no_translation( "null" ) ),
86       _description_female( no_translation( "null" ) )
87 {
88 }
89 
load_profession(const JsonObject & jo,const std::string & src)90 void profession::load_profession( const JsonObject &jo, const std::string &src )
91 {
92     all_profs.load( jo, src );
93 }
94 
95 class skilllevel_reader : public generic_typed_reader<skilllevel_reader>
96 {
97     public:
get_next(JsonIn & jin) const98         std::pair<skill_id, int> get_next( JsonIn &jin ) const {
99             JsonObject jo = jin.get_object();
100             return std::pair<skill_id, int>( skill_id( jo.get_string( "name" ) ), jo.get_int( "level" ) );
101         }
102         template<typename C>
erase_next(JsonIn & jin,C & container) const103         void erase_next( JsonIn &jin, C &container ) const {
104             const skill_id id = skill_id( jin.get_string() );
105             reader_detail::handler<C>().erase_if( container, [&id]( const std::pair<skill_id, int> &e ) {
106                 return e.first == id;
107             } );
108         }
109 };
110 
111 class addiction_reader : public generic_typed_reader<addiction_reader>
112 {
113     public:
get_next(JsonIn & jin) const114         addiction get_next( JsonIn &jin ) const {
115             JsonObject jo = jin.get_object();
116             return addiction( addiction_type( jo.get_string( "type" ) ), jo.get_int( "intensity" ) );
117         }
118         template<typename C>
erase_next(JsonIn & jin,C & container) const119         void erase_next( JsonIn &jin, C &container ) const {
120             const add_type type = addiction_type( jin.get_string() );
121             reader_detail::handler<C>().erase_if( container, [&type]( const addiction & e ) {
122                 return e.type == type;
123             } );
124         }
125 };
126 
127 class item_reader : public generic_typed_reader<item_reader>
128 {
129     public:
get_next(JsonIn & jin) const130         profession::itypedec get_next( JsonIn &jin ) const {
131             // either a plain item type id string, or an array with item type id
132             // and as second entry the item description.
133             if( jin.test_string() ) {
134                 return profession::itypedec( jin.get_string() );
135             }
136             JsonArray jarr = jin.get_array();
137             const auto id = jarr.get_string( 0 );
138             const snippet_id snippet( jarr.get_string( 1 ) );
139             return profession::itypedec( id, snippet );
140         }
141         template<typename C>
erase_next(JsonIn & jin,C & container) const142         void erase_next( JsonIn &jin, C &container ) const {
143             const std::string id = jin.get_string();
144             reader_detail::handler<C>().erase_if( container, [&id]( const profession::itypedec & e ) {
145                 return e.type_id.str() == id;
146             } );
147         }
148 };
149 
load(const JsonObject & jo,const std::string &)150 void profession::load( const JsonObject &jo, const std::string & )
151 {
152     //If the "name" is an object then we have to deal with gender-specific titles,
153     if( jo.has_object( "name" ) ) {
154         JsonObject name_obj = jo.get_object( "name" );
155         // Specifying translation context here to avoid adding unnecessary json code for every profession
156         // NOLINTNEXTLINE(cata-json-translation-input)
157         _name_male = to_translation( "profession_male", name_obj.get_string( "male" ) );
158         // NOLINTNEXTLINE(cata-json-translation-input)
159         _name_female = to_translation( "profession_female", name_obj.get_string( "female" ) );
160     } else if( jo.has_string( "name" ) ) {
161         // Same profession names for male and female in English.
162         // Still need to different names in other languages.
163         const std::string name = jo.get_string( "name" );
164         _name_female = to_translation( "profession_female", name );
165         _name_male = to_translation( "profession_male", name );
166     } else if( !was_loaded ) {
167         jo.throw_error( "missing mandatory member \"name\"" );
168     }
169 
170     if( !was_loaded || jo.has_member( "description" ) ) {
171         std::string desc;
172         mandatory( jo, false, "description", desc, text_style_check_reader() );
173         // These also may differ depending on the language settings!
174         _description_male = to_translation( "prof_desc_male", desc );
175         _description_female = to_translation( "prof_desc_female", desc );
176     }
177     if( jo.has_string( "vehicle" ) ) {
178         _starting_vehicle = vproto_id( jo.get_string( "vehicle" ) );
179     }
180     if( jo.has_array( "pets" ) ) {
181         for( JsonObject subobj : jo.get_array( "pets" ) ) {
182             int count = subobj.get_int( "amount" );
183             mtype_id mon = mtype_id( subobj.get_string( "name" ) );
184             for( int start = 0; start < count; ++start ) {
185                 _starting_pets.push_back( mon );
186             }
187         }
188     }
189 
190     if( jo.has_array( "spells" ) ) {
191         for( JsonObject subobj : jo.get_array( "spells" ) ) {
192             int level = subobj.get_int( "level" );
193             spell_id sp = spell_id( subobj.get_string( "id" ) );
194             _starting_spells.emplace( sp, level );
195         }
196     }
197 
198     mandatory( jo, was_loaded, "points", _point_cost );
199 
200     if( !was_loaded || jo.has_member( "items" ) ) {
201         std::string c = "items for profession " + id.str();
202         JsonObject items_obj = jo.get_object( "items" );
203 
204         if( items_obj.has_array( "both" ) ) {
205             optional( items_obj, was_loaded, "both", legacy_starting_items, item_reader {} );
206         }
207         if( items_obj.has_object( "both" ) ) {
208             _starting_items = item_group::load_item_group(
209                                   items_obj.get_member( "both" ), "collection", c );
210         }
211         if( items_obj.has_array( "male" ) ) {
212             optional( items_obj, was_loaded, "male", legacy_starting_items_male, item_reader {} );
213         }
214         if( items_obj.has_object( "male" ) ) {
215             _starting_items_male = item_group::load_item_group(
216                                        items_obj.get_member( "male" ), "collection", c );
217         }
218         if( items_obj.has_array( "female" ) ) {
219             optional( items_obj, was_loaded, "female",  legacy_starting_items_female, item_reader {} );
220         }
221         if( items_obj.has_object( "female" ) ) {
222             _starting_items_female = item_group::load_item_group( items_obj.get_member( "female" ),
223                                      "collection", c );
224         }
225     }
226     optional( jo, was_loaded, "no_bonus", no_bonus );
227 
228     optional( jo, was_loaded, "skills", _starting_skills, skilllevel_reader {} );
229     optional( jo, was_loaded, "addictions", _starting_addictions, addiction_reader {} );
230     // TODO: use string_id<bionic_type> or so
231     optional( jo, was_loaded, "CBMs", _starting_CBMs, auto_flags_reader<bionic_id> {} );
232     optional( jo, was_loaded, "proficiencies", _starting_proficiencies );
233     // TODO: use string_id<mutation_branch> or so
234     optional( jo, was_loaded, "traits", _starting_traits, auto_flags_reader<trait_id> {} );
235     optional( jo, was_loaded, "forbidden_traits", _forbidden_traits, auto_flags_reader<trait_id> {} );
236     optional( jo, was_loaded, "flags", flags, auto_flags_reader<> {} );
237 }
238 
generic()239 const profession *profession::generic()
240 {
241     return &generic_profession_id.obj();
242 }
243 
get_all()244 const std::vector<profession> &profession::get_all()
245 {
246     return all_profs.get_all();
247 }
248 
reset()249 void profession::reset()
250 {
251     all_profs.reset();
252     item_substitutions.reset();
253 }
254 
check_definitions()255 void profession::check_definitions()
256 {
257     item_substitutions.check_consistency();
258     for( const auto &prof : all_profs.get_all() ) {
259         prof.check_definition();
260     }
261 }
262 
check_item_definitions(const itypedecvec & items) const263 void profession::check_item_definitions( const itypedecvec &items ) const
264 {
265     for( const auto &itd : items ) {
266         if( !item::type_is_defined( itd.type_id ) ) {
267             debugmsg( "profession %s: item %s does not exist", id.str(), itd.type_id.str() );
268         } else if( !itd.snip_id.is_null() ) {
269             const itype *type = item::find_type( itd.type_id );
270             if( type->snippet_category.empty() ) {
271                 debugmsg( "profession %s: item %s has no snippet category - no description can "
272                           "be set", id.str(), itd.type_id.str() );
273             } else {
274                 if( !itd.snip_id.is_valid() ) {
275                     debugmsg( "profession %s: there's no snippet with id %s",
276                               id.str(), itd.snip_id.str() );
277                 }
278             }
279         }
280     }
281 }
282 
check_definition() const283 void profession::check_definition() const
284 {
285     check_item_definitions( legacy_starting_items );
286     check_item_definitions( legacy_starting_items_female );
287     check_item_definitions( legacy_starting_items_male );
288     if( !no_bonus.is_empty() && !item::type_is_defined( no_bonus ) ) {
289         debugmsg( "no_bonus item '%s' is not an itype_id", no_bonus.c_str() );
290     }
291 
292     if( !item_group::group_is_defined( _starting_items ) ) {
293         debugmsg( "_starting_items group is undefined" );
294     }
295     if( !item_group::group_is_defined( _starting_items_male ) ) {
296         debugmsg( "_starting_items_male group is undefined" );
297     }
298     if( !item_group::group_is_defined( _starting_items_female ) ) {
299         debugmsg( "_starting_items_female group is undefined" );
300     }
301     if( _starting_vehicle && !_starting_vehicle.is_valid() ) {
302         debugmsg( "vehicle prototype %s for profession %s does not exist", _starting_vehicle.c_str(),
303                   id.c_str() );
304     }
305     for( const auto &a : _starting_CBMs ) {
306         if( !a.is_valid() ) {
307             debugmsg( "bionic %s for profession %s does not exist", a.c_str(), id.c_str() );
308         }
309     }
310 
311     for( const proficiency_id &pid : _starting_proficiencies ) {
312         if( !pid.is_valid() ) {
313             debugmsg( "proficiency %s for profession %s does not exist", pid.str(), id.str() );
314         }
315     }
316 
317     for( const auto &t : _starting_traits ) {
318         if( !t.is_valid() ) {
319             debugmsg( "trait %s for profession %s does not exist", t.c_str(), id.c_str() );
320         }
321     }
322     for( const auto &elem : _starting_pets ) {
323         if( !elem.is_valid() ) {
324             debugmsg( "starting pet %s for profession %s does not exist", elem.c_str(), id.c_str() );
325         }
326     }
327     for( const auto &elem : _starting_skills ) {
328         if( !elem.first.is_valid() ) {
329             debugmsg( "skill %s for profession %s does not exist", elem.first.c_str(), id.c_str() );
330         }
331     }
332 }
333 
has_initialized()334 bool profession::has_initialized()
335 {
336     return generic_profession_id.is_valid();
337 }
338 
ident() const339 const string_id<profession> &profession::ident() const
340 {
341     return id;
342 }
343 
gender_appropriate_name(bool male) const344 std::string profession::gender_appropriate_name( bool male ) const
345 {
346     if( male ) {
347         return _name_male.translated();
348     } else {
349         return _name_female.translated();
350     }
351 }
352 
description(bool male) const353 std::string profession::description( bool male ) const
354 {
355     if( male ) {
356         return _description_male.translated();
357     } else {
358         return _description_female.translated();
359     }
360 }
361 
advanced_spawn_time()362 static time_point advanced_spawn_time()
363 {
364     const int initial_days = get_option<int>( "INITIAL_DAY" );
365     return calendar::before_time_starts + 1_days * initial_days;
366 }
367 
point_cost() const368 signed int profession::point_cost() const
369 {
370     return _point_cost;
371 }
372 
clear_faults(item & it)373 static void clear_faults( item &it )
374 {
375     if( it.get_var( "dirt", 0 ) > 0 ) {
376         it.set_var( "dirt", 0 );
377     }
378     if( it.is_faulty() ) {
379         it.faults.clear();
380     }
381 }
382 
items(bool male,const std::vector<trait_id> & traits) const383 std::list<item> profession::items( bool male, const std::vector<trait_id> &traits ) const
384 {
385     std::list<item> result;
386     auto add_legacy_items = [&result]( const itypedecvec & vec ) {
387         for( const itypedec &elem : vec ) {
388             item it( elem.type_id, advanced_spawn_time(), item::default_charges_tag {} );
389             if( !elem.snip_id.is_null() ) {
390                 it.set_snippet( elem.snip_id );
391             }
392             it = it.in_its_container();
393             result.push_back( it );
394         }
395     };
396 
397     add_legacy_items( legacy_starting_items );
398     add_legacy_items( male ? legacy_starting_items_male : legacy_starting_items_female );
399 
400     const std::vector<item> group_both = item_group::items_from( _starting_items,
401                                          advanced_spawn_time() );
402     const std::vector<item> group_gender = item_group::items_from( male ? _starting_items_male :
403                                            _starting_items_female, advanced_spawn_time() );
404     result.insert( result.begin(), group_both.begin(), group_both.end() );
405     result.insert( result.begin(), group_gender.begin(), group_gender.end() );
406 
407     if( !has_flag( "NO_BONUS_ITEMS" ) ) {
408         const std::vector<item> &items = item_substitutions.get_bonus_items( traits );
409         for( const item &it : items ) {
410             if( it.typeId() != no_bonus ) {
411                 result.push_back( it );
412             }
413         }
414     }
415     for( auto iter = result.begin(); iter != result.end(); ) {
416         const auto sub = item_substitutions.get_substitution( *iter, traits );
417         if( !sub.empty() ) {
418             result.insert( result.begin(), sub.begin(), sub.end() );
419             iter = result.erase( iter );
420         } else {
421             ++iter;
422         }
423     }
424     for( item &it : result ) {
425         it.visit_items( []( item * it, item * ) {
426             clear_faults( *it );
427             return VisitResponse::NEXT;
428         } );
429         if( it.has_flag( flag_VARSIZE ) ) {
430             it.set_flag( flag_FIT );
431         }
432     }
433 
434     if( result.empty() ) {
435         // No need to do the below stuff. Plus it would cause said below stuff to crash
436         return result;
437     }
438 
439     // Merge charges for items that stack with each other
440     for( auto outer = result.begin(); outer != result.end(); ++outer ) {
441         if( !outer->count_by_charges() ) {
442             continue;
443         }
444         for( auto inner = std::next( outer ); inner != result.end(); ) {
445             if( outer->stacks_with( *inner ) ) {
446                 outer->merge_charges( *inner );
447                 inner = result.erase( inner );
448             } else {
449                 ++inner;
450             }
451         }
452     }
453 
454     result.sort( []( const item & first, const item & second ) {
455         return first.get_layer() < second.get_layer();
456     } );
457     return result;
458 }
459 
vehicle() const460 vproto_id profession::vehicle() const
461 {
462     return _starting_vehicle;
463 }
464 
pets() const465 std::vector<mtype_id> profession::pets() const
466 {
467     return _starting_pets;
468 }
469 
addictions() const470 std::vector<addiction> profession::addictions() const
471 {
472     return _starting_addictions;
473 }
474 
CBMs() const475 std::vector<bionic_id> profession::CBMs() const
476 {
477     return _starting_CBMs;
478 }
479 
proficiencies() const480 std::vector<proficiency_id> profession::proficiencies() const
481 {
482     return _starting_proficiencies;
483 }
484 
get_locked_traits() const485 std::vector<trait_id> profession::get_locked_traits() const
486 {
487     return _starting_traits;
488 }
489 
get_forbidden_traits() const490 std::set<trait_id> profession::get_forbidden_traits() const
491 {
492     return _forbidden_traits;
493 }
494 
skills() const495 profession::StartingSkillList profession::skills() const
496 {
497     return _starting_skills;
498 }
499 
has_flag(const std::string & flag) const500 bool profession::has_flag( const std::string &flag ) const
501 {
502     return flags.count( flag ) != 0;
503 }
504 
can_pick(const player & u,const int points) const505 bool profession::can_pick( const player &u, const int points ) const
506 {
507     return point_cost() - u.prof->point_cost() <= points;
508 }
509 
is_locked_trait(const trait_id & trait) const510 bool profession::is_locked_trait( const trait_id &trait ) const
511 {
512     return std::find( _starting_traits.begin(), _starting_traits.end(), trait ) !=
513            _starting_traits.end();
514 }
515 
is_forbidden_trait(const trait_id & trait) const516 bool profession::is_forbidden_trait( const trait_id &trait ) const
517 {
518     return _forbidden_traits.count( trait ) != 0;
519 }
520 
spells() const521 std::map<spell_id, int> profession::spells() const
522 {
523     return _starting_spells;
524 }
525 
learn_spells(avatar & you) const526 void profession::learn_spells( avatar &you ) const
527 {
528     for( const std::pair<spell_id, int> spell_pair : spells() ) {
529         you.magic->learn_spell( spell_pair.first, you, true );
530         spell &sp = you.magic->get_spell( spell_pair.first );
531         while( sp.get_level() < spell_pair.second && !sp.is_max_level() ) {
532             sp.gain_level();
533         }
534     }
535 }
536 
537 // item_substitution stuff:
538 
load_item_substitutions(const JsonObject & jo)539 void profession::load_item_substitutions( const JsonObject &jo )
540 {
541     item_substitutions.load( jo );
542 }
543 
reset()544 void json_item_substitution::reset()
545 {
546     substitutions.clear();
547     bonuses.clear();
548 }
549 
info(const JsonValue & value)550 json_item_substitution::substitution::info::info( const JsonValue &value )
551 {
552     if( value.test_string() ) {
553         value.read( new_item, true );
554     } else {
555         const JsonObject jo = value.get_object();
556         jo.read( "item", new_item, true );
557         ratio = jo.get_float( "ratio" );
558         if( ratio <= 0.0 ) {
559             jo.throw_error( "Ratio must be positive", "ratio" );
560         }
561     }
562 }
563 
trait_requirements(const JsonObject & obj)564 json_item_substitution::trait_requirements::trait_requirements( const JsonObject &obj )
565 {
566     for( const std::string line : obj.get_array( "present" ) ) {
567         present.emplace_back( line );
568     }
569     for( const std::string line : obj.get_array( "absent" ) ) {
570         absent.emplace_back( line );
571     }
572 }
573 
load(const JsonObject & jo)574 void json_item_substitution::load( const JsonObject &jo )
575 {
576     auto check_duplicate_item = [&]( const itype_id & it ) {
577         return substitutions.find( it ) != substitutions.end();
578     };
579 
580     if( jo.has_member( "item" ) ) {
581         // items mode
582         if( check_duplicate_item( itype_id( jo.get_string( "item" ) ) ) ) {
583             jo.throw_error( "Duplicate definition of item" );
584         }
585 
586         for( const JsonValue sub : jo.get_array( "sub" ) ) {
587             substitution s;
588             JsonObject obj = sub.get_object();
589             s.trait_reqs = trait_requirements( obj );
590             for( const JsonValue info : obj.get_array( "new" ) ) {
591                 s.infos.emplace_back( info );
592             }
593             substitutions[itype_id( jo.get_string( "item" ) )].push_back( s );
594         }
595     } else if( jo.has_member( "trait" ) ) {
596         // traits mode
597         for( const JsonObject sub : jo.get_array( "sub" ) ) {
598             substitution s;
599             itype_id old_it;
600             sub.read( "item", old_it, true );
601             if( check_duplicate_item( old_it ) ) {
602                 sub.throw_error( "Duplicate definition of item" );
603             }
604             s.trait_reqs.present.push_back( trait_id( jo.get_string( "trait" ) ) );
605             for( const JsonValue info : sub.get_array( "new" ) ) {
606                 s.infos.emplace_back( info );
607             }
608             substitutions[old_it].push_back( s );
609         }
610     } else if( jo.has_member( "bonus" ) ) {
611         // bonuses mode
612         const item_group_id &bonus_items = item_group::load_item_group( jo.get_member( "group" ),
613                                            "collection", "bonus items" );
614         bonuses.emplace_back( bonus_items, trait_requirements( jo.get_object( "bonus" ) ) );
615     }
616 }
617 
check_consistency()618 void json_item_substitution::check_consistency()
619 {
620     auto check_if_trait = []( const trait_id & t ) {
621         if( !t.is_valid() ) {
622             debugmsg( "%s is not a trait", t.c_str() );
623         }
624     };
625     auto check_if_itype = []( const itype_id & i ) {
626         if( !item::type_is_defined( i ) ) {
627             debugmsg( "%s is not an itype_id", i.c_str() );
628         }
629     };
630     auto check_if_igroup = []( const item_group_id & gr ) {
631         if( !item_group::group_is_defined( gr ) ) {
632             debugmsg( "%s is not an item_group_id", gr.c_str() );
633         }
634     };
635     auto check_trait_reqs = [&check_if_trait]( const trait_requirements & tr ) {
636         for( const trait_id &str : tr.present ) {
637             check_if_trait( str );
638         }
639         for( const trait_id &str : tr.absent ) {
640             check_if_trait( str );
641         }
642     };
643 
644     for( const auto &pair : substitutions ) {
645         check_if_itype( pair.first );
646         for( const substitution &s : pair.second ) {
647             check_trait_reqs( s.trait_reqs );
648             for( const substitution::info &inf : s.infos ) {
649                 check_if_itype( inf.new_item );
650             }
651         }
652     }
653     for( const auto &pair : bonuses ) {
654         check_if_igroup( pair.first );
655         check_trait_reqs( pair.second );
656     }
657 }
658 
meets_condition(const std::vector<trait_id> & traits) const659 bool json_item_substitution::trait_requirements::meets_condition( const std::vector<trait_id>
660         &traits ) const
661 {
662     const auto pred = [&traits]( const trait_id & s ) {
663         return std::find( traits.begin(), traits.end(), s ) != traits.end();
664     };
665     return std::all_of( present.begin(), present.end(), pred ) &&
666            std::none_of( absent.begin(), absent.end(), pred );
667 }
668 
get_substitution(const item & it,const std::vector<trait_id> & traits) const669 std::vector<item> json_item_substitution::get_substitution( const item &it,
670         const std::vector<trait_id> &traits ) const
671 {
672     auto iter = substitutions.find( it.typeId() );
673     std::vector<item> ret;
674     if( iter == substitutions.end() ) {
675         for( const item *con : it.contents.all_items_top() ) {
676             const auto sub = get_substitution( *con, traits );
677             ret.insert( ret.end(), sub.begin(), sub.end() );
678         }
679         return ret;
680     }
681 
682     const auto sub = std::find_if( iter->second.begin(),
683     iter->second.end(), [&traits]( const substitution & s ) {
684         return s.trait_reqs.meets_condition( traits );
685     } );
686     if( sub == iter->second.end() ) {
687         return ret;
688     }
689 
690     const int old_amt = it.count();
691     for( const substitution::info &inf : sub->infos ) {
692         item result( inf.new_item, advanced_spawn_time() );
693         int new_amount = std::max( 1, static_cast<int>( std::round( inf.ratio * old_amt ) ) );
694 
695         if( !result.count_by_charges() ) {
696             for( int i = 0; i < new_amount; i++ ) {
697                 ret.push_back( result.in_its_container() );
698             }
699         } else {
700             while( new_amount > 0 ) {
701                 const item pushed = result.in_its_container( new_amount );
702                 new_amount -= pushed.charges_of( inf.new_item );
703                 ret.push_back( pushed );
704             }
705         }
706     }
707     return ret;
708 }
709 
get_bonus_items(const std::vector<trait_id> & traits) const710 std::vector<item> json_item_substitution::get_bonus_items( const std::vector<trait_id> &traits )
711 const
712 {
713     std::vector<item> ret;
714     for( const auto &pair : bonuses ) {
715         if( pair.second.meets_condition( traits ) ) {
716             const std::vector<item> &items = item_group::items_from( pair.first, advanced_spawn_time() );
717             // ret = ret + items
718             ret.reserve( ret.size() + items.size() );
719             ret.insert( ret.end(), items.begin(), items.end() );
720         }
721     }
722     return ret;
723 }
724