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