1 #include <array>
2 #include <iosfwd>
3 #include <memory>
4 #include <new>
5 #include <set>
6 #include <string>
7 #include <vector>
8 
9 #include "calendar.h"
10 #include "catch/catch.hpp"
11 #include "character.h"
12 #include "game.h"
13 #include "game_constants.h"
14 #include "map.h"
15 #include "map_helpers.h"
16 #include "monster.h"
17 #include "optional.h"
18 #include "point.h"
19 #include "tileray.h"
20 #include "type_id.h"
21 #include "units.h"
22 #include "veh_type.h"
23 #include "vehicle.h"
24 #include "vpart_position.h"
25 #include "vpart_range.h"
26 
clear_game_and_set_ramp(const int transit_x,bool use_ramp,bool up)27 static void clear_game_and_set_ramp( const int transit_x, bool use_ramp, bool up )
28 {
29     // Set to turn 0 to prevent solars from producing power
30     calendar::turn = calendar::turn_zero;
31     clear_map();
32     clear_vehicles();
33 
34     Character &player_character = get_player_character();
35     // Move player somewhere safe
36     REQUIRE_FALSE( player_character.in_vehicle );
37 
38     map &here = get_map();
39     build_test_map( ter_id( "t_pavement" ) );
40     if( use_ramp ) {
41         const int upper_zlevel = up ? 1 : 0;
42         const int lower_zlevel = up - 1;
43         const int highx = transit_x + ( up ? 0 : 1 );
44         const int lowx = transit_x + ( up ? 1 : 0 );
45 
46         // up   z1    ......  rdh  rDl
47         //      z0            rUh  rul .................
48         // down z0            rDl  rdh .................
49         //      z-1   ......  rdl  rUh
50         //                    60   61
51         for( int y = 0; y < SEEY * MAPSIZE; y++ ) {
52             for( int x = 0; x < transit_x; x++ ) {
53                 const int mid = up ? upper_zlevel : lower_zlevel;
54                 here.ter_set( tripoint( x, y, mid - 2 ), ter_id( "t_rock" ) );
55                 here.ter_set( tripoint( x, y, mid - 1 ), ter_id( "t_rock" ) );
56                 here.ter_set( tripoint( x, y, mid ), ter_id( "t_pavement" ) );
57                 here.ter_set( tripoint( x, y, mid + 1 ), ter_id( "t_open_air" ) );
58                 here.ter_set( tripoint( x, y, mid + 2 ), ter_id( "t_open_air" ) );
59             }
60             const tripoint ramp_up_low = tripoint( lowx, y, lower_zlevel );
61             const tripoint ramp_up_high = tripoint( highx, y, lower_zlevel );
62             const tripoint ramp_down_low = tripoint( lowx, y, upper_zlevel );
63             const tripoint ramp_down_high = tripoint( highx, y, upper_zlevel );
64             here.ter_set( ramp_up_low, ter_id( "t_ramp_up_low" ) );
65             here.ter_set( ramp_up_high, ter_id( "t_ramp_up_high" ) );
66             here.ter_set( ramp_down_low, ter_id( "t_ramp_down_low" ) );
67             here.ter_set( ramp_down_high, ter_id( "t_ramp_down_high" ) );
68             for( int x = transit_x + 2; x < SEEX * MAPSIZE; x++ ) {
69                 here.ter_set( tripoint( x, y, 1 ), ter_id( "t_open_air" ) );
70                 here.ter_set( tripoint( x, y, 0 ), ter_id( "t_pavement" ) );
71                 here.ter_set( tripoint( x, y, -1 ), ter_id( "t_rock" ) );
72             }
73         }
74     }
75     here.invalidate_map_cache( 0 );
76     here.build_map_cache( 0, true );
77 }
78 
79 // Algorithm goes as follows:
80 // Clear map and create a ramp
81 // Spawn a vehicle
82 // Drive it over the ramp, and confirm that the vehicle changes z-levels
ramp_transition_angled(const vproto_id & veh_id,const units::angle angle,const int transition_x,bool use_ramp,bool up)83 static void ramp_transition_angled( const vproto_id &veh_id, const units::angle angle,
84                                     const int transition_x, bool use_ramp, bool up )
85 {
86     map &here = get_map();
87     clear_game_and_set_ramp( transition_x, use_ramp, up );
88 
89     const tripoint map_starting_point( transition_x + 4, 60, 0 );
90     REQUIRE( here.ter( map_starting_point ) == ter_id( "t_pavement" ) );
91     if( here.ter( map_starting_point ) != ter_id( "t_pavement" ) ) {
92         return;
93     }
94     vehicle *veh_ptr = here.add_vehicle( veh_id, map_starting_point, angle, 1, 0 );
95 
96     REQUIRE( veh_ptr != nullptr );
97     if( veh_ptr == nullptr ) {
98         return;
99     }
100 
101     vehicle &veh = *veh_ptr;
102     veh.check_falling_or_floating();
103 
104     REQUIRE( !veh.is_in_water() );
105 
106     veh.tags.insert( "IN_CONTROL_OVERRIDE" );
107     veh.engine_on = true;
108     Character &player_character = get_player_character();
109     player_character.setpos( map_starting_point );
110 
111     REQUIRE( player_character.pos() == map_starting_point );
112     if( player_character.pos() != map_starting_point ) {
113         return;
114     }
115     get_map().board_vehicle( map_starting_point, &player_character );
116     REQUIRE( player_character.pos() == map_starting_point );
117     if( player_character.pos() != map_starting_point ) {
118         return;
119     }
120     const int transition_cycle = 3;
121     veh.cruise_velocity = 0;
122     veh.velocity = 0;
123     here.vehmove();
124 
125     const int target_velocity = 400;
126     veh.cruise_velocity = target_velocity;
127     veh.velocity = target_velocity;
128     CHECK( veh.safe_velocity() > 0 );
129     int cycles = 0;
130     const int target_z = use_ramp ? ( up ? 1 : -1 ) : 0;
131 
132     std::set<tripoint> vpts = veh.get_points();
133     while( veh.engine_on && veh.safe_velocity() > 0 && cycles < 10 ) {
134         CAPTURE( cycles );
135         for( const tripoint &checkpt : vpts ) {
136             int partnum = 0;
137             vehicle *check_veh = here.veh_at_internal( checkpt, partnum );
138             CAPTURE( veh_ptr->global_pos3() );
139             CAPTURE( veh_ptr->face.dir() );
140             CAPTURE( checkpt );
141             CHECK( check_veh == veh_ptr );
142         }
143         vpts.clear();
144         here.vehmove();
145         CHECK( veh.velocity == target_velocity );
146         // If the vehicle starts skidding, the effects become random and test is RUINED
147         REQUIRE( !veh.skidding );
148         for( const tripoint &pos : veh.get_points() ) {
149             REQUIRE( here.ter( pos ) );
150         }
151         for( const vpart_reference &vp : veh.get_all_parts() ) {
152             if( vp.info().location != "structure" ) {
153                 continue;
154             }
155             const point &pmount = vp.mount();
156             CAPTURE( pmount );
157             const tripoint &ppos = vp.pos();
158             CAPTURE( ppos );
159             if( cycles > ( transition_cycle - pmount.x ) ) {
160                 CHECK( ppos.z == target_z );
161             } else {
162                 CHECK( ppos.z == 0 );
163             }
164             if( pmount.x == 0 && pmount.y == 0 ) {
165                 CHECK( player_character.pos() == ppos );
166             }
167         }
168         vpts = veh.get_points();
169         cycles++;
170     }
171     const cata::optional<vpart_reference> vp = here.veh_at( player_character.pos() ).part_with_feature(
172                 VPFLAG_BOARDABLE, true );
173     REQUIRE( vp );
174     if( vp ) {
175         const int z_change = map_starting_point.z - player_character.pos().z;
176         here.unboard_vehicle( *vp, &player_character, false );
177         here.ter_set( map_starting_point, ter_id( "t_pavement" ) );
178         player_character.setpos( map_starting_point );
179         if( z_change ) {
180             g->vertical_move( z_change, true );
181         }
182     }
183 }
184 
test_ramp(const std::string & type,const int transition_x)185 static void test_ramp( const std::string &type, const int transition_x )
186 {
187     CAPTURE( type );
188     SECTION( "no ramp" ) {
189         ramp_transition_angled( vproto_id( type ), 180_degrees, transition_x, false, false );
190     }
191     SECTION( "ramp up" ) {
192         ramp_transition_angled( vproto_id( type ), 180_degrees, transition_x, true, true );
193     }
194     SECTION( "ramp down" ) {
195         ramp_transition_angled( vproto_id( type ), 180_degrees, transition_x, true, false );
196     }
197     SECTION( "angled no ramp" ) {
198         ramp_transition_angled( vproto_id( type ), 225_degrees, transition_x, false, false );
199     }
200     SECTION( "angled ramp down" ) {
201         ramp_transition_angled( vproto_id( type ), 225_degrees, transition_x, true, false );
202     }
203     SECTION( "angled ramp up" ) {
204         ramp_transition_angled( vproto_id( type ), 225_degrees, transition_x, true, true );
205     }
206 }
207 
208 static std::vector<std::string> ramp_vehs_to_test = {{
209         "motorcycle",
210     }
211 };
212 
213 // I'd like to do this in a single loop, but that doesn't work for some reason
214 TEST_CASE( "vehicle_ramp_test_59", "[vehicle][ramp]" )
215 {
216     for( const std::string &veh : ramp_vehs_to_test ) {
217         test_ramp( veh, 59 );
218     }
219 }
220 TEST_CASE( "vehicle_ramp_test_60", "[vehicle][ramp]" )
221 {
222     for( const std::string &veh : ramp_vehs_to_test ) {
223         test_ramp( veh, 60 );
224     }
225 }
226 TEST_CASE( "vehicle_ramp_test_61", "[vehicle][ramp]" )
227 {
228     for( const std::string &veh : ramp_vehs_to_test ) {
229         test_ramp( veh, 61 );
230     }
231 }
232 
level_out(const vproto_id & veh_id,const bool drop_pos)233 static void level_out( const vproto_id &veh_id, const bool drop_pos )
234 {
235     map &here = get_map();
236     clear_game_and_set_ramp( 75, drop_pos, false );
237     const int start_z = drop_pos ? 1 : 0;
238 
239     const tripoint map_starting_point( 60, 60, start_z );
240     vehicle *veh_ptr = here.add_vehicle( veh_id, map_starting_point, 180_degrees, 1, 0 );
241 
242     REQUIRE( veh_ptr != nullptr );
243     if( veh_ptr == nullptr ) {
244         return;
245     }
246     vehicle &veh = *veh_ptr;
247     veh.check_falling_or_floating();
248 
249     REQUIRE( !veh.is_in_water() );
250 
251     veh.tags.insert( "IN_CONTROL_OVERRIDE" );
252     veh.engine_on = true;
253 
254     const int target_velocity = 800;
255     veh.cruise_velocity = target_velocity;
256     veh.velocity = target_velocity;
257     CHECK( veh.safe_velocity() > 0 );
258 
259     std::vector<vehicle_part *> all_parts;
260     for( const tripoint &pos : veh.get_points() ) {
261         for( vehicle_part *prt : veh.get_parts_at( pos, "", part_status_flag::any ) ) {
262             all_parts.push_back( prt );
263             if( drop_pos && prt->mount.x < 0 ) {
264                 prt->precalc[0].z = -1;
265                 prt->precalc[1].z = -1;
266             } else if( !drop_pos && prt->mount.x > 1 ) {
267                 prt->precalc[0].z = 1;
268                 prt->precalc[1].z = 1;
269             }
270         }
271     }
272     std::set<int> z_span;
273     for( vehicle_part *prt : all_parts ) {
274         z_span.insert( veh.global_part_pos3( *prt ).z );
275     }
276     REQUIRE( z_span.size() > 1 );
277 
278     monster *dmon_p = g->place_critter_at( mtype_id( "debug_mon" ), map_starting_point );
279     REQUIRE( dmon_p );
280     monster &dmon = *dmon_p;
281 
282     for( int y = 0; y < SEEY * MAPSIZE; y++ ) {
283         for( int x = 0; x < SEEX * MAPSIZE; x++ ) {
284             here.ter_set( tripoint( x, y, 1 ), ter_id( "t_open_air" ) );
285             here.ter_set( tripoint( x, y, 0 ), ter_id( "t_pavement" ) );
286         }
287     }
288 
289     here.vehmove();
290     for( vehicle_part *prt : all_parts ) {
291         CHECK( veh.global_part_pos3( *prt ).z == 0 );
292     }
293     CHECK( dmon.posz() == 0 );
294     CHECK( veh.global_pos3().z == 0 );
295 }
296 
test_leveling(const std::string & type)297 static void test_leveling( const std::string &type )
298 {
299     SECTION( type + " body drop" ) {
300         level_out( vproto_id( type ), true );
301     }
302     SECTION( type + " edge drop" ) {
303         level_out( vproto_id( type ), false );
304     }
305 }
306 
307 static std::vector<std::string> level_vehs_to_test = {{
308         "beetle",
309     }
310 };
311 
312 TEST_CASE( "vehicle_level_test", "[vehicle][ramp]" )
313 {
314     for( const std::string &veh : level_vehs_to_test ) {
315         test_leveling( veh );
316     }
317 }
318