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