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