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