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 }