1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173
|
#include <set>
#include <sstream>
#include <string>
#include <unordered_map>
#include <vector>
#include "catch/catch.hpp"
#include "game.h"
#include "item.h"
#include "itype.h"
#include "mutation.h"
#include "player.h"
#include "profession.h"
#include "scenario.h"
#include "string_id.h"
std::ostream &operator<<( std::ostream &s, const std::vector<trait_id> &v )
{
for( const auto &e : v ) {
s << e.c_str() << " ";
}
return s;
}
static std::vector<trait_id> next_subset( const std::vector<trait_id> &set )
{
// Doing it this way conveniently returns a vector containing solely set[foo] before
// it returns any other vectors with set[foo] in it
static unsigned bitset = 0;
std::vector<trait_id> ret;
++bitset;
// Check each bit position for a match
for( unsigned idx = 0; idx < set.size(); idx++ ) {
if( bitset & ( 1 << idx ) ) {
ret.push_back( set[idx] );
}
}
return ret;
}
static bool try_set_traits( const std::vector<trait_id> &traits )
{
g->u.empty_traits();
g->u.add_traits(); // mandatory prof/scen traits
for( const trait_id &tr : traits ) {
if( g->u.has_conflicting_trait( tr ) || !g->scen->traitquery( tr ) ) {
return false;
} else if( !g->u.has_trait( tr ) ) {
g->u.set_mutation( tr );
}
}
return true;
}
static player get_sanitized_player()
{
// You'd think that this hp stuff would be in the c'tor...
player ret = player();
ret.recalc_hp();
for( int i = 0; i < num_hp_parts; i++ ) {
ret.hp_cur[i] = ret.hp_max[i];
}
// Set these insanely high so can_eat doesn't return TOO_FULL
ret.set_hunger( 10000 );
ret.set_thirst( 10000 );
return ret;
}
struct failure {
string_id<profession> prof;
std::vector<trait_id> mut;
itype_id item_name;
std::string reason;
};
namespace std
{
template<>
struct less<failure> {
bool operator()( const failure &lhs, const failure &rhs ) const {
return lhs.prof < rhs.prof;
}
};
}
// TODO: According to profiling (interrupt, backtrace, wait a few seconds, repeat) with a sample
// size of 20, 70% of the time is due to the call to Character::set_mutation in try_set_traits.
// When the mutation stuff isn't commented out, the test takes 110 minutes (not a typo)!
TEST_CASE( "starting_items" )
{
// Every starting trait that interferes with food/clothing
const std::vector<trait_id> mutations = {
trait_id( "ANTIFRUIT" ),
trait_id( "ANTIJUNK" ),
trait_id( "ANTIWHEAT" ),
//trait_id( "ARM_TENTACLES" ),
//trait_id( "BEAK" ),
trait_id( "CANNIBAL" ),
//trait_id( "CARNIVORE" ),
//trait_id( "HERBIVORE" ),
//trait_id( "HOOVES" ),
trait_id( "LACTOSE" ),
//trait_id( "LEG_TENTACLES" ),
trait_id( "MEATARIAN" ),
//trait_id( "RAP_TALONS" ),
//trait_id( "TAIL_FLUFFY" ),
//trait_id( "TAIL_LONG" ),
trait_id( "VEGETARIAN" ),
trait_id( "WOOLALLERGY" )
};
// Prof/scen combinations that need to be checked.
std::unordered_map<const scenario *, std::vector<string_id<profession>>> scen_prof_combos;
for( const auto &id : scenario::generic()->permitted_professions() ) {
scen_prof_combos[scenario::generic()].push_back( id );
}
std::set<failure> failures;
g->u = get_sanitized_player();
// Avoid false positives from ingredients like salt and cornmeal.
const player control = get_sanitized_player();
std::vector<trait_id> traits = next_subset( mutations );
for( ; !traits.empty(); traits = next_subset( mutations ) ) {
for( const auto &pair : scen_prof_combos ) {
g->scen = pair.first;
for( const string_id<profession> &prof : pair.second ) {
g->u.prof = &prof.obj();
if( !try_set_traits( traits ) ) {
continue; // Trait conflict: this prof/scen/trait combo is impossible to attain
}
for( int i = 0; i < 2; i++ ) {
g->u.worn.clear();
g->u.reset_encumbrance();
g->u.male = i == 0;
std::list<item> items = prof->items( g->u.male, traits );
for( const item &it : items ) {
items.insert( items.begin(), it.contents.begin(), it.contents.end() );
}
for( const item &it : items ) {
const bool is_food = !it.is_seed() && it.is_food() &&
!g->u.can_eat( it ).success() && control.can_eat( it ).success();
const bool is_armor = it.is_armor() && !g->u.wear_item( it, false );
// Seeds don't count- they're for growing things, not eating
if( is_food || is_armor ) {
failures.insert( failure{ prof->ident(), g->u.get_mutations(), it.typeId(), is_food ? "Couldn't eat it" : "Couldn't wear it." } );
}
const bool is_holster = it.is_armor() && it.type->get_use( "holster" );
if( is_holster ) {
const item &holstered_it = it.get_contained();
const bool empty_holster = holstered_it.is_null();
if( !empty_holster && !it.can_holster( holstered_it, true ) ) {
failures.insert( failure{ prof->ident(), g->u.get_mutations(), it.typeId(), "Couldn't put item back to holster" } );
}
}
}
} // all genders
} // all profs
} // all scens
}
std::stringstream failure_messages;
for( const failure &f : failures ) {
failure_messages << f.prof.c_str() << " " << f.mut <<
" " << f.item_name << ": " << f.reason << "\n";
}
INFO( failure_messages.str() );
REQUIRE( failures.empty() );
}
|