1 #include <functional>
2 #include <cstddef>
3 #include <functional>
4 #include <list>
5 #include <memory>
6 #include <set>
7 #include <sstream>
8 #include <unordered_map>
9 #include <utility>
10 #include <vector>
11 
12 #include "avatar.h"
13 #include "catch/catch.hpp"
14 #include "inventory.h"
15 #include "item.h"
16 #include "pimpl.h"
17 #include "profession.h"
18 #include "scenario.h"
19 #include "string_formatter.h"
20 #include "stringmaker.h" // IWYU pragma: keep
21 #include "type_id.h"
22 #include "visitable.h"
23 
operator <<(std::ostream & s,const std::vector<trait_id> & v)24 static std::ostream &operator<<( std::ostream &s, const std::vector<trait_id> &v )
25 {
26     for( const auto &e : v ) {
27         s << e.c_str() << " ";
28     }
29     return s;
30 }
31 
next_subset(const std::vector<trait_id> & set)32 static std::vector<trait_id> next_subset( const std::vector<trait_id> &set )
33 {
34     // Doing it this way conveniently returns a vector containing solely set[foo] before
35     // it returns any other vectors with set[foo] in it
36     static unsigned bitset = 0;
37     std::vector<trait_id> ret;
38 
39     ++bitset;
40     // Check each bit position for a match
41     for( size_t idx = 0; idx < set.size(); idx++ ) {
42         if( bitset & ( 1 << idx ) ) {
43             ret.push_back( set[idx] );
44         }
45     }
46     return ret;
47 }
48 
try_set_traits(const std::vector<trait_id> & traits)49 static bool try_set_traits( const std::vector<trait_id> &traits )
50 {
51     avatar &player_character = get_avatar();
52     player_character.clear_mutations();
53     player_character.add_traits(); // mandatory prof/scen traits
54     std::vector<trait_id> oked_traits;
55     for( const trait_id &tr : traits ) {
56         if( player_character.has_conflicting_trait( tr ) ||
57             !get_scenario()->traitquery( tr ) ) {
58             return false;
59         } else if( !player_character.has_trait( tr ) ) {
60             oked_traits.push_back( tr );
61         }
62     }
63     player_character.set_mutations( oked_traits );
64     return true;
65 }
66 
get_sanitized_player()67 static avatar get_sanitized_player()
68 {
69     // You'd think that this hp stuff would be in the c'tor...
70     avatar ret = avatar();
71     ret.set_body();
72     ret.recalc_hp();
73 
74     // Set these insanely high so can_eat doesn't return TOO_FULL
75     ret.set_hunger( 10000 );
76     ret.set_thirst( 10000 );
77     return ret;
78 }
79 
80 struct failure {
81     string_id<profession> prof;
82     std::vector<trait_id> mut;
83     itype_id item_name;
84     std::string reason;
85 };
86 
87 namespace std
88 {
89 template<>
90 struct less<failure> {
operator ()std::less91     bool operator()( const failure &lhs, const failure &rhs ) const {
92         return lhs.prof < rhs.prof;
93     }
94 };
95 } // namespace std
96 
97 // TODO: According to profiling (interrupt, backtrace, wait a few seconds, repeat) with a sample
98 // size of 20, 70% of the time is due to the call to Character::set_mutation in try_set_traits.
99 // When the mutation stuff isn't commented out, the test takes 110 minutes (not a typo)!
100 
101 /**
102  * Disabled temporarily because 3169 profession combinations do not work and need to be fixed in json
103  */
104 TEST_CASE( "starting_items", "[slow]" )
105 {
106     // Every starting trait that interferes with food/clothing
107     const std::vector<trait_id> mutations = {
108         trait_id( "ALBINO" ),
109         trait_id( "ANTIFRUIT" ),
110         trait_id( "ANTIJUNK" ),
111         trait_id( "ANTIWHEAT" ),
112         //trait_id( "ARM_TENTACLES" ),
113         //trait_id( "BEAK" ),
114         //trait_id( "CARNIVORE" ),
115         //trait_id( "HERBIVORE" ),
116         //trait_id( "HOOVES" ),
117         trait_id( "LACTOSE" ),
118         //trait_id( "LEG_TENTACLES" ),
119         trait_id( "MEATARIAN" ),
120         trait_id( "ASTHMA" ),
121         //trait_id( "RAP_TALONS" ),
122         //trait_id( "TAIL_FLUFFY" ),
123         //trait_id( "TAIL_LONG" ),
124         trait_id( "VEGETARIAN" ),
125         trait_id( "WOOLALLERGY" )
126     };
127     // Prof/scen combinations that need to be checked.
128     std::unordered_map<const scenario *, std::vector<string_id<profession>>> scen_prof_combos;
129     for( const auto &id : scenario::generic()->permitted_professions() ) {
130         scen_prof_combos[scenario::generic()].push_back( id );
131     }
132 
133     std::set<failure> failures;
134 
135     avatar &player_character = get_avatar();
136     player_character = get_sanitized_player();
137     // Avoid false positives from ingredients like salt and cornmeal.
138     const avatar control = get_sanitized_player();
139 
140     std::vector<trait_id> traits = next_subset( mutations );
141     for( ; !traits.empty(); traits = next_subset( mutations ) ) {
142         CAPTURE( traits );
143         for( const auto &pair : scen_prof_combos ) {
144             set_scenario( pair.first );
145             INFO( "Scenario = " + pair.first->ident().str() );
146             for( const string_id<profession> &prof : pair.second ) {
147                 CAPTURE( prof );
148                 player_character.prof = &prof.obj();
149                 if( !try_set_traits( traits ) ) {
150                     continue; // Trait conflict: this prof/scen/trait combo is impossible to attain
151                 }
152                 for( int i = 0; i < 2; i++ ) {
153                     player_character.worn.clear();
154                     player_character.remove_weapon();
155                     player_character.inv->clear();
156                     player_character.calc_encumbrance();
157                     player_character.male = i == 0;
158 
159                     player_character.add_profession_items();
160                     std::set<const item *> items_visited;
__anon732cee190102( const item * it, auto ) 161                     const auto visitable_counter = [&items_visited]( const item * it, auto ) {
162                         items_visited.emplace( it );
163                         return VisitResponse::NEXT;
164                     };
165                     player_character.visit_items( visitable_counter );
166                     player_character.inv->visit_items( visitable_counter );
167                     const int num_items_pre_migration = items_visited.size();
168                     items_visited.clear();
169 
170                     player_character.migrate_items_to_storage( true );
171                     player_character.visit_items( visitable_counter );
172                     const int num_items_post_migration = items_visited.size();
173                     items_visited.clear();
174 
175                     if( num_items_pre_migration != num_items_post_migration ) {
176                         failure cur_fail;
177                         cur_fail.prof = player_character.prof->ident();
178                         cur_fail.mut = player_character.get_mutations();
179                         cur_fail.reason = string_format( "does not have enough space to store all items." );
180 
181                         failures.insert( cur_fail );
182                     }
183                     CAPTURE( player_character.prof->ident().c_str() );
184                     CHECK( num_items_pre_migration == num_items_post_migration );
185                 } // all genders
186             } // all profs
187         } // all scens
188     }
189     std::stringstream failure_messages;
190     for( const failure &f : failures ) {
191         failure_messages << f.prof.c_str() << " " << f.mut <<
192                          " " << f.item_name.str() << ": " << f.reason << "\n";
193     }
194     INFO( failure_messages.str() );
195     REQUIRE( failures.empty() );
196 }
197