1 #include <functional>
2 #include <list>
3 #include <memory>
4 #include <set>
5 #include <vector>
6
7 #include "avatar.h"
8 #include "calendar.h"
9 #include "catch/catch.hpp"
10 #include "game.h"
11 #include "item.h"
12 #include "item_contents.h"
13 #include "item_location.h"
14 #include "item_pocket.h"
15 #include "itype.h"
16 #include "map.h"
17 #include "map_helpers.h"
18 #include "player.h"
19 #include "player_activity.h"
20 #include "player_helpers.h"
21 #include "point.h"
22 #include "ret_val.h"
23 #include "type_id.h"
24 #include "units.h"
25 #include "value_ptr.h"
26
27 TEST_CASE( "reload_gun_with_integral_magazine", "[reload],[gun]" )
28 {
29 player &dummy = get_avatar();
30
31 clear_avatar();
32 // Make sure the player doesn't drop anything :P
33 dummy.wear_item( item( "backpack", calendar::turn_zero ) );
34
35 item &ammo = dummy.i_add( item( "40sw", calendar::turn_zero, item::default_charges_tag{} ) );
36 item &gun = dummy.i_add( item( "sw_610", calendar::turn_zero, item::default_charges_tag{} ) );
37
38 REQUIRE( dummy.has_item( ammo ) );
39 REQUIRE( gun.ammo_remaining() == 0 );
40 REQUIRE( gun.magazine_integral() );
41
42 bool success = gun.reload( dummy, item_location( dummy, &ammo ), ammo.charges );
43
44 REQUIRE( success );
45 REQUIRE( gun.remaining_ammo_capacity() == 0 );
46 }
47
48 TEST_CASE( "reload_gun_with_integral_magazine_using_speedloader", "[reload],[gun]" )
49 {
50 player &dummy = get_avatar();
51
52 clear_avatar();
53 // Make sure the player doesn't drop anything :P
54 dummy.wear_item( item( "backpack", calendar::turn_zero ) );
55
56 item &ammo = dummy.i_add( item( "38_special", calendar::turn_zero,
57 item::default_charges_tag{} ) );
58 item &speedloader = dummy.i_add( item( "38_speedloader", calendar::turn_zero, false ) );
59 item &gun = dummy.i_add( item( "sw_619", calendar::turn_zero, false ) );
60
61 REQUIRE( dummy.has_item( ammo ) );
62 REQUIRE( gun.ammo_remaining() == 0 );
63 REQUIRE( gun.magazine_integral() );
64 REQUIRE( dummy.has_item( speedloader ) );
65 REQUIRE( speedloader.ammo_remaining() == 0 );
66 REQUIRE( speedloader.has_flag( flag_id( "SPEEDLOADER" ) ) );
67
68 bool speedloader_success = speedloader.reload( dummy, item_location( dummy, &ammo ), ammo.charges );
69
70 REQUIRE( speedloader_success );
71 REQUIRE( speedloader.remaining_ammo_capacity() == 0 );
72
73 bool success = gun.reload( dummy, item_location( dummy, &speedloader ),
74 speedloader.ammo_remaining() );
75
76 REQUIRE( success );
77 REQUIRE( gun.remaining_ammo_capacity() == 0 );
78 // Speedloader is still in inventory.
79 REQUIRE( dummy.has_item( speedloader ) );
80 }
81
82 TEST_CASE( "reload_gun_with_swappable_magazine", "[reload],[gun]" )
83 {
84 player &dummy = get_avatar();
85
86 clear_avatar();
87 // Make sure the player doesn't drop anything :P
88 dummy.wear_item( item( "backpack", calendar::turn_zero ) );
89
90 item &ammo = dummy.i_add( item( "9mm", calendar::turn_zero, item::default_charges_tag{} ) );
91 const cata::value_ptr<islot_ammo> &ammo_type = ammo.type->ammo;
92 REQUIRE( ammo_type );
93
94 const item mag( "glockmag", calendar::turn_zero, 0 );
95 const cata::value_ptr<islot_magazine> &magazine_type = mag.type->magazine;
96 REQUIRE( magazine_type );
97 REQUIRE( magazine_type->type.count( ammo_type->type ) != 0 );
98
99 item gun( "glock_19" );
100 gun.put_in( mag, item_pocket::pocket_type::MAGAZINE_WELL );
101 REQUIRE( gun.magazine_current() != nullptr );
102 REQUIRE( gun.magazine_current()->ammo_types().count( ammo_type->type ) != 0 );
103 dummy.i_add( gun );
104
__anon72c16b6a0102( const item & it ) 105 const std::vector<item *> guns = dummy.items_with( []( const item & it ) {
106 return it.typeId() == itype_id( "glock_19" );
107 } );
108 REQUIRE( guns.size() == 1 );
109 item &glock = *guns.front();
110 REQUIRE( glock.magazine_current() != nullptr );
111 // We're expecting the magazine to end up in the inventory.
112 item_location glock_loc( dummy, &glock );
113 REQUIRE( dummy.unload( glock_loc ) );
__anon72c16b6a0202( const item & it ) 114 const std::vector<item *> glock_mags = dummy.items_with( []( const item & it ) {
115 return it.typeId() == itype_id( "glockmag" );
116 } );
117 REQUIRE( glock_mags.size() == 1 );
118 item &magazine = *glock_mags.front();
119 REQUIRE( magazine.ammo_remaining() == 0 );
120
121 REQUIRE( dummy.has_item( ammo ) );
122
123 bool magazine_success = magazine.reload( dummy, item_location( dummy, &ammo ), ammo.charges );
124
125 REQUIRE( magazine_success );
126 REQUIRE( magazine.remaining_ammo_capacity() == 0 );
127
128 REQUIRE( gun.ammo_remaining() == 0 );
129 REQUIRE( gun.magazine_integral() == false );
130
131 bool gun_success = gun.reload( dummy, item_location( dummy, &magazine ), 1 );
132
133 CHECK( gun_success );
134 REQUIRE( gun.remaining_ammo_capacity() == 0 );
135 }
136
reload_a_revolver(player & dummy,item & gun,item & ammo)137 static void reload_a_revolver( player &dummy, item &gun, item &ammo )
138 {
139 if( !dummy.is_wielding( gun ) ) {
140 if( dummy.has_weapon() ) {
141 // to avoid dispose_option in player::unwield()
142 dummy.i_add( dummy.weapon );
143 dummy.remove_weapon();
144 }
145 dummy.wield( gun );
146 }
147 while( dummy.weapon.remaining_ammo_capacity() > 0 ) {
148 g->reload_weapon( false );
149 REQUIRE( dummy.activity );
150 process_activity( dummy );
151 CAPTURE( dummy.weapon.typeId() );
152 CAPTURE( ammo.typeId() );
153 CHECK( !dummy.weapon.contents.empty() );
154 CHECK( dummy.weapon.ammo_current() == ammo.type->get_id() );
155 }
156 }
157
158 TEST_CASE( "automatic_reloading_action", "[reload],[gun]" )
159 {
160 player &dummy = get_avatar();
161
162 clear_avatar();
163 // Make sure the player doesn't drop anything :P
164 dummy.wear_item( item( "backpack", calendar::turn_zero ) );
165
166 GIVEN( "an unarmed player" ) {
167 REQUIRE( !dummy.is_armed() );
168 WHEN( "the player triggers auto reload" ) {
169 g->reload_weapon( false );
170 THEN( "No activity is generated" ) {
171 CHECK( !dummy.activity );
172 }
173 }
174 }
175
176 GIVEN( "a player armed with a revolver and ammo for it" ) {
177 item &ammo = dummy.i_add( item( "40sw", calendar::turn_zero, 100 ) );
178 REQUIRE( ammo.is_ammo() );
179
180 dummy.weapon = item( "sw_610", calendar::turn_zero, 0 );
181 REQUIRE( dummy.weapon.ammo_remaining() == 0 );
182 REQUIRE( dummy.weapon.can_reload_with( ammo.type->get_id() ) );
183
184 WHEN( "the player triggers auto reload until the revolver is full" ) {
185 reload_a_revolver( dummy, dummy.weapon, ammo );
186 WHEN( "the player triggers auto reload again" ) {
187 g->reload_weapon( false );
188 THEN( "no activity is generated" ) {
189 CHECK( !dummy.activity );
190 }
191 }
192 }
193 GIVEN( "the player has another gun with ammo" ) {
194 item &gun2 = dummy.i_add( item( "sw_610", calendar::turn_zero, 0 ) );
195 REQUIRE( gun2.ammo_remaining() == 0 );
196 REQUIRE( gun2.can_reload_with( ammo.type->get_id() ) );
197 WHEN( "the player triggers auto reload until the first revolver is full" ) {
198 reload_a_revolver( dummy, dummy.weapon, ammo );
199 WHEN( "the player triggers auto reload until the second revolver is full" ) {
200 reload_a_revolver( dummy, gun2, ammo );
201 WHEN( "the player triggers auto reload again" ) {
202 g->reload_weapon( false );
203 THEN( "no activity is generated" ) {
204 CHECK( !dummy.activity );
205 }
206 }
207 }
208 }
209 }
210 }
211
212 GIVEN( "a player wielding an unloaded gun, carrying an unloaded magazine, and carrying ammo for the magazine" ) {
213 dummy.worn.clear();
214 dummy.worn.push_back( item( "backpack" ) );
215 item &ammo = dummy.i_add( item( "9mm", calendar::turn_zero, 50 ) );
216 const cata::value_ptr<islot_ammo> &ammo_type = ammo.type->ammo;
217 REQUIRE( ammo_type );
218
219 item &mag = dummy.i_add( item( "glockmag", calendar::turn_zero, 0 ) );
220 const cata::value_ptr<islot_magazine> &magazine_type = mag.type->magazine;
221 REQUIRE( magazine_type );
222 REQUIRE( magazine_type->type.count( ammo_type->type ) != 0 );
223 REQUIRE( mag.ammo_remaining() == 0 );
224
225 dummy.weapon = item( "glock_19", calendar::turn_zero, 0 );
226 REQUIRE( dummy.weapon.ammo_remaining() == 0 );
227
228 WHEN( "the player triggers auto reload" ) {
229 g->reload_weapon( false );
230 REQUIRE( dummy.activity );
231 process_activity( dummy );
232
233 THEN( "the associated magazine is reloaded" ) {
__anon72c16b6a0302( const item & it ) 234 const std::vector<item *> mags = dummy.items_with( []( const item & it ) {
235 return it.typeId() == itype_id( "glockmag" );
236 } );
237 REQUIRE( mags.size() == 1 );
238 REQUIRE( !mags.front()->contents.empty() );
239 CHECK( mags.front()->contents.first_ammo().type == ammo.type );
240 }
241 WHEN( "the player triggers auto reload again" ) {
242 g->reload_weapon( false );
243 REQUIRE( dummy.activity );
244 process_activity( dummy );
245
246 THEN( "The magazine is loaded into the gun" ) {
247 CHECK( dummy.weapon.ammo_remaining() > 0 );
248 }
249 WHEN( "the player triggers auto reload again" ) {
250 g->reload_weapon( false );
251 THEN( "No activity is generated" ) {
252 CHECK( !dummy.activity );
253 }
254 }
255 }
256 }
257 GIVEN( "the player also has an extended magazine" ) {
258 item &mag2 = dummy.i_add( item( "glockbigmag", calendar::turn_zero, 0 ) );
259 const cata::value_ptr<islot_magazine> &magazine_type2 = mag2.type->magazine;
260 REQUIRE( magazine_type2 );
261 REQUIRE( magazine_type2->type.count( ammo_type->type ) != 0 );
262 REQUIRE( mag2.ammo_remaining() == 0 );
263
264 WHEN( "the player triggers auto reload" ) {
265 g->reload_weapon( false );
266 REQUIRE( dummy.activity );
267 process_activity( dummy );
268
269 THEN( "the associated magazine is reloaded" ) {
__anon72c16b6a0402( const item & it ) 270 const std::vector<item *> mags = dummy.items_with( []( const item & it ) {
271 return it.typeId() == itype_id( "glockmag" );
272 } );
273 REQUIRE( mags.size() == 1 );
274 REQUIRE( !mags.front()->contents.empty() );
275 CHECK( mags.front()->contents.first_ammo().type == ammo.type );
276 }
277 WHEN( "the player triggers auto reload again" ) {
278 g->reload_weapon( false );
279 REQUIRE( dummy.activity );
280 process_activity( dummy );
281
282 THEN( "The magazine is loaded into the gun" ) {
283 CHECK( dummy.weapon.ammo_remaining() > 0 );
284 }
285 WHEN( "the player triggers auto reload again" ) {
286 g->reload_weapon( false );
287 REQUIRE( dummy.activity );
288 process_activity( dummy );
289
290 THEN( "the second associated magazine is reloaded" ) {
__anon72c16b6a0502( const item & it ) 291 const std::vector<item *> mags = dummy.items_with( []( const item & it ) {
292 return it.typeId() == itype_id( "glockbigmag" );
293 } );
294 REQUIRE( mags.size() == 1 );
295 REQUIRE( !mags.front()->contents.empty() );
296 CHECK( mags.front()->contents.first_ammo().type == ammo.type );
297 }
298 WHEN( "the player triggers auto reload again" ) {
299 g->reload_weapon( false );
300 THEN( "No activity is generated" ) {
301 CHECK( !dummy.activity );
302 }
303 }
304 }
305 }
306 }
307 }
308 }
309 }
310
311 // TODO: nested containers and frozen liquids.
312 TEST_CASE( "reload_liquid_container", "[reload],[liquid]" )
313 {
314 player &dummy = get_avatar();
315 clear_avatar();
316 clear_map();
317 item backpack( item( "bigback" ) );
318 dummy.wear_item( backpack );
319 item canteen( item( "2lcanteen" ) );
320 REQUIRE( dummy.wield( canteen ) ) ;
321
322 item &ammo_jug = dummy.i_add( item( "jug_plastic" ) );
323 ammo_jug.put_in( item( "water_clean", calendar::turn_zero, 2 ),
324 item_pocket::pocket_type::CONTAINER );
325 units::volume ammo_volume = ammo_jug.contents.total_contained_volume();
326
327 SECTION( "reload liquid into empty container" ) {
328 g->reload_wielded();
329 REQUIRE( dummy.activity );
330 process_activity( dummy );
331 CHECK( dummy.weapon.contents.total_contained_volume() == ammo_volume );
332 CHECK( ammo_jug.contents.total_contained_volume() == units::volume() );
333 }
334
335 SECTION( "reload liquid into partially filled container with same type liquid" ) {
336 item water_one( "water_clean", calendar::turn_zero, 1 );
337 units::volume initial_volume = water_one.volume();
338 dummy.weapon.put_in( water_one, item_pocket::pocket_type::CONTAINER );
339 g->reload_wielded();
340 REQUIRE( dummy.activity );
341 process_activity( dummy );
342 CHECK( dummy.weapon.contents.total_contained_volume() == ammo_volume + initial_volume );
343 CHECK( ammo_jug.contents.total_contained_volume() == units::volume() );
344 }
345
346 SECTION( "reload liquid into partially filled container with different type liquid" ) {
347 item milk_one( "milk", calendar::turn_zero, 1 );
348 units::volume initial_volume = milk_one.volume();
349 dummy.weapon.put_in( milk_one, item_pocket::pocket_type::CONTAINER );
350 g->reload_wielded();
351 if( !!dummy.activity ) {
352 process_activity( dummy );
353 }
354 CHECK( dummy.weapon.contents.total_contained_volume() == initial_volume );
355 CHECK( ammo_jug.contents.total_contained_volume() == ammo_volume );
356 }
357
358 SECTION( "reload liquid into container containing a non-liquid" ) {
359 item pebble( "pebble", calendar::turn_zero, 1 );
360 units::volume initial_volume = pebble.volume();
361 dummy.weapon.put_in( pebble, item_pocket::pocket_type::CONTAINER );
362 g->reload_wielded();
363 if( !!dummy.activity ) {
364 process_activity( dummy );
365 }
366 CHECK( dummy.weapon.contents.total_contained_volume() == initial_volume );
367 CHECK( ammo_jug.contents.total_contained_volume() == ammo_volume );
368 }
369
370 SECTION( "reload liquid container with more liquid than it can hold" ) {
371 ammo_jug.fill_with( item( "water_clean", calendar::turn_zero, 1 ) );
372 ammo_volume = ammo_jug.contents.total_contained_volume();
373 g->reload_wielded();
374 REQUIRE( dummy.activity );
375 process_activity( dummy );
376 CHECK( dummy.weapon.contents.remaining_container_capacity() == units::volume() );
377 CHECK( ammo_jug.contents.total_contained_volume() +
378 dummy.weapon.contents.total_contained_volume() == ammo_volume );
379 }
380
381 SECTION( "liquid reload from map" ) {
382 const tripoint test_origin( 60, 60, 0 );
383 map &here = get_map();
384 dummy.setpos( test_origin );
385 const tripoint near_point = test_origin + tripoint_east;
386
387 SECTION( "liquid in container on floor" ) {
388 ammo_jug = here.add_item( near_point, item( "bottle_plastic" ) );
389 ammo_jug.fill_with( item( "water_clean" ) );
390 ammo_volume = ammo_jug.contents.total_contained_volume();
391 g->reload_wielded();
392 REQUIRE( dummy.activity );
393 process_activity( dummy );
394 CHECK( dummy.weapon.contents.total_contained_volume() == ammo_volume );
395 CHECK( ammo_jug.contents.total_contained_volume() == units::volume() );
396 }
397
398 SECTION( "liquid spill on floor" ) {
399 REQUIRE( ammo_jug.spill_contents( near_point ) );
400 g->reload_wielded();
401 if( !!dummy.activity ) {
402 process_activity( dummy );
403 }
404 CHECK( ammo_jug.contents.total_contained_volume() == units::volume() );
405 CHECK( dummy.weapon.contents.total_contained_volume() == units::volume() );
406 }
407 }
408 }
409