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