1 #include <algorithm> 2 #include <iosfwd> 3 #include <list> 4 #include <set> 5 #include <string> 6 #include <utility> 7 #include <vector> 8 9 #include "catch/catch.hpp" 10 #include "flag.h" 11 #include "item.h" 12 #include "item_contents.h" 13 #include "item_group.h" 14 #include "type_id.h" 15 16 TEST_CASE( "truncate_spawn_when_items_dont_fit", "[item_group]" ) 17 { 18 // This item group is a 10L container with three 4L objects. We should 19 // always see exactly 2 of the 3. 20 // Moreover, we should see all possible pairs chosen from amongst those 3. 21 item_group_id truncate_test_id( "test_truncating_to_container" ); 22 std::set<std::pair<itype_id, itype_id>> observed_pairs; 23 24 for( int i = 0; i < 100; ++i ) { 25 const item_group::ItemList items = item_group::items_from( truncate_test_id ); 26 REQUIRE( items.size() == 1 ); 27 REQUIRE( items[0].typeId().str() == "test_balloon" ); 28 std::list<const item *> contents = items[0].contents.all_items_top(); 29 REQUIRE( contents.size() == 2 ); 30 observed_pairs.emplace( contents.front()->typeId(), contents.back()->typeId() ); 31 } 32 33 CAPTURE( observed_pairs ); 34 35 CHECK( observed_pairs.size() == 6 ); 36 } 37 38 TEST_CASE( "spill_when_items_dont_fit", "[item_group]" ) 39 { 40 // This item group is a 10L container with three 4L objects. We should 41 // always see 2 in the container and one outside. 42 // Moreover, we should see all possible combinations chosen from amongst those 3. 43 item_group_id truncate_test_id( "test_spilling_from_container" ); 44 std::set<std::pair<itype_id, itype_id>> observed_pairs_inside; 45 std::set<itype_id> observed_outside; 46 47 for( int i = 0; i < 100; ++i ) { 48 const item_group::ItemList items = item_group::items_from( truncate_test_id ); 49 REQUIRE( items.size() == 2 ); 50 const item *container; 51 const item *other; 52 if( items[0].typeId().str() == "test_balloon" ) { 53 container = &items[0]; 54 other = &items[1]; 55 } else { 56 container = &items[1]; 57 other = &items[0]; 58 } 59 REQUIRE( container->typeId().str() == "test_balloon" ); 60 std::list<const item *> contents = container->contents.all_items_top(); 61 REQUIRE( contents.size() == 2 ); 62 observed_pairs_inside.emplace( contents.front()->typeId(), contents.back()->typeId() ); 63 observed_outside.emplace( other->typeId() ); 64 } 65 66 CAPTURE( observed_pairs_inside ); 67 CAPTURE( observed_outside ); 68 69 CHECK( observed_pairs_inside.size() == 6 ); 70 CHECK( observed_outside.size() == 3 ); 71 } 72 73 TEST_CASE( "spawn with default charges and with ammo", "[item_group]" ) 74 { 75 Item_modifier default_charges; 76 default_charges.with_ammo = 100; 77 SECTION( "tools without ammo" ) { 78 item matches( "matches" ); 79 REQUIRE( matches.ammo_default() == itype_id( "match" ) ); 80 default_charges.modify( matches, "modifier test (matches ammo)" ); 81 CHECK( matches.remaining_ammo_capacity() == 0 ); 82 } 83 84 SECTION( "gun with ammo type" ) { 85 item glock( "glock_19" ); 86 REQUIRE( !glock.magazine_default().is_null() ); 87 default_charges.modify( glock, "modifier test (glock ammo)" ); 88 CHECK( glock.remaining_ammo_capacity() == 0 ); 89 } 90 } 91 92 TEST_CASE( "Item_modifier damages item", "[item_group]" ) 93 { 94 Item_modifier damaged; 95 damaged.damage.first = 1; 96 damaged.damage.second = 1; 97 SECTION( "except when it's an ammunition" ) { 98 item rock( "rock" ); 99 REQUIRE( rock.damage() == 0 ); 100 REQUIRE( rock.max_damage() == 0 ); 101 damaged.modify( rock, "modifier test (rock damage)" ); 102 CHECK( rock.damage() == 0 ); 103 } 104 SECTION( "when it can be damaged" ) { 105 item glock( "glock_19" ); 106 REQUIRE( glock.damage() == 0 ); 107 REQUIRE( glock.max_damage() > 0 ); 108 damaged.modify( glock, "modifier test (glock damage)" ); 109 CHECK( glock.damage() == 1 ); 110 } 111 } 112 113 TEST_CASE( "Item_modifier gun fouling", "[item_group]" ) 114 { 115 Item_modifier fouled; 116 fouled.dirt.first = 1; 117 SECTION( "guns can be fouled" ) { 118 item glock( "glock_19" ); 119 REQUIRE( !glock.has_flag( flag_PRIMITIVE_RANGED_WEAPON ) ); 120 REQUIRE( !glock.has_var( "dirt" ) ); 121 fouled.modify( glock, "modifier test (glock fouling)" ); 122 CHECK( glock.get_var( "dirt", 0.0 ) > 0.0 ); 123 } 124 SECTION( "bows can't be fouled" ) { 125 item bow( "longbow" ); 126 REQUIRE( !bow.has_var( "dirt" ) ); 127 REQUIRE( bow.has_flag( flag_PRIMITIVE_RANGED_WEAPON ) ); 128 fouled.modify( bow, "modifier test (bow fouling)" ); 129 CHECK( !bow.has_var( "dirt" ) ); 130 } 131 } 132 133 TEST_CASE( "item_modifier modifies charges for item", "[item_group]" ) 134 { 135 GIVEN( "an ammo item that uses charges" ) { 136 const std::string item_id = "40x46mm_m1006"; 137 item subject( item_id ); 138 139 const int default_charges = 6; 140 141 REQUIRE( subject.is_ammo() ); 142 REQUIRE( subject.count_by_charges() ); 143 REQUIRE( subject.count() == default_charges ); 144 REQUIRE( subject.charges == default_charges ); 145 146 AND_GIVEN( "a modifier that does not modify charges" ) { 147 Item_modifier modifier; 148 149 WHEN( "the item is modified" ) { 150 modifier.modify( subject, "modifier test (" + item_id + " ammo default)" ); 151 152 THEN( "charges should be unchanged" ) { 153 CHECK( subject.charges == default_charges ); 154 } 155 } 156 } 157 158 AND_GIVEN( "a modifier that sets min and max charges to 0" ) { 159 const int min_charges = 0; 160 const int max_charges = 0; 161 Item_modifier modifier; 162 modifier.charges = { min_charges, max_charges }; 163 164 WHEN( "the item is modified" ) { 165 modifier.modify( subject, "modifier test (" + item_id + " ammo set to 1)" ); 166 167 THEN( "charges are set to 1" ) { 168 CHECK( subject.charges == 1 ); 169 } 170 } 171 } 172 173 AND_GIVEN( "a modifier that sets min and max charges to -1" ) { 174 const int min_charges = -1; 175 const int max_charges = -1; 176 Item_modifier modifier; 177 modifier.charges = { min_charges, max_charges }; 178 179 WHEN( "the item is modified" ) { 180 modifier.modify( subject, "modifier test (" + item_id + " ammo explicitly default)" ); 181 182 THEN( "charges should be unchanged" ) { 183 CHECK( subject.charges == default_charges ); 184 } 185 } 186 } 187 188 AND_GIVEN( "a modifier that sets min and max charges to a range [1, 4]" ) { 189 const int min_charges = 1; 190 const int max_charges = 4; 191 Item_modifier modifier; 192 modifier.charges = { min_charges, max_charges }; 193 194 WHEN( "the item is modified" ) { 195 // We have to repeat this a bunch because the charges assignment 196 // actually does rng between min and max, and if we only checked once 197 // we might get a false success. 198 std::vector<int> results; 199 results.reserve( 100 ); 200 for( int i = 0; i < 100; i++ ) { 201 modifier.modify( subject, "modifier test (" + item_id + " ammo between 1 and 4)" ); 202 results.emplace_back( subject.charges ); 203 } 204 205 THEN( "charges are set to the expected range of values" ) { __anon76e6fcd00102( int v ) 206 CHECK( std::all_of( results.begin(), results.end(), [&]( int v ) { 207 return v >= min_charges && v <= max_charges; 208 } ) ); 209 } 210 } 211 } 212 213 AND_GIVEN( "a modifier that sets min and max charges to 4" ) { 214 const int expected = 4; 215 const int min_charges = expected; 216 const int max_charges = expected; 217 Item_modifier modifier; 218 modifier.charges = { min_charges, max_charges }; 219 220 WHEN( "the item is modified" ) { 221 // We have to repeat this a bunch because the charges assignment 222 // actually does rng between min and max, and if we only checked once 223 // we might get a false success. 224 std::vector<int> results; 225 results.reserve( 100 ); 226 for( int i = 0; i < 100; i++ ) { 227 modifier.modify( subject, "modifier test (" + item_id + " ammo set to 4)" ); 228 results.emplace_back( subject.charges ); 229 } 230 231 THEN( "charges are set to the expected value" ) { __anon76e6fcd00202( int v ) 232 CHECK( std::all_of( results.begin(), results.end(), [&]( int v ) { 233 return v == expected; 234 } ) ); 235 } 236 } 237 } 238 } 239 } 240