1 /***************************************************************************
2 Ferrari AI and Logic Routines.
3 Used by Attract Mode and the end of game Bonus Sequence.
4
5 This code contains a port of the original AI and a new enhanced AI.
6
7 Enhanced AI Bug-Fixes:
8 ----------------------
9 - AI is much better at driving tracks without crashing into scenery.
10 - No weird juddering when turning corners.
11 - No brake light flickering.
12 - Can drive any stage in the game competently.
13 - Selects a true random route, rather than a pre-defined route.
14 - Can handle split tracks correctly.
15
16 It still occasionally collides with scenery on the later stages, but
17 I think this is ok - as we want to demo a few collisions!
18
19 Notes on the original AI:
20 -------------------------
21 The final behaviour of the AI differs from the original game.
22
23 This is because the core Ferrari logic the AI relies on is in turn
24 dependent on timing behaviour between the two 68k CPUs.
25
26 Differences in timing lead to subtle differences in the road x position
27 at that particular point of time. Over time, these differences are
28 magnified.
29
30 MAME does not accurately reproduce the arcade machine with regard to
31 this. And the Saturn conversion also exhibits different behaviour.
32
33 The differences are relatively minor but noticeable.
34
35 Copyright Chris White.
36 See license.txt for more details.
37 ***************************************************************************/
38
39 #include <cstdlib>
40 #include <time.h>
41
42 #include "engine/oattractai.hpp"
43 #include "engine/oferrari.hpp"
44 #include "engine/oinputs.hpp"
45 #include "engine/ostats.hpp"
46 #include "engine/otraffic.hpp"
47
48 OAttractAI oattractai;
49
OAttractAI(void)50 OAttractAI::OAttractAI(void)
51 {
52 srand((unsigned int) time(NULL));
53 }
54
55
~OAttractAI(void)56 OAttractAI::~OAttractAI(void)
57 {
58 }
59
init()60 void OAttractAI::init()
61 {
62 last_stage = -1;
63 }
64
65 // ------------------------------------------------------------------------------------------------
66 // ENHANCED AI CODE
67 // ------------------------------------------------------------------------------------------------
68
tick_ai_enhanced()69 void OAttractAI::tick_ai_enhanced()
70 {
71 // --------------------------------------------------------------------------------------------
72 // Choose Route At Random
73 // --------------------------------------------------------------------------------------------
74
75 if (last_stage != ostats.cur_stage)
76 {
77 last_stage = ostats.cur_stage;
78 oferrari.sprite_ai_x = std::rand() & 1;
79 }
80
81 // --------------------------------------------------------------------------------------------
82 // Steering
83 // --------------------------------------------------------------------------------------------
84 int16_t future_x;
85
86 // Select road at road split
87 if (oinitengine.rd_split_state > 0 && oinitengine.rd_split_state < 8)
88 future_x = oferrari.sprite_ai_x ? oroad.road0_h[0x40] : oroad.road1_h[0x40];
89 // Roads swap position at merge
90 else if (oinitengine.rd_split_state == 9 || oinitengine.rd_split_state == 10)
91 future_x = oferrari.sprite_ai_x ? oroad.road1_h[0x40] : oroad.road0_h[0x40];
92 // Otherwise just use a standard x-horizon point
93 else
94 future_x = oroad.road0_h[0x40];
95
96 oferrari.sprite_ai_steer = (future_x * 3);
97
98 if (oferrari.sprite_ai_steer > 0x7F)
99 oferrari.sprite_ai_steer = 0x7F;
100 else if (oferrari.sprite_ai_steer < -0x7F)
101 oferrari.sprite_ai_steer = -0x7F;
102
103 oinputs.steering_adjust = oferrari.sprite_ai_steer;
104
105 // --------------------------------------------------------------------------------------------
106 // Braking: Brake when we get too close to the edge of the track
107 // --------------------------------------------------------------------------------------------
108 oinputs.brake_adjust = 0;
109 if (oinitengine.car_increment >> 16 >= 0x80)
110 {
111 int16_t x = oinitengine.car_x_pos;
112 uint16_t road_width = oroad.road_width >> 16;
113
114 // Single Road
115 if (oroad.road_ctrl == ORoad::ROAD_R0 || oroad.road_ctrl == ORoad::ROAD_R1)
116 {
117 x += road_width;
118
119 static int16_t NEAR = 0xD4 - 0x4A;
120 static int16_t FAR = 0x104 - 0x4A;
121
122 // Don't break
123 if (x > -NEAR && x <= NEAR) oinputs.brake_adjust = 0;
124 // Both Wheels Off
125 else if (x < -FAR || x > FAR) oinputs.brake_adjust = OInputs::BRAKE_THRESHOLD4;
126 // Right Wheel Nearly Off
127 else if (x > NEAR && x <= FAR) oinputs.brake_adjust = OInputs::BRAKE_THRESHOLD3;
128 // Left Wheel Nearly Off
129 else if (x <= NEAR && x >= -FAR) oinputs.brake_adjust = OInputs::BRAKE_THRESHOLD3;
130 }
131 // Two Roads (But we only bother braking if they are joined)
132 else if (oroad.road_ctrl == ORoad::ROAD_BOTH_P0 || oroad.road_ctrl == ORoad::ROAD_BOTH_P1)
133 {
134 // Roads are Joined
135 if (road_width <= 0xFF)
136 {
137 static int16_t FAR = 0x104 - 0x4A;
138 road_width += FAR;
139 if (x < 0) x = -x;
140 if (x > road_width)
141 oinputs.brake_adjust = OInputs::BRAKE_THRESHOLD4; // Both Wheels Nearly Off
142 else if (x > road_width - 0x30)
143 oinputs.brake_adjust = OInputs::BRAKE_THRESHOLD3; // One Wheel Nearly Off
144 }
145 }
146 }
147
148 // Brake when traffic nearby
149 if (otraffic.ai_traffic)
150 {
151 if (oinitengine.car_increment >> 16 >= 0xF0)
152 oinputs.brake_adjust = OInputs::BRAKE_THRESHOLD4;
153 otraffic.ai_traffic = 0;
154 }
155
156 // --------------------------------------------------------------------------------------------
157 // Acceleration: Always accelerate unless we're breaking
158 // --------------------------------------------------------------------------------------------
159 if (!oinputs.brake_adjust)
160 oinputs.acc_adjust = 0xFF;
161 else
162 oinputs.acc_adjust = 0;
163 }
164
165
166 // ------------------------------------------------------------------------------------------------
167 // ORIGINAL AI CODE
168 // ------------------------------------------------------------------------------------------------
169
170 // Attract Mode AI Code
171 //
172 // Source: 0xA084
tick_ai()173 void OAttractAI::tick_ai()
174 {
175 // Check upcoming road segment for straight/curve.
176 // Choose route from pre defined table at road split.
177 check_road();
178
179 // Set steering value based on upcoming road segment
180 set_steering();
181
182 oinputs.brake_adjust = 0;
183
184 // If speed is below a certain amount, just accelerate
185 if (oinitengine.car_increment >> 16 < 0xFA)
186 {
187 oinputs.acc_adjust = 0xFF; // max value
188 return;
189 }
190
191 // If AI Traffic is close, set brake on
192 if (otraffic.ai_traffic)
193 {
194 otraffic.ai_traffic = 0;
195 oinputs.brake_adjust = OInputs::BRAKE_THRESHOLD3;
196 }
197
198 // If either wheel of the car is off-road, set brake on
199 else if (oferrari.wheel_state != OFerrari::WHEELS_ON)
200 {
201 oinputs.brake_adjust = OInputs::BRAKE_THRESHOLD3;
202 }
203
204 // Upcoming road: Straight Road
205 if (oinitengine.road_curve_next == 0)
206 {
207 oferrari.sprite_ai_counter = 0; // Clear AI Curve Counter
208 }
209 // Upcoming road: Curved Road
210 else
211 {
212 // Increment AI Curve Counter
213 if (++oferrari.sprite_ai_counter == 1)
214 {
215 // Set road curve value based on hard coded road data.
216 // High value = Sharper Bend
217 int16_t sprite_ai_curve = 0x96 - oinitengine.road_curve_next;
218 if (sprite_ai_curve >= 0)
219 oferrari.sprite_ai_curve = sprite_ai_curve;
220 }
221 // Curve: Toggle brake. You'll notice the brake flickers on/off in OutRun attract mode
222 else if (oferrari.sprite_ai_curve)
223 {
224 if (oferrari.sprite_ai_curve <= 0xA || oferrari.sprite_ai_curve & BIT_3)
225 oinputs.brake_adjust = OInputs::BRAKE_THRESHOLD2;
226
227 oferrari.sprite_ai_curve--;
228 }
229 }
230
231 // Set accelerator to max value
232 oinputs.acc_adjust = 0xFF;
233 }
234
235 // Check upcoming road segment for straight/curve
236 // Check upcoming road segment for road split
237 //
238 // Source: 0xA318
239
check_road()240 void OAttractAI::check_road()
241 {
242 // --------------------------------------------------------------------------------------------
243 // Process Upcoming Curve
244 // --------------------------------------------------------------------------------------------
245
246 const int16_t STEER = 0xb4;
247
248 // Upcoming Road: Straight or No Change
249 if (oinitengine.road_type_next <= OInitEngine::ROAD_STRAIGHT)
250 {
251 if (oinitengine.road_type_next == OInitEngine::ROAD_STRAIGHT)
252 {
253 oferrari.sprite_ai_x = oinitengine.road_type == OInitEngine::ROAD_RIGHT ? STEER : -STEER;
254 }
255 else // NO CHANGE
256 {
257 if (oinitengine.road_type == OInitEngine::ROAD_LEFT)
258 oferrari.sprite_ai_x = STEER;
259 else if (oinitengine.road_type != OInitEngine::ROAD_RIGHT)
260 {
261 oferrari.sprite_ai_x = 0;
262 return;
263 }
264 else
265 oferrari.sprite_ai_x = -STEER;
266 }
267 }
268 // Upcoming Road: Curve
269 else
270 {
271 oferrari.sprite_ai_x = oinitengine.road_type_next == OInitEngine::ROAD_LEFT ? STEER : -STEER;
272 }
273
274 // --------------------------------------------------------------------------------------------
275 // Process Road Split
276 // --------------------------------------------------------------------------------------------
277
278 if (oinitengine.rd_split_state > 0 && oinitengine.rd_split_state < 4)
279 {
280 // Route information for stages
281 // 0 = Turn Left, 1 = Turn Right
282 const uint8_t ROUTE_INFO[] = { 0, 1, 1, 0, 0 };
283
284 if (ROUTE_INFO[ostats.cur_stage])
285 oferrari.sprite_ai_x = -oferrari.sprite_ai_x;
286 }
287 }
288
289 // Set steering value based on road split, previously set curve info
290 //
291 // Source: 0xA3C2
set_steering()292 void OAttractAI::set_steering()
293 {
294 int16_t steering = 0; // d0
295 int16_t car_x_diff = 0; // d1
296 int16_t x_change = 0; // d2
297 int16_t x = 0; // d3
298 int16_t car_x = 0; // d4
299
300 // Mid Road Split
301 if (oinitengine.rd_split_state >= 4)
302 {
303 x_change = oroad.road_width >> 16; // d2
304 x = oinitengine.car_x_pos; // d3
305
306 // Right Route Selected
307 if (oinitengine.route_selected == 0)
308 x += x_change;
309 else
310 x -= x_change;
311
312 car_x = x;
313 x_change = oferrari.sprite_ai_x - x;
314 }
315 // Start Road Split / Not Road Split
316 else
317 {
318 car_x = oinitengine.car_x_pos;
319 x_change = oferrari.sprite_ai_x - car_x;
320 }
321
322 // A404
323 x = x_change;
324 x_change = (x_change < 0) ? -x_change : x_change;
325 if (x_change > 6)
326 x_change = 6;
327 // A414 RHS Of Road
328 if (x >= 0)
329 {
330 car_x_diff = car_x - oferrari.sprite_car_x_bak;
331 if (car_x_diff || x_change)
332 {
333 if (car_x_diff < 1) steering = -1;
334 else if (car_x_diff > 1) steering = 1;
335 else
336 {
337 // set_steering
338 oferrari.sprite_car_x_bak = car_x;
339 oinputs.steering_adjust = oferrari.sprite_ai_steer;
340 return;
341 }
342 }
343 else
344 {
345 // set_steering
346 oferrari.sprite_car_x_bak = car_x;
347 oinputs.steering_adjust = oferrari.sprite_ai_steer;
348 return;
349 }
350
351 }
352 // A43A - LHS Of Road
353 else
354 {
355 car_x_diff = oferrari.sprite_car_x_bak - car_x;
356 if (car_x_diff || x_change)
357 {
358 if (car_x_diff < 1) steering = 1;
359 else if (car_x_diff > 1) steering = -1;
360 else
361 {
362 // set_steering
363 oferrari.sprite_car_x_bak = car_x;
364 oinputs.steering_adjust = oferrari.sprite_ai_steer;
365 return;
366 }
367 }
368 else
369 {
370 // set_steering
371 oferrari.sprite_car_x_bak = car_x;
372 oinputs.steering_adjust = oferrari.sprite_ai_steer;
373 return;
374 }
375 }
376
377 // A462
378 x_change++;
379
380 steering = (steering * x_change) + oferrari.sprite_ai_steer;
381
382 if (steering > 0x7F)
383 steering = 0x7F;
384 else if (steering < -0x7F)
385 steering = -0x7F;
386
387 oferrari.sprite_ai_steer = steering;
388 oinputs.steering_adjust = steering;
389 oferrari.sprite_car_x_bak = car_x;
390 }
391
392
393 // ------------------------------------------------------------------------------------------------
394 // END OF GAME AI CODE
395 // ------------------------------------------------------------------------------------------------
396
397 // Bonus Mode: Set x steering adjustment
398 // Check upcoming road segment for straight/curve
399 //
400 // Source: 0xA498
check_road_bonus()401 void OAttractAI::check_road_bonus()
402 {
403 // Upcoming Road: Straight or No Change
404 if (oinitengine.road_type_next <= OInitEngine::ROAD_STRAIGHT)
405 {
406 if (oinitengine.road_type_next == OInitEngine::ROAD_STRAIGHT)
407 {
408 oferrari.sprite_ai_x = oinitengine.road_type == OInitEngine::ROAD_RIGHT ? -0xB4 : 0xB4; // different from check_road()
409 }
410 else // NO CHANGE
411 {
412 if (oinitengine.road_type == OInitEngine::ROAD_LEFT)
413 oferrari.sprite_ai_x = 0xB4;
414 else if (oinitengine.road_type != OInitEngine::ROAD_RIGHT)
415 oferrari.sprite_ai_x = 0;
416 else
417 oferrari.sprite_ai_x = -0xB4;
418 }
419 }
420 // Upcoming Road: Curve
421 else
422 {
423 oferrari.sprite_ai_x = oinitengine.road_type_next == OInitEngine::ROAD_LEFT ? 0xB4 : -0xB4;
424 }
425 }
426
427 // Bonus Mode: Set steering value configured in check_road_bonus()
428 //
429 // Source: 0xA510
set_steering_bonus()430 void OAttractAI::set_steering_bonus()
431 {
432 int16_t steering = oinitengine.car_x_pos; // d4
433
434 // Road Split During Bonus Mode
435 if (oinitengine.rd_split_state >= 0x14)
436 {
437 int16_t road_width = oroad.road_width >> 16; // d2
438
439 // Right Route Selected
440 if (oinitengine.route_selected == 0)
441 steering += road_width;
442 else
443 steering -= road_width;
444 }
445
446 // check_steering:
447 // Check adjusted steering value is between bounds and set
448 steering = oferrari.sprite_ai_x - steering;
449
450 if (steering)
451 {
452 steering = -steering;
453
454 if (steering > 0x7F)
455 steering = 0x7F;
456 else if (steering < -0x7F)
457 steering = -0x7F;
458
459 oinputs.steering_adjust = steering;
460 }
461 }